diff --git a/lib/cartodb/controllers/map.js b/lib/cartodb/controllers/map.js index a5555a2f..a09b9572 100644 --- a/lib/cartodb/controllers/map.js +++ b/lib/cartodb/controllers/map.js @@ -16,6 +16,7 @@ var NamedMapsCacheEntry = require('../cache/model/named_maps_entry'); var TablesCacheEntry = require('../cache/model/database_tables_entry'); var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter'); +var MapConfigAnalysisLayersAdapter = require('../models/mapconfig_analysis_layers_adapter'); var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider'); var CreateLayergroupMapConfigProvider = require('../models/mapconfig/create_layergroup_provider'); var MapConfigOverviewsAdapter = require('../models/mapconfig_overviews_adapter'); @@ -49,6 +50,7 @@ function MapController(authApi, pgConnection, templateMaps, mapBackend, metadata this.userLimitsApi = userLimitsApi; this.layergroupAffectedTables = layergroupAffectedTables; + this.analysisLayersAdapter = new MapConfigAnalysisLayersAdapter(); this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps); this.overviewsAdapter = new MapConfigOverviewsAdapter(this.overviewsMetadataApi); } @@ -137,6 +139,10 @@ MapController.prototype.create = function(req, res, prepareConfigFn) { self.req2params(req, this); }, prepareConfigFn, + function prepareAnalysisLayers(err, requestMapConfig) { + assert.ifError(err); + self.analysisLayersAdapter.getLayers(req.context.user, requestMapConfig, this); + }, function beforeLayergroupCreate(err, requestMapConfig) { assert.ifError(err); var next = this; diff --git a/lib/cartodb/models/mapconfig_analysis_layers_adapter.js b/lib/cartodb/models/mapconfig_analysis_layers_adapter.js new file mode 100644 index 00000000..920dfa97 --- /dev/null +++ b/lib/cartodb/models/mapconfig_analysis_layers_adapter.js @@ -0,0 +1,128 @@ +var queue = require('queue-async'); +var _ = require('underscore'); +var Datasource = require('windshaft').model.Datasource; + +var camshaft = require('camshaft'); +var dot = require('dot'); +dot.templateSettings.strip = false; + +function MapConfigAnalysisLayersAdapter(templateMaps) { + this.templateMaps = templateMaps; +} + +module.exports = MapConfigAnalysisLayersAdapter; + + +multitypeStyleTemplate = dot.template([ + "#points['mapnik::geometry_type'=1] {", + " marker-fill-opacity: {{=it._opacity}};", + " marker-line-color: #FFF;", + " marker-line-width: 0.5;", + " marker-line-opacity: {{=it._opacity}};", + " marker-placement: point;", + " marker-type: ellipse;", + " marker-width: 4;", + " marker-fill: {{=it._color}};", + " marker-allow-overlap: true;", + "}", + "#lines['mapnik::geometry_type'=2] {", + " line-color: {{=it._color}};", + " line-width: 2;", + " line-opacity: {{=it._opacity}};", + "}", + "#polygons['mapnik::geometry_type'=3] {", + " polygon-fill: {{=it._color}};", + " polygon-opacity: 0.7;", + " line-color: #FFF;", + " line-width: 0.5;", + " line-opacity: {{=it._opacity}};", + "}" +].join('\n')); + +function multiTypeStyle(color, opacity) { + return multitypeStyleTemplate({ + _opacity: opacity || 1.0, + _color: color || 'red' + }); +} + +var layerQueryTemplate = dot.template([ + 'SELECT ST_Transform(the_geom, 3857) the_geom_webmercator', + 'FROM ({{=it._query}}) _cdb_analysis_query' +].join('\n')); + +function getLayer(query, color, opacity) { + + console.log(multiTypeStyle(color, opacity)); + return { + type: 'mapnik', + options: { + sql: layerQueryTemplate({ _query: query } ), + cartocss: multiTypeStyle(color, opacity), + cartocss_version: '2.3.0' + } + }; +} + +MapConfigAnalysisLayersAdapter.prototype.getLayers = function(username, requestMapConfig, callback) { + + function adaptLayer(layer, done) { + if (isAnalysisTypeLayer(layer)) { + + var analysisDefinition = JSON.parse(layer.options.def); + console.log(JSON.stringify(analysisDefinition, null, 2)); + + camshaft.create(username, analysisDefinition, function(err, analysis) { + if (err) { + return done(err); + } + + var layers = []; + + analysis.getSortedNodes().reverse().forEach(function(node) { + layers.push(getLayer(node.getQuery(), 'grey', 0.2)); + }); + + layers.push(getLayer(analysis.getQuery())); + + return done(null, { layers: layers }); + }); + } else { + return done(null, { layers: [layer] }); + } + } + + function layersAdaptQueueFinish(err, layersResults) { + if (err) { + return callback(err); + } + + if (!layersResults || layersResults.length === 0) { + return callback(new Error('Missing layers array from layergroup config')); + } + + var layers = []; + layersResults.forEach(function(layersResult) { + layers = layers.concat(layersResult.layers); + }); + + requestMapConfig.layers = layers; + + return callback(null, requestMapConfig); + } + + var adaptLayersQueue = queue(requestMapConfig.layers.length); + + if (_.some(requestMapConfig.layers, isAnalysisTypeLayer)) { + requestMapConfig.layers.forEach(function(layer) { + adaptLayersQueue.defer(adaptLayer, layer); + }); + adaptLayersQueue.awaitAll(layersAdaptQueueFinish); + } else { + return callback(null, requestMapConfig); + } +}; + +function isAnalysisTypeLayer(layer) { + return layer.type === 'analysis'; +} diff --git a/package.json b/package.json index e31e26ff..4f26468b 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "underscore" : "~1.6.0", "dot": "~1.0.2", "windshaft": "1.9.0", + "camshaft": "https://github.com/CartoDB/camshaft/tarball/master", "step": "~0.0.6", "queue-async": "~1.0.7", "request": "~2.62.0",