CartoDB is a great service for generating online maps and is particularly useful if you’re working with big data that might not render quickly using APIs such as Leaflet and Google Maps. For details on CartoDB we did a write-up of the nice functionality here. In working with CartoDB layers, though, one of the challenges we’ve faced is how to generate infowindows for overlapping features.
On a recent project we were working with water system polygons — a messy geographic file with many, many overlapping features (see below). We wanted to allow users to click on a location and see all of the water systems serving that location — not just information for the polygon which would be the default. Below we show pseudo-code for how we used CartoDB spatial analysis functionality to identify all features at a clicked location and render details about them in a tabbed infowindow. Here is what the final map and infowindows look like.
Click on a Water System To See InfoWindow
Pseudo-Code Showing How the InfoWindow Handler Was Constructed
In order to create info windows like the ones you see above we took advantage of CartoDB’s backend spatial database powered by PostgreSQL/PostGIS. We start with the code to add the layer (note that ‘USER’ and ‘DATA’ would be your username and data file name). This code adds a CartoDB layer and sets up the infowindow function to call our customized callinfowindow
function into which we feed the lat/long and the ID of the feature clicked (our ID is kml_key). We then parse the data using, in part, underscore.js and create the tabbed info windows.
Add CartoDB layer
cartodb.createLayer(map, 'http://USER.cartodb.com/api/v2/viz/DATA/viz.json', {
query: 'SELECT * FROM {{table_name}} ORDER BY thearea DESC'
})
.on('done', function(layer) {
map.overlayMapTypes.setAt(1, layer);
var sublayer = layer.getSubLayer(0);
sublayer.setCartoCSS(systemcartoCSS);
layers.SystemLyr = sublayer
var infowindow = sublayer.infowindow
infowindow.set('template', function(data) {
var clickPosLatLng = this.model.get('latlng');
var fields = this.model.get('content').fields;
if (fields && fields[0].type !== 'loading') {
var obj = _.find(fields, function(obj) {
return obj.title == 'kml_key'
}).value
callinfowindow(clickPosLatLng, obj)
}
}); // end infowindow set
}); // end on.done for the water systems
InfoWindow Function
Now when a user actually clicks on the map this triggers a call to our callinfowindow
function. In CartoDB a user click can give you the ID of the feature clicked and the latitude and longitude of the point clicked. We used these to identify the top polygon by ID. We used PostGIS' function ST_Contains to identify the underlying polygons that 'contain' the clicked point.
function callinfowindow(clickPosLatLng, obj) {
//Query: select the water systems where the ID (kml_key) equals that of the clicked
//polygon OR the polygon contains the clicked point.
var q = "SELECT kml_key,pwsname, pwsid, edited, locadd, locstreet, loccity, phone, population, connection, \
pws_class FROM service_areas WHERE (kml_key=" + obj + "OR (ST_Contains(service_areas.the_geom, \
ST_GeomFromText('POINT(" + clickPosLatLng[1] + " " + clickPosLatLng[0] + ")', 4326))))"
$.ajax({
url: "http://USER.cartodb.com/api/v1/sql?q=" + q,
dataType: "jsonp",
success: function(data) {
var dat = data.rows
// pull out the fields of interest and order the results
var kml_key = _.pluck(dat, 'kml_key');
var topsystemindx = _.indexOf(kml_key, obj)
var topsystem = [dat[topsystemindx]]
dat.splice(topsystemindx, 1)
dat = topsystem.concat(dat)
var pwsname = _.pluck(dat, 'pwsname');
var pwsid = _.pluck(dat, 'pwsid');
// ... CODE REDACTED FOR BREVITY -- JUST MORE PLUCKING
var tabHead = 'Water System(s) Serving<br>this Location';
addGoogleMarker(clickPosLatLng)
// Set up the tabs so we have a tab for each overalpping water system
// infohtml.push('<div id="tabs">')
for (var i = 0; i < pwsname.length; i++) {
var tabID = "System" + (i + 1);
tabhtml.push(' <li><a href="#' + tabID + '">' + tabID + '</a></li>');
// if field is null leave blank
address[i] = address[i] || '';
address2[i] = address2[i] || '';
//create the pop-ups
infohtml.push('<div id="' + tabID + '"><ul>' +
'<li><span class="strong">' + pwsname[i].toProperCase() + '</span></li>' +
'<li><span class="light">ID:</span> ' + pwsid[i].toProperCase() + '</li>' +
'<li><span class="light">Last Edited:</span> ' + edit[i].toProperCase() + '</li>' +
'<li><span class="light">Address:</span> ' + address[i].toProperCase() + '</li>' +
//CODE REDACTED FOR BREVITY - JUST MORE FIELDS
'</ul></div>')
};
var infohtml = infohtml.join('')
var tabhtml = tabhtml.join('')
if ($('#tabs').length) {
$('#tabs').remove()
}
$('.systeminfo').append("<div id='tabs'></div>")
//$('#tabs').html('<h4>' + tabHead + '</h4><ul>' + tabhtml + '</ul>' + infohtml)
$('#tabs').html('<ul><h4>' + tabHead + '</h4>' + tabhtml + '</ul>' + infohtml)
$("#tabs").tabs();
$('.systeminfo .close').css('visibility', 'visible');
}
}); // end get data for infowindow
}
Hi. Nice job but I am afraid it doesn’t work anymore. I know it used to work but something’s wrong now.
I really appreciate you reporting this! This seems to be related to a change in how CartoDB names its visualizations and should be fixed here. You might need to do a browser hard refresh.
Excellent job!
but I think is there any code missing, isn’t it?, something relative to the template
Could you help us?, thanks in advance
You need to be more specific than this, please post a stackoverflow comment and send me the link.
Sorry, I’m new at Carto, and tried to reproduce the window for my demo.
It may be my ignorance, but I Cannot make run the code in the last lines:
$(‘.systeminfo’).append(“”)
$(‘#tabs’).html(” + tabHead + ” + tabhtml + ” + infohtml)
$(“#tabs”).tabs();
$(‘.systeminfo .close’).css(‘visibility’, ‘visible’)
I guessed there were some css missing before to mount the tabs.
Thanks for your attention
Perhaps you’re getting this error because we left off a piece of the code! Try adding these 2 lines of code after the line:
var pwsid = _.pluck(dat, 'pwsid')
var infohtml = [];
var tabhtml = [];
I will try it.
thanks for your help, your project is very nice