881 lines
22 KiB
JavaScript
881 lines
22 KiB
JavaScript
|
|
||
|
/*
|
||
|
* extend infowindow to serialize only the data we need
|
||
|
*/
|
||
|
_.extend(cdb.geo.ui.InfowindowModel.prototype, {
|
||
|
toJSON: function() {
|
||
|
var fields = [];
|
||
|
|
||
|
if (!this.attributes.disabled) {
|
||
|
fields = _.clone(this.attributes.fields);
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
fields: fields,
|
||
|
template_name: this.attributes.template_name,
|
||
|
template: this.attributes.template,
|
||
|
alternative_names: this.attributes.alternative_names,
|
||
|
old_fields: this.attributes.old_fields,
|
||
|
old_template_name: this.attributes.old_template_name,
|
||
|
width: this.attributes.width,
|
||
|
maxHeight: this.attributes.maxHeight
|
||
|
};
|
||
|
},
|
||
|
|
||
|
removeMissingFields: function(columns) {
|
||
|
var columnsSet = {}
|
||
|
for(var i = 0; i < columns.length; ++i) {
|
||
|
var c = columns[i];
|
||
|
columnsSet[c] = true;
|
||
|
}
|
||
|
var fields = this.get('fields');
|
||
|
if (!fields) {
|
||
|
return;
|
||
|
}
|
||
|
for(var i = 0; i < fields.length; ++i) {
|
||
|
var name = fields[i].name;
|
||
|
if (! (name in columnsSet)) {
|
||
|
this.removeField(name);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
addMissingFields: function(columns) {
|
||
|
var fieldsSet = {};
|
||
|
var fields = this.get('fields');
|
||
|
|
||
|
for(var i = 0; i < fields.length; ++i) {
|
||
|
var c = fields[i].name;
|
||
|
fieldsSet[c] = true;
|
||
|
}
|
||
|
|
||
|
for(var i = 0; i < columns.length; ++i) {
|
||
|
var name = columns[i];
|
||
|
if (! (name in fieldsSet)) {
|
||
|
this.addField(name);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
mergeFields: function(columns) {
|
||
|
// remove fields that no longer exist
|
||
|
this.removeMissingFields(columns);
|
||
|
// add new fields that exists
|
||
|
this.addMissingFields(columns);
|
||
|
},
|
||
|
|
||
|
// return the list of columns involved in the infowindow
|
||
|
// ready to set interactivity in a cartodb layer
|
||
|
getInteractivity: function() {
|
||
|
var fields = this.get('fields') || [];
|
||
|
var columns = [];
|
||
|
for(var i = 0; i < fields.length; ++i) {
|
||
|
columns.push(fields[i].name);
|
||
|
}
|
||
|
return columns;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* extend gmaps layer for data serialization
|
||
|
*/
|
||
|
cdb.admin.GMapsBaseLayer = cdb.geo.GMapsBaseLayer.extend({
|
||
|
|
||
|
clone: function() {
|
||
|
return new cdb.admin.GMapsBaseLayer(_.clone(this.attributes));
|
||
|
},
|
||
|
|
||
|
parse: function(data) {
|
||
|
var c = {};
|
||
|
_.extend(c, data.options, {
|
||
|
id: data.id,
|
||
|
type: 'GMapsBase',
|
||
|
order: data.order,
|
||
|
parent_id: data.parent_id
|
||
|
});
|
||
|
return c;
|
||
|
},
|
||
|
|
||
|
toJSON: function() {
|
||
|
var c = _.clone(this.attributes);
|
||
|
|
||
|
var d = {
|
||
|
kind: 'gmapsbase',
|
||
|
options: c,
|
||
|
order: c.order
|
||
|
};
|
||
|
|
||
|
if(c.id !== undefined) {
|
||
|
d.id = c.id;
|
||
|
}
|
||
|
return d;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* extend wms layer for data serialization
|
||
|
*/
|
||
|
cdb.admin.WMSLayer = cdb.geo.WMSLayer.extend({
|
||
|
|
||
|
clone: function() {
|
||
|
return new cdb.admin.WMSLayer(_.clone(this.attributes));
|
||
|
},
|
||
|
|
||
|
/*
|
||
|
* Create className from the urlTemplate of the basemap
|
||
|
*/
|
||
|
_generateClassName: function(urlTemplate) {
|
||
|
if (urlTemplate) {
|
||
|
var className = urlTemplate;
|
||
|
|
||
|
if (className && parseInt(className) && _.isNumber(parseInt(className))) {
|
||
|
className = "w" + className;
|
||
|
}
|
||
|
|
||
|
return className.replace(/\s+/g, '').replace(/[^a-zA-Z_0-9 ]/g, "").toLowerCase();
|
||
|
|
||
|
} else return "";
|
||
|
},
|
||
|
|
||
|
parse: function(data) {
|
||
|
|
||
|
var self = this;
|
||
|
var c = {};
|
||
|
|
||
|
_.extend(c, data.options, {
|
||
|
id: data.id,
|
||
|
className: self._generateClassName(data.options.layers),
|
||
|
type: 'WMS',
|
||
|
order: data.order,
|
||
|
parent_id: data.parent_id
|
||
|
});
|
||
|
|
||
|
return c;
|
||
|
},
|
||
|
|
||
|
toJSON: function() {
|
||
|
var c = _.clone(this.attributes);
|
||
|
|
||
|
var d = {
|
||
|
kind: 'wms',
|
||
|
options: c,
|
||
|
order: c.order
|
||
|
};
|
||
|
|
||
|
if(c.id !== undefined) {
|
||
|
d.id = c.id;
|
||
|
}
|
||
|
return d;
|
||
|
}
|
||
|
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* extend plain layer for data serialization
|
||
|
*/
|
||
|
cdb.admin.PlainLayer = cdb.geo.PlainLayer.extend({
|
||
|
|
||
|
parse: function(data) {
|
||
|
var c = {};
|
||
|
_.extend(c, data.options, {
|
||
|
id: data.id,
|
||
|
type: 'Plain',
|
||
|
order: data.order,
|
||
|
parent_id: data.parent_id
|
||
|
});
|
||
|
return c;
|
||
|
},
|
||
|
|
||
|
toJSON: function() {
|
||
|
var c = _.clone(this.attributes);
|
||
|
|
||
|
var d = {
|
||
|
kind: 'background',
|
||
|
options: c,
|
||
|
order: c.order
|
||
|
};
|
||
|
|
||
|
if(c.id !== undefined) {
|
||
|
d.id = c.id;
|
||
|
}
|
||
|
return d;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* extend tiled layer to adapt serialization
|
||
|
*/
|
||
|
cdb.admin.TileLayer = cdb.geo.TileLayer.extend({
|
||
|
|
||
|
clone: function() {
|
||
|
return new cdb.admin.TileLayer(_.clone(this.attributes));
|
||
|
},
|
||
|
|
||
|
/*
|
||
|
* Create className from the urlTemplate of the basemap
|
||
|
*/
|
||
|
_generateClassName: function(urlTemplate) {
|
||
|
if (urlTemplate) {
|
||
|
return urlTemplate.replace(/\s+/g, '').replace(/[^a-zA-Z_0-9 ]/g, "").toLowerCase();
|
||
|
} else return "";
|
||
|
},
|
||
|
|
||
|
parse: function(data) {
|
||
|
var self = this;
|
||
|
var c = {};
|
||
|
|
||
|
_.extend(c, data.options, {
|
||
|
id: data.id,
|
||
|
className: self._generateClassName(data.options.urlTemplate),
|
||
|
type: 'Tiled',
|
||
|
order: data.order,
|
||
|
parent_id: data.parent_id
|
||
|
});
|
||
|
|
||
|
return c;
|
||
|
},
|
||
|
|
||
|
toJSON: function() {
|
||
|
var c = _.clone(this.attributes);
|
||
|
|
||
|
var d = {
|
||
|
kind: 'tiled',
|
||
|
options: c,
|
||
|
order: c.order
|
||
|
};
|
||
|
|
||
|
if(c.id !== undefined) {
|
||
|
d.id = c.id;
|
||
|
}
|
||
|
return d;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* validateTemplateURL - Validates current urlTemplate of layer.
|
||
|
*
|
||
|
* @param {Object} callbacks with success and error functions defined to be called depending on validation outcome.
|
||
|
*/
|
||
|
validateTemplateURL: function(callbacks) {
|
||
|
var subdomains = ['a', 'b', 'c'];
|
||
|
var image = new Image();
|
||
|
image.onload = callbacks.success;
|
||
|
image.onerror = callbacks.error;
|
||
|
image.src = this.get('urlTemplate').replace(/\{s\}/g, function() {
|
||
|
return subdomains[Math.floor(Math.random() * 3)];
|
||
|
})
|
||
|
.replace(/\{x\}/g, '0')
|
||
|
.replace(/\{y\}/g, '0')
|
||
|
.replace(/\{z\}/g, '0');
|
||
|
}
|
||
|
|
||
|
}, {
|
||
|
|
||
|
/**
|
||
|
* @param {String} url
|
||
|
* @param {Boolean} tms
|
||
|
* @return {cdb.admin.TileLayer}
|
||
|
*/
|
||
|
byCustomURL: function(url, tms) {
|
||
|
// Minimal test for "valid URL" w/o having to complicate it with regex
|
||
|
if (url && url.indexOf('/') === -1) throw new TypeError('invalid URL');
|
||
|
|
||
|
// Only lowercase the placeholder variables, since the URL may contain case-sensitive data (e.g. API keys and such)
|
||
|
url = url.replace(/\{S\}/g, "{s}")
|
||
|
.replace(/\{X\}/g, "{x}")
|
||
|
.replace(/\{Y\}/g, "{y}")
|
||
|
.replace(/\{Z\}/g, "{z}");
|
||
|
|
||
|
var layer = new cdb.admin.TileLayer({
|
||
|
urlTemplate: url,
|
||
|
attribution: null,
|
||
|
maxZoom: 21,
|
||
|
minZoom: 0,
|
||
|
name: '',
|
||
|
tms: tms
|
||
|
});
|
||
|
layer.set('className', layer._generateClassName(url));
|
||
|
|
||
|
return layer;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
cdb.admin.TorqueLayer = cdb.admin.CartoDBLayer.extend({
|
||
|
|
||
|
/*parse: function(data, options) {
|
||
|
var c = cdb.admin.CartoDBLayer.prototype.parse.call(this, data, options);
|
||
|
c.type = 'torque';
|
||
|
return c;
|
||
|
}*/
|
||
|
|
||
|
});
|
||
|
|
||
|
cdb.admin.Layers = cdb.geo.Layers.extend({
|
||
|
|
||
|
_DATA_LAYERS: ['CartoDB', 'torque'],
|
||
|
|
||
|
// the model class works here like a factory
|
||
|
// depending on the kind of layer creates a
|
||
|
// type of layer or other
|
||
|
model: function(attrs, options) {
|
||
|
var typeClass = {
|
||
|
'Tiled': cdb.admin.TileLayer,
|
||
|
'CartoDB': cdb.admin.CartoDBLayer,
|
||
|
'Plain': cdb.admin.PlainLayer,
|
||
|
'GMapsBase': cdb.admin.GMapsBaseLayer,
|
||
|
'WMS': cdb.admin.WMSLayer,
|
||
|
'torque': cdb.admin.CartoDBLayer
|
||
|
};
|
||
|
var typeMap = {
|
||
|
'Layer::Tiled': 'Tiled',
|
||
|
'Layer::Carto': 'CartoDB',
|
||
|
'Layer::Background': 'Plain',
|
||
|
'tiled': 'Tiled',
|
||
|
'carto': 'CartoDB',
|
||
|
'wms': 'WMS',
|
||
|
'background': 'Plain',
|
||
|
'gmapsbase': 'GMapsBase',
|
||
|
'torque': 'torque'
|
||
|
};
|
||
|
|
||
|
return new typeClass[typeMap[attrs.kind]](attrs, options);
|
||
|
},
|
||
|
|
||
|
initialize: function() {
|
||
|
this.bind('change:order', function() {
|
||
|
if (!this._isSorted()) this.sort();
|
||
|
});
|
||
|
cdb.geo.Layers.prototype.initialize.call(this);
|
||
|
},
|
||
|
|
||
|
add: function(models, options) {
|
||
|
return Backbone.Collection.prototype.add.apply(this, arguments);
|
||
|
},
|
||
|
|
||
|
getTorqueLayers: function() {
|
||
|
return this.where({ type: 'torque' });
|
||
|
},
|
||
|
|
||
|
getTiledLayers: function() {
|
||
|
return this.where({ type: 'Tiled' });
|
||
|
},
|
||
|
|
||
|
// given layer model returns the index inside the layer definition
|
||
|
getLayerDefIndex: function(layer) {
|
||
|
var cartodbLayers = this.getLayersByType('CartoDB');
|
||
|
if(!cartodbLayers.length) return -1;
|
||
|
for(var i = 0, c = 0; i < cartodbLayers.length; ++i) {
|
||
|
if(cartodbLayers[i].get('visible')) {
|
||
|
if(cartodbLayers[i].cid === layer.cid) {
|
||
|
return c;
|
||
|
}
|
||
|
++c;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
},
|
||
|
|
||
|
getLayerDef: function() {
|
||
|
var cartodbLayers = this.getLayersByType('CartoDB');
|
||
|
var layerDef = {
|
||
|
version:'1.0.1',
|
||
|
layers: []
|
||
|
};
|
||
|
|
||
|
for(var i = 0; i < cartodbLayers.length; ++i) {
|
||
|
if(cartodbLayers[i].get('visible')) {
|
||
|
layerDef.layers.push(cartodbLayers[i].getLayerDef());
|
||
|
}
|
||
|
}
|
||
|
return layerDef;
|
||
|
},
|
||
|
|
||
|
/** return non-base layers */
|
||
|
getDataLayers: function() {
|
||
|
var self = this;
|
||
|
return this.filter(function(lyr) {
|
||
|
return _.contains(self._DATA_LAYERS, lyr.get('type'));
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/** without non-base layers */
|
||
|
getTotalDataLayers: function() {
|
||
|
return this.getDataLayers().length;
|
||
|
},
|
||
|
|
||
|
/** without non-base layers */
|
||
|
getTotalDataLegends: function() {
|
||
|
var self = this;
|
||
|
return this.filter(function(lyr) {
|
||
|
return _.contains(self._DATA_LAYERS, lyr.get('type')) &&
|
||
|
lyr.get('legend') &&
|
||
|
lyr.get('legend').type &&
|
||
|
lyr.get('legend').type.toLowerCase() !== "none";
|
||
|
}).length;
|
||
|
},
|
||
|
|
||
|
getLayersByType: function(type) {
|
||
|
if (!type || type === '' ) {
|
||
|
cdb.log.info("a layer type is necessary to get layers");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return this.filter(function(lyr) {
|
||
|
return lyr.get('type') === type;
|
||
|
});
|
||
|
},
|
||
|
|
||
|
isLayerOnTopOfDataLayers: function(layer) {
|
||
|
var dataLayerOnTop = this.getDataLayers().splice(-1)[0];
|
||
|
return dataLayerOnTop.cid === layer.cid;
|
||
|
},
|
||
|
|
||
|
url: function(method) {
|
||
|
var version = cdb.config.urlVersion('layer', method);
|
||
|
return '/api/' + version + '/maps/' + this.map.id + '/layers';
|
||
|
},
|
||
|
|
||
|
parse: function(data) {
|
||
|
return data.layers;
|
||
|
},
|
||
|
|
||
|
saveLayers: function(opts) {
|
||
|
opts = opts || {};
|
||
|
this.save(null, opts);
|
||
|
},
|
||
|
|
||
|
save: function(attrs, opts) {
|
||
|
Backbone.sync('update', this, opts);
|
||
|
},
|
||
|
|
||
|
toJSON: function(options) {
|
||
|
// We can't use the default toJSON because it uses this.map(function(){...})
|
||
|
// function within it but we override it using map containing all map stuff there.
|
||
|
// And we have to send all layers data within a variable called layers.
|
||
|
var array = _.map(this.models, function(model) {
|
||
|
return model.toJSON(options);
|
||
|
});
|
||
|
|
||
|
return { layers: array }
|
||
|
},
|
||
|
|
||
|
clone: function(layers) {
|
||
|
layers = layers || new cdb.admin.Layers();
|
||
|
this.each(function(layer) {
|
||
|
if(layer.clone) {
|
||
|
var lyr = layer.clone();
|
||
|
lyr.unset('id');
|
||
|
layers.add(lyr);
|
||
|
} else {
|
||
|
var attrs = _.clone(layer.attributes);
|
||
|
delete attrs.id;
|
||
|
layers.add(attrs);
|
||
|
}
|
||
|
});
|
||
|
return layers;
|
||
|
},
|
||
|
|
||
|
_isSorted: function() {
|
||
|
var sorted = true;
|
||
|
|
||
|
var layers = _(this.models).map(function(m) {
|
||
|
return { cid: m.cid, order: m.get('order')}
|
||
|
});
|
||
|
|
||
|
layers.sort(function(a, b) {
|
||
|
return a.order - b.order;
|
||
|
})
|
||
|
|
||
|
return _.isEqual(
|
||
|
_(layers).map(function(m) { return m.cid; }),
|
||
|
_(this.models).map(function(m) { return m.cid; })
|
||
|
)
|
||
|
},
|
||
|
|
||
|
moveLayer: function(movingLayer, options) {
|
||
|
options = options || {};
|
||
|
var newIndex = options.to;
|
||
|
var layerAtNewIndex = this.at(newIndex);
|
||
|
movingLayer.set('order', layerAtNewIndex.get('order'), { silent: true });
|
||
|
|
||
|
// Remove and add the layer again at the new position
|
||
|
this.remove(movingLayer, { silent: true });
|
||
|
this.add(movingLayer, { at: newIndex, silent: true });
|
||
|
|
||
|
// Update the order of all layers
|
||
|
for (var i = 0; i < this.size(); i++) {
|
||
|
var layer = this.at(i);
|
||
|
layer.set('order', i);
|
||
|
}
|
||
|
|
||
|
this.trigger('reset');
|
||
|
this.saveLayers({
|
||
|
complete: options.complete,
|
||
|
error: function() {
|
||
|
throw 'Error saving layers after moving them'
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* this is a specialization of generic map prepared to hold two layers:
|
||
|
* - a base layer
|
||
|
* - a data layer which contains the table data
|
||
|
*
|
||
|
* cartodb only supports one data layer per map so this will change when
|
||
|
* that changes
|
||
|
*/
|
||
|
|
||
|
cdb.admin.Map = cdb.geo.Map.extend({
|
||
|
|
||
|
urlRoot: '/api/v1/maps',
|
||
|
|
||
|
initialize: function() {
|
||
|
this.constructor.__super__.initialize.apply(this);
|
||
|
this.sync = Backbone.delayedSaveSync(Backbone.syncAbort, 500);
|
||
|
this.bind('change:id', this._fetchLayers, this);
|
||
|
|
||
|
this.layers = new cdb.admin.Layers();
|
||
|
this.layers.map = this;
|
||
|
this.layers.bind('reset add change', this._layersChanged, this);
|
||
|
this.layers.bind('reset add remove change:attribution', this._updateAttributions, this);
|
||
|
},
|
||
|
|
||
|
saveLayers: function(opts) {
|
||
|
opts = opts || {};
|
||
|
var none = function() {}
|
||
|
this.layers.saveLayers({
|
||
|
success: opts.success || none,
|
||
|
error: opts.error || none
|
||
|
});
|
||
|
},
|
||
|
|
||
|
_layersChanged: function() {
|
||
|
if(this.layers.size() >= 1) {
|
||
|
this._adjustZoomtoLayer(this.layers.at(0));
|
||
|
if(this.layers.size() >= 2) {
|
||
|
this.set({ dataLayer: this.layers.at(1) });
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// fetch related layers
|
||
|
_fetchLayers: function() {
|
||
|
this.layers.fetch();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* link to a table
|
||
|
*/
|
||
|
relatedTo: function(table) {
|
||
|
this.table = table;
|
||
|
this.table.bind('change:map_id', this._fetchOrCreate, this);
|
||
|
},
|
||
|
|
||
|
parse: function(data) {
|
||
|
data.bounding_box_ne = JSON.parse(data.bounding_box_ne);
|
||
|
data.bounding_box_sw = JSON.parse(data.bounding_box_sw);
|
||
|
data.view_bounds_ne = JSON.parse(data.view_bounds_ne);
|
||
|
data.view_bounds_sw = JSON.parse(data.view_bounds_sw);
|
||
|
data.center = JSON.parse(data.center);
|
||
|
return data;
|
||
|
},
|
||
|
|
||
|
_fetchOrCreate: function() {
|
||
|
var self = this;
|
||
|
var map_id = this.table.get('map_id');
|
||
|
if(!map_id) {
|
||
|
this.create();
|
||
|
} else {
|
||
|
this.set({ id: map_id });
|
||
|
this.fetch({
|
||
|
error: function() {
|
||
|
cdb.log.info("creating map for table");
|
||
|
self.create();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* change base layer and save all the layers to preserve the order
|
||
|
*/
|
||
|
setBaseLayer: function(baseLayer) {
|
||
|
this.trigger('savingLayers');
|
||
|
|
||
|
// Check if the selected base layer is already selected
|
||
|
if (this.isBaseLayerAdded(baseLayer)) {
|
||
|
this.trigger('savingLayersFinish');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
var self = this;
|
||
|
var newBaseLayer = baseLayer;
|
||
|
var currentBaseLayer = this.layers.at(0);
|
||
|
var newBaseLayerHasLabels = newBaseLayer.get('labels') && newBaseLayer.get('labels').urlTemplate;
|
||
|
|
||
|
// Sets the base layer
|
||
|
var options = {
|
||
|
success: function() {
|
||
|
if (!newBaseLayerHasLabels) {
|
||
|
self.trigger('savingLayersFinish');
|
||
|
}
|
||
|
},
|
||
|
error: function() {
|
||
|
cdb.log.error("error changing the basemap");
|
||
|
self.trigger('savingLayersFinish');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (currentBaseLayer) {
|
||
|
if (currentBaseLayer.get('type') === newBaseLayer.get('type')) {
|
||
|
this._updateBaseLayer(currentBaseLayer, newBaseLayer, options);
|
||
|
} else {
|
||
|
this._replaceBaseLayer(currentBaseLayer, newBaseLayer, options);
|
||
|
}
|
||
|
} else {
|
||
|
this._addBaseLayer(newBaseLayer, options);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Adds/updates/removes layer with labels at the top
|
||
|
options.success = function() {
|
||
|
self.trigger('savingLayersFinish');
|
||
|
}
|
||
|
|
||
|
if (newBaseLayerHasLabels) {
|
||
|
if (this._hasLabelsLayer()) {
|
||
|
this._updateLabelsLayer(newBaseLayer, options);
|
||
|
} else {
|
||
|
this._addLabelsLayer(newBaseLayer, options);
|
||
|
}
|
||
|
} else {
|
||
|
if (this._hasLabelsLayer()) {
|
||
|
this._destroyLabelsLayer(options);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return newBaseLayer;
|
||
|
},
|
||
|
|
||
|
_updateBaseLayer: function(currentBaseLayer, newBaseLayer, opts) {
|
||
|
var newAttributes = _.extend(_.clone(newBaseLayer.attributes), {
|
||
|
id: currentBaseLayer.get('id'),
|
||
|
order: currentBaseLayer.get('order')
|
||
|
});
|
||
|
currentBaseLayer.clear({ silent: true });
|
||
|
currentBaseLayer.set(newAttributes);
|
||
|
currentBaseLayer.save(null, opts);
|
||
|
},
|
||
|
|
||
|
_replaceBaseLayer: function(currentBaseLayer, newBaseLayer, opts) {
|
||
|
this.layers.remove(currentBaseLayer);
|
||
|
newBaseLayer.set({
|
||
|
id: currentBaseLayer.get('id'),
|
||
|
order: currentBaseLayer.get('order')
|
||
|
});
|
||
|
this.layers.add(newBaseLayer, { at: 0 });
|
||
|
newBaseLayer.save(null, opts);
|
||
|
},
|
||
|
|
||
|
_addBaseLayer: function(newBaseLayer, opts) {
|
||
|
this.layers.add(newBaseLayer, { at: 0 });
|
||
|
newBaseLayer.save(null, opts);
|
||
|
},
|
||
|
|
||
|
_hasLabelsLayer: function() {
|
||
|
return this.layers.size() > 1 && this.layers.last().get('type') === 'Tiled';
|
||
|
},
|
||
|
|
||
|
_updateLabelsLayer: function(baseLayer, opts) {
|
||
|
var labelsLayer = this.layers.last();
|
||
|
labelsLayer.set({
|
||
|
name: this._labelsLayerNameFromBaseLayer(baseLayer),
|
||
|
urlTemplate: baseLayer.get('labels').urlTemplate,
|
||
|
attribution: baseLayer.get('attribution'),
|
||
|
minZoom: baseLayer.get('minZoom'),
|
||
|
maxZoom: baseLayer.get('maxZoom'),
|
||
|
subdomains: baseLayer.get('subdomains')
|
||
|
});
|
||
|
labelsLayer.save(null, opts);
|
||
|
},
|
||
|
|
||
|
_addLabelsLayer: function(baseLayer, opts) {
|
||
|
this.layers.add({
|
||
|
name: this._labelsLayerNameFromBaseLayer(baseLayer),
|
||
|
urlTemplate: baseLayer.get('labels').urlTemplate,
|
||
|
attribution: baseLayer.get('attribution'),
|
||
|
minZoom: baseLayer.get('minZoom'),
|
||
|
maxZoom: baseLayer.get('maxZoom'),
|
||
|
subdomains: baseLayer.get('subdomains'),
|
||
|
kind: "tiled"
|
||
|
});
|
||
|
var labelsLayer = this.layers.last();
|
||
|
labelsLayer.save(null, opts);
|
||
|
},
|
||
|
|
||
|
_destroyLabelsLayer: function(opts) {
|
||
|
this.layers.last().destroy(opts);
|
||
|
},
|
||
|
|
||
|
_labelsLayerNameFromBaseLayer: function(baseLayer) {
|
||
|
return baseLayer.get('name') + " Labels";
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* the first version of cartodb contains one single layer
|
||
|
* per table with information.
|
||
|
*/
|
||
|
addDataLayer: function(lyr) {
|
||
|
this.addLayer(lyr);
|
||
|
this.set({ dataLayer: lyr });
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* create a new map. this is a helper to use from javascript command line
|
||
|
*/
|
||
|
create: function() {
|
||
|
this.unset('id');
|
||
|
this.set({ table_id: this.table.id });
|
||
|
this.save();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* enable save map each time the viewport changes
|
||
|
* not working
|
||
|
*/
|
||
|
autoSave: function() {
|
||
|
this.bind('change:center', this.save);
|
||
|
this.bind('change:zoom', this.save);
|
||
|
},
|
||
|
|
||
|
toJSON: function() {
|
||
|
var c = _.clone(this.attributes);
|
||
|
// data layer is a helper to work in local
|
||
|
delete c.dataLayer;
|
||
|
return c;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* change provider and optionally baselayer
|
||
|
*/
|
||
|
changeProvider: function(provider, baselayer) {
|
||
|
var self = this;
|
||
|
|
||
|
if(baselayer && baselayer.get('id')) {
|
||
|
cdb.log.error("the baselayer should not be saved in the server");
|
||
|
return;
|
||
|
}
|
||
|
var _changeBaseLayer = function() {
|
||
|
if(baselayer) {
|
||
|
self.setBaseLayer(baselayer);
|
||
|
}
|
||
|
}
|
||
|
if(this.get('provider') !== provider) {
|
||
|
this.save({ provider: provider }, {
|
||
|
success: function() {
|
||
|
_changeBaseLayer();
|
||
|
self.change();
|
||
|
},
|
||
|
error: function(e, resp) {
|
||
|
self.error(_t('error switching base layer'), resp);
|
||
|
},
|
||
|
silent: true
|
||
|
});
|
||
|
} else {
|
||
|
_changeBaseLayer();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
isProviderGmaps: function() {
|
||
|
var provider = this.get("provider");
|
||
|
return provider && provider.toLowerCase().indexOf("googlemaps") !== -1
|
||
|
},
|
||
|
|
||
|
clone: function(m) {
|
||
|
m = m || new cdb.admin.Map();
|
||
|
var attrs = _.clone(this.attributes)
|
||
|
delete attrs.id;
|
||
|
m.set(attrs);
|
||
|
|
||
|
// clone lists
|
||
|
m.set({
|
||
|
center: _.clone(this.attributes.center),
|
||
|
bounding_box_sw: _.clone(this.attributes.bounding_box_sw),
|
||
|
bounding_box_ne: _.clone(this.attributes.bounding_box_ne),
|
||
|
view_bounds_sw: _.clone(this.attributes.view_bounds_sw),
|
||
|
view_bounds_ne: _.clone(this.attributes.view_bounds_ne),
|
||
|
attribution: _.clone(this.attributes.attribution)
|
||
|
});
|
||
|
|
||
|
// layers
|
||
|
this.layers.clone(m.layers);
|
||
|
m.layers.map = m;
|
||
|
|
||
|
return m;
|
||
|
},
|
||
|
|
||
|
notice: function(msg, type, timeout) {
|
||
|
this.trigger('notice', msg, type, timeout);
|
||
|
},
|
||
|
|
||
|
error: function(msg, resp) {
|
||
|
var err = resp && JSON.parse(resp.responseText).errors[0];
|
||
|
this.trigger('notice', msg + " " + err, 'error');
|
||
|
},
|
||
|
|
||
|
addCartodbLayerFromTable: function(tableName, userName, opts) {
|
||
|
opts = opts || {};
|
||
|
/*var newLayer = cdb.admin.CartoDBLayer.createDefaultLayerForTable(tableName, userName);
|
||
|
this.layers.add(newLayer);
|
||
|
newLayer.save(null, opts);
|
||
|
*/
|
||
|
|
||
|
var self = this;
|
||
|
var table = new cdb.admin.CartoDBTableMetadata({ id: tableName });
|
||
|
table.fetch({
|
||
|
success: function() {
|
||
|
// Get the layers for the map
|
||
|
var map = new cdb.admin.Map({ id: table.get('map_id') });
|
||
|
map.layers.bind('reset', function() {
|
||
|
var newLayer = map.layers.at(1).clone();
|
||
|
newLayer.unset('order');
|
||
|
|
||
|
function layerReady() {
|
||
|
newLayer.table.unbind('change:geometry_types', layerReady);
|
||
|
// when the layer is torque and there are other torque layers in the map, switch it to a
|
||
|
// simple visualization layer
|
||
|
if (newLayer.wizard_properties.get('type') === 'torque' && self.layers.getTorqueLayers().length) {
|
||
|
newLayer.wizard_properties.active('polygon');
|
||
|
}
|
||
|
// wait: true is used to make sure the layer is not added until confirmed it was added successfully
|
||
|
// pass opts for success/error callbacks to be triggered as expected
|
||
|
self.layers.create(newLayer, _.extend({ wait: true }, opts));
|
||
|
}
|
||
|
|
||
|
// Wait until the layer is totally ready in order to add it to the layers and save it
|
||
|
if (newLayer.isTableLoaded()) {
|
||
|
layerReady();
|
||
|
} else {
|
||
|
newLayer.table.bind('change:geometry_types', layerReady);
|
||
|
newLayer.table.data().fetch();
|
||
|
}
|
||
|
});
|
||
|
map.layers.fetch();
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
|
||
|
// moves the map to interval [-180, 180]
|
||
|
clamp: function() {
|
||
|
var fmod = function (a,b) { return Number((a - (Math.floor(a / b) * b)).toPrecision(8)); };
|
||
|
var latlng = this.get('center');
|
||
|
var lon = latlng[1];
|
||
|
if(lon < -180 || lon > 180) {
|
||
|
lon = fmod(180 + lon, 360) - 180;
|
||
|
this.set('center', [latlng[0], lon]);
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
});
|