cdb
This commit is contained in:
152
lib/assets/javascripts/cdb/src/api/core_lib.js
Normal file
152
lib/assets/javascripts/cdb/src/api/core_lib.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* set of functions from other libraries used to pack the library
|
||||
* as much as posible
|
||||
*/
|
||||
|
||||
;(function() {
|
||||
|
||||
var slice = Array.prototype.slice;
|
||||
var splice = Array.prototype.splice;
|
||||
var eventSplitter = /\s+/;
|
||||
|
||||
// A module that can be mixed in to *any object* in order to provide it with
|
||||
// custom events. You may bind with `on` or remove with `off` callback functions
|
||||
// to an event; trigger`-ing an event fires all callbacks in succession.
|
||||
//
|
||||
// var object = {};
|
||||
// _.extend(object, Backbone.Events);
|
||||
// object.on('expand', function(){ alert('expanded'); });
|
||||
// object.trigger('expand');
|
||||
//
|
||||
var Events = Backbone.Events = {
|
||||
|
||||
// Bind one or more space separated events, `events`, to a `callback`
|
||||
// function. Passing `"all"` will bind the callback to all events fired.
|
||||
on: function(events, callback, context) {
|
||||
|
||||
var calls, event, node, tail, list;
|
||||
if (!callback) return this;
|
||||
events = events.split(eventSplitter);
|
||||
calls = this._callbacks || (this._callbacks = {});
|
||||
|
||||
// Create an immutable callback list, allowing traversal during
|
||||
// modification. The tail is an empty object that will always be used
|
||||
// as the next node.
|
||||
while (event = events.shift()) {
|
||||
list = calls[event];
|
||||
node = list ? list.tail : {};
|
||||
node.next = tail = {};
|
||||
node.context = context;
|
||||
node.callback = callback;
|
||||
calls[event] = {tail: tail, next: list ? list.next : node};
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
// Remove one or many callbacks. If `context` is null, removes all callbacks
|
||||
// with that function. If `callback` is null, removes all callbacks for the
|
||||
// event. If `events` is null, removes all bound callbacks for all events.
|
||||
off: function(events, callback, context) {
|
||||
var event, calls, node, tail, cb, ctx;
|
||||
|
||||
// No events, or removing *all* events.
|
||||
if (!(calls = this._callbacks)) return;
|
||||
if (!(events || callback || context)) {
|
||||
delete this._callbacks;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Loop through the listed events and contexts, splicing them out of the
|
||||
// linked list of callbacks if appropriate.
|
||||
events = events ? events.split(eventSplitter) : _.keys(calls);
|
||||
while (event = events.shift()) {
|
||||
node = calls[event];
|
||||
delete calls[event];
|
||||
if (!node || !(callback || context)) continue;
|
||||
// Create a new list, omitting the indicated callbacks.
|
||||
tail = node.tail;
|
||||
while ((node = node.next) !== tail) {
|
||||
cb = node.callback;
|
||||
ctx = node.context;
|
||||
if ((callback && cb !== callback) || (context && ctx !== context)) {
|
||||
this.on(event, cb, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
// Trigger one or many events, firing all bound callbacks. Callbacks are
|
||||
// passed the same arguments as `trigger` is, apart from the event name
|
||||
// (unless you're listening on `"all"`, which will cause your callback to
|
||||
// receive the true name of the event as the first argument).
|
||||
trigger: function(events) {
|
||||
var event, node, calls, tail, args, all, rest;
|
||||
if (!(calls = this._callbacks)) return this;
|
||||
all = calls.all;
|
||||
events = events.split(eventSplitter);
|
||||
rest = slice.call(arguments, 1);
|
||||
|
||||
// For each event, walk through the linked list of callbacks twice,
|
||||
// first to trigger the event, then to trigger any `"all"` callbacks.
|
||||
while (event = events.shift()) {
|
||||
if (node = calls[event]) {
|
||||
tail = node.tail;
|
||||
while ((node = node.next) !== tail) {
|
||||
node.callback.apply(node.context || this, rest);
|
||||
}
|
||||
}
|
||||
if (node = all) {
|
||||
tail = node.tail;
|
||||
args = [event].concat(rest);
|
||||
while ((node = node.next) !== tail) {
|
||||
node.callback.apply(node.context || this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Aliases for backwards compatibility.
|
||||
Events.bind = Events.on;
|
||||
Events.unbind = Events.off;
|
||||
|
||||
if(cartodb._Promise === undefined) {
|
||||
function _Promise() { }
|
||||
_Promise.prototype = Events;
|
||||
_Promise.prototype.done = function(fn) {
|
||||
return this.on('done', fn);
|
||||
}
|
||||
_Promise.prototype.error = function(fn) {
|
||||
return this.on('error', fn);
|
||||
}
|
||||
cartodb._Promise = _Promise;
|
||||
}
|
||||
|
||||
if(typeof(_) === 'undefined') {
|
||||
var _ = {
|
||||
extend: function(obj, prop) {
|
||||
for(var p in prop) { obj[p] = prop[p]; }
|
||||
return obj;
|
||||
},
|
||||
defaults: function(obj, def) {
|
||||
for(var p in def) {
|
||||
if(obj[p] == undefined) {
|
||||
obj[p] = def[p];
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
isFunction: function(fn) {
|
||||
return typeof(fn) === 'function';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})();
|
||||
229
lib/assets/javascripts/cdb/src/api/layers.js
Normal file
229
lib/assets/javascripts/cdb/src/api/layers.js
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* public api for cartodb
|
||||
*/
|
||||
(function() {
|
||||
function _Promise() {
|
||||
|
||||
}
|
||||
_.extend(_Promise.prototype, Backbone.Events, {
|
||||
done: function(fn) {
|
||||
return this.bind('done', fn);
|
||||
},
|
||||
error: function(fn) {
|
||||
return this.bind('error', fn);
|
||||
}
|
||||
});
|
||||
|
||||
cdb._Promise = _Promise;
|
||||
|
||||
var _requestCache = {};
|
||||
|
||||
/**
|
||||
* compose cartodb url
|
||||
*/
|
||||
function cartodbUrl(opts) {
|
||||
var host = opts.host || 'carto.com';
|
||||
var protocol = opts.protocol || 'https';
|
||||
return protocol + '://' + opts.user + '.' + host + '/api/v1/viz/' + opts.table + '/viz.json';
|
||||
}
|
||||
|
||||
/**
|
||||
* given layer params fetchs the layer json
|
||||
*/
|
||||
function _getLayerJson(layer, callback) {
|
||||
var url = null;
|
||||
if(layer.layers !== undefined || ((layer.kind || layer.type) !== undefined)) {
|
||||
// layer object contains the layer data
|
||||
_.defer(function() { callback(layer); });
|
||||
return;
|
||||
} else if(layer.table !== undefined && layer.user !== undefined) {
|
||||
// layer object points to cartodbjson
|
||||
url = cartodbUrl(layer);
|
||||
} else if(layer.indexOf) {
|
||||
// fetch from url
|
||||
url = layer;
|
||||
}
|
||||
if(url) {
|
||||
cdb.core.Loader.get(url, callback);
|
||||
} else {
|
||||
_.defer(function() { callback(null); });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create a layer for the specified map
|
||||
*
|
||||
* @param map should be a L.Map object, or equivalent depending on what provider you have.
|
||||
* @param layer should be an url or a javascript object with the data to create the layer
|
||||
* @param options layer options
|
||||
*
|
||||
*/
|
||||
cartodb.createLayer = function(map, layer, options, callback) {
|
||||
if(map === undefined) {
|
||||
throw new TypeError("map should be provided");
|
||||
}
|
||||
if(layer === undefined) {
|
||||
throw new TypeError("layer should be provided");
|
||||
}
|
||||
|
||||
var layerView, MapType;
|
||||
var options = options || {};
|
||||
var args = arguments;
|
||||
var fn = args[args.length -1];
|
||||
if(_.isFunction(fn)) {
|
||||
callback = fn;
|
||||
}
|
||||
var promise = new _Promise();
|
||||
|
||||
promise.addTo = function(map, position) {
|
||||
promise.on('done', function() {
|
||||
MapType.addLayerToMap(layerView, map, position);
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
_getLayerJson(layer, function(visData) {
|
||||
|
||||
var layerData;
|
||||
|
||||
if(!visData) {
|
||||
promise.trigger('error');
|
||||
return;
|
||||
}
|
||||
|
||||
// extract layer data from visualization data
|
||||
if(visData.layers) {
|
||||
if(visData.layers.length < 2) {
|
||||
promise.trigger('error', "visualization file does not contain layer info");
|
||||
}
|
||||
var index = options.layerIndex;
|
||||
if (index !== undefined) {
|
||||
if(visData.layers.length <= index) {
|
||||
promise.trigger('error', 'layerIndex out of bounds');
|
||||
return;
|
||||
}
|
||||
layerData = visData.layers[index];
|
||||
} else {
|
||||
var DATA_LAYER_TYPES = ['namedmap', 'layergroup', 'torque'];
|
||||
|
||||
// Select the first data layer (namedmap or layergroup)
|
||||
layerData = _.find(visData.layers, function(layer){
|
||||
return DATA_LAYER_TYPES.indexOf(layer.type) !== -1;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
layerData = visData;
|
||||
}
|
||||
|
||||
if(!layerData) {
|
||||
promise.trigger('error');
|
||||
return;
|
||||
}
|
||||
|
||||
// update options
|
||||
if(options && !_.isFunction(options)) {
|
||||
layerData.options = layerData.options || {};
|
||||
_.extend(layerData.options, options);
|
||||
}
|
||||
|
||||
options = _.defaults(options, {
|
||||
infowindow: true,
|
||||
https: false,
|
||||
legends: true,
|
||||
time_slider: true,
|
||||
tooltip: true
|
||||
});
|
||||
|
||||
// check map type
|
||||
// TODO: improve checking
|
||||
if(typeof(map.overlayMapTypes) !== "undefined") {
|
||||
MapType = cdb.geo.GoogleMapsMapView;
|
||||
// check if leaflet is loaded globally
|
||||
} else if(map instanceof L.Map || (window.L && map instanceof window.L.Map)) {
|
||||
MapType = cdb.geo.LeafletMapView;
|
||||
} else {
|
||||
promise.trigger('error', "cartodb.js can't guess the map type");
|
||||
return promise;
|
||||
}
|
||||
|
||||
// create a dummy viz
|
||||
var viz = map.viz;
|
||||
if(!viz) {
|
||||
var mapView = new MapType({
|
||||
map_object: map,
|
||||
map: new cdb.geo.Map()
|
||||
});
|
||||
|
||||
map.viz = viz = new cdb.vis.Vis({
|
||||
mapView: mapView
|
||||
});
|
||||
|
||||
viz.updated_at = visData.updated_at;
|
||||
viz.https = options.https;
|
||||
}
|
||||
|
||||
function createLayer() {
|
||||
layerView = viz.createLayer(layerData, { no_base_layer: true });
|
||||
|
||||
var torqueLayer;
|
||||
var mobileEnabled = /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
var addMobileLayout = (options.mobile_layout && mobileEnabled) || options.force_mobile;
|
||||
|
||||
if(!layerView) {
|
||||
promise.trigger('error', "layer not supported");
|
||||
return promise;
|
||||
}
|
||||
|
||||
if(options.infowindow) {
|
||||
viz.addInfowindow(layerView);
|
||||
}
|
||||
|
||||
if(options.tooltip) {
|
||||
viz.addTooltip(layerView);
|
||||
}
|
||||
|
||||
if(options.legends) {
|
||||
var layerModel = cdb.vis.Layers.create(layerData.type || layerData.kind, viz, layerData);
|
||||
|
||||
viz._addLegends(viz._createLayerLegendView(layerModel.attributes, layerView))
|
||||
}
|
||||
|
||||
if(options.time_slider && layerView.model.get('type') === 'torque') {
|
||||
|
||||
if (!addMobileLayout) { // don't add the overlay if we are in mobile
|
||||
viz.addTimeSlider(layerView);
|
||||
}
|
||||
|
||||
torqueLayer = layerView;
|
||||
}
|
||||
|
||||
if (addMobileLayout) {
|
||||
|
||||
options.mapView = map.viz.mapView;
|
||||
|
||||
viz.addOverlay({
|
||||
type: 'mobile',
|
||||
layerView: layerView,
|
||||
overlays: [],
|
||||
torqueLayer: torqueLayer,
|
||||
options: options
|
||||
});
|
||||
}
|
||||
|
||||
callback && callback(layerView);
|
||||
promise.trigger('done', layerView);
|
||||
}
|
||||
|
||||
// load needed modules
|
||||
if(!viz.checkModules([layerData])) {
|
||||
viz.loadModules([layerData], function() {
|
||||
createLayer();
|
||||
});
|
||||
} else {
|
||||
createLayer();
|
||||
}
|
||||
});
|
||||
|
||||
return promise;
|
||||
};
|
||||
})();
|
||||
691
lib/assets/javascripts/cdb/src/api/sql.js
Normal file
691
lib/assets/javascripts/cdb/src/api/sql.js
Normal file
@@ -0,0 +1,691 @@
|
||||
|
||||
;(function() {
|
||||
|
||||
var root = this;
|
||||
|
||||
root.cartodb = root.cartodb || {};
|
||||
|
||||
function SQL(options) {
|
||||
if(cartodb === this || window === this) {
|
||||
return new SQL(options);
|
||||
}
|
||||
if(!options.user) {
|
||||
throw new Error("user should be provided");
|
||||
}
|
||||
var loc = new String(window.location.protocol);
|
||||
loc = loc.slice(0, loc.length - 1);
|
||||
if(loc == 'file') {
|
||||
loc = 'https';
|
||||
}
|
||||
|
||||
this.ajax = options.ajax || (typeof(jQuery) !== 'undefined' ? jQuery.ajax: reqwest);
|
||||
if(!this.ajax) {
|
||||
throw new Error("jQuery or reqwest should be loaded");
|
||||
}
|
||||
|
||||
this.options = _.defaults(options, {
|
||||
version: 'v2',
|
||||
protocol: loc,
|
||||
jsonp: typeof(jQuery) !== 'undefined' ? !jQuery.support.cors: false
|
||||
})
|
||||
|
||||
if (!this.options.sql_api_template) {
|
||||
var opts = this.options;
|
||||
var template = null;
|
||||
if(opts && opts.completeDomain) {
|
||||
template = opts.completeDomain;
|
||||
} else {
|
||||
var host = opts.host || 'carto.com';
|
||||
var protocol = opts.protocol || 'https';
|
||||
template = protocol + '://{user}.' + host;
|
||||
}
|
||||
this.options.sql_api_template = template;
|
||||
}
|
||||
}
|
||||
|
||||
SQL.prototype._host = function() {
|
||||
var opts = this.options;
|
||||
return opts.sql_api_template.replace('{user}', opts.user) + '/api/' + opts.version + '/sql';
|
||||
},
|
||||
|
||||
/**
|
||||
* var sql = new SQL('cartodb_username');
|
||||
* sql.execute("select * from {{ table }} where id = {{ id }}", {
|
||||
* table: 'test',
|
||||
* id: '1'
|
||||
* })
|
||||
*/
|
||||
SQL.prototype.execute = function(sql, vars, options, callback) {
|
||||
|
||||
//Variable that defines if a query should be using get method or post method
|
||||
var MAX_LENGTH_GET_QUERY = 1024;
|
||||
|
||||
var promise = new cartodb._Promise();
|
||||
if(!sql) {
|
||||
throw new TypeError("sql should not be null");
|
||||
}
|
||||
// setup arguments
|
||||
var args = arguments,
|
||||
fn = args[args.length -1];
|
||||
if(_.isFunction(fn)) {
|
||||
callback = fn;
|
||||
}
|
||||
options = _.defaults(options || {}, this.options);
|
||||
var params = {
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
crossDomain: true
|
||||
};
|
||||
|
||||
if(options.cache !== undefined) {
|
||||
params.cache = options.cache;
|
||||
}
|
||||
|
||||
if(options.jsonp) {
|
||||
delete params.crossDomain;
|
||||
if (options.jsonpCallback) {
|
||||
params.jsonpCallback = options.jsonpCallback;
|
||||
}
|
||||
params.dataType = 'jsonp';
|
||||
}
|
||||
|
||||
// Substitute mapnik tokens
|
||||
// resolution at zoom level 0
|
||||
var res = '156543.03515625';
|
||||
// full webmercator extent
|
||||
var ext = 'ST_MakeEnvelope(-20037508.5,-20037508.5,20037508.5,20037508.5,3857)';
|
||||
sql = sql.replace('!bbox!', ext)
|
||||
.replace('!pixel_width!', res)
|
||||
.replace('!pixel_height!', res);
|
||||
|
||||
// create query
|
||||
var query = Mustache.render(sql, vars);
|
||||
|
||||
// check method: if we are going to send by get or by post
|
||||
var isGetRequest = query.length < MAX_LENGTH_GET_QUERY;
|
||||
|
||||
// generate url depending on the http method
|
||||
var reqParams = ['format', 'dp', 'api_key'];
|
||||
// request params
|
||||
if (options.extra_params) {
|
||||
reqParams = reqParams.concat(options.extra_params);
|
||||
}
|
||||
|
||||
params.url = this._host() ;
|
||||
if (isGetRequest) {
|
||||
var q = 'q=' + encodeURIComponent(query);
|
||||
for(var i in reqParams) {
|
||||
var r = reqParams[i];
|
||||
var v = options[r];
|
||||
if(v) {
|
||||
q += '&' + r + "=" + v;
|
||||
}
|
||||
}
|
||||
|
||||
params.url += '?' + q;
|
||||
} else {
|
||||
var objPost = {'q': query};
|
||||
for(var i in reqParams) {
|
||||
var r = reqParams[i];
|
||||
var v = options[r];
|
||||
if (v) {
|
||||
objPost[r] = v;
|
||||
}
|
||||
}
|
||||
|
||||
params.data = objPost;
|
||||
//Check if we are using jQuery(uncompressed) or reqwest (core)
|
||||
if ((typeof(jQuery) !== 'undefined')) {
|
||||
params.type = 'post';
|
||||
} else {
|
||||
params.method = 'post';
|
||||
}
|
||||
}
|
||||
|
||||
// wrap success and error functions
|
||||
var success = options.success;
|
||||
var error = options.error;
|
||||
if(success) delete options.success;
|
||||
if(error) delete error.success;
|
||||
|
||||
params.error = function(resp) {
|
||||
var res = resp.responseText || resp.response;
|
||||
var errors = res && JSON.parse(res);
|
||||
promise.trigger('error', errors && errors.error, resp)
|
||||
if(error) error(resp);
|
||||
}
|
||||
params.success = function(resp, status, xhr) {
|
||||
// manage rewest
|
||||
if(status == undefined) {
|
||||
status = resp.status;
|
||||
xhr = resp;
|
||||
resp = JSON.parse(resp.response);
|
||||
}
|
||||
//Timeout explanation. CartoDB.js ticket #336
|
||||
//From St.Ov.: "what setTimeout does is add a new event to the browser event queue
|
||||
//and the rendering engine is already in that queue (not entirely true, but close enough)
|
||||
//so it gets executed before the setTimeout event."
|
||||
setTimeout(function() {
|
||||
promise.trigger('done', resp, status, xhr);
|
||||
if(success) success(resp, status, xhr);
|
||||
if(callback) callback(resp);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// call ajax
|
||||
delete options.jsonp;
|
||||
this.ajax(_.extend(params, options));
|
||||
return promise;
|
||||
}
|
||||
|
||||
SQL.prototype.getBounds = function(sql, vars, options, callback) {
|
||||
var promise = new cartodb._Promise();
|
||||
var args = arguments,
|
||||
fn = args[args.length -1];
|
||||
if(_.isFunction(fn)) {
|
||||
callback = fn;
|
||||
}
|
||||
var s = 'SELECT ST_XMin(ST_Extent(the_geom)) as minx,' +
|
||||
' ST_YMin(ST_Extent(the_geom)) as miny,'+
|
||||
' ST_XMax(ST_Extent(the_geom)) as maxx,' +
|
||||
' ST_YMax(ST_Extent(the_geom)) as maxy' +
|
||||
' from ({{{ sql }}}) as subq';
|
||||
sql = Mustache.render(sql, vars);
|
||||
this.execute(s, { sql: sql }, options)
|
||||
.done(function(result) {
|
||||
if (result.rows && result.rows.length > 0 && result.rows[0].maxx != null) {
|
||||
var c = result.rows[0];
|
||||
var minlat = -85.0511;
|
||||
var maxlat = 85.0511;
|
||||
var minlon = -179;
|
||||
var maxlon = 179;
|
||||
|
||||
var clamp = function(x, min, max) {
|
||||
return x < min ? min : x > max ? max : x;
|
||||
}
|
||||
|
||||
var lon0 = clamp(c.maxx, minlon, maxlon);
|
||||
var lon1 = clamp(c.minx, minlon, maxlon);
|
||||
var lat0 = clamp(c.maxy, minlat, maxlat);
|
||||
var lat1 = clamp(c.miny, minlat, maxlat);
|
||||
|
||||
var bounds = [[lat0, lon0], [lat1, lon1]];
|
||||
promise.trigger('done', bounds);
|
||||
callback && callback(bounds);
|
||||
}
|
||||
})
|
||||
.error(function(err) {
|
||||
promise.trigger('error', err);
|
||||
})
|
||||
|
||||
return promise;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* var people_under_10 = sql
|
||||
* .table('test')
|
||||
* .columns(['age', 'column2'])
|
||||
* .filter('age < 10')
|
||||
* .limit(15)
|
||||
* .order_by('age')
|
||||
*
|
||||
* people_under_10(function(results) {
|
||||
* })
|
||||
*/
|
||||
|
||||
SQL.prototype.table = function(name) {
|
||||
|
||||
var _name = name;
|
||||
var _filters;
|
||||
var _columns = [];
|
||||
var _limit;
|
||||
var _order;
|
||||
var _orderDir;
|
||||
var _sql = this;
|
||||
|
||||
function _table() {
|
||||
_table.fetch.apply(_table, arguments);
|
||||
}
|
||||
|
||||
_table.fetch = function(vars) {
|
||||
vars = vars || {}
|
||||
var args = arguments,
|
||||
fn = args[args.length -1];
|
||||
if(_.isFunction(fn)) {
|
||||
callback = fn;
|
||||
if(args.length === 1) vars = {};
|
||||
}
|
||||
_sql.execute(_table.sql(), vars, callback);
|
||||
}
|
||||
|
||||
_table.sql = function() {
|
||||
var s = "select"
|
||||
if(_columns.length) {
|
||||
s += ' ' + _columns.join(',') + ' '
|
||||
} else {
|
||||
s += ' * '
|
||||
}
|
||||
|
||||
s += "from " + _name;
|
||||
|
||||
if(_filters) {
|
||||
s += " where " + _filters;
|
||||
}
|
||||
if(_limit) {
|
||||
s += " limit " + _limit;
|
||||
}
|
||||
if(_order) {
|
||||
s += " order by " + _order;
|
||||
}
|
||||
if(_orderDir) {
|
||||
s += ' ' + _orderDir;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
_table.filter = function(f) {
|
||||
_filters = f;
|
||||
return _table;
|
||||
}
|
||||
|
||||
_table.order_by= function(o) {
|
||||
_order = o;
|
||||
return _table;
|
||||
}
|
||||
_table.asc = function() {
|
||||
_orderDir = 'asc'
|
||||
return _table;
|
||||
}
|
||||
|
||||
_table.desc = function() {
|
||||
_orderDir = 'desc'
|
||||
return _table;
|
||||
}
|
||||
|
||||
_table.columns = function(c) {
|
||||
_columns = c;
|
||||
return _table;
|
||||
}
|
||||
|
||||
_table.limit = function(l) {
|
||||
_limit = l;
|
||||
return _table;
|
||||
}
|
||||
|
||||
return _table;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* sql.filter(sql.f().distance('< 10km')
|
||||
*/
|
||||
/*cartodb.SQL.geoFilter = function() {
|
||||
var _sql;
|
||||
function f() {}
|
||||
|
||||
f.distance = function(qty) {
|
||||
qty.replace('km', '*1000')
|
||||
_sql += 'st_distance(the_geom) ' + qty
|
||||
}
|
||||
f.or = function() {
|
||||
}
|
||||
|
||||
f.and = function() {
|
||||
}
|
||||
return f;
|
||||
}
|
||||
*/
|
||||
function array_agg(s) {
|
||||
return JSON.parse(s.replace(/^{/, '[').replace(/}$/,']'));
|
||||
}
|
||||
|
||||
|
||||
SQL.prototype.describeString = function(sql, column, callback) {
|
||||
|
||||
var s = [
|
||||
'WITH t as (',
|
||||
' SELECT count(*) as total,',
|
||||
' count(DISTINCT {{column}}) as ndist',
|
||||
' FROM ({{sql}}) _wrap',
|
||||
' ), a as (',
|
||||
' SELECT ',
|
||||
' count(*) cnt, ',
|
||||
' {{column}}',
|
||||
' FROM ',
|
||||
' ({{sql}}) _wrap ',
|
||||
' GROUP BY ',
|
||||
' {{column}} ',
|
||||
' ORDER BY ',
|
||||
' cnt DESC',
|
||||
' ), b As (',
|
||||
' SELECT',
|
||||
' row_number() OVER (ORDER BY cnt DESC) rn,',
|
||||
' cnt',
|
||||
' FROM a',
|
||||
' ), c As (',
|
||||
' SELECT ',
|
||||
' sum(cnt) OVER (ORDER BY rn ASC) / t.total cumperc,',
|
||||
' rn,',
|
||||
' cnt ',
|
||||
' FROM b, t',
|
||||
' LIMIT 10',
|
||||
' ),',
|
||||
'stats as (',
|
||||
'select count(distinct({{column}})) as uniq, ',
|
||||
' count(*) as cnt, ',
|
||||
' sum(case when COALESCE(NULLIF({{column}},\'\')) is null then 1 else 0 end)::numeric as null_count, ',
|
||||
' sum(case when COALESCE(NULLIF({{column}},\'\')) is null then 1 else 0 end)::numeric / count(*)::numeric as null_ratio, ',
|
||||
// ' CDB_DistinctMeasure(array_agg({{column}}::text)) as cat_weight ',
|
||||
' (SELECT max(cumperc) weight FROM c) As skew ',
|
||||
'from ({{sql}}) __wrap',
|
||||
'),',
|
||||
'hist as (',
|
||||
'select array_agg(row(d, c)) array_agg from (select distinct({{column}}) d, count(*) as c from ({{sql}}) __wrap, stats group by 1 limit 100) _a',
|
||||
')',
|
||||
'select * from stats, hist'
|
||||
];
|
||||
|
||||
var query = Mustache.render(s.join('\n'), {
|
||||
column: column,
|
||||
sql: sql
|
||||
});
|
||||
|
||||
var normalizeName = function(str) {
|
||||
var normalizedStr = str.replace(/^"(.+(?="$))?"$/, '$1'); // removes surrounding quotes
|
||||
return normalizedStr.replace(/""/g, '"'); // removes duplicated quotes
|
||||
}
|
||||
|
||||
this.execute(query, function(data) {
|
||||
var row = data.rows[0];
|
||||
var weight = 0;
|
||||
var histogram = [];
|
||||
|
||||
try {
|
||||
var s = array_agg(row.array_agg);
|
||||
|
||||
var histogram = _(s).map(function(row) {
|
||||
var r = row.match(/\((.*),(\d+)/);
|
||||
var name = normalizeName(r[1]);
|
||||
return [name, +r[2]];
|
||||
});
|
||||
|
||||
weight = row.skew * (1 - row.null_ratio) * (1 - row.uniq / row.cnt) * ( row.uniq > 1 ? 1 : 0);
|
||||
} catch(e) {
|
||||
|
||||
}
|
||||
|
||||
callback({
|
||||
type: 'string',
|
||||
hist: histogram,
|
||||
distinct: row.uniq,
|
||||
count: row.cnt,
|
||||
null_count: row.null_count,
|
||||
null_ratio: row.null_ratio,
|
||||
skew: row.skew,
|
||||
weight: weight
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SQL.prototype.describeDate = function(sql, column, callback) {
|
||||
var s = [
|
||||
'with minimum as (',
|
||||
'SELECT min({{column}}) as start_time FROM ({{sql}}) _wrap), ',
|
||||
'maximum as (SELECT max({{column}}) as end_time FROM ({{sql}}) _wrap), ',
|
||||
'null_ratio as (SELECT sum(case when {{column}} is null then 1 else 0 end)::numeric / count(*)::numeric as null_ratio FROM ({{sql}}) _wrap), ',
|
||||
'moments as (SELECT count(DISTINCT {{column}}) as moments FROM ({{sql}}) _wrap)',
|
||||
'SELECT * FROM minimum, maximum, moments, null_ratio'
|
||||
];
|
||||
var query = Mustache.render(s.join('\n'), {
|
||||
column: column,
|
||||
sql: sql
|
||||
});
|
||||
|
||||
this.execute(query, function(data) {
|
||||
var row = data.rows[0];
|
||||
var e = new Date(row.end_time);
|
||||
var s = new Date(row.start_time);
|
||||
|
||||
var moments = row.moments;
|
||||
|
||||
var steps = Math.min(row.moments, 1024);
|
||||
|
||||
callback({
|
||||
type: 'date',
|
||||
start_time: s,
|
||||
end_time: e,
|
||||
range: e - s,
|
||||
steps: steps,
|
||||
null_ratio: row.null_ratio
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SQL.prototype.describeBoolean = function(sql, column, callback){
|
||||
var s = [
|
||||
'with stats as (',
|
||||
'select count(distinct({{column}})) as uniq,',
|
||||
'count(*) as cnt',
|
||||
'from ({{sql}}) _wrap ',
|
||||
'),',
|
||||
'null_ratio as (',
|
||||
'SELECT sum(case when {{column}} is null then 1 else 0 end)::numeric / count(*)::numeric as null_ratio FROM ({{sql}}) _wrap), ',
|
||||
'true_ratio as (',
|
||||
'SELECT sum(case when {{column}} is true then 1 else 0 end)::numeric / count(*)::numeric as true_ratio FROM ({{sql}}) _wrap) ',
|
||||
'SELECT * FROM true_ratio, null_ratio, stats'
|
||||
];
|
||||
var query = Mustache.render(s.join('\n'), {
|
||||
column: column,
|
||||
sql: sql
|
||||
});
|
||||
|
||||
this.execute(query, function(data) {
|
||||
var row = data.rows[0];
|
||||
|
||||
callback({
|
||||
type: 'boolean',
|
||||
null_ratio: row.null_ratio,
|
||||
true_ratio: row.true_ratio,
|
||||
distinct: row.uniq,
|
||||
count: row.cnt
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SQL.prototype.describeGeom = function(sql, column, callback) {
|
||||
var s = [
|
||||
'with stats as (',
|
||||
'select st_asgeojson(st_extent({{column}})) as bbox',
|
||||
'from ({{sql}}) _wrap',
|
||||
'),',
|
||||
'geotype as (',
|
||||
'select st_geometrytype({{column}}) as geometry_type from ({{sql}}) _w where {{column}} is not null limit 1',
|
||||
'),',
|
||||
'clusters as (',
|
||||
'with clus as (',
|
||||
'SELECT distinct(ST_snaptogrid(the_geom, 10)) as cluster, count(*) as clustercount FROM ({{sql}}) _wrap group by 1 order by 2 desc limit 3),',
|
||||
'total as (',
|
||||
'SELECT count(*) FROM ({{sql}}) _wrap)',
|
||||
'SELECT sum(clus.clustercount)/sum(total.count) AS clusterrate FROM clus, total',
|
||||
'),',
|
||||
'density as (',
|
||||
'SELECT count(*) / st_area(st_extent(the_geom)) as density FROM ({{sql}}) _wrap',
|
||||
')',
|
||||
'select * from stats, geotype, clusters, density'
|
||||
];
|
||||
|
||||
var query = Mustache.render(s.join('\n'), {
|
||||
column: column,
|
||||
sql: sql
|
||||
});
|
||||
function simplifyType(g) {
|
||||
return {
|
||||
'st_multipolygon': 'polygon',
|
||||
'st_polygon': 'polygon',
|
||||
'st_multilinestring': 'line',
|
||||
'st_linestring': 'line',
|
||||
'st_multipoint': 'point',
|
||||
'st_point': 'point'
|
||||
}[g.toLowerCase()]
|
||||
};
|
||||
|
||||
this.execute(query, function(data) {
|
||||
var row = data.rows[0];
|
||||
var bbox = JSON.parse(row.bbox).coordinates[0]
|
||||
callback({
|
||||
type: 'geom',
|
||||
//lon,lat -> lat, lon
|
||||
bbox: [[bbox[0][0],bbox[0][1]], [bbox[2][0], bbox[2][1]]],
|
||||
geometry_type: row.geometry_type,
|
||||
simplified_geometry_type: simplifyType(row.geometry_type),
|
||||
cluster_rate: row.clusterrate,
|
||||
density: row.density
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SQL.prototype.columns = function(sql, options, callback) {
|
||||
var args = arguments,
|
||||
fn = args[args.length -1];
|
||||
if(_.isFunction(fn)) {
|
||||
callback = fn;
|
||||
}
|
||||
var s = "select * from (" + sql + ") __wrap limit 0";
|
||||
var exclude = ['cartodb_id','latitude','longitude','created_at','updated_at','lat','lon','the_geom_webmercator'];
|
||||
this.execute(s, function(data) {
|
||||
var t = {}
|
||||
for (var i in data.fields) {
|
||||
if (exclude.indexOf(i) === -1) {
|
||||
t[i] = data.fields[i].type;
|
||||
}
|
||||
}
|
||||
callback(t);
|
||||
});
|
||||
};
|
||||
|
||||
SQL.prototype.describeFloat = function(sql, column, callback) {
|
||||
var s = [
|
||||
'with stats as (',
|
||||
'select min({{column}}) as min,',
|
||||
'max({{column}}) as max,',
|
||||
'avg({{column}}) as avg,',
|
||||
'count(DISTINCT {{column}}) as cnt,',
|
||||
'count(distinct({{column}})) as uniq,',
|
||||
'count(*) as cnt,',
|
||||
'sum(case when {{column}} is null then 1 else 0 end)::numeric / count(*)::numeric as null_ratio,',
|
||||
'stddev_pop({{column}}) / count({{column}}) as stddev,',
|
||||
'CASE WHEN abs(avg({{column}})) > 1e-7 THEN stddev({{column}}) / abs(avg({{column}})) ELSE 1e12 END as stddevmean,',
|
||||
'CDB_DistType(array_agg("{{column}}"::numeric)) as dist_type ',
|
||||
'from ({{sql}}) _wrap ',
|
||||
'),',
|
||||
'params as (select min(a) as min, (max(a) - min(a)) / 7 as diff from ( select {{column}} as a from ({{sql}}) _table_sql where {{column}} is not null ) as foo ),',
|
||||
'histogram as (',
|
||||
'select array_agg(row(bucket, range, freq)) as hist from (',
|
||||
'select CASE WHEN uniq > 1 then width_bucket({{column}}, min-0.01*abs(min), max+0.01*abs(max), 100) ELSE 1 END as bucket,',
|
||||
'numrange(min({{column}})::numeric, max({{column}})::numeric) as range,',
|
||||
'count(*) as freq',
|
||||
'from ({{sql}}) _w, stats',
|
||||
'group by 1',
|
||||
'order by 1',
|
||||
') __wrap',
|
||||
'),',
|
||||
'hist as (',
|
||||
'select array_agg(row(d, c)) cat_hist from (select distinct({{column}}) d, count(*) as c from ({{sql}}) __wrap, stats group by 1 limit 100) _a',
|
||||
'),',
|
||||
'buckets as (',
|
||||
'select CDB_QuantileBins(array_agg(distinct({{column}}::numeric)), 7) as quantiles, ',
|
||||
' (select array_agg(x::numeric) FROM (SELECT (min + n * diff)::numeric as x FROM generate_series(1,7) n, params) p) as equalint,',
|
||||
// ' CDB_EqualIntervalBins(array_agg({{column}}::numeric), 7) as equalint, ',
|
||||
' CDB_JenksBins(array_agg(distinct({{column}}::numeric)), 7) as jenks, ',
|
||||
' CDB_HeadsTailsBins(array_agg(distinct({{column}}::numeric)), 7) as headtails ',
|
||||
'from ({{sql}}) _table_sql where {{column}} is not null',
|
||||
')',
|
||||
'select * from histogram, stats, buckets, hist'
|
||||
];
|
||||
|
||||
var query = Mustache.render(s.join('\n'), {
|
||||
column: column,
|
||||
sql: sql
|
||||
});
|
||||
|
||||
this.execute(query, function(data) {
|
||||
var row = data.rows[0];
|
||||
var s = array_agg(row.hist);
|
||||
var h = array_agg(row.cat_hist);
|
||||
callback({
|
||||
type: 'number',
|
||||
cat_hist:
|
||||
_(h).map(function(row) {
|
||||
var r = row.match(/\((.*),(\d+)/);
|
||||
return [+r[1], +r[2]];
|
||||
}),
|
||||
hist: _(s).map(function(row) {
|
||||
if(row.indexOf("empty") > -1) return;
|
||||
var els = row.split('"');
|
||||
return { index: els[0].replace(/\D/g,''),
|
||||
range: els[1].split(",").map(function(d){return d.replace(/\D/g,'')}),
|
||||
freq: els[2].replace(/\D/g,'') };
|
||||
}),
|
||||
stddev: row.stddev,
|
||||
null_ratio: row.null_ratio,
|
||||
count: row.cnt,
|
||||
distinct: row.uniq,
|
||||
//lstddev: row.lstddev,
|
||||
avg: row.avg,
|
||||
max: row.max,
|
||||
min: row.min,
|
||||
stddevmean: row.stddevmean,
|
||||
weight: (row.uniq > 1 ? 1 : 0) * (1 - row.null_ratio) * (row.stddev < -1 ? 1 : (row.stddev < 1 ? 0.5 : (row.stddev < 3 ? 0.25 : 0.1))),
|
||||
quantiles: row.quantiles,
|
||||
equalint: row.equalint,
|
||||
jenks: row.jenks,
|
||||
headtails: row.headtails,
|
||||
dist_type: row.dist_type
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// describe a column
|
||||
SQL.prototype.describe = function(sql, column, options) {
|
||||
var self = this;
|
||||
var args = arguments,
|
||||
fn = args[args.length -1];
|
||||
if(_.isFunction(fn)) {
|
||||
var _callback = fn;
|
||||
}
|
||||
var callback = function(data) {
|
||||
data.column = column;
|
||||
_callback(data);
|
||||
}
|
||||
var s = "select * from (" + sql + ") __wrap limit 0";
|
||||
this.execute(s, function(data) {
|
||||
|
||||
var type = (options && options.type) ? options.type : data.fields[column].type;
|
||||
|
||||
if (!type) {
|
||||
callback(new Error("column does not exist"));
|
||||
return;
|
||||
}
|
||||
|
||||
else if (type === 'string') {
|
||||
self.describeString(sql, column, callback);
|
||||
} else if (type === 'number') {
|
||||
self.describeFloat(sql, column, callback);
|
||||
} else if (type === 'geometry') {
|
||||
self.describeGeom(sql, column, callback);
|
||||
} else if (type === 'date') {
|
||||
self.describeDate(sql, column, callback);
|
||||
} else if (type === 'boolean') {
|
||||
self.describeBoolean(sql, column, callback);
|
||||
} else {
|
||||
callback(new Error("column type is not supported"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
root.cartodb.SQL = SQL;
|
||||
|
||||
})();
|
||||
49
lib/assets/javascripts/cdb/src/api/tiles.js
Normal file
49
lib/assets/javascripts/cdb/src/api/tiles.js
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
;(function() {
|
||||
|
||||
var root = this;
|
||||
|
||||
root.cartodb = root.cartodb || {};
|
||||
|
||||
var defaults = {
|
||||
tiler_domain: "carto.com",
|
||||
tiler_port: "80",
|
||||
tiler_protocol: "http",
|
||||
subdomains: ['{s}'],
|
||||
extra_params: {
|
||||
cache_policy: 'persist'
|
||||
}
|
||||
};
|
||||
|
||||
var Tiles = function(options) {
|
||||
_.defaults(options, defaults);
|
||||
if(!options.sublayers) {
|
||||
throw new Error("sublayers should be passed");
|
||||
}
|
||||
if(!options.user_name) {
|
||||
throw new Error("username should be passed");
|
||||
}
|
||||
|
||||
options.layer_definition = LayerDefinition.layerDefFromSubLayers(options.sublayers);
|
||||
|
||||
options.ajax = reqwest.compat;
|
||||
|
||||
LayerDefinition.call(this, options.layer_definition, options);
|
||||
};
|
||||
|
||||
_.extend(Tiles.prototype, LayerDefinition.prototype);
|
||||
|
||||
|
||||
root.cartodb.Tiles = Tiles;
|
||||
|
||||
/**
|
||||
* return the tile url template for the layer requested
|
||||
*/
|
||||
Tiles.getTiles = function(options, callback) {
|
||||
var t = new Tiles(options);
|
||||
t.getTiles(callback);
|
||||
return t;
|
||||
};
|
||||
|
||||
|
||||
})();
|
||||
35
lib/assets/javascripts/cdb/src/api/vis.js
Normal file
35
lib/assets/javascripts/cdb/src/api/vis.js
Normal file
@@ -0,0 +1,35 @@
|
||||
(function() {
|
||||
|
||||
cartodb.createVis = function(el, vizjson, options, callback) {
|
||||
|
||||
if (!el) {
|
||||
throw new TypeError("a DOM element should be provided");
|
||||
}
|
||||
|
||||
var
|
||||
args = arguments,
|
||||
fn = args[args.length -1];
|
||||
|
||||
if (_.isFunction(fn)) {
|
||||
callback = fn;
|
||||
}
|
||||
|
||||
el = (typeof el === 'string' ? document.getElementById(el) : el);
|
||||
|
||||
var vis = new cartodb.vis.Vis({ el: el });
|
||||
|
||||
if (vizjson) {
|
||||
|
||||
vis.load(vizjson, options);
|
||||
|
||||
if (callback) {
|
||||
vis.done(callback);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return vis;
|
||||
|
||||
};
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user