542 lines
19 KiB
JavaScript
542 lines
19 KiB
JavaScript
/**
|
|
* @name cartodb-gmapsv3 for Google Maps V3 API
|
|
* @version 0.2 [December 14, 2011]
|
|
* @author: xavijam@gmail.com
|
|
* @fileoverview <b>Author:</b> xavijam@gmail.com<br/> <b>Licence:</b>
|
|
* Licensed under <a
|
|
* href="http://opensource.org/licenses/mit-license.php">MIT</a>
|
|
* license.<br/> This library lets you use CartoDB with google
|
|
* maps v3.
|
|
*
|
|
*/
|
|
/**
|
|
* @name google
|
|
* @class The fundamental namespace for Google APIs
|
|
*/
|
|
/**
|
|
* @name google.maps
|
|
* @class The fundamental namespace for Google Maps V3 API
|
|
*/
|
|
/*
|
|
* - Map style of cartodb
|
|
* - Infowindow of cartodb
|
|
* - Tiles style of cartodb
|
|
*/
|
|
|
|
// Namespace
|
|
var CartoDB = CartoDB || {};
|
|
|
|
(function($) {
|
|
if (typeof(google.maps.CartoDBLayer) === "undefined") {
|
|
/**
|
|
* @params {}
|
|
* map_canvas - Gmapsv3 canvas id (necesary for showing the infowindow)
|
|
* map - Your gmapsv3 map
|
|
* user_name - CartoDB user name
|
|
* table_name CartoDB table name
|
|
* query If you want to apply any sql sentence to the table...
|
|
* tile_style - If you want to add other style to the layer
|
|
* map_style - If you want to see the map styles created on cartodb (opcional - default = false)
|
|
* infowindow - If you want to see infowindows when click in a geometry (opcional - default = false)
|
|
* auto_bound - Let cartodb auto-bound-zoom in the map (opcional - default = false)
|
|
* debug - Do you want to debug the library? Set to true
|
|
*/
|
|
|
|
google.maps.CartoDBLayer = function (params) {
|
|
|
|
this.params = params;
|
|
this.params.feature = params.infowindow;
|
|
|
|
if (this.params.map_style) setCartoDBMapStyle(this.params); // Map style? ok, let's style.
|
|
if (this.params.auto_bound) autoBound(this.params); // Bounds? CartoDB does it.
|
|
|
|
if (this.params.infowindow) {
|
|
addWaxCartoDBTiles(this.params);
|
|
} else {
|
|
addSimpleCartoDBTiles(this.params); // Always add cartodb tiles, simple or with wax.
|
|
}
|
|
|
|
this.params.visible = true;
|
|
this.params.active = true;
|
|
|
|
// Zoom to cartodb geometries
|
|
function autoBound(params) {
|
|
// Zoom to your geometries
|
|
// If the table is private you can't auto zoom without being authenticated
|
|
if (!params.map_key) {
|
|
$.ajax({
|
|
url:'http://'+params.user_name+'.cartodb.com/api/v1/sql/?q='+escape('select ST_Extent(the_geom) from '+ params.table_name),
|
|
dataType: 'jsonp',
|
|
timeout: 2000,
|
|
callbackParameter: 'callback',
|
|
success: function(result) {
|
|
if (result.rows[0].st_extent!=null) {
|
|
var coordinates = result.rows[0].st_extent.replace('BOX(','').replace(')','').split(',');
|
|
|
|
var coor1 = coordinates[0].split(' ');
|
|
var coor2 = coordinates[1].split(' ');
|
|
var bounds = new google.maps.LatLngBounds();
|
|
|
|
// Check bounds
|
|
if (coor1[0] > 180 || coor1[0] < -180 || coor1[1] > 90 || coor1[1] < -90
|
|
|| coor2[0] > 180 || coor2[0] < -180 || coor2[1] > 90 || coor2[1] < -90) {
|
|
coor1[0] = '-30';
|
|
coor1[1] = '-50';
|
|
coor2[0] = '110';
|
|
coor2[1] = '80';
|
|
}
|
|
|
|
bounds.extend(new google.maps.LatLng(coor1[1],coor1[0]));
|
|
bounds.extend(new google.maps.LatLng(coor2[1],coor2[0]));
|
|
|
|
params.map.fitBounds(bounds);
|
|
}
|
|
},
|
|
error: function(e,msg) {
|
|
params.debug && console.debug('Error setting table bounds: ' + msg);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Set the map styles of your cartodb table/map
|
|
function setCartoDBMapStyle(params) {
|
|
$.ajax({
|
|
url: 'http://' + params.user_name + '.cartodb.com/tiles/' + params.table_name + '/map_metadata?'+ 'map_key=' + (params.map_key || ''),
|
|
dataType: 'jsonp',
|
|
timeout: 2000,
|
|
callbackParameter: 'callback',
|
|
success: function(result) {
|
|
var map_style = $.parseJSON(result.map_metadata);
|
|
|
|
if (!map_style || map_style.google_maps_base_type=="roadmap") {
|
|
params.map.setOptions({mapTypeId: google.maps.MapTypeId.ROADMAP});
|
|
} else if (map_style.google_maps_base_type=="satellite") {
|
|
params.map.setOptions({mapTypeId: google.maps.MapTypeId.SATELLITE});
|
|
} else if (map_style.google_maps_base_type=="terrain") {
|
|
params.map.setOptions({mapTypeId: google.maps.MapTypeId.TERRAIN});
|
|
} else {
|
|
var mapStyles = [ { stylers: [ { saturation: -65 }, { gamma: 1.52 } ] },{ featureType: "administrative", stylers: [ { saturation: -95 }, { gamma: 2.26 } ] },{ featureType: "water", elementType: "labels", stylers: [ { visibility: "off" } ] },{ featureType: "administrative.locality", stylers: [ { visibility: "off" } ] },{ featureType: "road", stylers: [ { visibility: "simplified" }, { saturation: -99 }, { gamma: 2.22 } ] },{ featureType: "poi", elementType: "labels", stylers: [ { visibility: "off" } ] },{ featureType: "road.arterial", stylers: [ { visibility: "off" } ] },{ featureType: "road.local", elementType: "labels", stylers: [ { visibility: "off" } ] },{ featureType: "transit", stylers: [ { visibility: "off" } ] },{ featureType: "road", elementType: "labels", stylers: [ { visibility: "off" } ] },{ featureType: "poi", stylers: [ { saturation: -55 } ] } ];
|
|
map_style.google_maps_customization_style = mapStyles;
|
|
params.map.setOptions({mapTypeId: google.maps.MapTypeId.ROADMAP});
|
|
}
|
|
|
|
// Custom tiles
|
|
if (!map_style) {
|
|
map_style = {google_maps_customization_style: []};
|
|
}
|
|
params.map.setOptions({styles: map_style.google_maps_customization_style});
|
|
},
|
|
error: function(e, msg) {
|
|
params.debug && console.debug('Error setting map style: ' + msg);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add cartodb tiles to the map
|
|
function addSimpleCartoDBTiles(params) {
|
|
// Add the cartodb tiles
|
|
var cartodb_layer = {
|
|
getTileUrl: function(coord, zoom) {
|
|
return 'http://' + params.user_name + '.cartodb.com/tiles/' + params.table_name + '/'+zoom+'/'+coord.x+'/'+coord.y+'.png?sql='+params.query;
|
|
},
|
|
tileSize: new google.maps.Size(256, 256),
|
|
name: params.query,
|
|
description: false
|
|
};
|
|
params.layer = new google.maps.ImageMapType(cartodb_layer);
|
|
params.map.overlayMapTypes.insertAt(0,params.layer);
|
|
}
|
|
|
|
// Add cartodb tiles to the map
|
|
function addWaxCartoDBTiles(params) {
|
|
// interaction placeholder
|
|
var currentCartoDbId;
|
|
|
|
params.tilejson = generateTileJson(params);
|
|
params.infowindow = new CartoDB.Infowindow(params);
|
|
params.cache_buster = 0;
|
|
|
|
params.waxOptions = {
|
|
callbacks: {
|
|
out: function(){
|
|
params.map.setOptions({draggableCursor: 'default'});
|
|
},
|
|
over: function(feature, div, opt3, evt){
|
|
params.map.setOptions({draggableCursor: 'pointer'});
|
|
},
|
|
click: function(feature, div, opt3, evt){
|
|
// If there are more than one cartodb layer, close all possible infowindows
|
|
params.infowindow.hideAll();
|
|
params.infowindow.open(feature,evt.latLng);
|
|
}
|
|
},
|
|
clickAction: 'full'
|
|
};
|
|
|
|
params.layer = new wax.g.connector(params.tilejson);
|
|
|
|
params.map.overlayMapTypes.insertAt(0,params.layer);
|
|
params.interaction = wax.g.interaction(params.map, params.tilejson, params.waxOptions);
|
|
}
|
|
|
|
// Refresh wax interaction
|
|
function refreshWax(params,sql) {
|
|
if (params.infowindow) {
|
|
params.cache_buster++;
|
|
params.query = sql;
|
|
params.tilejson = generateTileJson(params);
|
|
|
|
// Remove old wax
|
|
removeOldLayer(params.map,params.layer);
|
|
|
|
// Setup new wax
|
|
params.tilejson.grids = wax.util.addUrlData(params.tilejson.grids_base, 'cache_buster=' + params.cache_buster);
|
|
|
|
// Add map tiles
|
|
params.layer = new wax.g.connector(params.tilejson);
|
|
params.map.overlayMapTypes.insertAt(0,params.layer);
|
|
|
|
// Add interaction
|
|
params.interaction.remove();
|
|
params.interaction = wax.g.interaction(params.map, params.tilejson, params.waxOptions);
|
|
}
|
|
}
|
|
|
|
// Refresh tiles
|
|
function refreshTiles(params,sql) {
|
|
// If you are not using interaction on the tiles... let's update your tiles
|
|
if (!params.infowindow) {
|
|
|
|
// First remove previous cartodb - tiles.
|
|
removeOldLayer(params.map,params.layer);
|
|
|
|
// Then add the cartodb tiles
|
|
params.query = sql;
|
|
var cartodb_layer = {
|
|
getTileUrl: function(coord, zoom) {
|
|
return 'http://' + params.user_name + '.cartodb.com/tiles/' + params.table_name + '/'+zoom+'/'+coord.x+'/'+coord.y+'.png?sql='+params.query;
|
|
},
|
|
tileSize: new google.maps.Size(256, 256),
|
|
name: params.query,
|
|
description: false
|
|
};
|
|
|
|
params.layer = new google.maps.ImageMapType(cartodb_layer);
|
|
params.map.overlayMapTypes.insertAt(0,params.layer);
|
|
}
|
|
}
|
|
|
|
function generateTileJson(params) {
|
|
var core_url = 'http://' + params.user_name + '.cartodb.com';
|
|
var base_url = core_url + '/tiles/' + params.table_name + '/{z}/{x}/{y}';
|
|
var tile_url = base_url + '.png?cache_buster=0';
|
|
var grid_url = base_url + '.grid.json';
|
|
|
|
// SQL?
|
|
if (params.query) {
|
|
var query = 'sql=' + params.query;
|
|
tile_url = wax.util.addUrlData(tile_url, query);
|
|
grid_url = wax.util.addUrlData(grid_url, query);
|
|
}
|
|
|
|
// Map key ?
|
|
if (params.map_key) {
|
|
var map_key = 'map_key=' + params.map_key;
|
|
tile_url = wax.util.addUrlData(tile_url,map_key);
|
|
grid_url = wax.util.addUrlData(grid_url,map_key);
|
|
}
|
|
|
|
// Tiles style ?
|
|
if (params.tile_style) {
|
|
var style = 'style=' + encodeURIComponent(params.tile_style);
|
|
tile_url = wax.util.addUrlData(tile_url,style);
|
|
grid_url = wax.util.addUrlData(grid_url,style);
|
|
}
|
|
|
|
// Build up the tileJSON
|
|
// TODO: make a blankImage a real 'empty tile' image
|
|
return {
|
|
blankImage: 'blank_tile.png',
|
|
tilejson: '1.0.0',
|
|
scheme: 'xyz',
|
|
tiles: [tile_url],
|
|
grids: [grid_url],
|
|
tiles_base: tile_url,
|
|
grids_base: grid_url,
|
|
name: params.query,
|
|
description: true,
|
|
formatter: function(options, data) {
|
|
currentCartoDbId = data.cartodb_id;
|
|
return data.cartodb_id;
|
|
},
|
|
cache_buster: function(){
|
|
return params.cache_buster;
|
|
}
|
|
};
|
|
}
|
|
|
|
// Remove old cartodb layer added (wax or imagemaptype)
|
|
function removeOldLayer(map,layer) {
|
|
if (layer) {
|
|
var pos = -1;
|
|
map.overlayMapTypes.forEach(function(map_type,i){
|
|
if (layer == map_type && map_type.name == layer.name && map_type.description == layer.description) {
|
|
pos = i;
|
|
}
|
|
});
|
|
if (pos!=-1)
|
|
map.overlayMapTypes.removeAt(pos);
|
|
layer = null;
|
|
}
|
|
}
|
|
|
|
|
|
// Update tiles & interactivity layer;
|
|
google.maps.CartoDBLayer.prototype.update = function(sql) {
|
|
// Hide the infowindow
|
|
if (this.params.infowindow)
|
|
this.params.infowindow.hide();
|
|
// Refresh wax
|
|
refreshWax(this.params,sql);
|
|
// Refresh tiles
|
|
refreshTiles(this.params,sql);
|
|
|
|
this.params.active = true;
|
|
this.params.visible = true;
|
|
};
|
|
|
|
// Destroy layers from the map
|
|
google.maps.CartoDBLayer.prototype.destroy = function() {
|
|
// First remove previous cartodb - tiles.
|
|
removeOldLayer(this.params.map,this.params.layer);
|
|
|
|
if (this.params.infowindow) {
|
|
// Remove wax interaction
|
|
this.params.interaction.remove();
|
|
this.params.infowindow.hide();
|
|
}
|
|
|
|
this.params.active = false;
|
|
};
|
|
|
|
// Hide layers from the map
|
|
google.maps.CartoDBLayer.prototype.hide = function() {
|
|
this.destroy();
|
|
this.params.visible = false;
|
|
};
|
|
|
|
// Show layers from the map
|
|
google.maps.CartoDBLayer.prototype.show = function() {
|
|
if (!this.params.visible || !this.params.active) {
|
|
this.update(this.params.query);
|
|
this.params.visible = true;
|
|
}
|
|
};
|
|
|
|
// CartoDB layer visible?
|
|
google.maps.CartoDBLayer.prototype.isVisible = function() {
|
|
return this.params.visible;
|
|
};
|
|
};
|
|
}
|
|
|
|
/**
|
|
* CartoDB.Infowindow
|
|
* @xavijam
|
|
**/
|
|
CartoDB.Infowindow = function (params) {
|
|
this.latlng_ = new google.maps.LatLng(0,0);
|
|
this.feature_;
|
|
this.map_ = params.map;
|
|
this.columns_;
|
|
this.offsetHorizontal_ = -107;
|
|
this.width_ = 214;
|
|
this.setMap(params.map);
|
|
this.params_ = params;
|
|
};
|
|
|
|
CartoDB.Infowindow.prototype = new google.maps.OverlayView();
|
|
|
|
CartoDB.Infowindow.prototype.draw = function() {
|
|
var me = this;
|
|
|
|
var div = this.div_;
|
|
if (!div) {
|
|
div = this.div_ = document.createElement('DIV');
|
|
div.className = "cartodb_infowindow";
|
|
|
|
div.innerHTML = '<a href="#close" class="close">x</a>'+
|
|
'<div class="outer_top">'+
|
|
'<div class="top">'+
|
|
'</div>'+
|
|
'</div>'+
|
|
'<div class="bottom">'+
|
|
'<label>id:1</label>'+
|
|
'</div>';
|
|
|
|
$(div).find('a.close').click(function(ev){
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
me.hide();
|
|
});
|
|
|
|
google.maps.event.addDomListener(div,'click',function(ev){ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;});
|
|
google.maps.event.addDomListener(div,'dblclick',function(ev){ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;});
|
|
google.maps.event.addDomListener(div,'mousedown',function(ev){ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;});
|
|
google.maps.event.addDomListener(div,'mouseup',function(ev){ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;});
|
|
google.maps.event.addDomListener(div,'mousewheel',function(ev){ev.stopPropagation();});
|
|
google.maps.event.addDomListener(div,'DOMMouseScroll',function(ev){ev.stopPropagation();});
|
|
|
|
var panes = this.getPanes();
|
|
panes.floatPane.appendChild(div);
|
|
|
|
div.style.opacity = 0;
|
|
}
|
|
|
|
var pixPosition = this.getProjection().fromLatLngToDivPixel(this.latlng_);
|
|
if (pixPosition) {
|
|
div.style.width = this.width_ + 'px';
|
|
div.style.left = (pixPosition.x - 49) + 'px';
|
|
var actual_height = - $(div).height();
|
|
div.style.top = (pixPosition.y + actual_height + 5) + 'px';
|
|
}
|
|
};
|
|
|
|
CartoDB.Infowindow.prototype.setPosition = function() {
|
|
if (this.div_) {
|
|
var div = this.div_;
|
|
var pixPosition = this.getProjection().fromLatLngToDivPixel(this.latlng_);
|
|
if (pixPosition) {
|
|
div.style.width = this.width_ + 'px';
|
|
div.style.left = (pixPosition.x - 49) + 'px';
|
|
var actual_height = - $(div).height();
|
|
div.style.top = (pixPosition.y + actual_height + 10) + 'px';
|
|
}
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
CartoDB.Infowindow.prototype.open = function(feature,latlng){
|
|
var that = this
|
|
, infowindow_sql = 'SELECT * FROM ' + this.params_.table_name + ' WHERE cartodb_id=' + feature;
|
|
that.feature_ = feature;
|
|
|
|
// If the table is private, you can't run any api methods
|
|
if (this.params_.feature!=true) {
|
|
infowindow_sql = encodeURIComponent(this.params_.feature.replace('{{feature}}',feature));
|
|
}
|
|
|
|
|
|
$.ajax({
|
|
url:'http://'+ this.params_.user_name +'.cartodb.com/api/v1/sql/?q='+infowindow_sql,
|
|
dataType: 'jsonp',
|
|
timeout: 2000,
|
|
callbackParameter: 'callback',
|
|
success: function(result) {
|
|
positionateInfowindow(result.rows[0],latlng);
|
|
},
|
|
error: function(e,msg) {
|
|
that.params_.debug && console.debug('Error retrieving infowindow variables: ' + msg);
|
|
}
|
|
});
|
|
|
|
function positionateInfowindow(variables,center) {
|
|
if (that.div_) {
|
|
var div = that.div_;
|
|
// Get latlng position
|
|
that.latlng_ = latlng;
|
|
|
|
// Remove the unnecessary html
|
|
$('div.cartodb_infowindow div.outer_top div.top').html('');
|
|
$('div.cartodb_infowindow div.outer_top div.bottom label').html('');
|
|
|
|
// List all the new variables
|
|
for (p in variables) {
|
|
if (p!='cartodb_id' && p!='cdb_centre' && p!='the_geom_webmercator') {
|
|
$('div.cartodb_infowindow div.outer_top div.top').append('<label>'+p+'</label><p class="'+((variables[p]!=null && variables[p]!='')?'':'empty')+'">'+(variables[p] || 'empty')+'</p>');
|
|
}
|
|
}
|
|
|
|
// Show cartodb_id?
|
|
if (variables['cartodb_id']) {
|
|
$('div.cartodb_infowindow div.bottom label').html('id: <strong>'+feature+'</strong>');
|
|
}
|
|
|
|
that.moveMaptoOpen();
|
|
that.setPosition();
|
|
}
|
|
}
|
|
};
|
|
|
|
CartoDB.Infowindow.prototype.hide = function() {
|
|
if (this.div_) {
|
|
var div = this.div_;
|
|
$(div).animate({
|
|
top: '+=' + 10 + 'px',
|
|
opacity: 0},
|
|
100, 'swing',
|
|
function () {
|
|
div.style.visibility = "hidden";
|
|
}
|
|
);
|
|
}
|
|
};
|
|
|
|
CartoDB.Infowindow.prototype.show = function() {
|
|
if (this.div_) {
|
|
var div = this.div_;
|
|
div.style.opacity = 0;
|
|
div.style.visibility = "visible";
|
|
$(div).animate({
|
|
top: '-=' + 10 + 'px',
|
|
opacity: 1},
|
|
250
|
|
);
|
|
}
|
|
};
|
|
|
|
CartoDB.Infowindow.prototype.hideAll = function() {
|
|
$('div.cartodb_infowindow').css('visibility','hidden');
|
|
};
|
|
|
|
CartoDB.Infowindow.prototype.isVisible = function(marker_id) {
|
|
if (this.div_) {
|
|
var div = this.div_;
|
|
if (div.style.visibility == 'visible' && this.feature_!=null) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
CartoDB.Infowindow.prototype.transformGeoJSON = function(str) {
|
|
var json = $.parseJSON(str);
|
|
return new google.maps.LatLng(json.coordinates[1],json.coordinates[0]);
|
|
};
|
|
|
|
CartoDB.Infowindow.prototype.moveMaptoOpen = function() {
|
|
var left = 0;
|
|
var top = 0;
|
|
var div = this.div_;
|
|
var pixPosition = this.getProjection().fromLatLngToContainerPixel(this.latlng_);
|
|
|
|
if ((pixPosition.x + this.offsetHorizontal_) < 0) {
|
|
left = (pixPosition.x + this.offsetHorizontal_ - 20);
|
|
}
|
|
|
|
if ((pixPosition.x + 180) >= ($('#'+this.params_.map_canvas).width())) {
|
|
left = (pixPosition.x + 180 - $('#'+this.params_.map_canvas).width());
|
|
}
|
|
|
|
if ((pixPosition.y - $(div).height()) < 0) {
|
|
top = (pixPosition.y - $(div).height() - 30);
|
|
}
|
|
|
|
this.map_.panBy(left,top);
|
|
};
|
|
|
|
})(jQuery); |