This commit is contained in:
zhongjin
2020-06-15 12:07:54 +08:00
parent 610ed21a90
commit a96ef233c9
444 changed files with 0 additions and 0 deletions
@@ -0,0 +1,272 @@
/*
* common functions for cartodb connector
*/
function CartoDBLayerCommon() {
this.visible = true;
}
CartoDBLayerCommon.prototype = {
// the way to show/hidelayer is to set opacity
// removing the interactivty at the same time
show: function() {
this.setOpacity(this.options.previous_opacity === undefined ? 0.99: this.options.previous_opacity);
delete this.options.previous_opacity;
this._interactionDisabled = false;
this.visible = true;
},
hide: function() {
if(this.options.previous_opacity == undefined) {
this.options.previous_opacity = this.options.opacity;
}
this.setOpacity(0);
// disable here interaction for all the layers
this._interactionDisabled = true;
this.visible = false;
},
toggle: function() {
this.isVisible() ? this.hide() : this.show();
return this.isVisible();
},
/**
* Returns if the layer is visible or not
*/
isVisible: function() {
return this.visible;
},
/**
* Active or desactive interaction
* @params enable {Number} layer number
* @params layer {Boolean} Choose if wants interaction or not
*/
setInteraction: function(layer, b) {
// shift arguments to maintain caompatibility
if(b == undefined) {
b = layer;
layer = 0;
}
var layerInteraction;
this.interactionEnabled[layer] = b;
if(!b) {
layerInteraction = this.interaction[layer];
if(layerInteraction) {
layerInteraction.remove();
this.interaction[layer] = null;
}
} else {
// if urls is null it means that setInteraction will be called
// when the layergroup token was recieved, then the real interaction
// layer will be created
if(this.urls) {
// generate the tilejson from the urls. wax needs it
var layer_index = this.getLayerIndexByNumber(+layer);
var tilejson = this._tileJSONfromTiles(layer_index, this.urls);
// remove previous
layerInteraction = this.interaction[layer];
if(layerInteraction) {
layerInteraction.remove();
}
var self = this;
// add the new one
this.interaction[layer] = this.interactionClass()
.map(this.options.map)
.tilejson(tilejson)
.on('on', function(o) {
if (self._interactionDisabled) return;
o.layer = +layer;
self._manageOnEvents(self.options.map, o);
})
.on('off', function(o) {
if (self._interactionDisabled) return;
o = o || {}
o.layer = +layer;
self._manageOffEvents(self.options.map, o);
});
}
}
return this;
},
setOptions: function (opts) {
if (typeof opts != "object" || opts.length) {
throw new Error(opts + ' options must be an object');
}
_.extend(this.options, opts);
var opts = this.options;
this.options.query = this.options.query || "select * from " + this.options.table_name;
if(this.options.query_wrapper) {
this.options.query = _.template(this.options.query_wrapper)({ sql: this.options.query });
}
this.setSilent(true);
opts.interaction && this.setInteraction(opts.interaction);
opts.opacity && this.setOpacity(opts.opacity);
opts.query && this.setQuery(opts.query.replace(/\{\{table_name\}\}/g, this.options.table_name));
opts.tile_style && this.setCartoCSS(opts.tile_style.replace(new RegExp( opts.table_name, "g"), "layer0"));
opts.cartocss && this.setCartoCSS(opts.cartocss);
opts.interactivity && this.setInteractivity(opts.interactivity);
opts.visible ? this.show() : this.hide();
this.setSilent(false);
this._definitionUpdated();
},
_getLayerDefinition: function() {
// set params
var params = {};
var opts = this.options;
var sql, cartocss, cartocss_version;
sql = opts.query || "select * from " + opts.table_name;
if(opts.query_wrapper) {
sql = _.template(opts.query_wrapper)({ sql: sql });
}
cartocss = opts.tile_style;
cartocss_version = opts.cartocss_version || '2.1.0';
// extra_params?
for (var _param in opts.extra_params) {
var v = opts.extra_params[_param]
params[_param] = v.replace ? v.replace(/\{\{table_name\}\}/g, opts.table_name): v;
}
sql = sql.replace(/\{\{table_name\}\}/g, opts.table_name);
cartocss = cartocss.replace(/\{\{table_name\}\}/g, opts.table_name);
cartocss = cartocss.replace(new RegExp( opts.table_name, "g"), "layer0");
return {
sql: sql,
cartocss: cartocss,
cartocss_version: cartocss_version,
params: params,
interactivity: opts.interactivity
}
},
error: function(e) {
//console.log(e.error);
},
tilesOk: function() {
},
_clearInteraction: function() {
for(var i in this.interactionEnabled) {
if (this.interactionEnabled.hasOwnProperty(i) &&
this.interactionEnabled[i]) {
this.setInteraction(i, false);
}
}
},
_reloadInteraction: function() {
for(var i in this.interactionEnabled) {
if (this.interactionEnabled.hasOwnProperty(i) &&
this.interactionEnabled[i]) {
this.setInteraction(i, false);
this.setInteraction(i, true);
}
}
},
/**
* Check the tiles
*/
_checkTiles: function() {
var xyz = {z: 4, x: 6, y: 6}
, self = this
, img = new Image()
, urls = this._tileJSON()
getTiles(function(urls) {
var grid_url = urls.tiles[0]
.replace(/\{z\}/g,xyz.z)
.replace(/\{x\}/g,xyz.x)
.replace(/\{y\}/g,xyz.y);
this.options.ajax({
method: "get",
url: grid_url,
crossDomain: true,
success: function() {
self.tilesOk();
clearTimeout(timeout)
},
error: function(xhr, msg, data) {
clearTimeout(timeout);
self.error(xhr.responseText && JSON.parse(xhr.responseText));
}
});
});
var timeout = setTimeout(function(){
clearTimeout(timeout);
self.error("tile timeout");
}, 30000);
}
};
cdb.geo.common = {};
cdb.geo.common.CartoDBLogo = {
/**
* Check if any class already exists
* in the provided container
*/
isWadusAdded: function(container, className) {
// Check if any cartodb-logo exists within container
var a = [];
var re = new RegExp('\\b' + className + '\\b');
var els = container.getElementsByTagName("*");
for(var i=0,j=els.length; i<j; i++)
if(re.test(els[i].className))a.push(els[i]);
return a.length > 0;
},
/**
* Check if browser supports retina images
*/
isRetinaBrowser: function() {
return ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
window.matchMedia('(min-resolution:144dpi)').matches);
},
/**
* Add Cartodb logo
* It needs a position, timeout if it is needed and the container where to add it
*/
addWadus: function(position, timeout, container) {
var self = this;
setTimeout(function() {
if (!self.isWadusAdded(container, 'cartodb-logo')) {
var cartodb_link = document.createElement("div");
var is_retina = self.isRetinaBrowser();
cartodb_link.setAttribute('class','cartodb-logo');
cartodb_link.setAttribute('style',"position:absolute; bottom:0; left:0; display:block; border:none; z-index:1000000;");
var protocol = location.protocol.indexOf('https') === -1 ? 'http': 'https';
var link = cdb.config.get('cartodb_logo_link');
cartodb_link.innerHTML = "<a href='" + link + "' target='_blank'><img width='71' height='29' src='" + protocol + "://cartodb.s3.amazonaws.com/static/new_logo" + (is_retina ? '@2x' : '') + ".png' style='position:absolute; bottom:" +
( position.bottom || 0 ) + "px; left:" + ( position.left || 0 ) + "px; display:block; width:71px!important; height:29px!important; border:none; outline:none;' alt='CARTO' title='CARTO' />";
container.appendChild(cartodb_link);
}
},( timeout || 0 ));
}
};
@@ -0,0 +1,97 @@
/**
* geocoders for different services
*
* should implement a function called geocode the gets
* the address and call callback with a list of placemarks with lat, lon
* (at least)
*/
cdb.geo.geocoder.YAHOO = {
keys: {
app_id: ''
},
geocode: function (address, callback) {
console.warn('YAHOO geocoding service is deprecated. Please use MAPBOX geocoding provider `cdb.geo.geocoder.MAPBOX`');
}
};
cdb.geo.geocoder.MAPZEN = {
keys: {
app_id: ''
},
geocode: function (address, callback) {
console.warn('MAPZEN geocoding service is deprecated. Please use MAPBOX geocoding provider `cdb.geo.geocoder.MAPBOX`');
}
};
cdb.geo.geocoder.NOKIA = {
keys: {
app_id: '',
app_code: ''
},
geocode: function (address, callback) {
console.warn('HERE geocoding service is deprecated. Please use MAPBOX geocoding provider `cdb.geo.geocoder.MAPBOX`');
}
};
cdb.geo.geocoder.MAPBOX = {
keys: {
access_token: 'pk.eyJ1IjoiY2FydG8tdGVhbSIsImEiOiJjamNseTl3ZzQwZnFkMndudnIydnJoMXZxIn0.HycQBkaaV7ZwLkHm5hEmfg',
},
TYPES : {
country: 'country',
region: 'region',
postcode: 'postal-area',
district: 'localadmin',
place: 'venue',
locality: 'locality',
neighborhood: 'neighbourhood',
address: 'address',
poi: 'venue',
'poi.landmark': 'venue'
},
geocode: function (address, callback) {
address = address.toLowerCase()
.replace(/é/g, 'e')
.replace(/á/g, 'a')
.replace(/í/g, 'i')
.replace(/ó/g, 'o')
.replace(/ú/g, 'u');
var protocol = '';
if (location.protocol.indexOf('http') === -1) {
protocol = 'http:';
}
$.getJSON(protocol + '//api.mapbox.com/geocoding/v5/mapbox.places/' + encodeURIComponent(address) + '.json?access_token=' + this.keys.access_token, function (response) {
callback(this._formatResponse(response));
}.bind(this));
},
// Transform a raw response into a array with the cartodb format
_formatResponse: function (rawResponse) {
if (!rawResponse.features.length) {
return [];
}
return [
{
lat: rawResponse.features[0].center[1],
lon: rawResponse.features[0].center[0],
type: this.TYPES[rawResponse.features[0].type] || 'default',
title: rawResponse.features[0].text,
}
];
}
};
@@ -0,0 +1,52 @@
cdb.geo.geocoder.MAPBOX = {
keys: {
access_token: 'pk.eyJ1IjoiY2FydG8tdGVhbSIsImEiOiJjamNseTl3ZzQwZnFkMndudnIydnJoMXZxIn0.HycQBkaaV7ZwLkHm5hEmfg'
},
TYPES: {
country: 'country',
region: 'region',
postcode: 'postal-area',
district: 'localadmin',
place: 'venue',
locality: 'locality',
neighborhood: 'neighbourhood',
address: 'address',
poi: 'venue',
'poi.landmark': 'venue'
},
geocode: function (address, callback) {
address = address.toLowerCase()
.replace(/é/g, 'e')
.replace(/á/g, 'a')
.replace(/í/g, 'i')
.replace(/ó/g, 'o')
.replace(/ú/g, 'u');
var protocol = '';
if (location.protocol.indexOf('http') === -1) {
protocol = 'http:';
}
var requestUrl = protocol + '//api.mapbox.com/geocoding/v5/mapbox.places-permanent/' + encodeURIComponent(address) + '.json?access_token=' + this.keys.access_token;
$.getJSON(requestUrl, function (response) {
callback(this._formatResponse(response));
}.bind(this));
},
// Transform a raw response into a array with the cartodb format
_formatResponse: function (rawResponse) {
if (!rawResponse.features.length) {
return [];
}
return [{
lat: rawResponse.features[0].center[1],
lon: rawResponse.features[0].center[0],
type: this.TYPES[rawResponse.features[0].type] || 'default',
title: rawResponse.features[0].text,
}];
}
};
@@ -0,0 +1,47 @@
/***** DEPRECATED *****/
cdb.geo.geocoder.MAPZEN = {
keys: {
app_id: 'mapzen-YfBeDWS'
},
geocode: function (address, callback) {
address = address.toLowerCase()
.replace(/é/g, 'e')
.replace(/á/g, 'a')
.replace(/í/g, 'i')
.replace(/ó/g, 'o')
.replace(/ú/g, 'u');
var protocol = '';
if (location.protocol.indexOf('http') === -1) {
protocol = 'http:';
}
$.getJSON(protocol + '//search.mapzen.com/v1/search?text=' + encodeURIComponent(address) + '&api_key=' + this.keys.app_id, function (data) {
var coordinates = [];
if (data && data.features && data.features.length > 0) {
var res = data.features;
for (var i in res) {
var r = res[i];
var position = {
lat: r.geometry.coordinates[1],
lon: r.geometry.coordinates[0]
};
if (r.properties.layer) {
position.type = r.properties.layer;
}
if (r.properties.label) {
position.title = r.properties.label;
}
coordinates.push(position);
}
}
if (callback) {
callback.call(this, coordinates);
}
});
}
};
@@ -0,0 +1,60 @@
cdb.geo.geocoder.NOKIA = {
keys: {
app_id: 'KuYppsdXZznpffJsKT24',
app_code: 'A7tBPacePg9Mj_zghvKt9Q'
},
geocode: function (address, callback) {
address = address.toLowerCase()
.replace(/é/g, 'e')
.replace(/á/g, 'a')
.replace(/í/g, 'i')
.replace(/ó/g, 'o')
.replace(/ú/g, 'u');
var protocol = '';
if (location.protocol.indexOf('http') === -1) {
protocol = 'http:';
}
$.getJSON(protocol + '//places.nlp.nokia.com/places/v1/discover/search/?q=' + encodeURIComponent(address) + '&app_id=' + this.keys.app_id + '&app_code=' + this.keys.app_code + '&Accept-Language=en-US&at=0,0&callback=?', function (data) {
var coordinates = [];
if (data && data.results && data.results.items && data.results.items.length > 0) {
var res = data.results.items;
for (var i in res) {
var r = res[i],
position;
position = {
lat: r.position[0],
lon: r.position[1]
};
if (r.bbox) {
position.boundingbox = {
north: r.bbox[3],
south: r.bbox[1],
east: r.bbox[2],
west: r.bbox[0]
};
}
if (r.category) {
position.type = r.category.id;
}
if (r.title) {
position.title = r.title;
}
coordinates.push(position);
}
}
if (callback) {
callback.call(this, coordinates);
}
});
}
};
@@ -0,0 +1,102 @@
cdb.geo.geocoder.TOMTOM = {
keys: {
api_key: 'vrZphqNRlSCiU0J0ky0tFQGd0Pfbjxhr'
},
TYPES: {
'Geography': 'region',
'Geography:Country': 'country',
'Geography:CountrySubdivision': 'region',
'Geography:CountrySecondarySubdivision': 'region',
'Geography:CountryTertiarySubdivision': 'region',
'Geography:Municipality': 'localadmin',
'Geography:MunicipalitySubdivision': 'locality',
'Geography:Neighbourhood': 'neighbourhood',
'Geography:PostalCodeArea': 'postal-area',
'Street': 'neighbourhood',
'Address Range': 'neighbourhood',
'Point Address': 'address',
'Cross Street': 'address',
'POI': 'venue'
},
geocode: function (address, callback) {
address = address.toLowerCase()
.replace(/é/g, 'e')
.replace(/á/g, 'a')
.replace(/í/g, 'i')
.replace(/ó/g, 'o')
.replace(/ú/g, 'u');
var protocol = '';
if (location.protocol.indexOf('http') === -1) {
protocol = 'http:';
}
var tomtomVersion = '2';
var url = protocol + '//api.tomtom.com/search/' + tomtomVersion + '/search/' + encodeURIComponent(address) + '.json?key=' + this.keys.api_key;
$.getJSON(url, function (response) {
callback(this._formatResponse(response));
}.bind(this));
},
/**
* Transform a tomtom geocoder response into an object more friendly for our search widget.
* @param {object} rawTomTomResponse - The raw tomtom geocoding response, {@see https://developer.tomtom.com/search-api/search-api-documentation-geocoding/geocode}
*/
_formatResponse: function (rawResponse) {
if (!rawResponse.results.length) {
return [];
}
var bestCandidate = rawResponse.results[0];
return [{
boundingbox: this._getBoundingBox(bestCandidate),
center: this._getCenter(bestCandidate),
type: this._getType(bestCandidate)
}];
},
/**
* TomTom returns { lon, lat } while we use [lat, lon]
*/
_getCenter: function (result) {
return [result.position.lat, result.position.lon];
},
/**
* Transform the feature type into a well known enum.
*/
_getType: function (result) {
var type = result.type;
if (this.TYPES[type]) {
if (type === 'Geography' && result.entityType) {
type = type + ':' + result.entityType;
}
return this.TYPES[type];
}
return 'default';
},
/**
* Transform the feature bbox into a carto.js well known format.
*/
_getBoundingBox: function (result) {
if (!result.viewport) {
return;
}
var upperLeft = result.viewport.topLeftPoint;
var bottomRight = result.viewport.btmRightPoint;
return {
south: bottomRight.lat,
west: upperLeft.lon,
north: upperLeft.lat,
east: bottomRight.lon
};
}
};
@@ -0,0 +1,54 @@
cdb.geo.geocoder.YAHOO = {
keys: {
app_id: 'nLQPTdTV34FB9L3yK2dCXydWXRv3ZKzyu_BdCSrmCBAM1HgGErsCyCbBbVP2Yg--'
},
geocode: function (address, callback) {
address = address.toLowerCase()
.replace(/é/g, 'e')
.replace(/á/g, 'a')
.replace(/í/g, 'i')
.replace(/ó/g, 'o')
.replace(/ú/g, 'u')
.replace(/ /g, '+');
var protocol = '';
if (location.protocol.indexOf('http') === -1) {
protocol = 'http:';
}
$.getJSON(protocol + '//query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent('SELECT * FROM json WHERE url="http://where.yahooapis.com/geocode?q=' + address + '&appid=' + this.keys.app_id + '&flags=JX"') + '&format=json&callback=?', function (data) {
var coordinates = [];
if (data && data.query && data.query.results && data.query.results.json && data.query.results.json.ResultSet && data.query.results.json.ResultSet.Found != "0") {
// Could be an array or an object |arg!
var res;
if (_.isArray(data.query.results.json.ResultSet.Results)) {
res = data.query.results.json.ResultSet.Results;
} else {
res = [data.query.results.json.ResultSet.Results];
}
for (var i in res) {
var r = res[i],
position;
position = {
lat: r.latitude,
lon: r.longitude
};
if (r.boundingbox) {
position.boundingbox = r.boundingbox;
}
coordinates.push(position);
}
}
callback(coordinates);
});
}
};
@@ -0,0 +1,29 @@
/**
* basic geometries, all of them based on geojson
*/
cdb.geo.Geometry = cdb.core.Model.extend({
isPoint: function() {
var type = this.get('geojson').type;
if(type && type.toLowerCase() === 'point')
return true;
return false;
}
});
cdb.geo.Geometries = Backbone.Collection.extend({});
/**
* create a geometry
* @param geometryModel geojson based geometry model, see cdb.geo.Geometry
*/
function GeometryView() { }
_.extend(GeometryView.prototype, Backbone.Events,{
edit: function() {
throw new Error("to be implemented");
}
});
@@ -0,0 +1,257 @@
(function() {
/**
* view for markers
*/
function PointView(geometryModel) {
var self = this;
// events to link
var events = [
'click',
'dblclick',
'mousedown',
'mouseover',
'mouseout',
'dragstart',
'drag',
'dragend'
];
this._eventHandlers = {};
this.model = geometryModel;
this.points = [];
var style = _.clone(geometryModel.get('style')) || {};
var iconAnchor = this.model.get('iconAnchor');
var icon = {
url: this.model.get('iconUrl') || cdb.config.get('assets_url') + '/images/layout/default_marker.png',
anchor: {
x: iconAnchor && iconAnchor[0] || 10,
y: iconAnchor && iconAnchor[1] || 10,
}
};
this.geom = new GeoJSON (
geometryModel.get('geojson'),
{
icon: icon,
raiseOnDrag: false,
crossOnDrag: false
}
);
// bind events
var i;
for(i = 0; i < events.length; ++i) {
var e = events[i];
google.maps.event.addListener(this.geom, e, self._eventHandler(e));
}
// link dragging
this.bind('dragend', function(e, pos) {
geometryModel.set({
geojson: {
type: 'Point',
// geojson is lng,lat
coordinates: [pos[1], pos[0]]
}
});
});
}
PointView.prototype = new GeometryView();
PointView.prototype._eventHandler = function(evtType) {
var self = this;
var h = this._eventHandlers[evtType];
if(!h) {
h = function(e) {
var latlng = e.latLng;
var s = [latlng.lat(), latlng.lng()];
self.trigger(evtType, e, s);
};
this._eventHandlers[evtType] = h;
}
return h;
};
PointView.prototype.edit = function(enable) {
this.geom.setDraggable(enable);
};
/**
* view for other geometries (polygons/lines)
*/
function PathView(geometryModel) {
var self = this;
// events to link
var events = [
'click',
'dblclick',
'mousedown',
'mouseover',
'mouseout',
];
this._eventHandlers = {};
this.model = geometryModel;
this.points = [];
var style = _.clone(geometryModel.get('style')) || {};
this.geom = new GeoJSON (
geometryModel.get('geojson'),
style
);
/*_.each(this.geom._layers, function(g) {
g.setStyle(geometryModel.get('style'));
g.on('edit', function() {
geometryModel.set('geojson', L.GeoJSON.toGeoJSON(self.geom));
}, self);
});
*/
_.bindAll(this, '_updateModel');
var self = this;
function bindPath(p) {
google.maps.event.addListener(p, 'insert_at', self._updateModel);
/*
google.maps.event.addListener(p, 'remove_at', this._updateModel);
google.maps.event.addListener(p, 'set_at', this._updateModel);
*/
}
// TODO: check this conditions
if(this.geom.getPaths) {
var paths = this.geom.getPaths();
if (paths && paths[0]) {
// More than one path
for(var i = 0; i < paths.length; ++i) {
bindPath(paths[i]);
}
} else {
// One path
bindPath(paths);
google.maps.event.addListener(this.geom, 'mouseup', this._updateModel);
}
} else {
// More than one path
if (this.geom.length) {
for(var i = 0; i < this.geom.length; ++i) {
bindPath(this.geom[i].getPath());
google.maps.event.addListener(this.geom[i], 'mouseup', this._updateModel);
}
} else {
// One path
bindPath(this.geom.getPath());
google.maps.event.addListener(this.geom, 'mouseup', this._updateModel);
}
}
/*for(var i = 0; i < events.length; ++i) {
var e = events[i];
this.geom.on(e, self._eventHandler(e));
}*/
}
PathView.prototype = new GeometryView();
PathView.getGeoJSON = function(geom, gType) {
var coordFn = {
'Polygon': 'getPath',
'MultiPolygon': 'getPath',
'LineString': 'getPath',
'MultiLineString': 'getPath',
'Point': 'getPosition',
'MultiPoint': 'getPosition'
};
function _coord(latlng) {
return [latlng.lng(), latlng.lat()];
}
function _coords(latlngs) {
var c = [];
for(var i = 0; i < latlngs.length; ++i) {
c.push(_coord(latlngs.getAt(i)));
}
return c;
}
// single
if(!geom.length || geom.length == 1) {
var g = geom.length ? geom[0]: geom;
var coords;
if(gType == 'Point') {
coords = _coord(g.getPosition());
} else if(gType == 'MultiPoint') {
coords = [_coord(g.getPosition())]
} else if(gType == 'Polygon') {
coords = [_coords(g.getPath())];
coords[0].push(_coord(g.getPath().getAt(0)));
} else if(gType == 'MultiPolygon') {
coords = [];
for(var p = 0; p < g.getPaths().length; ++p) {
var c = _coords(g.getPaths().getAt(p));
c.push(_coord(g.getPaths().getAt(p).getAt(0)));
coords.push(c);
}
coords = [coords]
} else if(gType == 'LineString') {
coords = _coords(g.getPath());
} else if(gType == 'MultiLineString') {
//TODO: redo
coords = [_coords(g.getPath())];
}
return {
type: gType,
coordinates: coords
}
} else {
// poly
var c = [];
for(var i = 0; i < geom.length; ++i) {
c.push(PathView.getGeoJSON(geom[i], gType).coordinates[0]);
}
return {
type: gType,
coordinates: c
}
}
}
PathView.prototype._updateModel = function(e) {
var self = this;
setTimeout(function() {
self.model.set('geojson', PathView.getGeoJSON(self.geom, self.model.get('geojson').type ));
}, 100)
}
PathView.prototype.edit = function(enable) {
var fn = enable ? 'enable': 'disable';
var g = this.geom.length ? this.geom: [this.geom];
for(var i = 0; i < g.length; ++i) {
g[i].setEditable(enable);
}
if(!enable) {
this.model.set('geojson', PathView.getGeoJSON(this.geom, this.model.get('geojson').type));
}
};
cdb.geo.gmaps = cdb.geo.gmaps || {};
cdb.geo.gmaps.PointView = PointView;
cdb.geo.gmaps.PathView = PathView;
})();
@@ -0,0 +1,338 @@
// if google maps is not defined do not load the class
if(typeof(google) != "undefined" && typeof(google.maps) != "undefined") {
var DEFAULT_MAP_STYLE = [ { 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 } ] } ];
cdb.geo.GoogleMapsMapView = cdb.geo.MapView.extend({
layerTypeMap: {
"tiled": cdb.geo.GMapsTiledLayerView,
"cartodb": cdb.geo.GMapsCartoDBLayerView,
"carto": cdb.geo.GMapsCartoDBLayerView,
"plain": cdb.geo.GMapsPlainLayerView,
"gmapsbase": cdb.geo.GMapsBaseLayerView,
"layergroup": cdb.geo.GMapsCartoDBLayerGroupView,
"namedmap": cdb.geo.GMapsCartoDBNamedMapView,
"torque": function(layer, map) {
return new cdb.geo.GMapsTorqueLayerView(layer, map);
},
"wms": cdb.geo.LeafLetWMSLayerView
},
initialize: function() {
_.bindAll(this, '_ready');
this._isReady = false;
var self = this;
cdb.geo.MapView.prototype.initialize.call(this);
var bounds = this.map.getViewBounds();
if (bounds) {
this.showBounds(bounds);
}
var center = this.map.get('center');
if (!this.options.map_object) {
this.map_googlemaps = new google.maps.Map(this.el, {
center: new google.maps.LatLng(center[0], center[1]),
zoom: this.map.get('zoom'),
minZoom: this.map.get('minZoom'),
maxZoom: this.map.get('maxZoom'),
disableDefaultUI: true,
// Set scrollwheel options
scrollwheel: this.map.get("scrollwheel"),
// Allow dragging (and double click zoom)
draggable: this.map.get("drag"),
disableDoubleClickZoom: !this.map.get("drag"),
mapTypeControl:false,
mapTypeId: google.maps.MapTypeId.ROADMAP,
backgroundColor: 'white',
tilt: 0
});
this.map.bind('change:maxZoom', function() {
self.map_googlemaps.setOptions({ maxZoom: self.map.get('maxZoom') });
}, this);
this.map.bind('change:minZoom', function() {
self.map_googlemaps.setOptions({ minZoom: self.map.get('minZoom') });
}, this);
} else {
this.map_googlemaps = this.options.map_object;
this.setElement(this.map_googlemaps.getDiv());
// fill variables
var c = self.map_googlemaps.getCenter();
self._setModelProperty({ center: [c.lat(), c.lng()] });
self._setModelProperty({ zoom: self.map_googlemaps.getZoom() });
// unset bounds to not change mapbounds
self.map.unset('view_bounds_sw', { silent: true });
self.map.unset('view_bounds_ne', { silent: true });
}
this.map.geometries.bind('add', this._addGeometry, this);
this.map.geometries.bind('remove', this._removeGeometry, this);
this._bindModel();
this._addLayers();
this.setAttribution();
google.maps.event.addListener(this.map_googlemaps, 'center_changed', function() {
var c = self.map_googlemaps.getCenter();
self._setModelProperty({ center: [c.lat(), c.lng()] });
});
google.maps.event.addListener(this.map_googlemaps, 'zoom_changed', function() {
self._setModelProperty({
zoom: self.map_googlemaps.getZoom()
});
});
google.maps.event.addListener(this.map_googlemaps, 'click', function(e) {
self.trigger('click', e, [e.latLng.lat(), e.latLng.lng()]);
});
google.maps.event.addListener(this.map_googlemaps, 'dragend', function(e) {
var c = self.map_googlemaps.getCenter();
self.trigger('dragend', e, [c.lat(), c.lng()]);
});
google.maps.event.addListener(this.map_googlemaps, 'dblclick', function(e) {
self.trigger('dblclick', e);
});
this.map.layers.bind('add', this._addLayer, this);
this.map.layers.bind('remove', this._removeLayer, this);
this.map.layers.bind('reset', this._addLayers, this);
this.map.layers.bind('change:type', this._swicthLayerView, this);
this.projector = new cdb.geo.CartoDBLayerGroupGMaps.Projector(this.map_googlemaps);
this.projector.draw = this._ready;
},
_ready: function() {
this.projector.draw = function() {};
this.trigger('ready');
this._isReady = true;
},
_setKeyboard: function(model, z) {
this.map_googlemaps.setOptions({ keyboardShortcuts: z });
},
_setScrollWheel: function(model, z) {
this.map_googlemaps.setOptions({ scrollwheel: z });
},
_setZoom: function(model, z) {
z = z || 0;
this.map_googlemaps.setZoom(z);
},
_setCenter: function(model, center) {
var c = new google.maps.LatLng(center[0], center[1]);
this.map_googlemaps.setCenter(c);
},
createLayer: function(layer) {
var layer_view,
layerClass = this.layerTypeMap[layer.get('type').toLowerCase()];
if (layerClass) {
try {
layer_view = new layerClass(layer, this.map_googlemaps);
} catch(e) {
cdb.log.error("MAP: error creating '" + layer.get('type') + "' layer -> " + e.message);
}
} else {
cdb.log.error("MAP: " + layer.get('type') + " can't be created");
}
return layer_view;
},
_addLayer: function(layer, layers, opts) {
opts = opts || {};
var self = this;
var lyr, layer_view;
layer_view = this.createLayer(layer);
if (!layer_view) {
return;
}
return this._addLayerToMap(layer_view, opts);
},
_addLayerToMap: function(layer_view, opts) {
var layer = layer_view.model;
this.layers[layer.cid] = layer_view;
if (layer_view) {
var idx = _(this.layers).filter(function(lyr) { return !!lyr.getTile; }).length - 1;
var isBaseLayer = _.keys(this.layers).length === 1 || (opts && opts.index === 0) || layer.get('order') === 0;
// set base layer
if(isBaseLayer && !opts.no_base_layer) {
var m = layer_view.model;
if(m.get('type') === 'GMapsBase') {
layer_view._update();
} else {
layer_view.isBase = true;
layer_view._update();
}
} else {
idx -= 1;
idx = Math.max(0, idx); // avoid -1
if (layer_view.getTile) {
if (!layer_view.gmapsLayer) {
cdb.log.error("gmaps layer can't be null");
}
this.map_googlemaps.overlayMapTypes.setAt(idx, layer_view.gmapsLayer);
} else {
layer_view.gmapsLayer.setMap(this.map_googlemaps);
}
}
if(opts === undefined || !opts.silent) {
this.trigger('newLayerView', layer_view, layer, this);
}
} else {
cdb.log.error("layer type not supported");
}
return layer_view;
},
pixelToLatLon: function(pos) {
var latLng = this.projector.pixelToLatLng(new google.maps.Point(pos[0], pos[1]));
return {
lat: latLng.lat(),
lng: latLng.lng()
}
},
latLonToPixel: function(latlon) {
return this.projector.latLngToPixel(new google.maps.LatLng(latlon[0], latlon[1]));
},
getSize: function() {
return {
x: this.$el.width(),
y: this.$el.height()
};
},
panBy: function(p) {
var c = this.map.get('center');
var pc = this.latLonToPixel(c);
p.x += pc.x;
p.y += pc.y;
var ll = this.projector.pixelToLatLng(p);
this.map.setCenter([ll.lat(), ll.lng()]);
},
getBounds: function() {
if(this._isReady) {
var b = this.map_googlemaps.getBounds();
var sw = b.getSouthWest();
var ne = b.getNorthEast();
return [
[sw.lat(), sw.lng()],
[ne.lat(), ne.lng()]
];
}
return [ [0,0], [0,0] ];
},
setAttribution: function() {
// Remove old one
var old = document.getElementById("cartodb-gmaps-attribution")
, attribution = cdb.core.sanitize.html(this.map.get("attribution").join(", "));
// If div already exists, remove it
if (old) {
old.parentNode.removeChild(old);
}
// Add new one
var container = this.map_googlemaps.getDiv()
, cartodb_attribution = document.createElement("div");
cartodb_attribution.setAttribute('id','cartodb-gmaps-attribution');
cartodb_attribution.setAttribute('class', 'gmaps');
container.appendChild(cartodb_attribution);
cartodb_attribution.innerHTML = attribution;
},
setCursor: function(cursor) {
this.map_googlemaps.setOptions({ draggableCursor: cursor });
},
_addGeomToMap: function(geom) {
var geo = cdb.geo.GoogleMapsMapView.createGeometry(geom);
if(geo.geom.length) {
for(var i = 0 ; i < geo.geom.length; ++i) {
geo.geom[i].setMap(this.map_googlemaps);
}
} else {
geo.geom.setMap(this.map_googlemaps);
}
return geo;
},
_removeGeomFromMap: function(geo) {
if(geo.geom.length) {
for(var i = 0 ; i < geo.geom.length; ++i) {
geo.geom[i].setMap(null);
}
} else {
geo.geom.setMap(null);
}
},
getNativeMap: function() {
return this.map_googlemaps;
},
invalidateSize: function() {
google.maps.event.trigger(this.map_googlemaps, 'resize');
}
}, {
addLayerToMap: function(layer, map, pos) {
pos = pos || 0;
if (!layer) {
cdb.log.error("gmaps layer can't be null");
}
if (layer.getTile) {
map.overlayMapTypes.setAt(pos, layer);
} else {
layer.setMap(map);
}
},
/**
* create the view for the geometry model
*/
createGeometry: function(geometryModel) {
if(geometryModel.isPoint()) {
return new cdb.geo.gmaps.PointView(geometryModel);
}
return new cdb.geo.gmaps.PathView(geometryModel);
}
});
}
@@ -0,0 +1,79 @@
(function() {
if(typeof(google) == "undefined" || typeof(google.maps) == "undefined")
return;
/**
* base layer for all google maps
*/
var GMapsLayerView = function(layerModel, gmapsLayer, gmapsMap) {
this.gmapsLayer = gmapsLayer;
this.map = this.gmapsMap = gmapsMap;
this.model = layerModel;
this.model.bind('change', this._update, this);
this.type = layerModel.get('type') || layerModel.get('kind');
this.type = this.type.toLowerCase();
};
_.extend(GMapsLayerView.prototype, Backbone.Events);
_.extend(GMapsLayerView.prototype, {
// hack function to search layer inside google maps layers
_searchLayerIndex: function() {
var self = this;
var index = -1;
this.gmapsMap.overlayMapTypes.forEach(
function(layer, i) {
if (layer == self) {
index = i;
}
}
);
return index;
},
/**
* remove layer from the map and unbind events
*/
remove: function() {
if(!this.isBase) {
var self = this;
var idx = this._searchLayerIndex();
if(idx >= 0) {
this.gmapsMap.overlayMapTypes.removeAt(idx);
} else if (this.gmapsLayer.setMap){
this.gmapsLayer.setMap(null);
}
this.model.unbind(null, null, this);
this.unbind();
}
},
refreshView: function() {
var self = this;
//reset to update
if(this.isBase) {
var a = '_baseLayer';
this.gmapsMap.setMapTypeId(null);
this.gmapsMap.mapTypes.set(a, this.gmapsLayer);
this.gmapsMap.setMapTypeId(a);
} else {
var idx = this._searchLayerIndex();
if(idx >= 0) {
this.gmapsMap.overlayMapTypes.setAt(idx, this);
}
}
},
reload: function() { this.refreshView() ; },
_update: function() { this.refreshView(); }
});
cdb.geo.GMapsLayerView = GMapsLayerView;
})();
@@ -0,0 +1,41 @@
(function() {
if(typeof(google) == "undefined" || typeof(google.maps) == "undefined")
return;
var GMapsBaseLayerView = function(layerModel, gmapsMap) {
cdb.geo.GMapsLayerView.call(this, layerModel, null, gmapsMap);
};
_.extend(
GMapsBaseLayerView.prototype,
cdb.geo.GMapsLayerView.prototype,
{
_update: function() {
var m = this.model;
var types = {
"roadmap": google.maps.MapTypeId.ROADMAP,
"gray_roadmap": google.maps.MapTypeId.ROADMAP,
"dark_roadmap": google.maps.MapTypeId.ROADMAP,
"hybrid": google.maps.MapTypeId.HYBRID,
"satellite": google.maps.MapTypeId.SATELLITE,
"terrain": google.maps.MapTypeId.TERRAIN
};
this.gmapsMap.setOptions({
mapTypeId: types[m.get('base_type')]
});
this.gmapsMap.setOptions({
styles: m.get('style') || DEFAULT_MAP_STYLE
});
},
remove: function() { }
});
cdb.geo.GMapsBaseLayerView = GMapsBaseLayerView;
})();
@@ -0,0 +1,170 @@
(function() {
// if google maps is not defined do not load the class
if(typeof(google) == "undefined" || typeof(google.maps) == "undefined")
return;
// helper to get pixel position from latlon
var Projector = function(map) { this.setMap(map); };
Projector.prototype = new google.maps.OverlayView();
Projector.prototype.draw = function() {};
Projector.prototype.latLngToPixel = function(point) {
var p = this.getProjection();
if(p) {
return p.fromLatLngToContainerPixel(point);
}
return [0, 0];
};
Projector.prototype.pixelToLatLng = function(point) {
var p = this.getProjection();
if(p) {
return p.fromContainerPixelToLatLng(point);
}
return [0, 0];
//return this.map.getProjection().fromPointToLatLng(point);
};
var CartoDBLayer = function(options) {
var default_options = {
query: "SELECT * FROM {{table_name}}",
opacity: 0.99,
attribution: cdb.config.get('cartodb_attributions'),
opacity: 1,
debug: false,
visible: true,
added: false,
extra_params: {},
layer_definition_version: '1.0.0'
};
this.options = _.defaults(options, default_options);
if (!options.table_name || !options.user_name || !options.tile_style) {
throw ('cartodb-gmaps needs at least a CartoDB table name, user_name and tile_style');
}
this.options.layer_definition = {
version: this.options.layer_definition_version,
layers: [{
type: 'cartodb',
options: this._getLayerDefinition(),
infowindow: this.options.infowindow
}]
};
cdb.geo.CartoDBLayerGroupGMaps.call(this, this.options);
this.setOptions(this.options);
};
_.extend(CartoDBLayer.prototype, cdb.geo.CartoDBLayerGroupGMaps.prototype);
CartoDBLayer.prototype.setQuery = function (layer, sql) {
if(sql === undefined) {
sql = layer;
layer = 0;
}
sql = sql || 'select * from ' + this.options.table_name;
LayerDefinition.prototype.setQuery.call(this, layer, sql);
};
cdb.geo.CartoDBLayerGMaps = CartoDBLayer;
/**
* gmaps cartodb layer
*/
var GMapsCartoDBLayerView = function(layerModel, gmapsMap) {
var self = this;
_.bindAll(this, 'featureOut', 'featureOver', 'featureClick');
var opts = _.clone(layerModel.attributes);
opts.map = gmapsMap;
var // preserve the user's callbacks
_featureOver = opts.featureOver,
_featureOut = opts.featureOut,
_featureClick = opts.featureClick;
opts.featureOver = function() {
_featureOver && _featureOver.apply(this, arguments);
self.featureOver && self.featureOver.apply(this, arguments);
};
opts.featureOut = function() {
_featureOut && _featureOut.apply(this, arguments);
self.featureOut && self.featureOut.apply(this, arguments);
};
opts.featureClick = function() {
_featureClick && _featureClick.apply(this, arguments);
self.featureClick && self.featureClick.apply(opts, arguments);
};
cdb.geo.CartoDBLayerGMaps.call(this, opts);
cdb.geo.GMapsLayerView.call(this, layerModel, this, gmapsMap);
};
cdb.geo.GMapsCartoDBLayerView = GMapsCartoDBLayerView;
_.extend(
GMapsCartoDBLayerView.prototype,
cdb.geo.CartoDBLayerGMaps.prototype,
cdb.geo.GMapsLayerView.prototype,
{
_update: function() {
this.setOptions(this.model.attributes);
},
reload: function() {
this.model.invalidate();
},
remove: function() {
cdb.geo.GMapsLayerView.prototype.remove.call(this);
this.clear();
},
featureOver: function(e, latlon, pixelPos, data) {
// dont pass gmaps LatLng
this.trigger('featureOver', e, [latlon.lat(), latlon.lng()], pixelPos, data, 0);
},
featureOut: function(e) {
this.trigger('featureOut', e);
},
featureClick: function(e, latlon, pixelPos, data, layer) {
// dont pass leaflet lat/lon
this.trigger('featureClick', e, [latlon.lat(), latlon.lng()], pixelPos, data, 0);
},
error: function(e) {
if(this.model) {
//trigger the error form _checkTiles in the model
this.model.trigger('error', e?e.error:'unknown error');
this.model.trigger('tileError', e?e.error:'unknown error');
}
},
tilesOk: function(e) {
this.model.trigger('tileOk');
},
loading: function() {
this.trigger("loading");
},
finishLoading: function() {
this.trigger("load");
}
});
})();
@@ -0,0 +1,466 @@
(function() {
// if google maps is not defined do not load the class
if(typeof(google) == "undefined" || typeof(google.maps) == "undefined") {
return;
}
// helper to get pixel position from latlon
var Projector = function(map) { this.setMap(map); };
Projector.prototype = new google.maps.OverlayView();
Projector.prototype.draw = function() {};
Projector.prototype.latLngToPixel = function(point) {
var p = this.getProjection();
if(p) {
return p.fromLatLngToContainerPixel(point);
}
return [0, 0];
};
Projector.prototype.pixelToLatLng = function(point) {
var p = this.getProjection();
if(p) {
return p.fromContainerPixelToLatLng(point);
}
return [0, 0];
//return this.map.getProjection().fromPointToLatLng(point);
};
var default_options = {
opacity: 0.99,
attribution: cdb.config.get('cartodb_attributions'),
debug: false,
visible: true,
added: false,
tiler_domain: "carto.com",
tiler_port: "80",
tiler_protocol: "http",
sql_api_domain: "carto.com",
sql_api_port: "80",
sql_api_protocol: "http",
extra_params: {
},
cdn_url: null,
subdomains: null
};
var OPACITY_FILTER = "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00FFFFFF,endColorstr=#00FFFFFF)";
var CartoDBNamedMap = function(opts) {
this.options = _.defaults(opts, default_options);
this.tiles = 0;
this.tilejson = null;
this.interaction = [];
if (!opts.named_map && !opts.sublayers) {
throw new Error('cartodb-gmaps needs at least the named_map');
}
// Add CartoDB logo
if (this.options.cartodb_logo != false)
cdb.geo.common.CartoDBLogo.addWadus({ left: 74, bottom:8 }, 2000, this.options.map.getDiv());
wax.g.connector.call(this, opts);
// lovely wax connector overwrites options so set them again
// TODO: remove wax.connector here
_.extend(this.options, opts);
this.projector = new Projector(opts.map);
NamedMap.call(this, this.options.named_map, this.options);
CartoDBLayerCommon.call(this);
// precache
this.update();
};
var CartoDBLayerGroup = function(opts) {
this.options = _.defaults(opts, default_options);
this.tiles = 0;
this.tilejson = null;
this.interaction = [];
if (!opts.layer_definition && !opts.sublayers) {
throw new Error('cartodb-leaflet needs at least the layer_definition or sublayer list');
}
// if only sublayers is available, generate layer_definition from it
if(!opts.layer_definition) {
opts.layer_definition = LayerDefinition.layerDefFromSubLayers(opts.sublayers);
}
// Add CartoDB logo
if (this.options.cartodb_logo != false)
cdb.geo.common.CartoDBLogo.addWadus({ left: 74, bottom:8 }, 2000, this.options.map.getDiv());
wax.g.connector.call(this, opts);
// lovely wax connector overwrites options so set them again
// TODO: remove wax.connector here
_.extend(this.options, opts);
this.projector = new Projector(opts.map);
LayerDefinition.call(this, opts.layer_definition, this.options);
CartoDBLayerCommon.call(this);
// precache
this.update();
};
function setImageOpacityIE8(img, opacity) {
var v = Math.round(opacity*100);
if (v >= 99) {
img.style.filter = OPACITY_FILTER;
} else {
img.style.filter = "alpha(opacity=" + (opacity) + ");";
}
}
function CartoDBLayerGroupBase() {}
CartoDBLayerGroupBase.prototype.setOpacity = function(opacity) {
if (isNaN(opacity) || opacity > 1 || opacity < 0) {
throw new Error(opacity + ' is not a valid value, should be in [0, 1] range');
}
this.opacity = this.options.opacity = opacity;
for(var key in this.cache) {
var img = this.cache[key];
img.style.opacity = opacity;
setImageOpacityIE8(img, opacity);
}
};
CartoDBLayerGroupBase.prototype.setAttribution = function() {};
CartoDBLayerGroupBase.prototype.getTile = function(coord, zoom, ownerDocument) {
var EMPTY_GIF = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
var self = this;
var ie = 'ActiveXObject' in window,
ielt9 = ie && !document.addEventListener;
this.options.added = true;
var key = zoom + '/' + coord.x + '/' + coord.y;
if (!this.cache[key]) {
if (this.tiles === 0) {
this.loading && this.loading();
}
this.tiles++;
}
if (this.tilejson === null) {
var i = this.cache[key] = new Image(256, 256);
i.src = EMPTY_GIF;
i.setAttribute('gTileKey', key);
i.style.opacity = this.options.opacity;
return i;
}
var im = wax.g.connector.prototype.getTile.call(this, coord, zoom, ownerDocument);
// in IE8 semi transparency does not work and needs filter
if( ielt9 ) {
setImageOpacityIE8(im, this.options.opacity);
}
im.style.opacity = this.options.opacity;
var loadTime = cartodb.core.Profiler.metric('cartodb-js.tile.png.load.time').start();
var finished = function() {
loadTime.end();
self.tiles--;
if (self.tiles === 0) {
self.finishLoading && self.finishLoading();
}
};
im.onload = finished;
im.onerror = function() {
cartodb.core.Profiler.metric('cartodb-js.tile.png.error').inc();
finished();
}
return im;
};
CartoDBLayerGroupBase.prototype.onAdd = function () {
//this.update();
};
CartoDBLayerGroupBase.prototype.clear = function () {
this._clearInteraction();
self.finishLoading && self.finishLoading();
};
CartoDBLayerGroupBase.prototype.update = function (done) {
var self = this;
this.loading && this.loading();
this.getTiles(function(urls, err) {
if(urls) {
self.tilejson = urls;
self.options.tiles = urls.tiles;
self.tiles = 0;
self.cache = {};
self._reloadInteraction();
self.refreshView();
self.ok && self.ok();
done && done();
} else {
self.error && self.error(err)
}
});
};
CartoDBLayerGroupBase.prototype.refreshView = function() {
var self = this;
var map = this.options.map;
map.overlayMapTypes.forEach(
function(layer, i) {
if (layer == self) {
map.overlayMapTypes.setAt(i, self);
return;
}
}
);
}
CartoDBLayerGroupBase.prototype.onLayerDefinitionUpdated = function() {
this.update();
}
CartoDBLayerGroupBase.prototype._checkLayer = function() {
if (!this.options.added) {
throw new Error('the layer is not still added to the map');
}
}
CartoDBLayerGroupBase.prototype._findPos = function (map,o) {
var curleft = 0;
var curtop = 0;
var obj = map.getDiv();
var x, y;
if (o.e.changedTouches && o.e.changedTouches.length > 0) {
x = o.e.changedTouches[0].clientX + window.scrollX;
y = o.e.changedTouches[0].clientY + window.scrollY;
} else {
x = o.e.clientX;
y = o.e.clientY;
}
// If the map is fixed at the top of the window, we can't use offsetParent
// cause there might be some scrolling that we need to take into account.
if (obj.offsetParent && obj.offsetTop > 0) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
var point = this._newPoint(
x - curleft, y - curtop);
} else {
var rect = obj.getBoundingClientRect();
var scrollX = (window.scrollX || window.pageXOffset);
var scrollY = (window.scrollY || window.pageYOffset);
var point = this._newPoint(
(o.e.clientX? o.e.clientX: x) - rect.left - obj.clientLeft - scrollX,
(o.e.clientY? o.e.clientY: y) - rect.top - obj.clientTop - scrollY);
}
return point;
};
/**
* Creates an instance of a google.maps Point
*/
CartoDBLayerGroupBase.prototype._newPoint = function(x, y) {
return new google.maps.Point(x, y);
};
CartoDBLayerGroupBase.prototype._manageOffEvents = function(map, o){
if (this.options.featureOut) {
return this.options.featureOut && this.options.featureOut(o.e, o.layer);
}
};
CartoDBLayerGroupBase.prototype._manageOnEvents = function(map,o) {
var point = this._findPos(map, o);
var latlng = this.projector.pixelToLatLng(point);
var event_type = o.e.type.toLowerCase();
switch (event_type) {
case 'mousemove':
if (this.options.featureOver) {
return this.options.featureOver(o.e,latlng, point, o.data, o.layer);
}
break;
case 'click':
case 'touchend':
case 'touchmove': // for some reason android browser does not send touchend
case 'mspointerup':
case 'pointerup':
case 'pointermove':
if (this.options.featureClick) {
this.options.featureClick(o.e,latlng, point, o.data, o.layer);
}
break;
default:
break;
}
}
// CartoDBLayerGroup type
CartoDBLayerGroup.Projector = Projector;
CartoDBLayerGroup.prototype = new wax.g.connector();
_.extend(CartoDBLayerGroup.prototype, LayerDefinition.prototype, CartoDBLayerGroupBase.prototype, CartoDBLayerCommon.prototype);
CartoDBLayerGroup.prototype.interactionClass = wax.g.interaction;
// CartoDBNamedMap
CartoDBNamedMap.prototype = new wax.g.connector();
_.extend(CartoDBNamedMap.prototype, NamedMap.prototype, CartoDBLayerGroupBase.prototype, CartoDBLayerCommon.prototype);
CartoDBNamedMap.prototype.interactionClass = wax.g.interaction;
// export
cdb.geo.CartoDBLayerGroupGMaps = CartoDBLayerGroup;
cdb.geo.CartoDBNamedMapGMaps = CartoDBNamedMap;
/*
*
* cartodb layer group view
*
*/
function LayerGroupView(base) {
var GMapsCartoDBLayerGroupView = function(layerModel, gmapsMap) {
var self = this;
var hovers = [];
_.bindAll(this, 'featureOut', 'featureOver', 'featureClick');
var opts = _.clone(layerModel.attributes);
opts.map = gmapsMap;
var // preserve the user's callbacks
_featureOver = opts.featureOver,
_featureOut = opts.featureOut,
_featureClick = opts.featureClick;
var previousEvent;
var eventTimeout = -1;
opts.featureOver = function(e, latlon, pxPos, data, layer) {
if (!hovers[layer]) {
self.trigger('layerenter', e, latlon, pxPos, data, layer);
}
hovers[layer] = 1;
_featureOver && _featureOver.apply(this, arguments);
self.featureOver && self.featureOver.apply(this, arguments);
// if the event is the same than before just cancel the event
// firing because there is a layer on top of it
if (e.timeStamp === previousEvent) {
clearTimeout(eventTimeout);
}
eventTimeout = setTimeout(function() {
self.trigger('mouseover', e, latlon, pxPos, data, layer);
self.trigger('layermouseover', e, latlon, pxPos, data, layer);
}, 0);
previousEvent = e.timeStamp;
};
opts.featureOut = function(m, layer) {
if (hovers[layer]) {
self.trigger('layermouseout', layer);
}
hovers[layer] = 0;
if(!_.any(hovers)) {
self.trigger('mouseout');
}
_featureOut && _featureOut.apply(this, arguments);
self.featureOut && self.featureOut.apply(this, arguments);
};
opts.featureClick = _.debounce(function() {
_featureClick && _featureClick.apply(this, arguments);
self.featureClick && self.featureClick.apply(opts, arguments);
}, 10);
//CartoDBLayerGroup.call(this, opts);
base.call(this, opts);
cdb.geo.GMapsLayerView.call(this, layerModel, this, gmapsMap);
};
_.extend(
GMapsCartoDBLayerGroupView.prototype,
cdb.geo.GMapsLayerView.prototype,
base.prototype,
{
_update: function() {
this.setOptions(this.model.attributes);
},
reload: function() {
this.model.invalidate();
},
remove: function() {
cdb.geo.GMapsLayerView.prototype.remove.call(this);
this.clear();
},
featureOver: function(e, latlon, pixelPos, data, layer) {
// dont pass gmaps LatLng
this.trigger('featureOver', e, [latlon.lat(), latlon.lng()], pixelPos, data, layer);
},
featureOut: function(e, layer) {
this.trigger('featureOut', e, layer);
},
featureClick: function(e, latlon, pixelPos, data, layer) {
// dont pass leaflet lat/lon
this.trigger('featureClick', e, [latlon.lat(), latlon.lng()], pixelPos, data, layer);
},
error: function(e) {
if(this.model) {
//trigger the error form _checkTiles in the model
this.model.trigger('error', e?e.errors:'unknown error');
this.model.trigger('tileError', e?e.errors:'unknown error');
}
},
ok: function(e) {
this.model.trigger('tileOk');
},
tilesOk: function(e) {
this.model.trigger('tileOk');
},
loading: function() {
this.trigger("loading");
},
finishLoading: function() {
this.trigger("load");
}
});
return GMapsCartoDBLayerGroupView;
}
cdb.geo.GMapsCartoDBLayerGroupView = LayerGroupView(CartoDBLayerGroup);
cdb.geo.GMapsCartoDBNamedMapView = LayerGroupView(CartoDBNamedMap);
cdb.geo.CartoDBNamedMapGMaps = CartoDBNamedMap;
/**
* gmaps cartodb layer
*/
})();
@@ -0,0 +1,38 @@
(function() {
if(typeof(google) == "undefined" || typeof(google.maps) == "undefined")
return;
var GMapsPlainLayerView = function(layerModel, gmapsMap) {
this.color = layerModel.get('color')
cdb.geo.GMapsLayerView.call(this, layerModel, this, gmapsMap);
};
_.extend(
GMapsPlainLayerView.prototype,
cdb.geo.GMapsLayerView.prototype, {
_update: function() {
this.color = this.model.get('color')
this.refreshView();
},
getTile: function(coord, zoom, ownerDocument) {
var div = document.createElement('div');
div.style.width = this.tileSize.x;
div.style.height = this.tileSize.y;
div['background-color'] = this.color;
return div;
},
tileSize: new google.maps.Size(256,256),
maxZoom: 100,
minZoom: 0,
name:"plain layer",
alt: "plain layer"
});
cdb.geo.GMapsPlainLayerView = GMapsPlainLayerView;
})();
@@ -0,0 +1,48 @@
(function() {
if(typeof(google) == "undefined" || typeof(google.maps) == "undefined")
return;
// TILED LAYER
var GMapsTiledLayerView = function(layerModel, gmapsMap) {
cdb.geo.GMapsLayerView.call(this, layerModel, this, gmapsMap);
this.tileSize = new google.maps.Size(256, 256);
this.opacity = 1.0;
this.isPng = true;
this.maxZoom = 22;
this.minZoom = 0;
this.name= 'cartodb tiled layer';
google.maps.ImageMapType.call(this, this);
};
_.extend(
GMapsTiledLayerView.prototype,
cdb.geo.GMapsLayerView.prototype,
google.maps.ImageMapType.prototype, {
getTileUrl: function(tile, zoom) {
var y = tile.y;
var tileRange = 1 << zoom;
if (y < 0 || y >= tileRange) {
return null;
}
var x = tile.x;
if (x < 0 || x >= tileRange) {
x = (x % tileRange + tileRange) % tileRange;
}
if(this.model.get('tms')) {
y = tileRange - y - 1;
}
var urlPattern = this.model.get('urlTemplate');
return urlPattern
.replace("{x}",x)
.replace("{y}",y)
.replace("{z}",zoom);
}
});
cdb.geo.GMapsTiledLayerView = GMapsTiledLayerView;
})();
@@ -0,0 +1,105 @@
(function() {
if(typeof(google) == "undefined" || typeof(google.maps) == "undefined")
return;
var GMapsTorqueLayerView = function(layerModel, gmapsMap) {
var extra = layerModel.get('extra_params');
cdb.geo.GMapsLayerView.call(this, layerModel, this, gmapsMap);
var query = this._getQuery(layerModel);
torque.GMapsTorqueLayer.call(this, {
table: layerModel.get('table_name'),
user: layerModel.get('user_name'),
column: layerModel.get('property'),
blendmode: layerModel.get('torque-blend-mode'),
resolution: 1,
//TODO: manage time columns
countby: 'count(cartodb_id)',
sql_api_domain: layerModel.get('sql_api_domain'),
sql_api_protocol: layerModel.get('sql_api_protocol'),
sql_api_port: layerModel.get('sql_api_port'),
tiler_protocol: layerModel.get('tiler_protocol'),
tiler_domain: layerModel.get('tiler_domain'),
tiler_port: layerModel.get('tiler_port'),
maps_api_template: layerModel.get('maps_api_template'),
stat_tag: layerModel.get('stat_tag'),
animationDuration: layerModel.get('torque-duration'),
steps: layerModel.get('torque-steps'),
sql: query,
visible: layerModel.get('visible'),
extra_params: {
api_key: extra ? extra.map_key: ''
},
map: gmapsMap,
cartodb_logo: layerModel.get('cartodb_logo'),
attribution: layerModel.get('attribution'),
cartocss: layerModel.get('cartocss') || layerModel.get('tile_style'),
named_map: layerModel.get('named_map'),
auth_token: layerModel.get('auth_token'),
no_cdn: layerModel.get('no_cdn'),
loop: layerModel.get('loop') === false? false: true,
});
//this.setCartoCSS(this.model.get('tile_style'));
if (layerModel.get('visible')) {
this.play();
}
};
_.extend(
GMapsTorqueLayerView.prototype,
cdb.geo.GMapsLayerView.prototype,
torque.GMapsTorqueLayer.prototype,
{
_update: function() {
var changed = this.model.changedAttributes();
if(changed === false) return;
changed.tile_style && this.setCartoCSS(this.model.get('tile_style'));
if ('query' in changed || 'query_wrapper' in changed) {
this.setSQL(this._getQuery(this.model));
}
if ('visible' in changed)
this.model.get('visible') ? this.show(): this.hide();
},
_getQuery: function(layerModel) {
var query = layerModel.get('query');
var qw = layerModel.get('query_wrapper');
if(qw) {
query = _.template(qw)({ sql: query || ('select * from ' + layerModel.get('table_name')) });
}
return query;
},
refreshView: function() {
//TODO: update screen
},
onAdd: function() {
torque.GMapsTorqueLayer.prototype.onAdd.apply(this);
// Add CartoDB logo
if (this.options.cartodb_logo != false)
cdb.geo.common.CartoDBLogo.addWadus({ left: 74, bottom:8 }, 2000, this.map.getDiv())
},
onTilesLoaded: function() {
//this.trigger('load');
Backbone.Events.trigger.call(this, 'load');
},
onTilesLoading: function() {
Backbone.Events.trigger.call(this, 'loading');
}
});
cdb.geo.GMapsTorqueLayerView = GMapsTorqueLayerView;
})();
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,159 @@
(function() {
/**
* this module implements all the features related to overlay geometries
* in leaflet: markers, polygons, lines and so on
*/
/**
* view for markers
*/
function PointView(geometryModel) {
var self = this;
// events to link
var events = [
'click',
'dblclick',
'mousedown',
'mouseover',
'mouseout',
'dragstart',
'drag',
'dragend'
];
this._eventHandlers = {};
this.model = geometryModel;
this.points = [];
var icon = {
iconUrl: this.model.get('iconUrl') || cdb.config.get('assets_url') + '/images/layout/default_marker.png',
iconAnchor: this.model.get('iconAnchor') || [11, 11]
};
this.geom = L.GeoJSON.geometryToLayer(geometryModel.get('geojson'), function(geojson, latLng) {
//TODO: create marker depending on the visualizacion options
var p = L.marker(latLng, {
icon: L.icon(icon)
});
var i;
for(i = 0; i < events.length; ++i) {
var e = events[i];
p.on(e, self._eventHandler(e));
}
return p;
});
this.bind('dragend', function(e, pos) {
geometryModel.set({
geojson: {
type: 'Point',
//geojson is lng,lat
coordinates: [pos[1], pos[0]]
}
});
});
}
PointView.prototype = new GeometryView();
PointView.prototype.edit = function() {
this.geom.dragging.enable();
};
/**
* returns a function to handle events fot evtType
*/
PointView.prototype._eventHandler = function(evtType) {
var self = this;
var h = this._eventHandlers[evtType];
if(!h) {
h = function(e) {
var latlng = e.target.getLatLng();
var s = [latlng.lat, latlng.lng];
self.trigger(evtType, e.originalEvent, s);
};
this._eventHandlers[evtType] = h;
}
return h;
};
/**
* view for other geometries (polygons/lines)
*/
function PathView(geometryModel) {
var self = this;
// events to link
var events = [
'click',
'dblclick',
'mousedown',
'mouseover',
'mouseout',
];
this._eventHandlers = {};
this.model = geometryModel;
this.points = [];
this.geom = L.GeoJSON.geometryToLayer(geometryModel.get('geojson'));
this.geom.setStyle(geometryModel.get('style'));
/*for(var i = 0; i < events.length; ++i) {
var e = events[i];
this.geom.on(e, self._eventHandler(e));
}*/
}
PathView.prototype = new GeometryView();
PathView.prototype._leafletLayers = function() {
// check if this is a multi-feature or single-feature
if (this.geom.getLayers) {
return this.geom.getLayers();
}
return [this.geom];
};
PathView.prototype.enableEdit = function() {
var self = this;
var layers = this._leafletLayers();
_.each(layers, function(g) {
g.setStyle(self.model.get('style'));
g.on('edit', function() {
self.model.set('geojson', self.geom.toGeoJSON().geometry);
}, self);
});
};
PathView.prototype.disableEdit = function() {
var self = this;
var layers = this._leafletLayers();
_.each(layers, function(g) {
g.off('edit', null, self);
});
};
PathView.prototype.edit = function(enable) {
var self = this;
var fn = enable ? 'enable': 'disable';
var layers = this._leafletLayers();
_.each(layers, function(g) {
g.editing[fn]();
enable ? self.enableEdit(): self.disableEdit();
});
};
cdb.geo.leaflet = cdb.geo.leaflet || {};
cdb.geo.leaflet.PointView = PointView;
cdb.geo.leaflet.PathView = PathView;
})();
@@ -0,0 +1,426 @@
/**
* leaflet implementation of a map
*/
(function() {
if(typeof(L) == "undefined")
return;
/**
* leatlef impl
*/
cdb.geo.LeafletMapView = cdb.geo.MapView.extend({
initialize: function() {
_.bindAll(this, '_addLayer', '_removeLayer', '_setZoom', '_setCenter', '_setView');
cdb.geo.MapView.prototype.initialize.call(this);
var self = this;
var center = this.map.get('center');
var mapConfig = {
zoomControl: false,
center: new L.LatLng(center[0], center[1]),
zoom: this.map.get('zoom'),
minZoom: this.map.get('minZoom'),
maxZoom: this.map.get('maxZoom')
};
if (this.map.get('bounding_box_ne')) {
//mapConfig.maxBounds = [this.map.get('bounding_box_ne'), this.map.get('bounding_box_sw')];
}
if (!this.options.map_object) {
this.map_leaflet = new L.Map(this.el, mapConfig);
// remove the "powered by leaflet"
this.map_leaflet.attributionControl.setPrefix('');
// Disable scrollwheel
if (this.map.get("scrollwheel") == false) this.map_leaflet.scrollWheelZoom.disable();
// Disable keyboard
if (this.map.get("keyboard") == false) this.map_leaflet.keyboard.disable();
// Disable dragging (also doubleClickZoom)
if (this.map.get("drag") == false) {
this.map_leaflet.dragging.disable();
this.map_leaflet.doubleClickZoom.disable();
}
} else {
this.map_leaflet = this.options.map_object;
this.setElement(this.map_leaflet.getContainer());
var c = self.map_leaflet.getCenter();
self._setModelProperty({ center: [c.lat, c.lng] });
self._setModelProperty({ zoom: self.map_leaflet.getZoom() });
// unset bounds to not change mapbounds
self.map.unset('view_bounds_sw', { silent: true });
self.map.unset('view_bounds_ne', { silent: true });
}
this.map.bind('set_view', this._setView, this);
this.map.layers.bind('add', this._addLayer, this);
this.map.layers.bind('remove', this._removeLayer, this);
this.map.layers.bind('reset', this._addLayers, this);
this.map.layers.bind('change:type', this._swicthLayerView, this);
this.map.geometries.bind('add', this._addGeometry, this);
this.map.geometries.bind('remove', this._removeGeometry, this);
this._bindModel();
this._addLayers();
this.setAttribution();
this.map_leaflet.on('layeradd', function(lyr) {
this.trigger('layeradd', lyr, self);
}, this);
this.map_leaflet.on('zoomstart', function() {
self.trigger('zoomstart');
});
this.map_leaflet.on('click', function(e) {
self.trigger('click', e.originalEvent, [e.latlng.lat, e.latlng.lng]);
});
this.map_leaflet.on('dblclick', function(e) {
self.trigger('dblclick', e.originalEvent);
});
this.map_leaflet.on('zoomend', function() {
self._setModelProperty({
zoom: self.map_leaflet.getZoom()
});
self.trigger('zoomend');
}, this);
this.map_leaflet.on('move', function() {
var c = self.map_leaflet.getCenter();
self._setModelProperty({ center: [c.lat, c.lng] });
});
this.map_leaflet.on('dragend', function() {
var c = self.map_leaflet.getCenter();
this.trigger('dragend', [c.lat, c.lng]);
}, this);
this.map_leaflet.on('drag', function() {
var c = self.map_leaflet.getCenter();
self._setModelProperty({
center: [c.lat, c.lng]
});
self.trigger('drag');
}, this);
this.map.bind('change:maxZoom', function() {
L.Util.setOptions(self.map_leaflet, { maxZoom: self.map.get('maxZoom') });
}, this);
this.map.bind('change:minZoom', function() {
L.Util.setOptions(self.map_leaflet, { minZoom: self.map.get('minZoom') });
}, this);
this.trigger('ready');
// looks like leaflet dont like to change the bounds just after the inicialization
var bounds = this.map.getViewBounds();
if (bounds) {
this.showBounds(bounds);
}
},
// this replaces the default functionality to search for
// already added views so they are not replaced
_addLayers: function() {
var self = this;
var oldLayers = this.layers;
this.layers = {};
function findLayerView(layer) {
var lv = _.find(oldLayers, function(layer_view) {
var m = layer_view.model;
return m.isEqual(layer);
});
return lv;
}
function canReused(layer) {
return self.map.layers.find(function(m) {
return m.isEqual(layer);
});
}
// remove all
for(var layer in oldLayers) {
var layer_view = oldLayers[layer];
if (!canReused(layer_view.model)) {
layer_view.remove();
}
}
this.map.layers.each(function(lyr) {
var lv = findLayerView(lyr);
if (!lv) {
self._addLayer(lyr);
} else {
lv.setModel(lyr);
self.layers[lyr.cid] = lv;
self.trigger('newLayerView', lv, lv.model, self);
}
});
},
clean: function() {
//see https://github.com/CloudMade/Leaflet/issues/1101
L.DomEvent.off(window, 'resize', this.map_leaflet._onResize, this.map_leaflet);
// remove layer views
for(var layer in this.layers) {
var layer_view = this.layers[layer];
layer_view.remove();
delete this.layers[layer];
}
// do not change by elder
cdb.core.View.prototype.clean.call(this);
},
_setKeyboard: function(model, z) {
if (z) {
this.map_leaflet.keyboard.enable();
} else {
this.map_leaflet.keyboard.disable();
}
},
_setScrollWheel: function(model, z) {
if (z) {
this.map_leaflet.scrollWheelZoom.enable();
} else {
this.map_leaflet.scrollWheelZoom.disable();
}
},
_setZoom: function(model, z) {
this._setView();
},
_setCenter: function(model, center) {
this._setView();
},
_setView: function() {
this.map_leaflet.setView(this.map.get("center"), this.map.get("zoom") || 0 );
},
_addGeomToMap: function(geom) {
var geo = cdb.geo.LeafletMapView.createGeometry(geom);
geo.geom.addTo(this.map_leaflet);
return geo;
},
_removeGeomFromMap: function(geo) {
this.map_leaflet.removeLayer(geo.geom);
},
createLayer: function(layer) {
return cdb.geo.LeafletMapView.createLayer(layer, this.map_leaflet);
},
_addLayer: function(layer, layers, opts) {
var self = this;
var lyr, layer_view;
layer_view = cdb.geo.LeafletMapView.createLayer(layer, this.map_leaflet);
if (!layer_view) {
return;
}
return this._addLayerToMap(layer_view, opts);
},
_addLayerToMap: function(layer_view, opts) {
var layer = layer_view.model;
this.layers[layer.cid] = layer_view;
cdb.geo.LeafletMapView.addLayerToMap(layer_view, this.map_leaflet);
// reorder layers
for(var i in this.layers) {
var lv = this.layers[i];
lv.setZIndex(lv.model.get('order'));
}
if(opts === undefined || !opts.silent) {
this.trigger('newLayerView', layer_view, layer_view.model, this);
}
return layer_view;
},
pixelToLatLon: function(pos) {
var point = this.map_leaflet.containerPointToLatLng([pos[0], pos[1]]);
return point;
},
latLonToPixel: function(latlon) {
var point = this.map_leaflet.latLngToLayerPoint(new L.LatLng(latlon[0], latlon[1]));
return this.map_leaflet.layerPointToContainerPoint(point);
},
// return the current bounds of the map view
getBounds: function() {
var b = this.map_leaflet.getBounds();
var sw = b.getSouthWest();
var ne = b.getNorthEast();
return [
[sw.lat, sw.lng],
[ne.lat, ne.lng]
];
},
setAttribution: function() {
var attributionControl = this._getAttributionControl();
// Save the attributions that were in the map the first time a new layer
// is added and the attributions of the map have changed
if (!this._originalAttributions) {
this._originalAttributions = Object.keys(attributionControl._attributions);
}
// Clear the attributions and re-add the original and custom attributions in
// the order we want
attributionControl._attributions = {};
var newAttributions = this._originalAttributions.concat(this.map.get('attribution'));
_.each(newAttributions, function(attribution) {
attributionControl.addAttribution(cdb.core.sanitize.html(attribution));
});
},
_getAttributionControl: function() {
if (this._attributionControl) {
return this._attributionControl;
}
this._attributionControl = this.map_leaflet.attributionControl;
if (!this._attributionControl) {
this._attributionControl = L.control.attribution({ prefix: '' });
this.map_leaflet.addControl(this._attributionControl);
}
return this._attributionControl;
},
getSize: function() {
return this.map_leaflet.getSize();
},
panBy: function(p) {
this.map_leaflet.panBy(new L.Point(p.x, p.y));
},
setCursor: function(cursor) {
$(this.map_leaflet.getContainer()).css('cursor', cursor);
},
getNativeMap: function() {
return this.map_leaflet;
},
invalidateSize: function() {
// there is a race condition in leaflet. If size is invalidated
// and at the same time the center is set the final center is displaced
// so set pan to false so the map is not moved and then force the map
// to be at the place it should be
this.map_leaflet.invalidateSize({ pan: false })//, animate: false });
this.map_leaflet.setView(this.map.get("center"), this.map.get("zoom") || 0, {
animate: false
});
}
}, {
layerTypeMap: {
"tiled": cdb.geo.LeafLetTiledLayerView,
"wms": cdb.geo.LeafLetWMSLayerView,
"cartodb": cdb.geo.LeafLetLayerCartoDBView,
"carto": cdb.geo.LeafLetLayerCartoDBView,
"plain": cdb.geo.LeafLetPlainLayerView,
// Substitutes the GMaps baselayer w/ an equivalent Leaflet tiled layer, since not supporting Gmaps anymore
"gmapsbase": cdb.geo.LeafLetGmapsTiledLayerView,
"layergroup": cdb.geo.LeafLetCartoDBLayerGroupView,
"namedmap": cdb.geo.LeafLetCartoDBNamedMapView,
"torque": function(layer, map) {
return new cdb.geo.LeafLetTorqueLayer(layer, map);
}
},
createLayer: function(layer, map) {
var layer_view = null;
var layerClass = this.layerTypeMap[layer.get('type').toLowerCase()];
if (layerClass) {
try {
layer_view = new layerClass(layer, map);
} catch(e) {
cdb.log.error("MAP: error creating '" + layer.get('type') + "' layer -> " + e.message);
}
} else {
cdb.log.error("MAP: " + layer.get('type') + " can't be created");
}
return layer_view;
},
addLayerToMap: function(layer_view, map, pos) {
map.addLayer(layer_view.leafletLayer);
if(pos !== undefined) {
if (layer_view.setZIndex) {
layer_view.setZIndex(pos);
}
}
},
/**
* create the view for the geometry model
*/
createGeometry: function(geometryModel) {
if(geometryModel.isPoint()) {
return new cdb.geo.leaflet.PointView(geometryModel);
}
return new cdb.geo.leaflet.PathView(geometryModel);
}
});
// set the image path in order to be able to get leaflet icons
// code adapted from leaflet
L.Icon.Default.imagePath = (function () {
var scripts = document.getElementsByTagName('script'),
leafletRe = /\/?cartodb[\-\._]?([\w\-\._]*)\.js\??/;
var i, len, src, matches;
for (i = 0, len = scripts.length; i < len; i++) {
src = scripts[i].src;
matches = src.match(leafletRe);
if (matches) {
var bits = src.split('/')
delete bits[bits.length - 1];
return bits.join('/') + 'themes/css/images';
}
}
}());
})();
@@ -0,0 +1,61 @@
(function() {
/**
* base layer for all leaflet layers
*/
var LeafLetLayerView = function(layerModel, leafletLayer, leafletMap) {
this.leafletLayer = leafletLayer;
this.leafletMap = leafletMap;
this.model = layerModel;
this.setModel(layerModel);
this.type = layerModel.get('type') || layerModel.get('kind');
this.type = this.type.toLowerCase();
};
_.extend(LeafLetLayerView.prototype, Backbone.Events);
_.extend(LeafLetLayerView.prototype, {
setModel: function(model) {
if (this.model) {
this.model.unbind('change', this._modelUpdated, this);
}
this.model = model;
this.model.bind('change', this._modelUpdated, this);
},
/**
* remove layer from the map and unbind events
*/
remove: function() {
this.leafletMap.removeLayer(this.leafletLayer);
this.trigger('remove', this);
this.model.unbind(null, null, this);
this.unbind();
},
/*
show: function() {
this.leafletLayer.setOpacity(1.0);
},
hide: function() {
this.leafletLayer.setOpacity(0.0);
},
*/
/**
* reload the tiles
*/
reload: function() {
this.leafletLayer.redraw();
}
});
cdb.geo.LeafLetLayerView = LeafLetLayerView;
})();
@@ -0,0 +1,163 @@
(function() {
if(typeof(L) == "undefined")
return;
L.CartoDBLayer = L.CartoDBGroupLayer.extend({
options: {
query: "SELECT * FROM {{table_name}}",
opacity: 0.99,
attribution: cdb.config.get('cartodb_attributions'),
debug: false,
visible: true,
added: false,
extra_params: {},
layer_definition_version: '1.0.0'
},
initialize: function (options) {
L.Util.setOptions(this, options);
if (!options.table_name || !options.user_name || !options.tile_style) {
throw ('cartodb-leaflet needs at least a CartoDB table name, user_name and tile_style');
}
L.CartoDBGroupLayer.prototype.initialize.call(this, {
layer_definition: {
version: this.options.layer_definition_version,
layers: [{
type: 'cartodb',
options: this._getLayerDefinition(),
infowindow: this.options.infowindow
}]
}
});
this.setOptions(this.options);
},
setQuery: function(layer, sql) {
if(sql === undefined) {
sql = layer;
layer = 0;
}
sql = sql || 'select * from ' + this.options.table_name;
LayerDefinition.prototype.setQuery.call(this, layer, sql);
},
/**
* Returns if the layer is visible or not
*/
isVisible: function() {
return this.visible;
},
/**
* Returns if the layer belongs to the map
*/
isAdded: function() {
return this.options.added;
}
});
/**
* leatlet cartodb layer
*/
var LeafLetLayerCartoDBView = L.CartoDBLayer.extend({
//var LeafLetLayerCartoDBView = function(layerModel, leafletMap) {
initialize: function(layerModel, leafletMap) {
var self = this;
_.bindAll(this, 'featureOut', 'featureOver', 'featureClick');
var opts = _.clone(layerModel.attributes);
opts.map = leafletMap;
var // preserve the user's callbacks
_featureOver = opts.featureOver,
_featureOut = opts.featureOut,
_featureClick = opts.featureClick;
opts.featureOver = function() {
_featureOver && _featureOver.apply(this, arguments);
self.featureOver && self.featureOver.apply(this, arguments);
};
opts.featureOut = function() {
_featureOut && _featureOut.apply(this, arguments);
self.featureOut && self.featureOut.apply(this, arguments);
};
opts.featureClick = function() {
_featureClick && _featureClick.apply(this, arguments);
self.featureClick && self.featureClick.apply(opts, arguments);
};
layerModel.bind('change:visible', function() {
self.model.get('visible') ? self.show(): self.hide();
}, this);
L.CartoDBLayer.prototype.initialize.call(this, opts);
cdb.geo.LeafLetLayerView.call(this, layerModel, this, leafletMap);
},
_modelUpdated: function() {
var attrs = _.clone(this.model.attributes);
this.leafletLayer.setOptions(attrs);
},
featureOver: function(e, latlon, pixelPos, data) {
// dont pass leaflet lat/lon
this.trigger('featureOver', e, [latlon.lat, latlon.lng], pixelPos, data, 0);
},
featureOut: function(e) {
this.trigger('featureOut', e, 0);
},
featureClick: function(e, latlon, pixelPos, data) {
// dont pass leaflet lat/lon
this.trigger('featureClick', e, [latlon.lat, latlon.lng], pixelPos, data, 0);
},
reload: function() {
this.model.invalidate();
//this.redraw();
},
error: function(e) {
this.trigger('error', e?e.error:'unknown error');
this.model.trigger('tileError', e?e.error:'unknown error');
},
tilesOk: function(e) {
this.model.trigger('tileOk');
},
includes: [
cdb.geo.LeafLetLayerView.prototype,
Backbone.Events
]
});
/*_.extend(L.CartoDBLayer.prototype, CartoDBLayerCommon.prototype);
_.extend(
LeafLetLayerCartoDBView.prototype,
cdb.geo.LeafLetLayerView.prototype,
L.CartoDBLayer.prototype,
Backbone.Events, // be sure this is here to not use the on/off from leaflet
*/
cdb.geo.LeafLetLayerCartoDBView = LeafLetLayerCartoDBView;
})();
@@ -0,0 +1,455 @@
(function() {
if(typeof(L) == "undefined")
return;
L.CartoDBGroupLayerBase = L.TileLayer.extend({
interactionClass: wax.leaf.interaction,
includes: [
cdb.geo.LeafLetLayerView.prototype,
//LayerDefinition.prototype,
CartoDBLayerCommon.prototype
],
options: {
opacity: 0.99,
attribution: cdb.config.get('cartodb_attributions'),
debug: false,
visible: true,
added: false,
tiler_domain: "carto.com",
tiler_port: "80",
tiler_protocol: "http",
sql_api_domain: "carto.com",
sql_api_port: "80",
sql_api_protocol: "http",
maxZoom: 30, // default leaflet zoom level for a layers is 18, raise it
extra_params: {
},
cdn_url: null,
subdomains: null
},
initialize: function (options) {
options = options || {};
// Set options
L.Util.setOptions(this, options);
// Some checks
if (!options.layer_definition && !options.sublayers) {
throw new Error('cartodb-leaflet needs at least the layer_definition or sublayer list');
}
if(!options.layer_definition) {
this.options.layer_definition = LayerDefinition.layerDefFromSubLayers(options.sublayers);
}
LayerDefinition.call(this, this.options.layer_definition, this.options);
this.fire = this.trigger;
CartoDBLayerCommon.call(this);
L.TileLayer.prototype.initialize.call(this);
this.interaction = [];
this.addProfiling();
},
addProfiling: function() {
this.bind('tileloadstart', function(e) {
var s = this.tileStats || (this.tileStats = {});
s[e.tile.src] = cartodb.core.Profiler.metric('cartodb-js.tile.png.load.time').start();
});
var finish = function(e) {
var s = this.tileStats && this.tileStats[e.tile.src];
s && s.end();
};
this.bind('tileload', finish);
this.bind('tileerror', function(e) {
cartodb.core.Profiler.metric('cartodb-js.tile.png.error').inc();
finish(e);
});
},
// overwrite getTileUrl in order to
// support different tiles subdomains in tilejson way
getTileUrl: function (tilePoint) {
var EMPTY_GIF = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
this._adjustTilePoint(tilePoint);
var tiles = [EMPTY_GIF];
if(this.tilejson) {
tiles = this.tilejson.tiles;
}
var index = (tilePoint.x + tilePoint.y) % tiles.length;
return L.Util.template(tiles[index], L.Util.extend({
z: this._getZoomForUrl(),
x: tilePoint.x,
y: tilePoint.y
}, this.options));
},
/**
* Change opacity of the layer
* @params {Integer} New opacity
*/
setOpacity: function(opacity) {
if (isNaN(opacity) || opacity>1 || opacity<0) {
throw new Error(opacity + ' is not a valid value');
}
// Leaflet only accepts 0-0.99... Weird!
this.options.opacity = Math.min(opacity, 0.99);
if (this.options.visible) {
L.TileLayer.prototype.setOpacity.call(this, this.options.opacity);
this.fire('updated');
}
},
/**
* When Leaflet adds the layer... go!
* @params {map}
*/
onAdd: function(map) {
var self = this;
this.options.map = map;
// Add cartodb logo
if (this.options.cartodb_logo != false)
cdb.geo.common.CartoDBLogo.addWadus({ left:8, bottom:8 }, 0, map._container);
this.__update(function() {
// if while the layer was processed in the server is removed
// it should not be added to the map
var id = L.stamp(self);
if (!map._layers[id]) {
return;
}
L.TileLayer.prototype.onAdd.call(self, map);
self.fire('added');
self.options.added = true;
});
},
getAttribution: function() {
return cdb.core.sanitize.html(this.options.attribution);
},
/**
* When removes the layer, destroy interactivity if exist
*/
onRemove: function(map) {
if(this.options.added) {
this.options.added = false;
L.TileLayer.prototype.onRemove.call(this, map);
}
},
/**
* Update CartoDB layer
* generates a new url for tiles and refresh leaflet layer
* do not collide with leaflet _update
*/
__update: function(done) {
var self = this;
this.fire('updated');
this.fire('loading');
var map = this.options.map;
this.getTiles(function(urls, err) {
if(urls) {
self.tilejson = urls;
self.setUrl(self.tilejson.tiles[0]);
// manage interaction
self._reloadInteraction();
self.ok && self.ok();
done && done();
} else {
self.error && self.error(err);
done && done();
}
});
},
_checkLayer: function() {
if (!this.options.added) {
throw new Error('the layer is not still added to the map');
}
},
/**
* Set a new layer attribution
* @params {String} New attribution string
*/
setAttribution: function(attribution) {
this._checkLayer();
// Remove old one
this.map.attributionControl.removeAttribution(
cdb.core.sanitize.html(this.options.attribution)
);
// Change text
this.map.attributionControl.addAttribution(
cdb.core.sanitize.html(attribution)
);
// Set new attribution in the options
this.options.attribution = attribution;
// Change in the layer
this.tilejson.attribution = this.options.attribution;
this.fire('updated');
},
/**
* Bind events for wax interaction
* @param {Object} Layer map object
* @param {Event} Wax event
*/
_manageOnEvents: function(map, o) {
var layer_point = this._findPos(map,o);
if (!layer_point || isNaN(layer_point.x) || isNaN(layer_point.y)) {
// If layer_point doesn't contain x and y,
// we can't calculate event map position
return false;
}
var latlng = map.layerPointToLatLng(layer_point);
var event_type = o.e.type.toLowerCase();
var screenPos = map.layerPointToContainerPoint(layer_point);
switch (event_type) {
case 'mousemove':
if (this.options.featureOver) {
return this.options.featureOver(o.e,latlng, screenPos, o.data, o.layer);
}
break;
case 'click':
case 'touchend':
case 'touchmove': // for some reason android browser does not send touchend
case 'mspointerup':
case 'pointerup':
case 'pointermove':
if (this.options.featureClick) {
this.options.featureClick(o.e,latlng, screenPos, o.data, o.layer);
}
break;
default:
break;
}
},
/**
* Bind off event for wax interaction
*/
_manageOffEvents: function(map, o) {
if (this.options.featureOut) {
return this.options.featureOut && this.options.featureOut(o.e, o.layer);
}
},
/**
* Get the Leaflet Point of the event
* @params {Object} Map object
* @params {Object} Wax event object
*/
_findPos: function (map, o) {
var curleft = 0;
var curtop = 0;
var obj = map.getContainer();
var x, y;
if (o.e.changedTouches && o.e.changedTouches.length > 0) {
x = o.e.changedTouches[0].clientX + window.scrollX;
y = o.e.changedTouches[0].clientY + window.scrollY;
} else {
x = o.e.clientX;
y = o.e.clientY;
}
// If the map is fixed at the top of the window, we can't use offsetParent
// cause there might be some scrolling that we need to take into account.
if (obj.offsetParent && obj.offsetTop > 0) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
var point = this._newPoint(
x - curleft, y - curtop);
} else {
var rect = obj.getBoundingClientRect();
var scrollX = (window.scrollX || window.pageXOffset);
var scrollY = (window.scrollY || window.pageYOffset);
var point = this._newPoint(
(o.e.clientX? o.e.clientX: x) - rect.left - obj.clientLeft - scrollX,
(o.e.clientY? o.e.clientY: y) - rect.top - obj.clientTop - scrollY);
}
return map.containerPointToLayerPoint(point);
},
/**
* Creates an instance of a Leaflet Point
*/
_newPoint: function(x, y) {
return new L.Point(x, y);
}
});
L.CartoDBGroupLayer = L.CartoDBGroupLayerBase.extend({
includes: [
LayerDefinition.prototype,
],
_modelUpdated: function() {
this.setLayerDefinition(this.model.get('layer_definition'));
}
});
function layerView(base) {
var layerViewClass = base.extend({
includes: [
cdb.geo.LeafLetLayerView.prototype,
Backbone.Events
],
initialize: function(layerModel, leafletMap) {
var self = this;
var hovers = [];
var opts = _.clone(layerModel.attributes);
opts.map = leafletMap;
var // preserve the user's callbacks
_featureOver = opts.featureOver,
_featureOut = opts.featureOut,
_featureClick = opts.featureClick;
var previousEvent;
var eventTimeout = -1;
opts.featureOver = function(e, latlon, pxPos, data, layer) {
if (!hovers[layer]) {
self.trigger('layerenter', e, latlon, pxPos, data, layer);
}
hovers[layer] = 1;
_featureOver && _featureOver.apply(this, arguments);
self.featureOver && self.featureOver.apply(self, arguments);
// if the event is the same than before just cancel the event
// firing because there is a layer on top of it
if (e.timeStamp === previousEvent) {
clearTimeout(eventTimeout);
}
eventTimeout = setTimeout(function() {
self.trigger('mouseover', e, latlon, pxPos, data, layer);
self.trigger('layermouseover', e, latlon, pxPos, data, layer);
}, 0);
previousEvent = e.timeStamp;
};
opts.featureOut = function(m, layer) {
if (hovers[layer]) {
self.trigger('layermouseout', layer);
}
hovers[layer] = 0;
if(!_.any(hovers)) {
self.trigger('mouseout');
}
_featureOut && _featureOut.apply(this, arguments);
self.featureOut && self.featureOut.apply(self, arguments);
};
opts.featureClick = _.debounce(function() {
_featureClick && _featureClick.apply(self, arguments);
self.featureClick && self.featureClick.apply(self, arguments);
}, 10);
base.prototype.initialize.call(this, opts);
cdb.geo.LeafLetLayerView.call(this, layerModel, this, leafletMap);
},
featureOver: function(e, latlon, pixelPos, data, layer) {
// dont pass leaflet lat/lon
this.trigger('featureOver', e, [latlon.lat, latlon.lng], pixelPos, data, layer);
},
featureOut: function(e, layer) {
this.trigger('featureOut', e, layer);
},
featureClick: function(e, latlon, pixelPos, data, layer) {
// dont pass leaflet lat/lon
this.trigger('featureClick', e, [latlon.lat, latlon.lng], pixelPos, data, layer);
},
error: function(e) {
this.trigger('error', e ? (e.errors || e) : 'unknown error');
this.model.trigger('error', e?e.errors:'unknown error');
},
ok: function(e) {
this.model.trigger('tileOk');
},
onLayerDefinitionUpdated: function() {
this.__update();
}
});
return layerViewClass;
}
L.NamedMap = L.CartoDBGroupLayerBase.extend({
includes: [
cdb.geo.LeafLetLayerView.prototype,
NamedMap.prototype,
CartoDBLayerCommon.prototype
],
initialize: function (options) {
options = options || {};
// Set options
L.Util.setOptions(this, options);
// Some checks
if (!options.named_map && !options.sublayers) {
throw new Error('cartodb-leaflet needs at least the named_map');
}
NamedMap.call(this, this.options.named_map, this.options);
this.fire = this.trigger;
CartoDBLayerCommon.call(this);
L.TileLayer.prototype.initialize.call(this);
this.interaction = [];
this.addProfiling();
},
_modelUpdated: function() {
this.setLayerDefinition(this.model.get('named_map'));
}
});
cdb.geo.LeafLetCartoDBLayerGroupView = layerView(L.CartoDBGroupLayer);
cdb.geo.LeafLetCartoDBNamedMapView = layerView(L.NamedMap);
})();
@@ -0,0 +1,63 @@
(function() {
if(typeof(L) == "undefined")
return;
var stamenSubstitute = function stamenSubstitute(type) {
return {
url: 'http://{s}.basemaps.cartocdn.com/'+ type +'_all/{z}/{x}/{y}.png',
subdomains: 'abcd',
minZoom: 0,
maxZoom: 18,
attribution: 'Map designs by <a href="http://stamen.com/">Stamen</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, Provided by <a href="https://carto.com">CARTO</a>'
};
};
var nokiaSubstitute = function nokiaSubstitute(type) {
return {
url: 'https://{s}.maps.nlp.nokia.com/maptile/2.1/maptile/newest/'+ type +'.day/{z}/{x}/{y}/256/png8?lg=eng&token=A7tBPacePg9Mj_zghvKt9Q&app_id=KuYppsdXZznpffJsKT24',
subdomains: '1234',
minZoom: 0,
maxZoom: 21,
attribution: '©2012 Nokia <a href="http://here.net/services/terms" target="_blank">Terms of use</a>'
};
};
var substitutes = {
roadmap: nokiaSubstitute('normal'),
gray_roadmap: stamenSubstitute('light'),
dark_roadmap: stamenSubstitute('dark'),
hybrid: nokiaSubstitute('hybrid'),
terrain: nokiaSubstitute('terrain'),
satellite: nokiaSubstitute('satellite')
};
var LeafLetGmapsTiledLayerView = L.TileLayer.extend({
initialize: function(layerModel, leafletMap) {
var substitute = substitutes[layerModel.get('base_type')];
L.TileLayer.prototype.initialize.call(this, substitute.url, {
tms: false,
attribution: substitute.attribution,
minZoom: substitute.minZoom,
maxZoom: substitute.maxZoom,
subdomains: substitute.subdomains,
errorTileUrl: '',
opacity: 1
});
cdb.geo.LeafLetLayerView.call(this, layerModel, this, leafletMap);
}
});
_.extend(LeafLetGmapsTiledLayerView.prototype, cdb.geo.LeafLetLayerView.prototype, {
_modelUpdated: function() {
// do nothing, this map type does not support updating
}
});
cdb.geo.LeafLetGmapsTiledLayerView = LeafLetGmapsTiledLayerView;
})();
@@ -0,0 +1,51 @@
(function() {
if(typeof(L) == "undefined")
return;
/**
* this is a dummy layer class that modifies the leaflet DOM element background
* instead of creating a layer with div
*/
var LeafLetPlainLayerView = L.Class.extend({
includes: L.Mixin.Events,
initialize: function(layerModel, leafletMap) {
cdb.geo.LeafLetLayerView.call(this, layerModel, this, leafletMap);
},
onAdd: function() {
this.redraw();
},
onRemove: function() {
var div = this.leafletMap.getContainer()
div.style.background = 'none';
},
_modelUpdated: function() {
this.redraw();
},
redraw: function() {
var div = this.leafletMap.getContainer()
div.style.backgroundColor = this.model.get('color') || '#FFF';
if (this.model.get('image')) {
var st = 'transparent url(' + this.model.get('image') + ') repeat center center';
div.style.background = st
}
},
// this method
setZIndex: function() {
}
});
_.extend(LeafLetPlainLayerView.prototype, cdb.geo.LeafLetLayerView.prototype);
cdb.geo.LeafLetPlainLayerView = LeafLetPlainLayerView;
})();
@@ -0,0 +1,50 @@
(function() {
if(typeof(L) == "undefined")
return;
var LeafLetTiledLayerView = L.TileLayer.extend({
initialize: function(layerModel, leafletMap) {
var tmpLayer = {
tms: layerModel.get('tms'),
attribution: layerModel.get('attribution'),
minZoom: layerModel.get('minZoom'),
maxZoom: layerModel.get('maxZoom'),
subdomains: layerModel.get('subdomains') || 'abc',
errorTileUrl: layerModel.get('errorTileUrl'),
opacity: layerModel.get('opacity')
};
if ( layerModel.get('tileSize') ) {
tmpLayer.tileSize = layerModel.get('tileSize');
}
if ( layerModel.get('zoomOffset') ) {
tmpLayer.zoomOffset = layerModel.get('zoomOffset');
}
L.TileLayer.prototype.initialize.call(this, layerModel.get('urlTemplate'), tmpLayer);
cdb.geo.LeafLetLayerView.call(this, layerModel, this, leafletMap);
}
});
_.extend(LeafLetTiledLayerView.prototype, cdb.geo.LeafLetLayerView.prototype, {
_modelUpdated: function() {
_.defaults(this.leafletLayer.options, _.clone(this.model.attributes));
this.leafletLayer.options.subdomains = this.model.get('subdomains') || 'abc';
this.leafletLayer.options.attribution = this.model.get('attribution');
this.leafletLayer.options.maxZoom = this.model.get('maxZoom');
this.leafletLayer.options.minZoom = this.model.get('minZoom');
// set url and reload
this.leafletLayer.setUrl(this.model.get('urlTemplate'));
}
});
cdb.geo.LeafLetTiledLayerView = LeafLetTiledLayerView;
})();
@@ -0,0 +1,48 @@
(function() {
if(typeof(L) == "undefined")
return;
var LeafLetWMSLayerView = L.TileLayer.WMS.extend({
initialize: function(layerModel, leafletMap) {
var tmpLayer = {
attribution: layerModel.get('attribution'),
layers: layerModel.get('layers'),
format: layerModel.get('format'),
transparent: layerModel.get('transparent'),
minZoom: layerModel.get('minZomm'),
maxZoom: layerModel.get('maxZoom'),
subdomains: layerModel.get('subdomains') || 'abc',
errorTileUrl: layerModel.get('errorTileUrl'),
opacity: layerModel.get('opacity')
};
if ( layerModel.get('tileSize') ) {
tmpLayer.tileSize = layerModel.get('tileSize');
}
if ( layerModel.get('zoomOffset') ) {
tmpLayer.zoomOffset = layerModel.get('zoomOffset');
}
L.TileLayer.WMS.prototype.initialize.call(this, layerModel.get('urlTemplate'), tmpLayer);
cdb.geo.LeafLetLayerView.call(this, layerModel, this, leafletMap);
}
});
_.extend(LeafLetWMSLayerView.prototype, cdb.geo.LeafLetLayerView.prototype, {
_modelUpdated: function() {
_.defaults(this.leafletLayer.options, _.clone(this.model.attributes));
this.leafletLayer.setUrl(this.model.get('urlTemplate'));
}
});
cdb.geo.LeafLetWMSLayerView = LeafLetWMSLayerView;
})();
@@ -0,0 +1,110 @@
(function() {
if(typeof(L) === "undefined")
return;
/**
* leaflet torque layer
*/
var LeafLetTorqueLayer = L.TorqueLayer.extend({
initialize: function(layerModel, leafletMap) {
var extra = layerModel.get('extra_params');
var query = this._getQuery(layerModel);
// initialize the base layers
L.TorqueLayer.prototype.initialize.call(this, {
table: layerModel.get('table_name'),
user: layerModel.get('user_name'),
column: layerModel.get('property'),
blendmode: layerModel.get('torque-blend-mode'),
resolution: 1,
//TODO: manage time columns
countby: 'count(cartodb_id)',
sql_api_domain: layerModel.get('sql_api_domain'),
sql_api_protocol: layerModel.get('sql_api_protocol'),
sql_api_port: layerModel.get('sql_api_port'),
tiler_protocol: layerModel.get('tiler_protocol'),
tiler_domain: layerModel.get('tiler_domain'),
tiler_port: layerModel.get('tiler_port'),
maps_api_template: layerModel.get('maps_api_template'),
stat_tag: layerModel.get('stat_tag'),
animationDuration: layerModel.get('torque-duration'),
steps: layerModel.get('torque-steps'),
sql: query,
visible: layerModel.get('visible'),
extra_params: {
api_key: extra ? extra.map_key: ''
},
cartodb_logo: layerModel.get('cartodb_logo'),
attribution: layerModel.get('attribution'),
cartocss: layerModel.get('cartocss') || layerModel.get('tile_style'),
named_map: layerModel.get('named_map'),
auth_token: layerModel.get('auth_token'),
no_cdn: layerModel.get('no_cdn'),
dynamic_cdn: layerModel.get('dynamic_cdn'),
loop: layerModel.get('loop') === false? false: true,
instanciateCallback: function() {
var cartocss = layerModel.get('cartocss') || layerModel.get('tile_style');
return '_cdbct_' + cdb.core.util.uniqueCallbackName(cartocss + query);
}
});
cdb.geo.LeafLetLayerView.call(this, layerModel, this, leafletMap);
// match leaflet events with backbone events
this.fire = this.trigger;
//this.setCartoCSS(layerModel.get('tile_style'));
if (layerModel.get('visible')) {
this.play();
}
this.bind('tilesLoaded', function() {
this.trigger('load');
}, this);
this.bind('tilesLoading', function() {
this.trigger('loading');
}, this);
},
onAdd: function(map) {
L.TorqueLayer.prototype.onAdd.apply(this, [map]);
// Add CartoDB logo
if (this.options.cartodb_logo != false)
cdb.geo.common.CartoDBLogo.addWadus({ left:8, bottom:8 }, 0, map._container)
},
_getQuery: function(layerModel) {
var query = layerModel.get('query');
var qw = layerModel.get('query_wrapper');
if(qw) {
query = _.template(qw)({ sql: query || ('select * from ' + layerModel.get('table_name')) });
}
return query;
},
_modelUpdated: function(model) {
var changed = this.model.changedAttributes();
if(changed === false) return;
changed.tile_style && this.setCartoCSS(this.model.get('tile_style'));
if ('query' in changed || 'query_wrapper' in changed) {
this.setSQL(this._getQuery(this.model));
}
if ('visible' in changed)
this.model.get('visible') ? this.show(): this.hide();
}
});
_.extend(LeafLetTorqueLayer.prototype, cdb.geo.LeafLetLayerView.prototype);
cdb.geo.LeafLetTorqueLayer = LeafLetTorqueLayer;
})();
+797
View File
@@ -0,0 +1,797 @@
/**
* Classes to manage maps
*/
/**
* Map layer, could be tiled or whatever
*/
cdb.geo.MapLayer = cdb.core.Model.extend({
defaults: {
visible: true,
type: 'Tiled'
},
/***
* Compare the layer with the received one
* @method isEqual
* @param layer {Layer}
*/
isEqual: function(layer) {
var me = this.toJSON()
, other = layer.toJSON()
// Select params generated when layer is added to the map
, map_params = ['id', 'order'];
// Delete from the layers copy
_.each(map_params, function(param){
delete me[param];
delete other[param];
if (me.options) delete me.options[param];
if (other.options) delete other.options[param];
});
var myType = me.type? me.type : me.options.type
, itsType = other.type? other.type : other.options.type;
if(myType && (myType === itsType)) {
if(myType === 'Tiled') {
var myTemplate = me.urlTemplate? me.urlTemplate : me.options.urlTemplate;
var itsTemplate = other.urlTemplate? other.urlTemplate : other.options.urlTemplate;
var myName = me.name? me.name : me.options.name;
var itsName = other.name? other.name : other.options.name;
return myTemplate === itsTemplate && myName === itsName;
} else if(myType === 'WMS') {
var myTemplate = me.urlTemplate? me.urlTemplate : me.options.urlTemplate;
var itsTemplate = other.urlTemplate? other.urlTemplate : other.options.urlTemplate;
var myLayer = me.layers? me.layers : me.options.layers;
var itsLayer = other.layers? other.layers : other.options.layers;
return myTemplate === itsTemplate && myLayer === itsLayer;
}
else if (myType === 'torque') {
return cdb.geo.TorqueLayer.prototype.isEqual.call(this, layer);
}
else if (myType === 'named_map') {
return cdb.geo.CartoDBNamedMapLayer.prototype.isEqual.call(this, layer);
} else { // same type but not tiled
var myBaseType = me.base_type? me.base_type : me.options.base_type;
var itsBaseType = other.base_type? other.base_type : other.options.base_type;
if(myBaseType) {
if(_.isEqual(me,other)) {
return true;
} else {
return false;
}
} else { // not gmaps
return true;
}
}
}
return false; // different type
}
});
// Good old fashioned tile layer
cdb.geo.TileLayer = cdb.geo.MapLayer.extend({
getTileLayer: function() {
}
});
cdb.geo.GMapsBaseLayer = cdb.geo.MapLayer.extend({
OPTIONS: ['roadmap', 'satellite', 'terrain', 'custom'],
defaults: {
type: 'GMapsBase',
base_type: 'gray_roadmap',
style: null
}
});
/**
* WMS layer support
*/
cdb.geo.WMSLayer = cdb.geo.MapLayer.extend({
defaults: {
service: 'WMS',
request: 'GetMap',
version: '1.1.1',
layers: '',
styles: '',
format: 'image/jpeg',
transparent: false
}
});
/**
* this layer allows to put a plain color or image as layer (instead of tiles)
*/
cdb.geo.PlainLayer = cdb.geo.MapLayer.extend({
defaults: {
type: 'Plain',
base_type: "plain",
className: "plain",
color: '#FFFFFF',
image: ''
}
});
cdb.geo.TorqueLayer = cdb.geo.MapLayer.extend({
defaults: {
type: 'torque',
visible: true
},
isEqual: function(other) {
var properties = ['query', 'query_wrapper', 'cartocss'];
var self = this;
return this.get('type') === other.get('type') && _.every(properties, function(p) {
return other.get(p) === self.get(p);
});
}
});
// CartoDB layer
cdb.geo.CartoDBLayer = cdb.geo.MapLayer.extend({
defaults: {
attribution: cdb.config.get('cartodb_attributions'),
type: 'CartoDB',
active: true,
query: null,
opacity: 0.99,
interactivity: null,
interaction: true,
debug: false,
tiler_domain: "carto.com",
tiler_port: "80",
tiler_protocol: "http",
sql_api_domain: "carto.com",
sql_api_port: "80",
sql_api_protocol: "http",
extra_params: {},
cdn_url: null,
maxZoom: 28
},
activate: function() {
this.set({active: true, opacity: 0.99, visible: true})
},
deactivate: function() {
this.set({active: false, opacity: 0, visible: false})
},
/**
* refresh the layer
*/
invalidate: function() {
var e = this.get('extra_params') || e;
e.cache_buster = new Date().getTime();
this.set('extra_params', e);
this.trigger('change', this);
},
toggle: function() {
if(this.get('active')) {
this.deactivate();
} else {
this.activate();
}
},
/*isEqual: function() {
return false;
}*/
});
cdb.geo.CartoDBGroupLayer = cdb.geo.MapLayer.extend({
defaults: {
visible: true,
type: 'layergroup'
},
initialize: function() {
this.sublayers = new cdb.geo.Layers();
},
isEqual: function() {
return false;
},
contains: function(layer) {
return layer.get('type') === 'cartodb';
}
});
cdb.geo.CartoDBNamedMapLayer = cdb.geo.MapLayer.extend({
defaults: {
visible: true,
type: 'namedmap'
},
isEqual: function(other) {
return _.isEqual(this.get('options').named_map, other.get('options').named_map);
}
});
var TILED_LAYER_TYPE = 'Tiled';
var CARTODB_LAYER_TYPE = 'CartoDB';
var TORQUE_LAYER_TYPE = 'torque';
cdb.geo.Layers = Backbone.Collection.extend({
model: cdb.geo.MapLayer,
initialize: function() {
this.comparator = function(m) {
return parseInt(m.get('order'), 10);
};
this.bind('add', this._assignIndexes);
this.bind('remove', this._assignIndexes);
},
/**
* each time a layer is added or removed
* the index should be recalculated
*/
_assignIndexes: function(model, col, options) {
if (this.size() > 0) {
// Assign an order of 0 to the first layer
this.at(0).set({ order: 0 });
if (this.size() > 1) {
var layersByType = {};
for (var i = 1; i < this.size(); ++i) {
var layer = this.at(i);
var layerType = layer.get('type');
layersByType[layerType] = layersByType[layerType] || [];
layersByType[layerType].push(layer);
}
var lastOrder = 0;
var sortedTypes = [CARTODB_LAYER_TYPE, TORQUE_LAYER_TYPE, TILED_LAYER_TYPE];
for (var i = 0; i < sortedTypes.length; ++i) {
var type = sortedTypes[i];
var layers = layersByType[type] || [];
for (var j = 0; j < layers.length; ++j) {
var layer = layers[j];
layer.set({
order: ++lastOrder
});
}
}
}
}
}
});
/**
* map model itself
*/
cdb.geo.Map = cdb.core.Model.extend({
defaults: {
attribution: [cdb.config.get('cartodb_attributions')],
center: [0, 0],
zoom: 3,
minZoom: 0,
maxZoom: 40,
scrollwheel: true,
drag: true,
keyboard: true,
provider: 'leaflet'
},
initialize: function() {
this.layers = new cdb.geo.Layers();
this.layers.bind('reset', function() {
if(this.layers.size() >= 1) {
this._adjustZoomtoLayer(this.layers.models[0]);
}
}, this);
this.layers.bind('reset', this._updateAttributions, this);
this.layers.bind('add', this._updateAttributions, this);
this.layers.bind('remove', this._updateAttributions, this);
this.layers.bind('change:attribution', this._updateAttributions, this);
this.geometries = new cdb.geo.Geometries();
},
_updateAttributions: function() {
var defaultCartoDBAttribution = cdb.core.sanitize.html(this.defaults.attribution[0]);
var attributions = _.chain(this.layers.models)
.map(function(layer) { return cdb.core.sanitize.html( layer.get('attribution') ); })
.reject(function(attribution) { return attribution == defaultCartoDBAttribution})
.compact()
.uniq()
.value();
attributions.push(defaultCartoDBAttribution);
this.set('attribution', attributions);
},
setView: function(latlng, zoom) {
this.set({
center: latlng,
zoom: zoom
}, {
silent: true
});
this.trigger("set_view");
},
setZoom: function(z) {
this.set({
zoom: z
});
},
enableKeyboard: function() {
this.set({
keyboard: true
});
},
disableKeyboard: function() {
this.set({
keyboard: false
});
},
enableScrollWheel: function() {
this.set({
scrollwheel: true
});
},
disableScrollWheel: function() {
this.set({
scrollwheel: false
});
},
getZoom: function() {
return this.get('zoom');
},
setCenter: function(latlng) {
this.set({
center: latlng
});
},
/**
* Change multiple options at the same time
* @params {Object} New options object
*/
setOptions: function(options) {
if (typeof options != "object" || options.length) {
if (this.options.debug) {
throw (options + ' options has to be an object');
} else {
return;
}
}
// Set options
_.defaults(this.options, options);
},
/**
* return getViewbounds if it is set
*/
getViewBounds: function() {
if(this.has('view_bounds_sw') && this.has('view_bounds_ne')) {
return [
this.get('view_bounds_sw'),
this.get('view_bounds_ne')
];
}
return null;
},
getLayerAt: function(i) {
return this.layers.at(i);
},
getLayerByCid: function(cid) {
return this.layers.getByCid(cid);
},
_adjustZoomtoLayer: function(layer) {
var maxZoom = parseInt(layer.get('maxZoom'), 10);
var minZoom = parseInt(layer.get('minZoom'), 10);
if (_.isNumber(maxZoom) && !_.isNaN(maxZoom)) {
if ( this.get("zoom") > maxZoom ) this.set({ zoom: maxZoom, maxZoom: maxZoom });
else this.set("maxZoom", maxZoom);
}
if (_.isNumber(minZoom) && !_.isNaN(minZoom)) {
if ( this.get("zoom") < minZoom ) this.set({ minZoom: minZoom, zoom: minZoom });
else this.set("minZoom", minZoom);
}
},
addLayer: function(layer, opts) {
if(this.layers.size() == 0) {
this._adjustZoomtoLayer(layer);
}
this.layers.add(layer, opts);
this.trigger('layerAdded');
if(this.layers.length === 1) {
this.trigger('firstLayerAdded');
}
return layer.cid;
},
removeLayer: function(layer) {
this.layers.remove(layer);
},
removeLayerByCid: function(cid) {
var layer = this.layers.getByCid(cid);
if (layer) this.removeLayer(layer);
else cdb.log.error("There's no layer with cid = " + cid + ".");
},
removeLayerAt: function(i) {
var layer = this.layers.at(i);
if (layer) this.removeLayer(layer);
else cdb.log.error("There's no layer in that position.");
},
clearLayers: function() {
while (this.layers.length > 0) {
this.removeLayer(this.layers.at(0));
}
},
// by default the base layer is the layer at index 0
getBaseLayer: function() {
return this.layers.at(0);
},
/**
* Checks if the base layer is already in the map as base map
*/
isBaseLayerAdded: function(layer) {
var baselayer = this.getBaseLayer()
return baselayer && layer.isEqual(baselayer);
},
/**
* gets the url of the template of the tile layer
* @method getLayerTemplate
*/
getLayerTemplate: function() {
var baseLayer = this.getBaseLayer();
if(baseLayer && baseLayer.get('options')) {
return baseLayer.get('options').urlTemplate;
}
},
addGeometry: function(geom) {
this.geometries.add(geom);
},
removeGeometry: function(geom) {
this.geometries.remove(geom);
},
setBounds: function(b) {
this.attributes.view_bounds_sw = [
b[0][0],
b[0][1]
];
this.attributes.view_bounds_ne = [
b[1][0],
b[1][1]
];
// change both at the same time
this.trigger('change:view_bounds_ne', this);
},
// set center and zoom according to fit bounds
fitBounds: function(bounds, mapSize) {
var z = this.getBoundsZoom(bounds, mapSize);
if(z === null) {
return;
}
// project -> calculate center -> unproject
var swPoint = cdb.geo.Map.latlngToMercator(bounds[0], z);
var nePoint = cdb.geo.Map.latlngToMercator(bounds[1], z);
var center = cdb.geo.Map.mercatorToLatLng({
x: (swPoint[0] + nePoint[0])*0.5,
y: (swPoint[1] + nePoint[1])*0.5
}, z);
this.set({
center: center,
zoom: z
})
},
// adapted from leaflat src
// @return {Number, null} Calculated zoom from given bounds or the maxZoom if no appropriate zoom level could be found
// or null if given mapSize has no size.
getBoundsZoom: function(boundsSWNE, mapSize) {
// sometimes the map reports size = 0 so return null
if(mapSize.x === 0 || mapSize.y === 0) return null;
var size = [mapSize.x, mapSize.y],
zoom = this.get('minZoom') || 0,
maxZoom = this.get('maxZoom') || 24,
ne = boundsSWNE[1],
sw = boundsSWNE[0],
boundsSize = [],
nePoint,
swPoint,
zoomNotFound = true;
do {
zoom++;
nePoint = cdb.geo.Map.latlngToMercator(ne, zoom);
swPoint = cdb.geo.Map.latlngToMercator(sw, zoom);
boundsSize[0] = Math.abs(nePoint[0] - swPoint[0]);
boundsSize[1] = Math.abs(swPoint[1] - nePoint[1]);
zoomNotFound = boundsSize[0] <= size[0] || boundsSize[1] <= size[1];
} while (zoomNotFound && zoom <= maxZoom);
if (zoomNotFound) {
return maxZoom;
}
return zoom - 1;
}
}, {
latlngToMercator: function(latlng, zoom) {
var ll = new L.LatLng(latlng[0], latlng[1]);
var pp = L.CRS.EPSG3857.latLngToPoint(ll, zoom);
return [pp.x, pp.y];
},
mercatorToLatLng: function(point, zoom) {
var ll = L.CRS.EPSG3857.pointToLatLng(point, zoom);
return [ll.lat, ll.lng]
}
});
/**
* Base view for all impl
*/
cdb.geo.MapView = cdb.core.View.extend({
initialize: function() {
if (this.options.map === undefined) {
throw "you should specify a map model";
}
this.map = this.options.map;
this.add_related_model(this.map);
this.add_related_model(this.map.layers);
this.autoSaveBounds = false;
// this var stores views information for each model
this.layers = {};
this.geometries = {};
this.bind('clean', this._removeLayers, this);
},
render: function() {
return this;
},
/**
* add a infowindow to the map
*/
addInfowindow: function(infoWindowView) {
this.addOverlay(infoWindowView);
},
addOverlay: function(overlay) {
if (overlay) {
this.$el.append(overlay.render().el);
this.addView(overlay);
}
},
/**
* search in the subviews and return the infowindows
*/
getInfoWindows: function() {
var result = [];
for (var s in this._subviews) {
if(this._subviews[s] instanceof cdb.geo.ui.Infowindow) {
result.push(this._subviews[s]);
}
}
return result;
},
showBounds: function(bounds) {
throw "to be implemented";
},
/*_removeLayers: function() {
for(var layer in this.layers) {
this.layers[layer].remove();
}
this.layers = {}
},*/
/**
* set model property but unbind changes first in order to not create an infinite loop
*/
_setModelProperty: function(prop) {
this._unbindModel();
this.map.set(prop);
if(prop.center !== undefined || prop.zoom !== undefined) {
var b = this.getBounds();
this.map.set({
view_bounds_sw: b[0],
view_bounds_ne: b[1]
});
if(this.autoSaveBounds) {
this._saveLocation();
}
}
this._bindModel();
},
/** bind model properties */
_bindModel: function() {
this._unbindModel();
this.map.bind('change:view_bounds_sw', this._changeBounds, this);
this.map.bind('change:view_bounds_ne', this._changeBounds, this);
this.map.bind('change:zoom', this._setZoom, this);
this.map.bind('change:scrollwheel', this._setScrollWheel, this);
this.map.bind('change:keyboard', this._setKeyboard, this);
this.map.bind('change:center', this._setCenter, this);
this.map.bind('change:attribution', this.setAttribution, this);
},
/** unbind model properties */
_unbindModel: function() {
this.map.unbind('change:view_bounds_sw', null, this);
this.map.unbind('change:view_bounds_ne', null, this);
this.map.unbind('change:zoom', null, this);
this.map.unbind('change:scrollwheel', null, this);
this.map.unbind('change:keyboard', null, this);
this.map.unbind('change:center', null, this);
this.map.unbind('change:attribution', null, this);
},
_changeBounds: function() {
var bounds = this.map.getViewBounds();
if(bounds) {
this.showBounds(bounds);
}
},
showBounds: function(bounds) {
this.map.fitBounds(bounds, this.getSize())
},
_addLayers: function() {
var self = this;
this._removeLayers();
this.map.layers.each(function(lyr) {
self._addLayer(lyr);
});
},
_removeLayers: function(layer) {
for(var i in this.layers) {
var layer_view = this.layers[i];
layer_view.remove();
delete this.layers[i];
}
},
_removeLayer: function(layer) {
var layer_view = this.layers[layer.cid];
if(layer_view) {
layer_view.remove();
delete this.layers[layer.cid];
}
},
_swicthLayerView: function(layer, attr, opts) {
this._removeLayer(layer);
this._addLayer(layer, this.map.layers, opts);
},
_removeGeometry: function(geo) {
var geo_view = this.geometries[geo.cid];
delete this.layers[layer.cid];
},
getLayerByCid: function(cid) {
var l = this.layers[cid];
if(!l) {
cdb.log.debug("layer with cid " + cid + " can't be get");
}
return l;
},
_setZoom: function(model, z) {
throw "to be implemented";
},
_setCenter: function(model, center) {
throw "to be implemented";
},
_addLayer: function(layer, layers, opts) {
throw "to be implemented";
},
_addGeomToMap: function(geom) {
throw "to be implemented";
},
_removeGeomFromMap: function(geo) {
throw "to be implemented";
},
setAutoSaveBounds: function() {
var self = this;
this.autoSaveBounds = true;
},
_saveLocation: _.debounce(function() {
this.map.save(null, { silent: true });
}, 1000),
_addGeometry: function(geom) {
var view = this._addGeomToMap(geom);
this.geometries[geom.cid] = view;
},
_removeGeometry: function(geo) {
var geo_view = this.geometries[geo.cid];
this._removeGeomFromMap(geo_view);
delete this.geometries[geo.cid];
}
}, {
_getClass: function(provider) {
var mapViewClass = cdb.geo.LeafletMapView;
if(provider === 'googlemaps') {
if(typeof(google) != "undefined" && typeof(google.maps) != "undefined") {
mapViewClass = cdb.geo.GoogleMapsMapView;
} else {
cdb.log.error("you must include google maps library _before_ include cdb");
}
}
return mapViewClass;
},
create: function(el, mapModel) {
var _mapViewClass = cdb.geo.MapView._getClass(mapModel.get('provider'));
return new _mapViewClass({
el: el,
map: mapModel
});
}
});
@@ -0,0 +1,310 @@
function SubLayerFactory() {};
SubLayerFactory.createSublayer = function(type, layer, position) {
type = type && type.toLowerCase();
if (!type || type === 'mapnik' || type === 'cartodb') {
return new CartoDBSubLayer(layer, position);
} else if (type === 'http') {
return new HttpSubLayer(layer, position);
} else {
throw 'Sublayer type not supported';
}
};
function SubLayerBase(_parent, position) {
this._parent = _parent;
this._position = position;
this._added = true;
}
SubLayerBase.prototype = {
toJSON: function() {
throw 'toJSON must be implemented';
},
isValid: function() {
throw 'isValid must be implemented';
},
remove: function() {
this._check();
this._parent.removeLayer(this._position);
this._added = false;
this.trigger('remove', this);
this._onRemove();
},
_onRemove: function() {},
toggle: function() {
this.get('hidden') ? this.show() : this.hide();
return !this.get('hidden');
},
show: function() {
if(this.get('hidden')) {
this.set({
hidden: false
});
}
},
hide: function() {
if(!this.get('hidden')) {
this.set({
hidden: true
});
}
},
set: function(new_attrs) {
this._check();
var def = this._parent.getLayer(this._position);
var attrs = def.options;
for(var i in new_attrs) {
attrs[i] = new_attrs[i];
}
this._parent.setLayer(this._position, def);
if (new_attrs.hidden !== undefined) {
this.trigger('change:visibility', this, new_attrs.hidden);
}
return this;
},
unset: function(attr) {
var def = this._parent.getLayer(this._position);
delete def.options[attr];
this._parent.setLayer(this._position, def);
},
get: function(attr) {
this._check();
var attrs = this._parent.getLayer(this._position);
return attrs.options[attr];
},
isVisible: function(){
return ! this.get('hidden');
},
_check: function() {
if(!this._added) throw "sublayer was removed";
},
_unbindInteraction: function() {
if(!this._parent.off) return;
this._parent.off(null, null, this);
},
_bindInteraction: function() {
if(!this._parent.on) return;
var self = this;
// binds a signal to a layer event and trigger on this sublayer
// in case the position matches
var _bindSignal = function(signal, signalAlias) {
signalAlias = signalAlias || signal;
self._parent.on(signal, function() {
var args = Array.prototype.slice.call(arguments);
if (parseInt(args[args.length - 1], 10) == self._position) {
self.trigger.apply(self, [signalAlias].concat(args));
}
}, self);
};
_bindSignal('featureOver');
_bindSignal('featureOut');
_bindSignal('featureClick');
_bindSignal('layermouseover', 'mouseover');
_bindSignal('layermouseout', 'mouseout');
},
_setPosition: function(p) {
this._position = p;
}
};
// give events capabilitues
_.extend(SubLayerBase.prototype, Backbone.Events);
// CartoDB / Mapnik sublayers
function CartoDBSubLayer(layer, position) {
SubLayerBase.call(this, layer, position);
this._bindInteraction();
var layer = this._parent.getLayer(this._position);
// TODO: Test this
if (Backbone.Model && layer) {
this.infowindow = new Backbone.Model(layer.infowindow);
this.infowindow.bind('change', function() {
layer.infowindow = this.infowindow.toJSON();
this._parent.setLayer(this._position, layer);
}, this);
}
};
CartoDBSubLayer.prototype = _.extend({}, SubLayerBase.prototype, {
toJSON: function() {
var json = {
type: 'cartodb',
options: {
sql: this.getSQL(),
cartocss: this.getCartoCSS(),
cartocss_version: this.get('cartocss_version') || '2.1.0'
}
};
var interactivity = this.getInteractivity();
if (interactivity && interactivity.length > 0) {
json.options.interactivity = interactivity;
var attributes = this.getAttributes();
if (attributes.length > 0) {
json.options.attributes = {
id: 'cartodb_id',
columns: attributes
}
}
}
if (this.get('raster')) {
json.options.raster = true;
json.options.geom_column = "the_raster_webmercator";
json.options.geom_type = "raster";
json.options.raster_band = this.get('raster_band') || 0;
// raster needs 2.3.0 to work
json.options.cartocss_version = this.get('cartocss_version') || '2.3.0';
}
return json;
},
isValid: function() {
return this.get('sql') && this.get('cartocss');
},
_onRemove: function() {
this._unbindInteraction();
},
setSQL: function(sql) {
return this.set({
sql: sql
});
},
setCartoCSS: function(cartocss) {
return this.set({
cartocss: cartocss
});
},
setInteractivity: function(fields) {
return this.set({
interactivity: fields
});
},
setInteraction: function(active) {
this._parent.setInteraction(this._position, active);
},
getSQL: function() {
return this.get('sql');
},
getCartoCSS: function() {
return this.get('cartocss');
},
getInteractivity: function() {
var interactivity = this.get('interactivity');
if (interactivity) {
if (typeof(interactivity) === 'string') {
interactivity = interactivity.split(',');
}
return this._trimArrayItems(interactivity);
}
},
getAttributes: function() {
var columns = [];
if (this.get('attributes')) {
columns = this.get('attributes');
} else {
columns = _.map(this.infowindow.get('fields'), function(field){
return field.name;
});
}
return this._trimArrayItems(columns);
},
_trimArrayItems: function(array) {
return _.map(array, function(item) {
return item.trim();
})
}
});
// Http sublayer
function HttpSubLayer(layer, position) {
SubLayerBase.call(this, layer, position);
};
HttpSubLayer.prototype = _.extend({}, SubLayerBase.prototype, {
toJSON: function() {
var json = {
type: 'http',
options: {
urlTemplate: this.getURLTemplate()
}
};
var subdomains = this.get('subdomains');
if (subdomains) {
json.options.subdomains = subdomains;
}
var tms = this.get('tms');
if (tms !== undefined) {
json.options.tms = tms;
}
return json;
},
isValid: function() {
return this.get('urlTemplate');
},
setURLTemplate: function(urlTemplate) {
return this.set({
urlTemplate: urlTemplate
});
},
setSubdomains: function(subdomains) {
return this.set({
subdomains: subdomains
});
},
setTms: function(tms) {
return this.set({
tms: tms
});
},
getURLTemplate: function(urlTemplate) {
return this.get('urlTemplate');
},
getSubdomains: function(subdomains) {
return this.get('subdomains');
},
getTms: function(tms) {
return this.get('tms');
}
});
@@ -0,0 +1,351 @@
cdb.geo.ui.Annotation = cdb.core.View.extend({
className: "cartodb-overlay overlay-annotation",
defaults: {
minZoom: 0,
maxZoom: 40,
style: {
textAlign: "left",
zIndex: 5,
color: "#ffffff",
fontSize: "13",
fontFamilyName: "Helvetica",
boxColor: "#333333",
boxOpacity: 0.7,
boxPadding: 10,
lineWidth: 50,
lineColor: "#333333"
}
},
template: cdb.core.Template.compile(
'<div class="content">\
<div class="text widget_text">{{{ text }}}</div>\
<div class="stick"><div class="ball"></div></div>\
</div>',
'mustache'
),
events: {
"click": "stopPropagation"
},
stopPropagation: function(e) {
e.stopPropagation();
},
initialize: function() {
this.template = this.options.template || this.template;
this.mapView = this.options.mapView;
this.mobileEnabled = /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
this._cleanStyleProperties(this.options.style);
_.defaults(this.options.style, this.defaults.style);
this._setupModels();
this._bindMap();
},
_setupModels: function() {
this.model = new cdb.core.Model({
display: true,
hidden: false,
text: this.options.text,
latlng: this.options.latlng,
minZoom: this.options.minZoom || this.defaults.minZoom,
maxZoom: this.options.maxZoom || this.defaults.maxZoom
});
this.model.on("change:display", this._onChangeDisplay, this);
this.model.on("change:text", this._onChangeText, this);
this.model.on('change:latlng', this._place, this);
this.model.on('change:minZoom', this._applyZoomLevelStyle, this);
this.model.on('change:maxZoom', this._applyZoomLevelStyle, this);
this.style = new cdb.core.Model(this.options.style);
this.style.on("change", this._applyStyle, this);
this.add_related_model(this.style);
},
_bindMap: function() {
this.mapView.map.bind('change', this._place, this);
this.mapView.map.bind('change:zoom', this._applyZoomLevelStyle, this);
this.mapView.bind('zoomstart', this.hide, this);
this.mapView.bind('zoomend', this.show, this);
},
_unbindMap: function() {
this.mapView.map.unbind('change', this._place, this);
this.mapView.map.unbind('change:zoom', this._applyZoomLevelStyle, this);
this.mapView.unbind('zoomstart', this.hide, this);
this.mapView.unbind('zoomend', this.show, this);
},
_onChangeDisplay: function() {
if (this.model.get("display")) this.show();
else this.hide();
},
_onChangeText: function() {
this.$el.find(".text").html(this._sanitizedText());
},
_sanitizedText: function() {
return cdb.core.sanitize.html(this.model.get("text"), this.model.get('sanitizeText'));
},
_getStandardPropertyName: function(name) {
if (!name) {
return;
}
var parts = name.split("-");
if (parts.length === 1) {
return name;
} else {
return parts[0] + _.map(parts.slice(1), function(l) {
return l.slice(0,1).toUpperCase() + l.slice(1);
}).join("");
}
},
_cleanStyleProperties: function(hash) {
var standardProperties = {};
_.each(hash, function(value, key) {
standardProperties[this._getStandardPropertyName(key)] = value;
}, this);
this.options.style = standardProperties;
},
_belongsToCanvas: function() {
var mobile = (this.options.device === "mobile") ? true : false;
return mobile === this.mobileEnabled;
},
show: function(callback) {
if (this.model.get("hidden") || !this._belongsToCanvas()) return;
var self = this;
this.$el.css({ opacity: 0, display: "inline-table" }); // makes the element to behave fine in the borders of the screen
this.$el.stop().animate({ opacity: 1 }, { duration: 150, complete: function() {
callback && callback();
}});
},
hide: function(callback) {
this.$el.stop().fadeOut(150, function() {
callback && callback();
});
},
_place: function() {
var latlng = this.model.get("latlng");
var lineWidth = this.style.get("lineWidth");
var textAlign = this.style.get("textAlign");
var pos = this.mapView.latLonToPixel(latlng);
if (pos) {
var top = pos.y - this.$el.height()/2;
var left = pos.x + lineWidth;
if (textAlign === "right") {
left = pos.x - this.$el.width() - lineWidth - this.$el.find(".ball").width();
}
this.$el.css({ top: top, left: left });
}
},
setMinZoom: function(zoom) {
this.model.set("minZoom", zoom);
},
setMaxZoom: function(zoom) {
this.model.set("maxZoom", zoom);
},
setPosition: function(latlng) {
this.model.set("latlng", latlng);
},
setText: function(text) {
this.model.set("text", text);
},
setStyle: function(property, value) {
var standardProperty = this._getStandardPropertyName(property);
if (standardProperty) {
this.style.set(standardProperty, value);
}
},
_applyStyle: function() {
var textColor = this.style.get("color");
var textAlign = this.style.get("textAlign");
var boxColor = this.style.get("boxColor");
var boxOpacity = this.style.get("boxOpacity");
var boxPadding = this.style.get("boxPadding");
var lineWidth = this.style.get("lineWidth");
var lineColor = this.style.get("lineColor");
var fontFamily = this.style.get("fontFamilyName");
this.$text = this.$el.find(".text");
this.$text.css({ color: textColor, textAlign: textAlign });
this.$el.find(".content").css("padding", boxPadding);
this.$text.css("font-size", this.style.get("fontSize") + "px");
this.$el.css("z-index", this.style.get("zIndex"));
this.$el.find(".stick").css({ width: lineWidth, left: -lineWidth });
var fontFamilyClass = "";
if (fontFamily == "Droid Sans") fontFamilyClass = "droid";
else if (fontFamily == "Vollkorn") fontFamilyClass = "vollkorn";
else if (fontFamily == "Open Sans") fontFamilyClass = "open_sans";
else if (fontFamily == "Roboto") fontFamilyClass = "roboto";
else if (fontFamily == "Lato") fontFamilyClass = "lato";
else if (fontFamily == "Graduate") fontFamilyClass = "graduate";
else if (fontFamily == "Gravitas One") fontFamilyClass = "gravitas_one";
else if (fontFamily == "Old Standard TT") fontFamilyClass = "old_standard_tt";
this.$el
.removeClass("droid")
.removeClass("vollkorn")
.removeClass("roboto")
.removeClass("open_sans")
.removeClass("lato")
.removeClass("graduate")
.removeClass("gravitas_one")
.removeClass("old_standard_tt");
this.$el.addClass(fontFamilyClass);
if (textAlign === "right") {
this.$el.addClass("align-right");
this.$el.find(".stick").css({ left: "auto", right: -lineWidth });
} else {
this.$el.removeClass("align-right");
}
this._place();
this._applyZoomLevelStyle();
},
_getRGBA: function(color, opacity) {
return 'rgba(' + parseInt(color.slice(-6,-4),16)
+ ',' + parseInt(color.slice(-4,-2),16)
+ ',' + parseInt(color.slice(-2),16)
+ ',' + opacity + ' )';
},
_applyZoomLevelStyle: function() {
var boxColor = this.style.get("boxColor");
var boxOpacity = this.style.get("boxOpacity");
var lineColor = this.style.get("lineColor");
var minZoom = this.model.get("minZoom");
var maxZoom = this.model.get("maxZoom");
var currentZoom = this.mapView.map.get("zoom");
if (currentZoom >= minZoom && currentZoom <= maxZoom) {
var rgbaLineCol = this._getRGBA(lineColor, 1);
var rgbaBoxCol = this._getRGBA(boxColor, boxOpacity);
this.$el.find(".text").animate({ opacity: 1 }, 150);
this.$el.css("background-color", rgbaBoxCol);
this.$el.find(".stick").css("background-color", rgbaLineCol);
this.$el.find(".ball").css("background-color", rgbaLineCol);
this.model.set("hidden", false);
this.model.set("display", true);
} else {
this.model.set("hidden", true);
this.model.set("display", false);
}
},
clean: function() {
this._unbindMap();
cdb.core.View.prototype.clean.call(this);
},
_fixLinks: function() {
this.$el.find("a").each(function(i, link) {
$(this).attr("target", "_top");
});
},
render: function() {
var d = _.clone(this.model.attributes);
d.text = this._sanitizedText();
this.$el.html(this.template(d));
this._fixLinks();
var self = this;
setTimeout(function() {
self._applyStyle();
self._applyZoomLevelStyle();
self.show();
}, 500);
return this;
}
});
@@ -0,0 +1,113 @@
/**
* FullScreen widget:
*
* var widget = new cdb.ui.common.FullScreen({
* doc: ".container", // optional; if not specified, we do the fullscreen of the whole window
* template: this.getTemplate("table/views/fullscreen")
* });
*
*/
cdb.ui.common.FullScreen = cdb.core.View.extend({
tagName: 'div',
className: 'cartodb-fullscreen',
events: {
"click a": "_toggleFullScreen"
},
initialize: function() {
_.bindAll(this, 'render');
_.defaults(this.options, this.default_options);
this._addWheelEvent();
},
_addWheelEvent: function() {
var self = this;
var mapView = this.options.mapView;
$(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange', function() {
if (!document.fullscreenElement && !document.webkitFullscreenElement && !document.mozFullScreenElement && !document.msFullscreenElement) {
if (self.model.get("allowWheelOnFullscreen")) {
mapView.options.map.set("scrollwheel", false);
}
}
mapView.invalidateSize();
});
},
_toggleFullScreen: function(ev) {
if (ev) {
this.killEvent(ev);
}
var doc = window.document;
var docEl = doc.documentElement;
if (this.options.doc) { // we use a custom element
docEl = $(this.options.doc)[0];
}
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
var mapView = this.options.mapView;
if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {
if (docEl.webkitRequestFullScreen) {
// Cartodb.js #361 :: Full screen button not working on Safari 8.0.3 #361
// Safari has a bug that fullScreen doestn't work with Element.ALLOW_KEYBOARD_INPUT);
// Reference: Ehttp://stackoverflow.com/questions/8427413/webkitrequestfullscreen-fails-when-passing-element-allow-keyboard-input-in-safar
requestFullScreen.call(docEl, undefined);
} else {
// CartoDB.js #412 :: Fullscreen button is throwing errors
// Nowadays (2015/03/25), fullscreen is not supported in iOS Safari. Reference: http://caniuse.com/#feat=fullscreen
if (requestFullScreen) {
requestFullScreen.call(docEl);
}
}
if (mapView && this.model.get("allowWheelOnFullscreen")) {
mapView.map.set("scrollwheel", true);
}
} else {
cancelFullScreen.call(doc);
}
},
render: function() {
var options = _.extend(
this.options,
{
mapUrl: location.href || ''
}
);
this.$el.html(this.options.template(options));
if (!this._canFullScreenBeEnabled()) {
this.undelegateEvents();
cdb.log.info('FullScreen API is deprecated on insecure origins. See https://goo.gl/rStTGz for more details.');
}
return this;
},
_canFullScreenBeEnabled: function() {
if (this._isInIframe()) {
var parentUrl = document.referrer;
if (parentUrl.search('https:') !== 0) {
return false;
}
}
return true;
},
_isInIframe: function() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
});
@@ -0,0 +1,61 @@
cdb.geo.ui.Header = cdb.core.View.extend({
className: 'cartodb-header',
initialize: function() {
var extra = this.model.get("extra");
this.model.set({
title: extra.title,
description: extra.description,
show_title: extra.show_title,
show_description: extra.show_description
}, { silent: true });
},
show: function() {
//var display = this.model.get("display");
var hasTitle = this.model.get("title") && this.model.get("show_title");
var hasDescription = this.model.get("description") && this.model.get("show_description");
if (hasTitle || hasDescription) {
this.$el.show();
if (hasTitle) this.$el.find(".content div.title").show();
if (hasDescription) this.$el.find(".content div.description").show();
}
},
// Add target attribute to all links
_setLinksTarget: function(str) {
if (!str) return str;
var reg = new RegExp(/<(a)([^>]+)>/g);
return str.replace(reg, "<$1 target=\"_blank\"$2>");
},
render: function() {
var data = _.clone(this.model.attributes);
data.title = cdb.core.sanitize.html(data.title);
data.description = this._setLinksTarget(cdb.core.sanitize.html(data.description));
this.$el.html(this.options.template(data));
if (this.options.slides) {
this.slides_controller = new cdb.geo.ui.SlidesController({
transitions: this.options.transitions,
slides: this.options.slides
});
this.$el.append(this.slides_controller.render().$el);
}
if (this.model.get("show_title") || this.model.get("show_description")) {
this.show();
} else {
this.hide();
}
return this;
}
});
@@ -0,0 +1,83 @@
cdb.geo.ui.Image = cdb.geo.ui.Text.extend({
className: "cartodb-overlay image-overlay",
events: {
"click": "stopPropagation"
},
default_options: { },
stopPropagation: function(e) {
e.stopPropagation();
},
initialize: function() {
_.defaults(this.options, this.default_options);
this.template = this.options.template;
var self = this;
$(window).on("map_resized", function() {
self._place();
});
$(window).on("resize", function() {
self._place();
});
},
_applyStyle: function() {
var style = this.model.get("style");
var boxColor = style["box-color"];
var boxOpacity = style["box-opacity"];
var boxWidth = style["box-width"];
this.$el.find(".text").css(style);
this.$el.css("z-index", style["z-index"]);
var rgbaCol = 'rgba(' + parseInt(boxColor.slice(-6,-4),16)
+ ',' + parseInt(boxColor.slice(-4,-2),16)
+ ',' + parseInt(boxColor.slice(-2),16)
+', ' + boxOpacity + ' )';
this.$el.css({
backgroundColor: rgbaCol
});
this.$el.find("img").css({ width: boxWidth });
},
render: function() {
var content;
if (this.model.get("extra").has_default_image) {
content = _.template('<img src="<%- url %>" />')({ url: this.model.get("extra").public_default_image_url });
} else {
content = cdb.core.sanitize.html(this.model.get("extra").rendered_text, this.model.get('sanitizeContent'));
}
var data = _.chain(this.model.attributes).clone().extend({ content: content }).value();
this.$el.html(this.template(data));
var self = this;
setTimeout(function() {
self._applyStyle();
self._place();
self.show();
}, 900);
return this;
}
});
@@ -0,0 +1,72 @@
cdb.geo.ui.InfoBox = cdb.core.View.extend({
className: 'cartodb-infobox',
defaults: {
pos_margin: 20,
position: 'bottom|right',
width: 200
},
initialize: function() {
var self = this;
_.defaults(this.options, this.defaults);
if(this.options.layer) {
this.enable();
}
this.setTemplate(this.options.template || this.defaultTemplate, 'mustache');
},
setTemplate: function(tmpl) {
this.template = cdb.core.Template.compile(tmpl, 'mustache');
},
enable: function() {
if(this.options.layer) {
this.options.layer
.on('featureOver', function(e, latlng, pos, data) {
this.render(data).show();
}, this)
.on('featureOut', function() {
this.hide();
}, this);
}
},
disable: function() {
if(this.options.layer) {
this.options.layer.off(null, null, this);
}
},
// set position based on a string like "top|right", "top|left", "bottom|righ"...
setPosition: function(pos) {
var props = {};
if(pos.indexOf('top') !== -1) {
props.top = this.options.pos_margin;
} else if(pos.indexOf('bottom') !== -1) {
props.bottom = this.options.pos_margin;
}
if(pos.indexOf('left') !== -1) {
props.left = this.options.pos_margin;
} else if(pos.indexOf('right') !== -1) {
props.right = this.options.pos_margin;
}
this.$el.css(props);
},
render: function(data) {
this.$el.html( this.template(data) );
if(this.options.width) {
this.$el.css('width', this.options.width);
}
if(this.options.position) {
this.setPosition(this.options.position);
}
return this;
}
});
@@ -0,0 +1,866 @@
/** Usage:
*
* Add Infowindow model:
*
* var infowindowModel = new cdb.geo.ui.InfowindowModel({
* template_name: 'infowindow_light',
* latlng: [72, -45],
* offset: [100, 10]
* });
*
* var infowindow = new cdb.geo.ui.Infowindow({
* model: infowindowModel,
* mapView: mapView
* });
*
* Show the infowindow:
* infowindow.showInfowindow();
*
*/
cdb.geo.ui.InfowindowModel = Backbone.Model.extend({
SYSTEM_COLUMNS: ['the_geom', 'the_geom_webmercator', 'created_at', 'updated_at', 'cartodb_id', 'cartodb_georef_status'],
defaults: {
template_name: 'infowindow_light',
latlng: [0, 0],
offset: [28, 0], // offset of the tip calculated from the bottom left corner
maxHeight: 180, // max height of the content, not the whole infowindow
autoPan: true,
template: "",
content: "",
visibility: false,
alternative_names: { },
fields: null // contains the fields displayed in the infowindow
},
clearFields: function() {
this.set({fields: []});
},
saveFields: function(where) {
where = where || 'old_fields';
this.set(where, _.clone(this.get('fields')));
},
fieldCount: function() {
var fields = this.get('fields')
if (!fields) return 0;
return fields.length
},
restoreFields: function(whiteList, from) {
from = from || 'old_fields';
var fields = this.get(from);
if(whiteList) {
fields = fields.filter(function(f) {
return _.contains(whiteList, f.name);
});
}
if(fields && fields.length) {
this._setFields(fields);
}
this.unset(from);
},
_cloneFields: function() {
return _(this.get('fields')).map(function(v) {
return _.clone(v);
});
},
_setFields: function(f) {
f.sort(function(a, b) { return a.position - b.position; });
this.set({'fields': f});
},
sortFields: function() {
this.get('fields').sort(function(a, b) { return a.position - b.position; });
},
_addField: function(fieldName, at) {
var dfd = $.Deferred();
if(!this.containsField(fieldName)) {
var fields = this.get('fields');
if(fields) {
at = at === undefined ? fields.length: at;
fields.push({ name: fieldName, title: true, position: at });
} else {
at = at === undefined ? 0 : at;
this.set('fields', [{ name: fieldName, title: true, position: at }], { silent: true});
}
}
dfd.resolve();
return dfd.promise();
},
addField: function(fieldName, at) {
var self = this;
$.when(this._addField(fieldName, at)).then(function() {
self.sortFields();
self.trigger('change:fields');
self.trigger('add:fields');
});
return this;
},
getFieldProperty: function(fieldName, k) {
if(this.containsField(fieldName)) {
var fields = this.get('fields') || [];
var idx = _.indexOf(_(fields).pluck('name'), fieldName);
return fields[idx][k];
}
return null;
},
setFieldProperty: function(fieldName, k, v) {
if(this.containsField(fieldName)) {
var fields = this._cloneFields() || [];
var idx = _.indexOf(_(fields).pluck('name'), fieldName);
fields[idx][k] = v;
this._setFields(fields);
}
return this;
},
getAlternativeName: function(fieldName) {
return this.get("alternative_names") && this.get("alternative_names")[fieldName];
},
setAlternativeName: function(fieldName, alternativeName) {
var alternativeNames = this.get("alternative_names") || [];
alternativeNames[fieldName] = alternativeName;
this.set({ 'alternative_names': alternativeNames });
this.trigger('change:alternative_names');
},
getFieldPos: function(fieldName) {
var p = this.getFieldProperty(fieldName, 'position');
if(p == undefined) {
return Number.MAX_VALUE;
}
return p;
},
containsAlternativeName: function(fieldName) {
var names = this.get('alternative_names') || [];
return names[fieldName];
},
containsField: function(fieldName) {
var fields = this.get('fields') || [];
return _.contains(_(fields).pluck('name'), fieldName);
},
removeField: function(fieldName) {
if(this.containsField(fieldName)) {
var fields = this._cloneFields() || [];
var idx = _.indexOf(_(fields).pluck('name'), fieldName);
if(idx >= 0) {
fields.splice(idx, 1);
}
this._setFields(fields);
this.trigger('remove:fields')
}
return this;
},
// updates content with attributes
updateContent: function(attributes) {
var fields = this.get('fields');
this.set('content', cdb.geo.ui.InfowindowModel.contentForFields(attributes, fields));
},
closeInfowindow: function(){
if (this.get('visibility')) {
this.set("visibility", false);
this.trigger('close');
}
}
}, {
contentForFields: function(attributes, fields, options) {
options = options || {};
var render_fields = [];
for(var j = 0; j < fields.length; ++j) {
var field = fields[j];
var value = attributes[field.name];
if(options.empty_fields || (value !== undefined && value !== null)) {
render_fields.push({
title: field.title ? field.name : null,
value: attributes[field.name],
index: j
});
}
}
// manage when there is no data to render
if (render_fields.length === 0) {
render_fields.push({
title: null,
value: 'No data available',
index: 0,
type: 'empty'
});
}
return {
fields: render_fields,
data: attributes
};
}
});
cdb.geo.ui.Infowindow = cdb.core.View.extend({
className: "cartodb-infowindow",
spin_options: {
lines: 10, length: 0, width: 4, radius: 6, corners: 1, rotate: 0, color: 'rgba(0,0,0,0.5)',
speed: 1, trail: 60, shadow: false, hwaccel: true, className: 'spinner', zIndex: 2e9,
top: 'auto', left: 'auto', position: 'absolute'
},
events: {
// Close bindings
"click .close": "_closeInfowindow",
"touchstart .close": "_closeInfowindow",
"MSPointerDown .close": "_closeInfowindow",
// Rest infowindow bindings
"dragstart": "_checkOrigin",
"mousedown": "_checkOrigin",
"touchstart": "_checkOrigin",
"MSPointerDown": "_checkOrigin",
"dblclick": "_stopPropagation",
"DOMMouseScroll": "_stopBubbling",
'MozMousePixelScroll': "_stopBubbling",
"mousewheel": "_stopBubbling",
"dbclick": "_stopPropagation",
"click": "_stopPropagation"
},
initialize: function(){
var self = this;
_.bindAll(this, "render", "setLatLng", "_setTemplate", "_updatePosition",
"_update", "toggle", "show", "hide");
this.mapView = this.options.mapView;
// Set template if it is defined in options
if (this.options.template) this.model.set('template', this.options.template);
// Set template view variable and
// compile it if it is necessary
if (this.model.get('template')) {
this._compileTemplate();
} else {
this._setTemplate();
}
this.model.bind('change:content', this.render, this);
this.model.bind('change:template_name', this._setTemplate, this);
this.model.bind('change:latlng', this._update, this);
this.model.bind('change:visibility', this.toggle, this);
this.model.bind('change:template', this._compileTemplate, this);
this.model.bind('change:sanitizeTemplate', this._compileTemplate, this);
this.model.bind('change:alternative_names', this.render, this);
this.model.bind('change:width', this.render, this);
this.model.bind('change:maxHeight', this.render, this);
this.mapView.map.bind('change', this._updatePosition, this);
this.mapView.bind('zoomstart', function(){
self.hide(true);
});
this.mapView.bind('zoomend', function() {
self.show(true);
});
this.add_related_model(this.mapView.map);
// Hide the element
this.$el.hide();
},
/**
* Render infowindow content
*/
render: function() {
if(this.template) {
// If there is content, destroy the jscrollpane first, then remove the content.
var $jscrollpane = this.$(".cartodb-popup-content");
if ($jscrollpane.length > 0 && $jscrollpane.data() != null) {
$jscrollpane.data().jsp && $jscrollpane.data().jsp.destroy();
}
// Clone fields and template name
var fields = _.map(this.model.attributes.content.fields, function(field){
return _.clone(field);
});
var data = this.model.get('content') ? this.model.get('content').data : {};
// If a custom template is not applied, let's sanitized
// fields for the template rendering
if (this.model.get('template_name')) {
var template_name = _.clone(this.model.attributes.template_name);
// Sanitized them
fields = this._fieldsToString(fields, template_name);
}
// Join plan fields values with content to work with
// custom infowindows and CartoDB infowindows.
var values = {};
_.each(this.model.get('content').fields, function(pair) {
values[pair.title] = pair.value;
})
var obj = _.extend({
content: {
fields: fields,
data: data
}
},values);
this.$el.html(
cdb.core.sanitize.html(this.template(obj), this.model.get('sanitizeTemplate'))
);
// Set width and max-height from the model only
// If there is no width set, we don't force our infowindow
if (this.model.get('width')) {
this.$('.cartodb-popup').css('width', this.model.get('width') + 'px');
}
this.$('.cartodb-popup .cartodb-popup-content').css('max-height', this.model.get('maxHeight') + 'px');
// Hello jscrollpane hacks!
// It needs some time to initialize, if not it doesn't render properly the fields
// Check the height of the content + the header if exists
var self = this;
setTimeout(function() {
var actual_height = self.$(".cartodb-popup-content").outerHeight();
if (self.model.get('maxHeight') <= actual_height)
self.$(".cartodb-popup-content").jScrollPane({
verticalDragMinHeight: 20,
autoReinitialise: true
});
}, 1);
// If the infowindow is loading, show spin
this._checkLoading();
// If the template is 'cover-enabled', load the cover
this._loadCover();
if(!this.isLoadingData()) {
this.model.trigger('domready', this, this.$el);
this.trigger('domready', this, this.$el);
}
}
return this;
},
_getModelTemplate: function() {
return this.model.get("template_name")
},
/**
* Change template of the infowindow
*/
_setTemplate: function() {
if (this.model.get('template_name')) {
this.template = cdb.templates.getTemplate(this._getModelTemplate());
this.render();
}
},
/**
* Compile template of the infowindow
*/
_compileTemplate: function() {
var template = this.model.get('template') ?
this.model.get('template') :
cdb.templates.getTemplate(this._getModelTemplate());
if(typeof(template) !== 'function') {
this.template = new cdb.core.Template({
template: template,
type: this.model.get('template_type') || 'mustache'
}).asFunction()
} else {
this.template = template
}
this.render();
},
/**
* Check event origin
*/
_checkOrigin: function(ev) {
// If the mouse down come from jspVerticalBar
// dont stop the propagation, but if the event
// is a touchstart, stop the propagation
var come_from_scroll = (($(ev.target).closest(".jspVerticalBar").length > 0) && (ev.type != "touchstart"));
if (!come_from_scroll) {
ev.stopPropagation();
}
},
/**
* Convert values to string unless value is NULL
*/
_fieldsToString: function(fields, template_name) {
var fields_sanitized = [];
if (fields && fields.length > 0) {
var self = this;
fields_sanitized = _.map(fields, function(field,i) {
// Return whole attribute sanitized
return self._sanitizeField(field, template_name, field.index || i);
});
}
return fields_sanitized;
},
/**
* Sanitize fields, what does it mean?
* - If value is null, transform to string
* - If value is an url, add it as an attribute
* - Cut off title if it is very long (in header or image templates).
* - If the value is a valid url, let's make it a link.
* - More to come...
*/
_sanitizeField: function(attr, template_name, pos) {
// Check null or undefined :| and set both to empty == ''
if (attr.value == null || attr.value == undefined) {
attr.value = '';
}
//Get the alternative title
var alternative_name = this.model.getAlternativeName(attr.title);
if (attr.title && alternative_name) {
// Alternative title
attr.title = alternative_name;
} else if (attr.title) {
// Remove '_' character from titles
attr.title = attr.title.replace(/_/g,' ');
}
// Cast all values to string due to problems with Mustache 0 number rendering
var new_value = attr.value.toString();
// If it is index 0, not any field type, header template type and length bigger than 30... cut off the text!
if (!attr.type && pos==0 && attr.value.length > 35 && template_name && template_name.search('_header_') != -1) {
new_value = attr.value.substr(0,32) + "...";
}
// If it is index 1, not any field type, header image template type and length bigger than 30... cut off the text!
if (!attr.type && pos==1 && attr.value.length > 35 && template_name && template_name.search('_header_with_image') != -1) {
new_value = attr.value.substr(0,32) + "...";
}
// Is it the value a link?
if (this._isValidURL(attr.value)) {
new_value = "<a href='" + attr.value + "' target='_blank'>" + new_value + "</a>"
}
// If it is index 0, not any field type, header image template type... don't cut off the text or add any link!!
if (pos==0 && template_name.search('_header_with_image') != -1) {
new_value = attr.value;
}
// Save new sanitized value
attr.value = new_value;
return attr;
},
isLoadingData: function() {
var content = this.model.get("content");
return content.fields && content.fields.length == 1 && content.fields[0].type === "loading";
},
/**
* Check if infowindow is loading the row content
*/
_checkLoading: function() {
if (this.isLoadingData()) {
this._startSpinner();
} else {
this._stopSpinner();
}
},
/**
* Stop loading spinner
*/
_stopSpinner: function() {
if (this.spinner)
this.spinner.stop()
},
/**
* Start loading spinner
*/
_startSpinner: function($el) {
this._stopSpinner();
var $el = this.$el.find('.loading');
if ($el) {
// Check if it is dark or other to change color
var template_dark = this.model.get('template_name').search('dark') != -1;
if (template_dark) {
this.spin_options.color = '#FFF';
} else {
this.spin_options.color = 'rgba(0,0,0,0.5)';
}
this.spinner = new Spinner(this.spin_options).spin();
$el.append(this.spinner.el);
}
},
/**
* Stop loading spinner
*/
_containsCover: function() {
return this.$el.find(".cartodb-popup.header").attr("data-cover") ? true : false;
},
/**
* Get cover URL
*/
_getCoverURL: function() {
var content = this.model.get("content");
if (content && content.fields && content.fields.length > 0) {
return (content.fields[0].value || '').toString();
}
return false;
},
/**
* Attempts to load the cover URL and show it
*/
_loadCover: function() {
if (!this._containsCover()) return;
var self = this;
var $cover = this.$(".cover");
var $img = $cover.find("img");
var $shadow = this.$(".shadow");
var url = this._getCoverURL();
if (!this._isValidURL(url)) {
$img.hide();
$shadow.hide();
cdb.log.info("Header image url not valid");
return;
}
// configure spinner
var target = document.getElementById('spinner');
var opts = { lines: 9, length: 4, width: 2, radius: 4, corners: 1, rotate: 0, color: '#ccc', speed: 1, trail: 60, shadow: true, hwaccel: false, zIndex: 2e9 };
var spinner = new Spinner(opts).spin(target);
// create the image
$img.hide(function() {
this.remove();
});
$img = $("<img />").attr("src", url);
$cover.append($img);
$img.load(function(){
spinner.stop();
var w = $img.width();
var h = $img.height();
var coverWidth = $cover.width();
var coverHeight = $cover.height();
var ratio = h / w;
var coverRatio = coverHeight / coverWidth;
// Resize rules
if ( w > coverWidth && h > coverHeight) { // bigger image
if ( ratio < coverRatio ) $img.css({ height: coverHeight });
else {
var calculatedHeight = h / (w / coverWidth);
$img.css({ width: coverWidth, top: "50%", position: "absolute", "margin-top": -1*parseInt(calculatedHeight, 10)/2 });
}
} else {
var calculatedHeight = h / (w / coverWidth);
$img.css({ width: coverWidth, top: "50%", position: "absolute", "margin-top": -1*parseInt(calculatedHeight, 10)/2 });
}
$img.fadeIn(300);
})
.error(function(){
spinner.stop();
});
},
/**
* Return true if the provided URL is valid
*/
_isValidURL: function(url) {
if (url) {
var urlPattern = /^(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&amp;:\/~+#-|]*[\w@?^=%&amp;\/~+#-])?$/
return String(url).match(urlPattern) != null ? true : false;
}
return false;
},
/**
* Toggle infowindow visibility
*/
toggle: function() {
this.model.get("visibility") ? this.show() : this.hide();
},
/**
* Stop event bubbling
*/
_stopBubbling: function (e) {
e.preventDefault();
e.stopPropagation();
},
/**
* Stop event propagation
*/
_stopPropagation: function(ev) {
ev.stopPropagation();
},
/**
* Set loading state adding its content
*/
setLoading: function() {
this.model.set({
content: {
fields: [{
title: null,
alternative_name: null,
value: 'Loading content...',
index: null,
type: "loading"
}],
data: {}
}
})
return this;
},
/**
* Set loading state adding its content
*/
setError: function() {
this.model.set({
content: {
fields: [{
title: null,
alternative_name: null,
value: 'There has been an error...',
index: null,
type: 'error'
}],
data: {}
}
})
return this;
},
/**
* Set the correct position for the popup
*/
setLatLng: function (latlng) {
this.model.set("latlng", latlng);
return this;
},
/**
* Close infowindow
*/
_closeInfowindow: function(ev) {
if (ev) {
ev.preventDefault();
ev.stopPropagation();
}
if (this.model.get("visibility")) {
this.model.set("visibility", false);
this.trigger('close');
}
},
/**
* Set visibility infowindow
*/
showInfowindow: function() {
this.model.set("visibility", true);
},
/**
* Show infowindow (update, pan, etc)
*/
show: function (no_pan) {
var self = this;
if (this.model.get("visibility")) {
self.$el.css({ left: -5000 });
self._update(no_pan);
}
},
/**
* Get infowindow visibility
*/
isHidden: function () {
return !this.model.get("visibility");
},
/**
* Set infowindow to hidden
*/
hide: function (force) {
if (force || !this.model.get("visibility")) this._animateOut();
},
/**
* Update infowindow
*/
_update: function (no_pan) {
if(!this.isHidden()) {
var delay = 0;
if (!no_pan) {
var delay = this.adjustPan();
}
this._updatePosition();
this._animateIn(delay);
}
},
/**
* Animate infowindow to show up
*/
_animateIn: function(delay) {
if (!cdb.core.util.ie || (cdb.core.util.browser.ie && cdb.core.util.browser.ie.version > 8)) {
this.$el.css({
'marginBottom':'-10px',
'display': 'block',
'visibility':'visible',
opacity:0
});
this.$el
.delay(delay)
.animate({
opacity: 1,
marginBottom: 0
},300);
} else {
this.$el.show();
}
},
/**
* Animate infowindow to disappear
*/
_animateOut: function() {
if (!$.browser.msie || ($.browser.msie && parseInt($.browser.version) > 8 )) {
var self = this;
this.$el.animate({
marginBottom: "-10px",
opacity: "0",
display: "block"
}, 180, function() {
self.$el.css({visibility: "hidden"});
});
} else {
this.$el.hide();
}
},
/**
* Update the position (private)
*/
_updatePosition: function () {
if(this.isHidden()) return;
var
offset = this.model.get("offset"),
pos = this.mapView.latLonToPixel(this.model.get("latlng")),
x = this.$el.position().left,
y = this.$el.position().top,
containerHeight = this.$el.outerHeight(true),
containerWidth = this.$el.width(),
left = pos.x - offset[0],
size = this.mapView.getSize(),
bottom = -1*(pos.y - offset[1] - size.y);
this.$el.css({ bottom: bottom, left: left });
},
/**
* Adjust pan to show correctly the infowindow
*/
adjustPan: function () {
var offset = this.model.get("offset");
if (!this.model.get("autoPan") || this.isHidden()) { return; }
var
x = this.$el.position().left,
y = this.$el.position().top,
containerHeight = this.$el.outerHeight(true) + 15, // Adding some more space
containerWidth = this.$el.width(),
pos = this.mapView.latLonToPixel(this.model.get("latlng")),
adjustOffset = {x: 0, y: 0};
size = this.mapView.getSize()
wait_callback = 0;
if (pos.x - offset[0] < 0) {
adjustOffset.x = pos.x - offset[0] - 10;
}
if (pos.x - offset[0] + containerWidth > size.x) {
adjustOffset.x = pos.x + containerWidth - size.x - offset[0] + 10;
}
if (pos.y - containerHeight < 0) {
adjustOffset.y = pos.y - containerHeight - 10;
}
if (pos.y - containerHeight > size.y) {
adjustOffset.y = pos.y + containerHeight - size.y;
}
if (adjustOffset.x || adjustOffset.y) {
this.mapView.panBy(adjustOffset);
wait_callback = 300;
}
return wait_callback;
}
});
@@ -0,0 +1,238 @@
/**
* Layer selector: it allows to select the layers that will be shown in the map
* - It needs the mapview, the element template and the dropdown template
*
* var layer_selector = new cdb.geo.ui.LayerSelector({
* mapView: mapView,
* template: element_template,
* dropdown_template: dropdown_template
* });
*/
cdb.geo.ui.LayerSelector = cdb.core.View.extend({
className: 'cartodb-layer-selector-box',
events: {
"click": '_openDropdown',
"dblclick": 'killEvent',
"mousedown": 'killEvent'
},
initialize: function() {
this.map = this.options.mapView.map;
this.mapView = this.options.mapView;
this.mapView.bind('click zoomstart drag', function() {
this.dropdown && this.dropdown.hide()
}, this);
this.add_related_model(this.mapView);
this.layers = [];
},
render: function() {
this.$el.html(this.options.template(this.options));
this.dropdown = new cdb.ui.common.Dropdown({
className:"cartodb-dropdown border",
template: this.options.dropdown_template,
target: this.$el.find("a"),
speedIn: 300,
speedOut: 200,
position: "position",
tick: "right",
vertical_position: "down",
horizontal_position: "right",
vertical_offset: 7,
horizontal_offset: 13
});
if (cdb.god) cdb.god.bind("closeDialogs", this.dropdown.hide, this.dropdown);
this.$el.append(this.dropdown.render().el);
this._getLayers();
this._setCount();
return this;
},
_getLayers: function() {
var self = this;
this.layers = [];
_.each(this.map.layers.models, function(layer) {
if (layer.get("type") == 'layergroup' || layer.get('type') === 'namedmap') {
var layerGroupView = self.mapView.getLayerByCid(layer.cid);
for (var i = 0 ; i < layerGroupView.getLayerCount(); ++i) {
var l = layerGroupView.getLayer(i);
var m = new cdb.core.Model(l);
m.set('order', i);
m.set('type', 'layergroup');
if (m.get("visible") === undefined) m.set('visible', true);
m.bind('change:visible', function(model) {
this.trigger("change:visible", model.get('visible'), model.get('order'), model);
}, self);
if(self.options.layer_names) {
m.set('layer_name', self.options.layer_names[i]);
} else {
m.set('layer_name', l.options.layer_name);
}
var layerView = self._createLayer('LayerViewFromLayerGroup', {
model: m,
layerView: layerGroupView,
layerIndex: i
});
layerView.bind('switchChanged', self._setCount, self);
self.layers.push(layerView);
}
} else if (layer.get("type") === "CartoDB" || layer.get('type') === 'torque') {
var layerView = self._createLayer('LayerView', { model: layer });
layerView.bind('switchChanged', self._setCount, self);
self.layers.push(layerView);
layerView.model.bind('change:visible', function(model) {
this.trigger("change:visible", model.get('visible'), model.get('order'), model);
}, self);
}
});
},
_createLayer: function(_class, opts) {
var layerView = new cdb.geo.ui[_class](opts);
this.$("ul").append(layerView.render().el);
this.addView(layerView);
return layerView;
},
_setCount: function() {
var count = 0;
for (var i = 0, l = this.layers.length; i < l; ++i) {
var lyr = this.layers[i];
if (lyr.model.get('visible')) {
count++;
}
}
this.$('.count').text(count);
this.trigger("switchChanged", this);
},
_openDropdown: function() {
this.dropdown.open();
}
});
/**
* View for each CartoDB layer
* - It needs a model to make it work.
*
* var layerView = new cdb.geo.ui.LayerView({
* model: layer_model,
* layer_definition: layer_definition
* });
*
*/
cdb.geo.ui.LayerView = cdb.core.View.extend({
tagName: "li",
defaults: {
template: '\
<a class="layer" href="#/change-layer"><%- layer_name %></a>\
<a href="#switch" class="right <%- visible ? "enabled" : "disabled" %> switch"><span class="handle"></span></a>\
'
},
events: {
"click": '_onSwitchClick'
},
initialize: function() {
if (!this.model.has('visible')) this.model.set('visible', false);
this.model.bind("change:visible", this._onSwitchSelected, this);
this.add_related_model(this.model);
this._onSwitchSelected();
// Template
this.template = this.options.template ? cdb.templates.getTemplate(this.options.template) : _.template(this.defaults.template);
},
render: function() {
var attrs = _.clone(this.model.attributes);
attrs.layer_name = attrs.layer_name || attrs.table_name;
this.$el.append(this.template(attrs));
return this;
},
/*
* Throw an event when the user clicks in the switch button
*/
_onSwitchSelected: function() {
var enabled = this.model.get('visible');
// Change switch
this.$el.find(".switch")
.removeClass(enabled ? 'disabled' : 'enabled')
.addClass(enabled ? 'enabled' : 'disabled');
// Send trigger
this.trigger('switchChanged');
},
_onSwitchClick: function(e){
this.killEvent(e);
// Set model
this.model.set("visible", !this.model.get("visible"));
}
});
/**
* View for each layer from a layer group
* - It needs a model and the layer_definition to make it work.
*
* var layerView = new cdb.geo.ui.LayerViewFromLayerGroup({
* model: layer_model,
* layerView: layweView
* });
*
*/
cdb.geo.ui.LayerViewFromLayerGroup = cdb.geo.ui.LayerView.extend({
_onSwitchSelected: function() {
cdb.geo.ui.LayerView.prototype._onSwitchSelected.call(this);
var sublayer = this.options.layerView.getSubLayer(this.options.layerIndex)
var visible = this.model.get('visible');
if (visible) {
sublayer.show();
} else {
sublayer.hide();
}
}
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,818 @@
cdb.geo.ui.MobileLayer = cdb.core.View.extend({
events: {
'click h3': "_toggle",
"dblclick": "_stopPropagation"
},
tagName: "li",
className: "cartodb-mobile-layer has-toggle",
template: cdb.core.Template.compile("<% if (show_title) { %><h3><%- layer_name %><% } %><a href='#' class='toggle<%- toggle_class %>'></a></h3>"),
/**
* Stop event propagation
*/
_stopPropagation: function(ev) {
ev.stopPropagation();
},
initialize: function() {
_.defaults(this.options, this.default_options);
this.model.bind("change:visible", this._onChangeVisible, this);
},
_onChangeVisible: function() {
this.$el.find(".legend")[ this.model.get("visible") ? "fadeIn":"fadeOut"](150);
this.$el[ this.model.get("visible") ? "removeClass":"addClass"]("hidden");
this.trigger("change_visibility", this);
},
_toggle: function(e) {
e.preventDefault();
e.stopPropagation();
if (this.options.hide_toggle) return;
this.model.set("visible", !this.model.get("visible"))
},
_renderLegend: function() {
if (!this.options.show_legends) return;
if (this.model.get("legend") && (this.model.get("legend").type == "none" || !this.model.get("legend").type)) return;
if (this.model.get("legend") && this.model.get("legend").items && this.model.get("legend").items.length == 0) return;
this.$el.addClass("has-legend");
var legend = new cdb.geo.ui.Legend(this.model.get("legend"));
legend.undelegateEvents();
this.$el.append(legend.render().$el);
},
_truncate: function(input, length) {
return input.substr(0, length-1) + (input.length > length ? '&hellip;' : '');
},
render: function() {
var layer_name = this.model.get("layer_name");
layer_name = layer_name ? this._truncate(layer_name, 23) : "untitled";
var attributes = _.extend(
this.model.attributes,
{
layer_name: this.options.show_title ? layer_name : "",
toggle_class: this.options.hide_toggle ? " hide" : ""
}
);
this.$el.html(this.template(_.extend(attributes, { show_title: this.options.show_title } )));
if (this.options.hide_toggle) this.$el.removeClass("has-toggle");
if (!this.model.get("visible")) this.$el.addClass("hidden");
if (this.model.get("legend")) this._renderLegend();
this._onChangeVisible();
return this;
}
});
cdb.geo.ui.Mobile = cdb.core.View.extend({
className: "cartodb-mobile",
events: {
"click .cartodb-attribution-button": "_onAttributionClick",
"click .toggle": "_toggle",
"click .fullscreen": "_toggleFullScreen",
"click .backdrop": "_onBackdropClick",
"dblclick .aside": "_stopPropagation",
"dragstart .aside": "_checkOrigin",
"mousedown .aside": "_checkOrigin",
"touchstart .aside": "_checkOrigin",
"MSPointerDown .aside": "_checkOrigin",
},
initialize: function() {
_.bindAll(this, "_toggle", "_reInitScrollpane");
_.defaults(this.options, this.default_options);
this.hasLayerSelector = false;
this.layersLoading = 0;
this.slides_data = this.options.slides_data;
this.visualization = this.options.visualization;
if (this.visualization) {
this.slides = this.visualization.slides;
}
this.mobileEnabled = /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
this.visibility_options = this.options.visibility_options || {};
this.mapView = this.options.mapView;
this.map = this.mapView.map;
this.template = this.options.template ? this.options.template : cdb.templates.getTemplate('geo/zoom');
this._selectOverlays();
this._setupModel();
window.addEventListener('orientationchange', _.bind(this.doOnOrientationChange, this));
this._addWheelEvent();
},
loadingTiles: function() {
if (this.loader) {
this.loader.show()
}
if (this.layersLoading === 0) {
this.trigger('loading');
}
this.layersLoading++;
},
loadTiles: function() {
if (this.loader) {
this.loader.hide();
}
this.layersLoading--;
// check less than 0 because loading event sometimes is
// thrown before visualization creation
if(this.layersLoading <= 0) {
this.layersLoading = 0;
this.trigger('load');
}
},
_selectOverlays: function() {
if (this.slides && this.slides_data) { // if there are slides…
var state = this.slides.state();
if (state == 0) this.overlays = this.options.overlays; // first slide == master vis
else {
this.overlays = this.slides_data[state - 1].overlays;
}
} else { // otherwise we load the regular overlays
this.overlays = this.options.overlays;
}
},
_addWheelEvent: function() {
var self = this;
var mapView = this.options.mapView;
$(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange', function() {
if ( !document.fullscreenElement && !document.webkitFullscreenElement && !document.mozFullScreenElement && !document.msFullscreenElement) {
mapView.options.map.set("scrollwheel", false);
}
mapView.invalidateSize();
});
},
_setupModel: function() {
this.model = new Backbone.Model({
open: false,
layer_count: 0
});
this.model.on("change:open", this._onChangeOpen, this);
this.model.on("change:layer_count", this._onChangeLayerCount, this);
},
/**
* Check event origin
*/
_checkOrigin: function(ev) {
// If the mouse down come from jspVerticalBar
// dont stop the propagation, but if the event
// is a touchstart, stop the propagation
var come_from_scroll = (($(ev.target).closest(".jspVerticalBar").length > 0) && (ev.type != "touchstart"));
if (!come_from_scroll) {
ev.stopPropagation();
}
},
_stopPropagation: function(ev) {
ev.stopPropagation();
},
_onBackdropClick: function(e) {
e.preventDefault();
e.stopPropagation();
this.$el.find(".backdrop").fadeOut(250);
this.$el.find(".cartodb-attribution").fadeOut(250);
},
_onAttributionClick: function(e) {
e.preventDefault();
e.stopPropagation();
this.$el.find(".backdrop").fadeIn(250);
this.$el.find(".cartodb-attribution").fadeIn(250);
},
_toggle: function(e) {
e.preventDefault();
e.stopPropagation();
this.model.set("open", !this.model.get("open"));
},
_toggleFullScreen: function(ev) {
ev.stopPropagation();
ev.preventDefault();
var doc = window.document;
var docEl = $("#map > div")[0];
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen;
var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen;
var mapView = this.options.mapView;
if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement) {
requestFullScreen.call(docEl);
if (mapView) {
mapView.options.map.set("scrollwheel", true);
}
} else {
cancelFullScreen.call(doc);
}
},
_open: function() {
var right = this.$el.find(".aside").width();
this.$el.find(".cartodb-header").animate({ right: right }, 200)
this.$el.find(".aside").animate({ right: 0 }, 200)
this.$el.find(".cartodb-attribution-button").animate({ right: right + parseInt(this.$el.find(".cartodb-attribution-button").css("right")) }, 200)
this.$el.find(".cartodb-attribution").animate({ right: right + parseInt(this.$el.find(".cartodb-attribution-button").css("right")) }, 200)
this._initScrollPane();
},
_close: function() {
this.$el.find(".cartodb-header").animate({ right: 0 }, 200)
this.$el.find(".aside").animate({ right: - this.$el.find(".aside").width() }, 200)
this.$el.find(".cartodb-attribution-button").animate({ right: 20 }, 200)
this.$el.find(".cartodb-attribution").animate({ right: 20 }, 200)
},
default_options: {
timeout: 0,
msg: ''
},
_stopPropagation: function(ev) {
ev.stopPropagation();
},
doOnOrientationChange: function() {
switch(window.orientation)
{
case -90:
case 90: this.recalc("landscape");
break;
default: this.recalc("portrait");
break;
}
},
recalc: function(orientation) {
var height = $(".legends > div.cartodb-legend-stack").height();
if (this.$el.hasClass("open") && height < 100 && !this.$el.hasClass("torque")) {
this.$el.css("height", height);
this.$el.find(".top-shadow").hide();
this.$el.find(".bottom-shadow").hide();
} else if (this.$el.hasClass("open") && height < 100 && this.$el.hasClass("legends") && this.$el.hasClass("torque")) {
this.$el.css("height", height + $(".legends > div.torque").height() );
this.$el.find(".top-shadow").hide();
this.$el.find(".bottom-shadow").hide();
}
},
_onChangeLayerCount: function() {
var layer_count = this.model.get("layer_count");
var msg = layer_count + " layer" + (layer_count != 1 ? "s" : "");
this.$el.find(".aside .layer-container > h3").html(msg);
},
_onChangeOpen: function() {
this.model.get("open") ? this._open() : this._close();
},
_createLayer: function(_class, opts) {
return new cdb.geo.ui[_class](opts);
},
_getLayers: function() {
this.layers = [];
// we add the layers to the array depending on the method used
// to sent us the layers
if (this.options.layerView) {
this._getLayersFromLayerView();
} else {
_.each(this.map.layers.models, this._getLayer, this);
}
},
_getLayersFromLayerView: function() {
if (this.options.layerView && this.options.layerView.model.get("type") == "layergroup") {
this.layers = _.map(this.options.layerView.layers, function(l, i) {
var m = new cdb.core.Model(l);
m.set('order', i);
m.set('type', 'layergroup');
m.set('visible', l.visible);
m.set('layer_name', l.options.layer_name);
layerView = this._createLayer('LayerViewFromLayerGroup', {
model: m,
layerView: this.options.layerView,
layerIndex: i
});
return layerView.model;
}, this);
} else if (this.options.layerView && (this.options.layerView.model.get("type") == "torque")) {
var layerView = this._createLayer('LayerView', { model: this.options.layerView.model });
this.layers.push(layerView.model);
}
},
_getLayer: function(layer) {
if (layer.get("type") == 'layergroup' || layer.get('type') === 'namedmap') {
var layerGroupView = this.mapView.getLayerByCid(layer.cid);
for (var i = 0 ; i < layerGroupView.getLayerCount(); ++i) {
var l = layerGroupView.getLayer(i);
var m = new cdb.core.Model(l);
m.set('order', i);
m.set('type', 'layergroup');
m.set('visible', l.visible);
m.set('layer_name', l.options.layer_name);
layerView = this._createLayer('LayerViewFromLayerGroup', {
model: m,
layerView: layerGroupView,
layerIndex: i
});
this.layers.push(layerView.model);
}
} else if (layer.get("type") === "CartoDB" || layer.get('type') === 'torque') {
if (layer.get('type') === 'torque') {
layer.on("change:visible", this._toggleSlider, this);
}
this.layers.push(layer);
}
},
_toggleSlider: function(m) {
if (m.get("visible")) {
this.$el.addClass("with-torque");
this.slider.show();
} else {
this.$el.removeClass("with-torque");
this.slider.hide();
}
},
_reInitScrollpane: function() {
this.$('.scrollpane').data('jsp') && this.$('.scrollpane').data('jsp').reinitialise();
},
_bindOrientationChange: function() {
var self = this;
var onOrientationChange = function() {
$(".cartodb-mobile .scrollpane").css("max-height", self.$el.height() - 30);
$('.cartodb-mobile .scrollpane').data('jsp') && $('.cartodb-mobile .scrollpane').data('jsp').reinitialise();
};
if (!window.addEventListener) {
window.attachEvent('orientationchange', onOrientationChange, this);
} else {
window.addEventListener('orientationchange', _.bind(onOrientationChange));
}
},
_renderOverlays: function() {
var hasSearchOverlay = false;
var hasZoomOverlay = false;
var hasLoaderOverlay = false;
var hasLayerSelector = false;
_.each(this.overlays, function(overlay) {
if (!this.visibility_options.search && overlay.type == 'search') {
if (this.visibility_options.search !== false && this.visibility_options.search !== "false") {
this._addSearch();
hasSearchOverlay = true;
}
}
if (!this.visibility_options.zoomControl && overlay.type === 'zoom') {
if (this.visibility_options.zoomControl !== "false") {
this._addZoom();
hasZoomOverlay = true;
}
}
if (!this.visibility_options.loaderControl && overlay.type === 'loader') {
if (this.visibility_options.loaderControl !== "false") {
this._addLoader();
hasLoaderOverlay = true;
}
}
if (overlay.type == 'fullscreen' && !this.mobileEnabled) {
this._addFullscreen();
}
if (overlay.type == 'header') {
this._addHeader(overlay);
}
if (overlay.type == 'layer_selector') {
hasLayerSelector = true;
}
}, this);
var search_visibility = this.visibility_options.search === true || this.visibility_options.search === "true";
var zoom_visibility = this.visibility_options.zoomControl === true || this.visibility_options.zoomControl === "true";
var loader_visibility = this.visibility_options.loaderControl === true || this.visibility_options.loaderControl === "true";
var layer_selector_visibility = this.visibility_options.layer_selector;
if (!hasSearchOverlay && search_visibility) this._addSearch();
if (!hasZoomOverlay && zoom_visibility) this._addZoom();
if (!hasLoaderOverlay && loader_visibility) this._addLoader();
if (layer_selector_visibility || hasLayerSelector && layer_selector_visibility == undefined) this.hasLayerSelector = true;
},
_initScrollPane: function() {
if (this.$scrollpane) return;
var self = this;
var height = this.$el.height();
this.$scrollpane = this.$el.find(".scrollpane");
setTimeout(function() {
self.$scrollpane.css("max-height", height - 60);
self.$scrollpane.jScrollPane({ showArrows: true });
}, 500);
},
_addZoom: function() {
var template = cdb.core.Template.compile('\
<a href="#zoom_in" class="zoom_in">+</a>\
<a href="#zoom_out" class="zoom_out">-</a>\
<div class="info"></div>', 'mustache'
);
var zoom = new cdb.geo.ui.Zoom({
model: this.options.map,
template: template
});
this.$el.append(zoom.render().$el);
this.$el.addClass("with-zoom");
},
_addLoader: function() {
var template = cdb.core.Template.compile('<div class="loader"></div>', 'mustache');
this.loader = new cdb.geo.ui.TilesLoader({
template: template
});
this.$el.append(this.loader.render().$el);
this.$el.addClass("with-loader");
},
_addFullscreen: function() {
if (this.visibility_options.fullscreen != false) {
this.hasFullscreen = true;
this.$el.addClass("with-fullscreen");
}
},
_addSearch: function() {
this.hasSearch = true;
var template = cdb.core.Template.compile('\
<form>\
<span class="loader"></span>\
<input type="text" class="text" placeholder="Search for places..." value="" />\
<input type="submit" class="submit" value="" />\
</form>\
', 'mustache'
);
var search = new cdb.geo.ui.Search({
template: template,
mapView: this.mapView,
model: this.mapView.map
});
this.$el.find(".aside").prepend(search.render().$el);
this.$el.find(".cartodb-searchbox").show();
this.$el.addClass("with-search");
},
_addHeader: function(overlay) {
this.hasHeader = true;
this.$header = this.$el.find(".cartodb-header");
var title_template = _.template('<div class="hgroup"><% if (show_title) { %><div class="title"><%= title %></div><% } %><% if (show_description) { %><div class="description"><%= description %><% } %></div></div>');
var extra = overlay.options.extra;
var has_header = false;
var show_title = false, show_description = false;
if (extra) {
if (this.visibility_options.title || this.visibility_options.title != false && extra.show_title) {
has_header = true;
show_title = true;
}
if (this.visibility_options.description || this.visibility_options.description != false && extra.show_description) {
has_header = true;
show_description = true;
}
if (this.slides) {
has_header = true;
}
var $hgroup = title_template({
title: cdb.core.sanitize.html(extra.title),
show_title:show_title,
description: cdb.core.sanitize.html(extra.description),
show_description: show_description
});
if (has_header) {
this.$el.addClass("with-header");
this.$header.find(".content").append($hgroup);
}
}
},
_addAttributions: function() {
var attributions = "";
this.options.mapView.$el.find(".leaflet-control-attribution").hide(); // TODO: remove this from here
if (this.options.layerView) {
attributions = this.options.layerView.model.get("attribution");
this.$el.find(".cartodb-attribution").append(attributions);
} else if (this.options.map.get("attribution")) {
attributions = this.options.map.get("attribution");
_.each(attributions, function(attribution) {
var $li = $("<li></li>");
var $el = $li.html(attribution);
this.$el.find(".cartodb-attribution").append($li);
}, this);
}
if (attributions) {
this.$el.find(".cartodb-attribution-button").fadeIn(250);
}
},
_renderLayers: function() {
var hasLegendOverlay = this.visibility_options.legends;
var legends = this.layers.filter(function(layer) {
return layer.get("legend") && layer.get("legend").type !== "none"
});
var hasLegends = legends.length ? true : false;
if (!this.hasLayerSelector && !hasLegendOverlay) return;
if (!this.hasLayerSelector && !hasLegends) return;
if (this.layers.length == 0) return;
if (this.layers.length == 1 && !hasLegends) return;
this.$el.addClass("with-layers");
this.model.set("layer_count", 0);
if (!this.hasSearch) this.$el.find(".aside .layer-container").prepend("<h3></h3>");
_.each(this.layers, this._renderLayer, this);
},
_renderLayer: function(data) {
var hasLegend = data.get("legend") && data.get("legend").type !== "" && data.get("legend").type !== "none";
// When the layer selector is disabled, don't show the layer if it doesn't have legends
if (!this.hasLayerSelector && !hasLegend) return;
if (!this.hasLayerSelector && !data.get("visible")) return;
var hide_toggle = (this.layers.length == 1 || !this.hasLayerSelector);
var show_legends = true;
if (this.visibility_options && this.visibility_options.legends !== undefined) {
show_legends = this.visibility_options.legends;
}
var layer = new cdb.geo.ui.MobileLayer({
model: data,
show_legends: show_legends,
show_title: !this.hasLayerSelector ? false : true,
hide_toggle: hide_toggle
});
this.$el.find(".aside .layers").append(layer.render().$el);
layer.bind("change_visibility", this._reInitScrollpane, this);
this.model.set("layer_count", this.model.get("layer_count") + 1);
},
_renderTorque: function() {
if (this.options.torqueLayer) {
this.hasTorque = true;
this.slider = new cdb.geo.ui.TimeSlider({type: "time_slider", layer: this.options.torqueLayer, map: this.options.map, pos_margin: 0, position: "none" , width: "auto" });
this.slider.bind("time_clicked", function() {
this.slider.toggleTime();
}, this);
this.$el.find(".torque").append(this.slider.render().$el);
if (this.options.torqueLayer.hidden) this.slider.hide();
else this.$el.addClass("with-torque");
}
},
_renderSlidesController: function() {
if (this.slides) {
this.$el.addClass("with-slides");
this.slidesController = new cdb.geo.ui.SlidesController({
show_counter: true,
transitions: this.options.transitions,
visualization: this.options.visualization,
slides: this.slides
});
this.$el.append(this.slidesController.render().$el);
}
},
render: function() {
this._bindOrientationChange();
this.$el.html(this.template(this.options));
this.$header = this.$el.find(".cartodb-header");
this.$header.show();
this._renderOverlays();
this._renderSlidesController();
this._addAttributions();
this._getLayers();
this._renderLayers();
this._renderTorque();
return this;
}
});
@@ -0,0 +1,243 @@
/**
* UI component to place the map in the
* location found by the geocoder.
*
*/
cdb.geo.ui.Search = cdb.core.View.extend({
className: 'cartodb-searchbox',
GEOCODER_SERVICE: 'TOMTOM',
_ZOOM_BY_CATEGORY: {
'building': 18,
'postal-area': 15,
'venue':18,
'region':8,
'address':18,
'country':5,
'county':8,
'locality':12,
'localadmin':11,
'neighbourhood':15,
'default': 12
},
events: {
"click input[type='text']": '_onFocus',
"submit form": '_onSubmit',
"click": '_stopPropagation',
"dblclick": '_stopPropagation',
"mousedown": '_stopPropagation'
},
options: {
searchPin: true,
infowindowTemplate: '<div class="cartodb-infowindow">'+
'<div class="cartodb-popup v2 centered">'+
'<a href="#close" class="cartodb-popup-close-button close">x</a>'+
'<div class="cartodb-popup-content-wrapper">'+
'<p>{{ address }}</p>'+
'</div>'+
'<div class="cartodb-popup-tip-container"></div>'+
'</div>',
infowindowWidth: 186,
infowindowOffset: [93, 90],
iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAfCAYAAADXwvzvAAACuklEQVR4Ae3PQ+AsNxzA8e8vo/Xus237vVN9qW3b7qW2bdu2caxt29bu/meSmaTpqW63Pfc7wemTZPh9K/Xv3zhzxIgVrho0aMsLGo2N9o+iuYDwV02E5NJpM7d5fMGC515dMP/7l6dNMc+OGJY9Uq99cVMc33I4LOJXCQBQuXPBglNnDRm0Xa1RAWewP3yL/vJLul99Q/pNm0/b+qsnbLHngXAVgAI4b9KkXWc1m9vV58ykst56lKdMptyokdTKRJUIV1MMTGTgbOTknWABgFo2SSbOjuN9wlgIBrSIJ0yiVG9QUgGxUigRRAlpCQYrBs+A/QClliuXV6ppPVibDPPqi5irL8G+/QY2S3FZhityrLNYBWkAI2G5WTA2nGTthKDTJfP/FH1sCb76nNBa7I8/knba6Eyj8wJjLbk4qlCdAFNClWXKiiL72kGRUkSRhwUuTUm7XTqZ3z3KnMM7QhAFUfiKMZ9OQci+ydFFH32BIsDh8hxjDF2T0y0KtHHUczCg34P3wgesfWhZozstW1R/cJpuohA8dI7cWrSfxqM4gwEOnoJnn4HXBVDHwHnriNr2W3G0I8FEkKufMbjcIw1DC+iCuRw2OBduEYAKDD8drlkGlk6BHwAtIEDioD/QBnsnnHAI7A9YAAAGenwEnPuAd8+DewHcS+CeB3szvL0b7ADE/FWzYf5BCxa9dMvqa7oLll7WbTlsxKkDYRi9dPqhRz743L0PuKtOPMXtutHmm/InKf5Y6Co15Upl8qSCqVajXiEeUTRb6GqNIojoGaLEDwEA6B0KIKL8lH8JBeS/3AgK73qAPfc/tCLiAACUCmyvsJHnphwEAYFStNs/NoHgn2ATWPmlF54b/9GHH/Khn88/+9SywJx/+q0SsKTZbB45d/6CO0aNHnutv3kbYDQg9JAAIRDwF/0EjlkjUi3fkAMAAAAASUVORK5CYII=',
iconAnchor: [7, 31]
},
initialize: function() {
this.mapView = this.options.mapView;
this.template = this.options.template;
},
render: function() {
this.$el.html(this.template(this.options));
return this;
},
_stopPropagation: function(ev) {
if (ev) {
ev.stopPropagation();
}
},
_onFocus: function(ev) {
if (ev) {
ev.preventDefault();
$(ev.target).focus();
}
},
_showLoader: function() {
this.$('span.loader').show();
},
_hideLoader: function() {
this.$('span.loader').hide();
},
_onSubmit: function(ev) {
ev.preventDefault();
var self = this;
var address = this.$('input.text').val();
if (!address) {
return;
}
// Show geocoder loader
this._showLoader();
// Remove previous pin
this._destroySearchPin();
cdb.geo.geocoder[this.GEOCODER_SERVICE].geocode(address, function(places) {
self._onResult(places);
// Hide loader
self._hideLoader();
});
},
_onResult: function(places) {
var position = '';
var address = this.$('input.text').val();
if (places && places.length>0) {
var location = places[0];
var validBBox = this._isBBoxValid(location);
// Get BBox if possible and set bounds
if (validBBox) {
var s = parseFloat(location.boundingbox.south);
var w = parseFloat(location.boundingbox.west);
var n = parseFloat(location.boundingbox.north);
var e = parseFloat(location.boundingbox.east);
var centerLon = (w + e)/2;
var centerLat = (s + n)/2;
position = [centerLat, centerLon];
this.model.setBounds([ [ s, w ], [ n, e ] ]);
}
// If location is defined,
// let's store it
if (location.lat && location.lon) {
position = [location.lat, location.lon];
}
// In the case that BBox is not valid, let's
// center the map using the position
if (!validBBox) {
this.model.setCenter(position);
this.model.setZoom(this._getZoomByCategory(location.type));
}
if (this.options.searchPin) {
this._createSearchPin(position, address);
}
}
},
// Getting zoom for each type of location
_getZoomByCategory: function(type) {
if (type && this._ZOOM_BY_CATEGORY[type]) {
return this._ZOOM_BY_CATEGORY[type];
}
return this._ZOOM_BY_CATEGORY['default'];
},
_isBBoxValid: function(location) {
if(!location.boundingbox || location.boundingbox.south == location.boundingbox.north ||
location.boundingbox.east == location.boundingbox.west) {
return false;
}
return true;
},
_createSearchPin: function(position, address) {
this._destroySearchPin();
this._createPin(position, address);
this._createInfowindow(position, address);
this._bindEvents();
},
_destroySearchPin: function() {
this._unbindEvents();
this._destroyPin();
this._destroyInfowindow();
},
_createInfowindow: function(position, address) {
var infowindowModel = new cdb.geo.ui.InfowindowModel({
template: this.options.infowindowTemplate,
latlng: position,
width: this.options.infowindowWidth,
offset: this.options.infowindowOffset,
content: {
fields: [{
title: 'address',
value: address
}]
}
});
this._searchInfowindow = new cdb.geo.ui.Infowindow({
model: infowindowModel,
mapView: this.mapView
});
this.mapView.$el.append(this._searchInfowindow.el);
infowindowModel.set('visibility', true);
},
_destroyInfowindow: function() {
if (this._searchInfowindow) {
// Hide it and then destroy it (when animation ends)
this._searchInfowindow.hide(true);
var infowindow = this._searchInfowindow;
setTimeout(function() {
infowindow.clean();
}, 1000);
}
},
_createPin: function(position, address) {
this._searchPin = this.mapView._addGeomToMap(
new cdb.geo.Geometry({
geojson: { type: "Point", "coordinates": [ position[1], position[0] ] },
iconUrl: this.options.iconUrl,
iconAnchor: this.options.iconAnchor
})
);
},
_toggleSearchInfowindow: function() {
var infowindowVisibility = this._searchInfowindow.model.get('visibility');
this._searchInfowindow.model.set('visibility', !infowindowVisibility);
},
_destroyPin: function() {
if (this._searchPin) {
this.mapView._removeGeomFromMap(this._searchPin);
delete this._searchPin;
}
},
_bindEvents: function() {
this._searchPin && this._searchPin.bind('click', this._toggleSearchInfowindow, this);
this.mapView.bind('click', this._destroySearchPin, this);
},
_unbindEvents: function() {
this._searchPin && this._searchPin.unbind('click', this._toggleSearchInfowindow, this);
this.mapView.unbind('click', this._destroySearchPin, this);
},
clean: function() {
this._unbindEvents();
this._destroySearchPin();
this.elder('clean');
}
});
@@ -0,0 +1,106 @@
cdb.geo.ui.Share = cdb.core.View.extend({
className: "cartodb-share",
events: {
"click a": "_onClick"
},
default_options: { },
initialize: function() {
_.bindAll(this, "_onClick");
_.defaults(this.options, this.default_options);
this.template = this.options.template;
},
_applyStyle: function() { },
_onClick: function(e) {
e.preventDefault();
e.stopPropagation();
this.dialog.show();
},
createDialog: function() {
var data = this.options;
data.template = "";
// Add the complete url for facebook and twitter
if (location.href) {
data.share_url = encodeURIComponent(location.href);
} else {
data.share_url = data.url;
}
var template = cdb.core.Template.compile(
data.template || '\
<div class="mamufas">\
<div class="block modal {{modal_type}}">\
<a href="#close" class="close">x</a>\
<div class="head">\
<h3>Share this map</h3>\
</div>\
<div class="content">\
<div class="buttons">\
<h4>Social</h4>\
<ul>\
<li><a class="facebook" target="_blank" href="{{ facebook_url }}">Share on Facebook</a></li>\
<li><a class="twitter" href="{{ twitter_url }}" target="_blank">Share on Twitter</a></li>\
<li><a class="link" href="{{ public_map_url }}" target="_blank">Link to this map</a></li>\
</ul>\
</div><div class="embed_code">\
<h4>Embed this map</h4>\
<textarea id="" name="" cols="30" rows="10">{{ code }}</textarea>\
</div>\
</div>\
</div>\
</div>\
',
data.templateType || 'mustache'
);
var url = location.href;
url = url.replace("public_map", "embed_map");
var public_map_url = url.replace("embed_map", "public_map"); // TODO: get real URL
var code = "<iframe width='100%' height='520' frameborder='0' src='" + url + "' allowfullscreen webkitallowfullscreen mozallowfullscreen oallowfullscreen msallowfullscreen></iframe>";
this.dialog = new cdb.ui.common.ShareDialog({
title: data.map.get("title"),
description: data.map.get("description"),
model: this.options.vis.map,
code: code,
url: data.url,
public_map_url: public_map_url,
share_url: data.share_url,
template: template,
target: $(".cartodb-share a"),
size: $(document).width() > 400 ? "" : "small",
width: $(document).width() > 400 ? 430 : 216
});
$(".cartodb-map-wrapper").append(this.dialog.render().$el);
this.addView(this.dialog);
},
render: function() {
this.$el.html(this.template(_.extend(this.model.attributes)));
return this;
}
});
@@ -0,0 +1,130 @@
cdb.geo.ui.SlidesControllerItem = cdb.core.View.extend({
tagName: "li",
events: {
"click a": "_onClick",
},
template: cdb.core.Template.compile('<a href="#" class="<%- transition_trigger %>"></a>'),
initialize: function() {
this.model = new cdb.core.Model(this.options);
this.model.bind("change:active", this._onChangeActive, this);
},
_onChangeActive: function(e) {
if (this.model.get("active")) {
this.$el.find("a").addClass("active");
} else {
this.$el.find("a").removeClass("active");
}
},
_onClick: function(e) {
if (e) this.killEvent(e);
this.trigger("onClick", this)
},
render: function() {
var options = _.extend({ transition_trigger: "click" }, this.options.transition_options);
this.$el.html(this.template(options));
this._onChangeActive();
return this;
}
});
cdb.geo.ui.SlidesController = cdb.core.View.extend({
defaults: {
show_counter: false
},
events: {
'click a.next': "_next",
'click a.prev': "_prev"
},
tagName: "div",
className: "cartodb-slides-controller",
template: cdb.core.Template.compile("<div class='slides-controller-content'><a href='#' class='prev'></a><% if (show_counter) {%><div class='counter'></div><% } else { %><ul></ul><% } %><a href='#' class='next'></a></div>"),
initialize: function() {
this.slidesCount = this.options.transitions.length;
this.visualization = this.options.visualization;
this.slides = this.visualization.slides;
},
_prev: function(e) {
if (e) this.killEvent(e);
this.visualization.sequence.prev();
},
_next: function(e) {
if (e) this.killEvent(e);
this.visualization.sequence.next();
},
_renderDots: function() {
var currentActiveSlide = this.slides.state();
for (var i = 0; i < this.options.transitions.length; i++) {
var item = new cdb.geo.ui.SlidesControllerItem({ num: i, transition_options: this.options.transitions[i], active: i == currentActiveSlide });
item.bind("onClick", this._onSlideClick, this);
this.$el.find("ul").append(item.render().$el);
}
},
_renderCounter: function() {
var currentActiveSlide = this.slides.state();
var currentTransition = this.options.transitions[currentActiveSlide];
var $counter = this.$el.find(".counter");
if (currentTransition && currentTransition.transition_trigger === "time") {
$counter.addClass("loading");
} else {
$counter.removeClass("loading");
}
$counter.html((currentActiveSlide + 1) + "/" + this.options.transitions.length)
},
_onSlideClick: function(slide) {
this.visualization.sequence.current(slide.options.num);
},
render: function() {
var options = _.extend(this.defaults, this.options);
this.$el.html(this.template(options));
if (this.slides && this.options.transitions) {
if (options.show_counter) {
this._renderCounter(); // we render: 1/N
} else {
this._renderDots(); // we render a list of dots
}
}
return this;
}
});
@@ -0,0 +1,115 @@
cdb.geo.ui.SwitcherItemModel = Backbone.Model.extend({ });
cdb.geo.ui.SwitcherItems = Backbone.Collection.extend({
model: cdb.geo.ui.SwitcherItemModel
});
cdb.geo.ui.SwitcherItem = cdb.core.View.extend({
tagName: "li",
events: {
"click a" : "select"
},
initialize: function() {
_.bindAll(this, "render");
this.template = cdb.templates.getTemplate('templates/map/switcher/item');
this.parent = this.options.parent;
this.model.on("change:selected", this.render);
},
select: function(e) {
e.preventDefault();
this.parent.toggle(this);
var callback = this.model.get("callback");
if (callback) {
callback();
}
},
render: function() {
if (this.model.get("selected") == true) {
this.$el.addClass("selected");
} else {
this.$el.removeClass("selected");
}
this.$el.html(this.template(this.model.toJSON()));
return this.$el;
}
});
cdb.geo.ui.Switcher = cdb.core.View.extend({
id: "switcher",
default_options: {
},
initialize: function() {
this.map = this.model;
this.add_related_model(this.model);
_.bindAll(this, "render", "show", "hide", "toggle");
_.defaults(this.options, this.default_options);
if (this.collection) {
this.model.collection = this.collection;
}
this.template = this.options.template ? this.options.template : cdb.templates.getTemplate('geo/switcher');
},
show: function() {
this.$el.fadeIn(250);
},
hide: function() {
this.$el.fadeOut(250);
},
toggle: function(clickedItem) {
if (this.collection) {
this.collection.each(function(item) {
item.set("selected", !item.get("selected"));
});
}
},
render: function() {
var self = this;
if (this.model != undefined) {
this.$el.html(this.template(this.model.toJSON()));
}
if (this.collection) {
this.collection.each(function(item) {
var view = new cdb.geo.ui.SwitcherItem({ parent: self, className: item.get("className"), model: item });
self.$el.find("ul").append(view.render());
});
}
return this;
}
});
@@ -0,0 +1,184 @@
cdb.geo.ui.Text = cdb.core.View.extend({
className: "cartodb-overlay overlay-text",
events: {
"click": "stopPropagation"
},
default_options: { },
stopPropagation: function(e) {
e.stopPropagation();
},
initialize: function() {
_.defaults(this.options, this.default_options);
this.template = this.options.template;
var self = this;
$(window).on("map_resized", function() {
self._place();
});
$(window).on("resize", function() {
self._place();
});
},
_applyStyle: function() {
var style = this.model.get("style");
var boxColor = style["box-color"];
var boxOpacity = style["box-opacity"];
var boxWidth = style["box-width"];
var fontFamily = style["font-family-name"];
this.$text = this.$el.find(".text");
this.$text.css(style);
this.$text.css("font-size", style["font-size"] + "px");
this.$el.css("z-index", style["z-index"]);
var fontFamilyClass = "";
if (fontFamily == "Droid Sans") fontFamilyClass = "droid";
else if (fontFamily == "Vollkorn") fontFamilyClass = "vollkorn";
else if (fontFamily == "Open Sans") fontFamilyClass = "open_sans";
else if (fontFamily == "Roboto") fontFamilyClass = "roboto";
else if (fontFamily == "Lato") fontFamilyClass = "lato";
else if (fontFamily == "Graduate") fontFamilyClass = "graduate";
else if (fontFamily == "Gravitas One") fontFamilyClass = "gravitas_one";
else if (fontFamily == "Old Standard TT") fontFamilyClass = "old_standard_tt";
var rgbaCol = 'rgba(' + parseInt(boxColor.slice(-6,-4),16)
+ ',' + parseInt(boxColor.slice(-4,-2),16)
+ ',' + parseInt(boxColor.slice(-2),16)
+', ' + boxOpacity + ' )';
this.$el
.removeClass("droid")
.removeClass("vollkorn")
.removeClass("roboto")
.removeClass("open_sans")
.removeClass("lato")
.removeClass("graduate")
.removeClass("gravitas_one")
.removeClass("old_standard_tt");
this.$el.addClass(fontFamilyClass);
this.$el.css({
backgroundColor: rgbaCol,
maxWidth: boxWidth
});
},
_place: function(position) {
var extra = position || this.model.get("extra");
var top = this.model.get("y");
var left = this.model.get("x");
var bottom_position = extra.bottom - this.$el.height();
var right_position = extra.right - this.$el.width();
// position percentages
var top_percentage = extra.top_percentage;
var left_percentage = extra.left_percentage;
var right = "auto";
var bottom = "auto";
var marginTop = 0;
var marginLeft = 0;
var width = extra.width;
var height = extra.height;
var portrait_dominant_side = extra.portrait_dominant_side;
var landscape_dominant_side = extra.landscape_dominant_side;
if (portrait_dominant_side === 'bottom' && bottom_position <= 250) {
top = "auto";
bottom = bottom_position;
} else if (top_percentage > 45 && top_percentage < 55) {
top = "50%";
marginTop = -height/2;
}
if (landscape_dominant_side === 'right' && right_position <= 250) {
left = "auto";
right = right_position;
} else if (left_percentage > 45 && left_percentage < 55) {
left = "50%";
marginLeft = -width/2;
}
this.$el.css({
marginLeft: marginLeft,
marginTop: marginTop,
top: top,
left: left,
right: right,
bottom: bottom
});
},
show: function(callback) {
this.$el.fadeIn(150, function() {
callback && callback();
});
},
hide: function(callback) {
this.$el.fadeOut(150, function() {
callback && callback();
});
},
_fixLinks: function() {
this.$el.find("a").each(function(i, link) {
$(this).attr("target", "_top");
});
},
render: function() {
var text = cdb.core.sanitize.html(this.model.get("extra").rendered_text, this.model.get('sanitizeText'));
var data = _.chain(this.model.attributes).clone().extend({ text: text }).value();
this.$el.html(this.template(data));
this._fixLinks();
var self = this;
setTimeout(function() {
self._applyStyle();
self._place();
self.show();
}, 900);
return this;
}
});
@@ -0,0 +1,56 @@
/**
* Show or hide tiles loader
*
* Usage:
*
* var tiles_loader = new cdb.geo.ui.TilesLoader();
* mapWrapper.$el.append(tiles_loader.render().$el);
*
*/
cdb.geo.ui.TilesLoader = cdb.core.View.extend({
className: "cartodb-tiles-loader",
default_options: {
animationSpeed: 500
},
initialize: function() {
_.defaults(this.options, this.default_options);
this.isVisible = 0;
this.template = this.options.template ? this.options.template : cdb.templates.getTemplate('geo/tiles_loader');
},
render: function() {
this.$el.html($(this.template(this.options)));
return this;
},
show: function(ev) {
if(this.isVisible) return;
if (!cdb.core.util.ie || (cdb.core.util.browser.ie && cdb.core.util.browser.ie.version >= 10)) {
this.$el.fadeTo(this.options.animationSpeed, 1)
} else {
this.$el.show();
}
this.isVisible++;
},
hide: function(ev) {
this.isVisible--;
if(this.isVisible > 0) return;
this.isVisible = 0;
if (!cdb.core.util.ie || (cdb.core.util.browser.ie && cdb.core.util.browser.ie.version >= 10)) {
this.$el.stop(true).fadeTo(this.options.animationSpeed, 0)
} else {
this.$el.hide();
}
},
visible: function() {
return this.isVisible > 0;
}
});
@@ -0,0 +1,231 @@
cdb.geo.ui.TimeSlider = cdb.geo.ui.InfoBox.extend({
DEFAULT_OFFSET_TOP: 30,
className: 'cartodb-timeslider',
defaultTemplate:
" <ul> " +
" <li class='controls'><a href='#/stop' class='button stop'>pause</a></li>" +
" <li class='time'><p class='value'></p></li>" +
" <li class='last'><div class='slider-wrapper'><div class='slider'></div></div></li>" +
" </ul> "
,
events: {
"click .button": "toggleTime",
"click .time": "_onClickTime",
"dragstart": "_stopPropagation",
"mousedown": "_stopPropagation",
"touchstart": "_stopPropagation",
"MSPointerDown": "_stopPropagation",
"dblclick": "_stopPropagation",
"mousewheel": "_stopPropagation",
"DOMMouseScroll": "_stopPropagation",
"click": "_stopPropagation"
},
initialize: function() {
_.bindAll(this, '_stop', '_start', '_slide', '_bindLayer', '_unbindLayer', 'updateSliderRange', 'updateSlider', 'updateTime', 'toggleTime', 'toggleButton');
var self = this;
this.options.template = this.options.template || this.defaultTemplate;
this.options.position = 'bottom|left';
this.options.width = null;
// Control variable to know if the layer was
// running before touching the slider
this.wasRunning = false;
this._bindLayer(this.options.layer);
this.on('clean', this._unbindLayer);
cdb.geo.ui.InfoBox.prototype.initialize.call(this);
},
setLayer: function(layer) {
this._unbindLayer();
this._bindLayer(layer);
this._initSlider();
},
_bindLayer: function(layer) {
this.torqueLayer = layer;
this.torqueLayer.on('change:time', this.updateSlider);
this.torqueLayer.on('change:time', this.updateTime);
this.torqueLayer.on('change:steps', this.updateSliderRange);
this.torqueLayer.on('play', this.toggleButton);
this.torqueLayer.on('pause', this.toggleButton);
return this;
},
_unbindLayer: function() {
this.torqueLayer.off('change:time', this.updateSlider);
this.torqueLayer.off('change:time', this.updateTime);
this.torqueLayer.off('change:steps', this.updateSliderRange);
return this;
},
updateSlider: function(changes) {
this.$(".slider" ).slider({ value: changes.step });
},
updateSliderRange: function(changes) {
if (changes.steps > 1){
this.show();
this.$(".slider" ).slider({ max: changes.steps - 1 });
}
else{
this.hide();
}
},
// each time time changes, move the slider
updateTime: function(changes) {
var self = this;
var tb = self.torqueLayer.getTimeBounds();
if (!tb) return;
if (tb.columnType === 'date' || this.options.force_format_date) {
if (tb && tb.start !== undefined) {
var f = self.options.formatter || this.formatterForRange(tb.start, tb.end);
// avoid showing invalid dates
if (!_.isNaN(changes.time.getYear())) {
self.$('.value').text(f(changes.time, this.torqueLayer));
}
}
} else {
self.$('.value').text(changes.step);
}
},
formatter: function(_) {
this.options.formatter = _;
},
formatterForRange: function(start, end) {
start = start.getTime ? start : new Date(start);
end = end.getTime ? end : new Date(end);
var range = (end.getTime() - start.getTime()) / 1000;
var ONE_DAY = 3600*24;
var THREE_DAYS = ONE_DAY*3;
var ONE_YEAR = ONE_DAY * 31 * 12;
if(this.torqueLayer.options){
var stepDurationMS = (end.getTime() - start.getTime()) / this.torqueLayer.options.steps;
}
function pad(n) {
return n < 10 ? '0' + n : n;
}
function toUSDateStr(date) {
return pad(date.getMonth() + 1) + "/" + pad(date.getDate()) + "/" + pad(date.getFullYear());
}
function toTimeStr(date) {
return pad(date.getHours()) + ":" + pad(date.getMinutes());
}
function toDateRange(date, torqueLayer) {
var stepStartTimeMS = date.getTime();
var tb = torqueLayer.getTimeBounds();
var stepDurationMS = (new Date(tb.end).getTime() - new Date(tb.start).getTime()) / torqueLayer.options.steps;
var stepEndTime = new Date(stepStartTimeMS + stepDurationMS);
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
return pad(months[date.getMonth()]) + " " + pad(date.getDate()) + " - "
+ pad(months[stepEndTime.getMonth()]) + " " + pad(stepEndTime.getDate());
}
if (range < THREE_DAYS) {
if (start.getDate() === end.getDate()) {
return toTimeStr;
} else {
// range is less than a day, but the range spans more than one day so render the date in addition to the time
return function(date) {
return toUSDateStr(date) +' '+ toTimeStr(date);
};
}
}
if (range < ONE_YEAR) {
if (stepDurationMS > ONE_DAY * 2000){ // More than 48 hours
return toDateRange;
}
return toUSDateStr;
}
// >= ONE_YEAR
return function(date) {
return pad(date.getMonth() + 1) + "/" + pad(date.getFullYear());
};
},
_slide: function(e, ui) {
this.killEvent(e);
var step = ui.value;
this.torqueLayer.setStep(step);
},
_start: function(e, ui) {
if(this.torqueLayer.isRunning()) {
this.wasRunning = true;
this.toggleTime();
}
},
_stop: function(e, ui) {
if (this.wasRunning) {
this.toggleTime()
}
this.wasRunning = false;
},
_initSlider: function() {
var torqueLayer = this.torqueLayer;
var slider = this.$(".slider");
slider.slider({
range: 'min',
min: 0,
max: this.torqueLayer.options.steps,
value: 0,
step: 1, //
value: this.torqueLayer.getStep(),
stop: this._stop,
start: this._start,
slide: this._slide
});
if(torqueLayer.provider){
this.updateSliderRange({"steps": torqueLayer.provider.getSteps()});
}
},
toggleTime: function(e) {
this.killEvent(e);
this.torqueLayer.toggle();
},
toggleButton: function() {
this.$('.button')
[(this.torqueLayer.isRunning() ? 'addClass': 'removeClass')]('stop')
.attr('href','#/' + (this.torqueLayer.isRunning() ? 'pause': 'play'))
.html(this.torqueLayer.isRunning() ? 'pause': 'play');
},
enable: function() {},
_stopPropagation: function(ev) {
ev.stopPropagation();
},
_onClickTime: function() {
this.trigger("time_clicked", this);
},
render: function() {
cdb.geo.ui.InfoBox.prototype.render.apply(this, arguments);
this._initSlider();
return this;
}
});
@@ -0,0 +1,194 @@
cdb.geo.ui.Tooltip = cdb.geo.ui.InfoBox.extend({
defaultTemplate: '<p>{{text}}</p>',
className: 'cartodb-tooltip',
defaults: {
vertical_offset: 0,
horizontal_offset: 0,
position: 'top|center'
},
initialize: function() {
if(!this.options.mapView) {
throw new Error("mapView should be present");
}
this.options.template = this.options.template || this.defaultTemplate;
cdb.geo.ui.InfoBox.prototype.initialize.call(this);
this._filter = null;
this.showing = false;
this.showhideTimeout = null;
},
setLayer: function(layer) {
this.options.layer = layer;
return this;
},
/**
* sets a filter to open the tooltip. If the feature being hovered
* pass the filter the tooltip is shown
* setFilter(null) removes the filter
*/
setFilter: function(f) {
this._filter = f;
return this;
},
setFields: function(fields) {
this.options.fields = fields;
return this;
},
setAlternativeNames: function(n) {
this.options.alternative_names = n;
},
enable: function() {
if(this.options.layer) {
// unbind previous events
this.options.layer.unbind(null, null, this);
this.options.layer
.on('mouseover', function(e, latlng, pos, data) {
if (this.options.fields && this.options.fields.length > 0) {
var non_valid_keys = ['fields', 'content'];
if (this.options.omit_columns) {
non_valid_keys = non_valid_keys.concat(this.options.omit_columns);
}
var c = cdb.geo.ui.InfowindowModel.contentForFields(data, this.options.fields, {
empty_fields: this.options.empty_fields
});
// Remove fields and content from data
// and make them visible for custom templates
data.content = _.omit(data, non_valid_keys);
// loop through content values
data.fields = c.fields;
// alternamte names
var names = this.options.alternative_names;
if (names) {
for(var i = 0; i < data.fields.length; ++i) {
var f = data.fields[i];
f.title = names[f.title] || f.title;
}
}
this.show(pos, data);
this.showing = true;
} else if (this.showing) {
this.hide();
this.showing = false;
}
}, this)
.on('mouseout', function() {
if (this.showing) {
this.hide();
this.showing = false;
}
}, this);
this.add_related_model(this.options.layer);
}
},
disable: function() {
if(this.options.layer) {
this.options.layer.unbind(null, null, this);
}
this.hide();
this.showing = false;
},
_visibility: function() {
var self = this;
clearTimeout(this.showhideTimeout);
this.showhideTimeout = setTimeout(self._showing ?
function() { self.$el.fadeIn(100); }
:
function() { self.$el.fadeOut(200); }
, 50);
},
hide: function() {
if (this._showing) {
this._showing = false;
this._visibility();
}
},
show: function(pos, data) {
if (this._filter && !this._filter(data)) {
return this;
}
this.render(data);
//this.elder('show', pos, data);
this.setPosition(pos);
if (!this._showing) {
this._showing = true;
this._visibility();
}
return this;
},
setPosition: function(point) {
var pos = this.options.position;
var height = this.$el.innerHeight();
var width = this.$el.innerWidth();
var mapViewSize = this.options.mapView.getSize();
var top = 0;
var left = 0;
// Vertically
if (pos.indexOf('top') !== -1) {
top = point.y - height;
} else if (pos.indexOf('middle') !== -1) {
top = point.y - (height/2);
} else { // bottom
top = point.y;
}
// Fix vertical overflow
if (top < 0) {
top = point.y;
} else if (top + height > mapViewSize.y) {
top = point.y - height;
}
// Horizontally
if(pos.indexOf('left') !== -1) {
left = point.x - width;
} else if(pos.indexOf('center') !== -1) {
left = point.x - (width/2);
} else { // right
left = point.x;
}
// Fix horizontal overflow
if (left < 0) {
left = point.x;
} else if (left + width > mapViewSize.x) {
left = point.x - width;
}
// Add offsets
top += this.options.vertical_offset;
left += this.options.horizontal_offset;
this.$el.css({
top: top,
left: left
});
},
render: function(data) {
var sanitizedOutput = cdb.core.sanitize.html(this.template(data));
this.$el.html( sanitizedOutput );
return this;
}
});
@@ -0,0 +1,63 @@
/**
* View to control the zoom of the map.
*
* Usage:
*
* var zoomControl = new cdb.geo.ui.Zoom({ model: map });
* mapWrapper.$el.append(zoomControl.render().$el);
*
*/
cdb.geo.ui.Zoom = cdb.core.View.extend({
className: "cartodb-zoom",
events: {
'click .zoom_in': 'zoom_in',
'click .zoom_out': 'zoom_out'
},
default_options: {
timeout: 0,
msg: ''
},
initialize: function() {
this.map = this.model;
_.defaults(this.options, this.default_options);
this.template = this.options.template ? this.options.template : cdb.templates.getTemplate('geo/zoom');
this.map.bind('change:zoom change:minZoom change:maxZoom', this._checkZoom, this);
},
render: function() {
this.$el.html(this.template(this.options));
this._checkZoom();
return this;
},
_checkZoom: function() {
var zoom = this.map.get('zoom');
this.$('.zoom_in')[ zoom < this.map.get('maxZoom') ? 'removeClass' : 'addClass' ]('disabled')
this.$('.zoom_out')[ zoom > this.map.get('minZoom') ? 'removeClass' : 'addClass' ]('disabled')
},
zoom_in: function(ev) {
if (this.map.get("maxZoom") > this.map.getZoom()) {
this.map.setZoom(this.map.getZoom() + 1);
}
ev.preventDefault();
ev.stopPropagation();
},
zoom_out: function(ev) {
if (this.map.get("minZoom") < this.map.getZoom()) {
this.map.setZoom(this.map.getZoom() - 1);
}
ev.preventDefault();
ev.stopPropagation();
}
});
@@ -0,0 +1,24 @@
/**
* View to know which is the map zoom.
*
* Usage:
*
* var zoomInfo = new cdb.geo.ui.ZoomInfo({ model: map });
* mapWrapper.$el.append(zoomInfo.render().$el);
*
*/
cdb.geo.ui.ZoomInfo = cdb.core.View.extend({
className: "cartodb-zoom-info",
initialize: function() {
this.model.bind("change:zoom", this.render, this);
},
render: function() {
this.$el.html(this.model.get("zoom"));
return this;
}
});