Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f75ba9d2c3 | ||
|
|
be61d41f5e | ||
|
|
f619a97f1a | ||
|
|
e47449e357 | ||
|
|
178345ab12 | ||
|
|
a8340fef68 | ||
|
|
052b58ab90 | ||
|
|
cc5443152b | ||
|
|
d937d8970d | ||
|
|
dab4b6d56b | ||
|
|
46b212b2cd | ||
|
|
f47842c96d | ||
|
|
050f90a07b | ||
|
|
c1642dfa73 | ||
|
|
bbfcc640d1 | ||
|
|
e9b8c512c9 | ||
|
|
15b54a2918 | ||
|
|
eefa9f4222 | ||
|
|
a0073da4b3 | ||
|
|
c6fbb08c8f | ||
|
|
affa254b9d | ||
|
|
ecd33e5561 | ||
|
|
3e0c19a669 | ||
|
|
ab6004f21e |
16
NEWS.md
16
NEWS.md
@@ -1,5 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## 2.29.0
|
||||
|
||||
Released 2016-03-14
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [1.14.0](https://github.com/CartoDB/Windshaft/releases/tag/1.14.0)
|
||||
|
||||
|
||||
## 2.28.0
|
||||
|
||||
Released 2016-03-14
|
||||
|
||||
New features:
|
||||
- Added [turbo-cartocss](https://github.com/CartoDB/turbo-cartocss) to preprocess CartoCSS.
|
||||
|
||||
|
||||
## 2.27.0
|
||||
|
||||
Released 2016-03-09
|
||||
|
||||
@@ -7,12 +7,13 @@ var queue = require('queue-async');
|
||||
|
||||
var LruCache = require("lru-cache");
|
||||
|
||||
function NamedMapProviderCache(templateMaps, pgConnection, userLimitsApi) {
|
||||
function NamedMapProviderCache(templateMaps, pgConnection, userLimitsApi, turboCartocssAdapter) {
|
||||
this.templateMaps = templateMaps;
|
||||
this.pgConnection = pgConnection;
|
||||
this.userLimitsApi = userLimitsApi;
|
||||
|
||||
this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
|
||||
this.turboCartocssAdapter = turboCartocssAdapter;
|
||||
|
||||
this.providerCache = new LruCache({ max: 2000 });
|
||||
}
|
||||
@@ -30,6 +31,7 @@ NamedMapProviderCache.prototype.get = function(user, templateId, config, authTok
|
||||
this.pgConnection,
|
||||
this.userLimitsApi,
|
||||
this.namedLayersAdapter,
|
||||
this.turboCartocssAdapter,
|
||||
user,
|
||||
templateId,
|
||||
config,
|
||||
|
||||
@@ -34,7 +34,7 @@ var MapConfigOverviewsAdapter = require('../models/mapconfig_overviews_adapter')
|
||||
*/
|
||||
function MapController(authApi, pgConnection, templateMaps, mapBackend, metadataBackend,
|
||||
overviewsMetadataApi,
|
||||
surrogateKeysCache, userLimitsApi, layergroupAffectedTables) {
|
||||
surrogateKeysCache, userLimitsApi, layergroupAffectedTables, turboCartoCssAdapter) {
|
||||
|
||||
BaseController.call(this, authApi, pgConnection);
|
||||
|
||||
@@ -46,6 +46,7 @@ function MapController(authApi, pgConnection, templateMaps, mapBackend, metadata
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
this.userLimitsApi = userLimitsApi;
|
||||
this.layergroupAffectedTables = layergroupAffectedTables;
|
||||
this.turboCartoCssAdapter = turboCartoCssAdapter;
|
||||
|
||||
this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
|
||||
this.overviewsAdapter = new MapConfigOverviewsAdapter(this.overviewsMetadataApi);
|
||||
@@ -152,20 +153,35 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
|
||||
);
|
||||
},
|
||||
function addOverviewsInformation(err, requestMapConfig, datasource) {
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
self.overviewsAdapter.getLayers(req.context.user, requestMapConfig.layers,
|
||||
function(err, layers) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
self.overviewsAdapter.getLayers(req.context.user, requestMapConfig.layers, function(err, layers) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (layers) {
|
||||
requestMapConfig.layers = layers;
|
||||
}
|
||||
return next(null, requestMapConfig, datasource);
|
||||
}
|
||||
);
|
||||
if (layers) {
|
||||
requestMapConfig.layers = layers;
|
||||
}
|
||||
|
||||
return next(null, requestMapConfig, datasource);
|
||||
});
|
||||
},
|
||||
function parseTurboCartoCss(err, requestMapConfig, datasource) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
self.turboCartoCssAdapter.getLayers(req.context.user, requestMapConfig.layers, function (err, layers) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (layers) {
|
||||
requestMapConfig.layers = layers;
|
||||
}
|
||||
|
||||
return next(null, requestMapConfig, datasource);
|
||||
});
|
||||
},
|
||||
function createLayergroup(err, requestMapConfig, datasource) {
|
||||
assert.ifError(err);
|
||||
@@ -215,6 +231,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
|
||||
self.pgConnection,
|
||||
self.userLimitsApi,
|
||||
self.namedLayersAdapter,
|
||||
self.turboCartoCssAdapter,
|
||||
cdbuser,
|
||||
req.params.template_id,
|
||||
templateParams,
|
||||
|
||||
@@ -65,6 +65,7 @@ NamedMapsAdminController.prototype.update = function(req, res) {
|
||||
var cdbuser = req.context.user;
|
||||
var template;
|
||||
var tpl_id;
|
||||
|
||||
step(
|
||||
function checkPerms(){
|
||||
self.authApi.authorizedByAPIKey(cdbuser, req, this);
|
||||
|
||||
@@ -12,11 +12,12 @@ var QueryTables = require('cartodb-query-tables');
|
||||
* @type {NamedMapMapConfigProvider}
|
||||
*/
|
||||
function NamedMapMapConfigProvider(templateMaps, pgConnection, userLimitsApi, namedLayersAdapter,
|
||||
owner, templateId, config, authToken, params) {
|
||||
turboCartoCssAdapter, owner, templateId, config, authToken, params) {
|
||||
this.templateMaps = templateMaps;
|
||||
this.pgConnection = pgConnection;
|
||||
this.userLimitsApi = userLimitsApi;
|
||||
this.namedLayersAdapter = namedLayersAdapter;
|
||||
this.turboCartoCssAdapter = turboCartoCssAdapter;
|
||||
|
||||
this.owner = owner;
|
||||
this.templateName = templateName(templateId);
|
||||
@@ -91,6 +92,18 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
|
||||
}
|
||||
);
|
||||
},
|
||||
function parseTurboCartoCss(err, _mapConfig, datasource) {
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
|
||||
self.turboCartoCssAdapter.getLayers(self.owner, _mapConfig.layers, function (err, layers) {
|
||||
if (!err && layers) {
|
||||
_mapConfig.layers = layers;
|
||||
}
|
||||
|
||||
return next(null, _mapConfig, datasource);
|
||||
});
|
||||
},
|
||||
function beforeLayergroupCreate(err, _mapConfig, _datasource) {
|
||||
assert.ifError(err);
|
||||
mapConfig = _mapConfig;
|
||||
|
||||
@@ -30,6 +30,8 @@ var PgConnection = require('./backends/pg_connection');
|
||||
var timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png';
|
||||
var timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});
|
||||
|
||||
var TurboCartocssParser = require('./utils/style/turbo-cartocss-parser');
|
||||
var TurboCartocssAdapter = require('./utils/style/turbo-cartocss-adapter');
|
||||
|
||||
module.exports = function(serverOptions) {
|
||||
// Make stats client globally accessible
|
||||
@@ -140,7 +142,16 @@ module.exports = function(serverOptions) {
|
||||
var layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();
|
||||
app.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
|
||||
|
||||
var namedMapProviderCache = new NamedMapProviderCache(templateMaps, pgConnection, userLimitsApi);
|
||||
var turboCartoCssParser = new TurboCartocssParser(pgQueryRunner);
|
||||
var turboCartocssAdapter = new TurboCartocssAdapter(turboCartoCssParser);
|
||||
|
||||
var namedMapProviderCache = new NamedMapProviderCache(
|
||||
templateMaps,
|
||||
pgConnection,
|
||||
userLimitsApi,
|
||||
turboCartocssAdapter
|
||||
);
|
||||
|
||||
['update', 'delete'].forEach(function(eventType) {
|
||||
templateMaps.on(eventType, namedMapProviderCache.invalidate.bind(namedMapProviderCache));
|
||||
});
|
||||
@@ -176,7 +187,8 @@ module.exports = function(serverOptions) {
|
||||
overviewsMetadataApi,
|
||||
surrogateKeysCache,
|
||||
userLimitsApi,
|
||||
layergroupAffectedTablesCache
|
||||
layergroupAffectedTablesCache,
|
||||
turboCartocssAdapter
|
||||
).register(app);
|
||||
|
||||
new controller.NamedMaps(
|
||||
|
||||
55
lib/cartodb/utils/style/postgres-datasource.js
Normal file
55
lib/cartodb/utils/style/postgres-datasource.js
Normal file
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
|
||||
function createTemplate(method) {
|
||||
return dot.template([
|
||||
'SELECT',
|
||||
method,
|
||||
'FROM ({{=it._sql}}) _table_sql WHERE {{=it._column}} IS NOT NULL'
|
||||
].join('\n'));
|
||||
}
|
||||
|
||||
var methods = {
|
||||
quantiles: 'CDB_QuantileBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as quantiles',
|
||||
equal: 'CDB_EqualIntervalBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as equal',
|
||||
jenks: 'CDB_JenksBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as jenks',
|
||||
headtails: 'CDB_HeadsTailsBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as headtails'
|
||||
};
|
||||
|
||||
var methodTemplates = Object.keys(methods).reduce(function(methodTemplates, methodName) {
|
||||
methodTemplates[methodName] = createTemplate(methods[methodName]);
|
||||
return methodTemplates;
|
||||
}, {});
|
||||
|
||||
function PostgresDatasource (pgQueryRunner, username, query) {
|
||||
this.pgQueryRunner = pgQueryRunner;
|
||||
this.username = username;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
PostgresDatasource.prototype.getName = function () {
|
||||
return 'PostgresDatasource';
|
||||
};
|
||||
|
||||
PostgresDatasource.prototype.getRamp = function (column, buckets, method, callback) {
|
||||
var methodName = methods.hasOwnProperty(method) ? method : 'quantiles';
|
||||
var template = methodTemplates[methodName];
|
||||
|
||||
var query = template({ _column: column, _sql: this.query, _buckets: buckets });
|
||||
|
||||
this.pgQueryRunner.run(this.username, query, function (err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var ramp = result[0][methodName].sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
return callback(null, ramp);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = PostgresDatasource;
|
||||
56
lib/cartodb/utils/style/turbo-cartocss-adapter.js
Normal file
56
lib/cartodb/utils/style/turbo-cartocss-adapter.js
Normal file
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
var queue = require('queue-async');
|
||||
|
||||
function TurboCartocssAdapter(turboCartocssParser) {
|
||||
this.turboCartocssParser = turboCartocssParser;
|
||||
}
|
||||
|
||||
module.exports = TurboCartocssAdapter;
|
||||
|
||||
TurboCartocssAdapter.prototype.getLayers = function (username, layers, callback) {
|
||||
var self = this;
|
||||
|
||||
if (!layers || layers.length === 0) {
|
||||
return callback(null, layers);
|
||||
}
|
||||
|
||||
var parseCartoCssQueue = queue(layers.length);
|
||||
|
||||
layers.forEach(function(layer) {
|
||||
parseCartoCssQueue.defer(self._parseCartoCss.bind(self), username, layer);
|
||||
});
|
||||
|
||||
parseCartoCssQueue.awaitAll(function (err, layers) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
return callback(null, layers);
|
||||
});
|
||||
};
|
||||
|
||||
TurboCartocssAdapter.prototype._parseCartoCss = function (username, layer, callback) {
|
||||
if (isNotLayerToParseCartocss(layer)) {
|
||||
return process.nextTick(function () {
|
||||
callback(null, layer);
|
||||
});
|
||||
}
|
||||
|
||||
this.turboCartocssParser.process(username, layer.options.cartocss, layer.options.sql, function (err, cartocss) {
|
||||
// Ignore turbo-cartocss errors and continue
|
||||
if (!err && cartocss) {
|
||||
layer.options.cartocss = cartocss;
|
||||
}
|
||||
|
||||
callback(null, layer);
|
||||
});
|
||||
};
|
||||
|
||||
function isNotLayerToParseCartocss(layer) {
|
||||
if (!layer || !layer.options || !layer.options.cartocss || !layer.options.sql) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
15
lib/cartodb/utils/style/turbo-cartocss-parser.js
Normal file
15
lib/cartodb/utils/style/turbo-cartocss-parser.js
Normal file
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
var turboCartoCss = require('turbo-cartocss');
|
||||
var PostgresDatasource = require('./postgres-datasource');
|
||||
|
||||
function TurboCartocssParser (pgQueryRunner) {
|
||||
this.pgQueryRunner = pgQueryRunner;
|
||||
}
|
||||
|
||||
module.exports = TurboCartocssParser;
|
||||
|
||||
TurboCartocssParser.prototype.process = function (username, cartocss, sql, callback) {
|
||||
var datasource = new PostgresDatasource(this.pgQueryRunner, username, sql);
|
||||
turboCartoCss(cartocss, datasource, callback);
|
||||
};
|
||||
1375
npm-shrinkwrap.json
generated
1375
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "2.27.0",
|
||||
"version": "2.29.0",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
@@ -26,7 +26,7 @@
|
||||
"node-statsd": "~0.0.7",
|
||||
"underscore" : "~1.6.0",
|
||||
"dot": "~1.0.2",
|
||||
"windshaft": "1.13.2",
|
||||
"windshaft": "1.14.0",
|
||||
"step": "~0.0.6",
|
||||
"queue-async": "~1.0.7",
|
||||
"request": "~2.62.0",
|
||||
@@ -37,7 +37,8 @@
|
||||
"lru-cache": "2.6.5",
|
||||
"lzma": "~1.3.7",
|
||||
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb",
|
||||
"cartodb-query-tables": "~0.1.0"
|
||||
"cartodb-query-tables": "~0.1.0",
|
||||
"turbo-cartocss": "0.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"istanbul": "~0.3.6",
|
||||
|
||||
128
test/acceptance/turbo-cartocss-anonymous-maps.js
Normal file
128
test/acceptance/turbo-cartocss-anonymous-maps.js
Normal file
@@ -0,0 +1,128 @@
|
||||
var assert = require('../support/assert');
|
||||
var TestClient = require('../support/test-client');
|
||||
|
||||
var IMAGE_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
function imageCompareFn(fixture, done) {
|
||||
return function(err, res, image) {
|
||||
assert.ok(!err, err);
|
||||
assert.imageIsSimilarToFile(image, './test/fixtures/' + fixture, IMAGE_TOLERANCE_PER_MIL, done);
|
||||
};
|
||||
}
|
||||
|
||||
function makeMapconfig(cartocss) {
|
||||
return {
|
||||
"version": "1.4.0",
|
||||
"layers": [
|
||||
{
|
||||
"type": 'mapnik',
|
||||
"options": {
|
||||
"cartocss_version": '2.3.0',
|
||||
"sql": [
|
||||
'SELECT test_table.*, _prices.price FROM test_table JOIN (' +
|
||||
' SELECT 1 AS cartodb_id, 10.00 AS price',
|
||||
' UNION',
|
||||
' SELECT 2, 10.50',
|
||||
' UNION',
|
||||
' SELECT 3, 11.00',
|
||||
' UNION',
|
||||
' SELECT 4, 12.00',
|
||||
' UNION',
|
||||
' SELECT 5, 21.00',
|
||||
') _prices ON _prices.cartodb_id = test_table.cartodb_id'
|
||||
].join('\n'),
|
||||
"cartocss": cartocss
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
describe('turbo-cartocss for anonymous maps', function() {
|
||||
describe('parsing ramp function with colorbrewer for greens and mapnik renderer', function () {
|
||||
beforeEach(function () {
|
||||
var turboCartocss = '#layer { marker-fill: ramp([price], colorbrewer(Greens)); }';
|
||||
this.testClient = new TestClient(makeMapconfig(turboCartocss));
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
this.testClient.drain(done);
|
||||
});
|
||||
|
||||
it('should get a tile with turbo-cartocss parsed properly', function (done) {
|
||||
var fixturePath = 'test_turbo_cartocss_greens_13_4011_3088.png';
|
||||
this.testClient.getTile(13, 4011, 3088, imageCompareFn(fixturePath, done));
|
||||
});
|
||||
});
|
||||
|
||||
describe('parsing ramp function with colorbrewer for reds and mapnik renderer', function () {
|
||||
beforeEach(function () {
|
||||
var turboCartocss = '#layer { marker-fill: ramp([price], colorbrewer(Reds)); }';
|
||||
this.testClient = new TestClient(makeMapconfig(turboCartocss));
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
this.testClient.drain(done);
|
||||
});
|
||||
|
||||
it('should get a tile with turbo-cartocss parsed properly', function (done) {
|
||||
var fixtureFileName = 'test_turbo_cartocss_reds_13_4011_3088.png';
|
||||
this.testClient.getTile(13, 4011, 3088, imageCompareFn(fixtureFileName, done));
|
||||
});
|
||||
});
|
||||
|
||||
describe('parsing ramp function with colorbrewer for greens and toque renderer', function () {
|
||||
var mapConfig = {
|
||||
version: '1.2.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'torque',
|
||||
options: {
|
||||
sql: "SELECT * FROM populated_places_simple_reduced where the_geom" +
|
||||
" && ST_MakeEnvelope(-90, 0, 90, 65)",
|
||||
cartocss: [
|
||||
'Map {',
|
||||
' buffer-size:0;',
|
||||
' -torque-frame-count:1;',
|
||||
' -torque-animation-duration:30;',
|
||||
' -torque-time-attribute:"cartodb_id";',
|
||||
' -torque-aggregation-function:"count(cartodb_id)";',
|
||||
' -torque-resolution:1;',
|
||||
' -torque-data-aggregation:linear;',
|
||||
'};',
|
||||
'#populated_places_simple_reduced {',
|
||||
' comp-op: multiply;',
|
||||
' marker-fill-opacity: 1;',
|
||||
' marker-line-color: #FFF;',
|
||||
' marker-line-width: 0;',
|
||||
' marker-line-opacity: 1;',
|
||||
' marker-type: rectangle;',
|
||||
' marker-width: 3;',
|
||||
' marker-fill: ramp([pop_max], colorbrewer(Greens));',
|
||||
'};'
|
||||
].join(' '),
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
this.testClient = new TestClient(mapConfig);
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
this.testClient.drain(done);
|
||||
});
|
||||
|
||||
it('should get a tile with turbo-cartocss parsed properly', function (done) {
|
||||
var z = 2;
|
||||
var x = 2;
|
||||
var y = 1;
|
||||
|
||||
var pngFixture = 'torque/populated_places_simple_reduced-turbo-cartocss-' + [z, x, y].join('.') + '.png';
|
||||
|
||||
this.testClient.getTile(z, x, y, { layers: 0, format: 'torque.png' }, imageCompareFn(pngFixture, done));
|
||||
});
|
||||
});
|
||||
});
|
||||
236
test/acceptance/turbo-cartocss-named-maps.js
Normal file
236
test/acceptance/turbo-cartocss-named-maps.js
Normal file
@@ -0,0 +1,236 @@
|
||||
var assert = require('../support/assert');
|
||||
var step = require('step');
|
||||
var LayergroupToken = require('../../lib/cartodb/models/layergroup_token');
|
||||
var testHelper = require(__dirname + '/../support/test_helper');
|
||||
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
var mapnik = require('windshaft').mapnik;
|
||||
var IMAGE_TOLERANCE_PER_MIL = 10;
|
||||
|
||||
describe('turbo-cartocss for named maps', function() {
|
||||
|
||||
var keysToDelete;
|
||||
|
||||
beforeEach(function() {
|
||||
keysToDelete = {};
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
testHelper.deleteRedisKeys(keysToDelete, done);
|
||||
});
|
||||
|
||||
var templateId = 'turbo-cartocss-template-1';
|
||||
|
||||
var template = {
|
||||
version: '0.0.1',
|
||||
name: templateId,
|
||||
auth: { method: 'open' },
|
||||
placeholders: {
|
||||
color: {
|
||||
type: "css_color",
|
||||
default: "Reds"
|
||||
}
|
||||
},
|
||||
layergroup: {
|
||||
version: '1.0.0',
|
||||
layers: [{
|
||||
options: {
|
||||
sql: [
|
||||
'SELECT test_table.*, _prices.price FROM test_table JOIN (' +
|
||||
' SELECT 1 AS cartodb_id, 10.00 AS price',
|
||||
' UNION',
|
||||
' SELECT 2, 10.50',
|
||||
' UNION',
|
||||
' SELECT 3, 11.00',
|
||||
' UNION',
|
||||
' SELECT 4, 12.00',
|
||||
' UNION',
|
||||
' SELECT 5, 21.00',
|
||||
') _prices ON _prices.cartodb_id = test_table.cartodb_id'
|
||||
].join('\n'),
|
||||
cartocss: [
|
||||
'#layer {',
|
||||
' marker-fill: ramp([price], colorbrewer(<%= color %>));',
|
||||
' marker-allow-overlap:true;',
|
||||
'}'
|
||||
].join('\n'),
|
||||
cartocss_version: '2.0.2'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var templateParamsReds = { color: 'Reds' };
|
||||
var templateParamsBlues = { color: 'Blues' };
|
||||
|
||||
it('should create a template with turbo-cartocss parsed properly', function (done) {
|
||||
step(
|
||||
function postTemplate() {
|
||||
var next = this;
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: { host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template)
|
||||
}, {},
|
||||
function (res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function checkTemplate(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.deepEqual(JSON.parse(res.body), {
|
||||
template_id: templateId
|
||||
});
|
||||
|
||||
return null;
|
||||
},
|
||||
function instantiateTemplateWithReds(err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/named/' + templateId,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(templateParamsReds)
|
||||
}, {},
|
||||
function(res, err) {
|
||||
return next(err, res);
|
||||
});
|
||||
},
|
||||
function checkInstanciationWithReds(err, res) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.equal(res.statusCode, 200);
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
|
||||
assert.ok(parsedBody.layergroupid);
|
||||
assert.ok(parsedBody.last_updated);
|
||||
|
||||
return parsedBody.layergroupid;
|
||||
},
|
||||
function requestTileReds(err, layergroupId) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
|
||||
method: 'GET',
|
||||
headers: { host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
}, {},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function checkTileReds(err, res) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.equal(res.headers['content-type'], 'image/png');
|
||||
|
||||
var fixturePath = './test/fixtures/turbo-cartocss-named-maps-reds.png';
|
||||
var image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||
|
||||
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, next);
|
||||
},
|
||||
function instantiateTemplateWithBlues(err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/named/' + templateId,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(templateParamsBlues)
|
||||
}, {},
|
||||
function(res, err) {
|
||||
return next(err, res);
|
||||
});
|
||||
},
|
||||
function checkInstanciationWithBlues(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200);
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
|
||||
assert.ok(parsedBody.layergroupid);
|
||||
assert.ok(parsedBody.last_updated);
|
||||
|
||||
return parsedBody.layergroupid;
|
||||
},
|
||||
function requestTileBlues(err, layergroupId) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
|
||||
method: 'GET',
|
||||
headers: { host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
}, {},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function checkTileBlues(err, res) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.equal(res.headers['content-type'], 'image/png');
|
||||
|
||||
var fixturePath = './test/fixtures/turbo-cartocss-named-maps-blues.png';
|
||||
var image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||
|
||||
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, next);
|
||||
},
|
||||
function deleteTemplate(err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/named/' + templateId + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: { host: 'localhost' }
|
||||
}, {}, function (res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function checkDeleteTemplate(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 204);
|
||||
assert.ok(!res.body);
|
||||
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
75
test/acceptance/turbo-cartocss-regressions.js
Normal file
75
test/acceptance/turbo-cartocss-regressions.js
Normal file
@@ -0,0 +1,75 @@
|
||||
require('../support/test_helper');
|
||||
|
||||
var assert = require('../support/assert');
|
||||
var TestClient = require('../support/test-client');
|
||||
|
||||
function makeMapconfig(cartocss) {
|
||||
return {
|
||||
"version": "1.4.0",
|
||||
"layers": [
|
||||
{
|
||||
"type": 'mapnik',
|
||||
"options": {
|
||||
"cartocss_version": '2.3.0',
|
||||
"sql": [
|
||||
'SELECT test_table.*, _prices.price FROM test_table JOIN (' +
|
||||
' SELECT 1 AS cartodb_id, 10.00 AS price',
|
||||
' UNION',
|
||||
' SELECT 2, 10.50',
|
||||
' UNION',
|
||||
' SELECT 3, 11.00',
|
||||
' UNION',
|
||||
' SELECT 4, 12.00',
|
||||
' UNION',
|
||||
' SELECT 5, 21.00',
|
||||
') _prices ON _prices.cartodb_id = test_table.cartodb_id'
|
||||
].join('\n'),
|
||||
"cartocss": cartocss
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
describe('turbo-cartocss regressions', function() {
|
||||
|
||||
var cartocss = [
|
||||
"/** simple visualization */",
|
||||
"",
|
||||
"Map {",
|
||||
" buffer-size: 256;",
|
||||
"}",
|
||||
"",
|
||||
"#county_points_with_population{",
|
||||
" marker-fill-opacity: 0.1;",
|
||||
" marker-line-color:#FFFFFF;//#CF1C90;",
|
||||
" marker-line-width: 0;",
|
||||
" marker-line-opacity: 0.3;",
|
||||
" marker-placement: point;",
|
||||
" marker-type: ellipse;",
|
||||
" //marker-comp-op: overlay;",
|
||||
" marker-width: [price];",
|
||||
" [zoom=5]{marker-width: [price]*2;}",
|
||||
" [zoom=6]{marker-width: [price]*4;}",
|
||||
" marker-fill: #000000;",
|
||||
" marker-allow-overlap: true;",
|
||||
" ",
|
||||
"",
|
||||
"}"
|
||||
].join('\n');
|
||||
|
||||
beforeEach(function () {
|
||||
this.testClient = new TestClient(makeMapconfig(cartocss));
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
this.testClient.drain(done);
|
||||
});
|
||||
|
||||
it('should accept // comments', function(done) {
|
||||
this.testClient.getTile(0, 0, 0, function(err) {
|
||||
assert.ok(!err, err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
BIN
test/fixtures/test_turbo_cartocss_greens_13_4011_3088.png
vendored
Normal file
BIN
test/fixtures/test_turbo_cartocss_greens_13_4011_3088.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
test/fixtures/test_turbo_cartocss_reds_13_4011_3088.png
vendored
Normal file
BIN
test/fixtures/test_turbo_cartocss_reds_13_4011_3088.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
test/fixtures/torque/populated_places_simple_reduced-turbo-cartocss-2.2.1.png
vendored
Normal file
BIN
test/fixtures/torque/populated_places_simple_reduced-turbo-cartocss-2.2.1.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
BIN
test/fixtures/turbo-cartocss-named-maps-blues.png
vendored
Normal file
BIN
test/fixtures/turbo-cartocss-named-maps-blues.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 356 B |
BIN
test/fixtures/turbo-cartocss-named-maps-reds.png
vendored
Normal file
BIN
test/fixtures/turbo-cartocss-named-maps-reds.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 349 B |
@@ -80,7 +80,7 @@ if test x"$PREPARE_PGSQL" = xyes; then
|
||||
|
||||
cat sql/_CDB_QueryStatements.sql | psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
||||
|
||||
SQL_SCRIPTS='CDB_QueryTables CDB_CartodbfyTable CDB_TableMetadata CDB_ForeignTable CDB_UserTables CDB_ColumnNames CDB_ZoomFromScale CDB_Overviews'
|
||||
SQL_SCRIPTS='CDB_QueryTables CDB_CartodbfyTable CDB_TableMetadata CDB_ForeignTable CDB_UserTables CDB_ColumnNames CDB_ZoomFromScale CDB_Overviews CDB_QuantileBins CDB_JenksBins CDB_HeadsTailsBins CDB_EqualIntervalBins'
|
||||
for i in ${SQL_SCRIPTS}
|
||||
do
|
||||
curl -L -s https://github.com/CartoDB/cartodb-postgresql/raw/master/scripts-available/$i.sql -o sql/$i.sql
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
var qs = require('querystring');
|
||||
var step = require('step');
|
||||
|
||||
var mapnik = require('windshaft').mapnik;
|
||||
|
||||
var LayergroupToken = require('../../lib/cartodb/models/layergroup_token');
|
||||
|
||||
var assert = require('./assert');
|
||||
@@ -13,8 +15,9 @@ var serverOptions = require('../../lib/cartodb/server_options');
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
|
||||
function TestClient(mapConfig) {
|
||||
function TestClient(mapConfig, apiKey) {
|
||||
this.mapConfig = mapConfig;
|
||||
this.apiKey = apiKey;
|
||||
this.keysToDelete = {};
|
||||
}
|
||||
|
||||
@@ -114,6 +117,114 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) {
|
||||
);
|
||||
};
|
||||
|
||||
TestClient.prototype.getTile = function(z, x, y, params, callback) {
|
||||
var self = this;
|
||||
|
||||
if (!callback) {
|
||||
callback = params;
|
||||
params = {};
|
||||
}
|
||||
|
||||
var url = '/api/v1/map';
|
||||
|
||||
if (this.apiKey) {
|
||||
url += '?' + qs.stringify({api_key: this.apiKey});
|
||||
}
|
||||
|
||||
var layergroupId;
|
||||
step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(self.mapConfig)
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
return next(null, JSON.parse(res.body).layergroupid);
|
||||
}
|
||||
);
|
||||
},
|
||||
function getTileResult(err, _layergroupId) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
layergroupId = _layergroupId;
|
||||
|
||||
url = '/api/v1/map/' + layergroupId + '/';
|
||||
|
||||
var layers = params.layers;
|
||||
|
||||
if (layers !== undefined) {
|
||||
layers = Array.isArray(layers) ? layers : [layers];
|
||||
url += layers.join(',') + '/';
|
||||
}
|
||||
|
||||
var format = params.format || 'png';
|
||||
|
||||
url += [z,x,y].join('/');
|
||||
url += '.' + format;
|
||||
|
||||
if (self.apiKey) {
|
||||
url += '?' + qs.stringify({api_key: self.apiKey});
|
||||
}
|
||||
|
||||
var request = {
|
||||
url: url,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
}
|
||||
};
|
||||
|
||||
var expectedResponse = {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
var isPng = format === 'png' || format === 'torque.png';
|
||||
|
||||
if (isPng) {
|
||||
request.encoding = 'binary';
|
||||
expectedResponse.headers['Content-Type'] = 'image/png';
|
||||
}
|
||||
|
||||
assert.response(server, request, expectedResponse, function(res, err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var image;
|
||||
|
||||
if (isPng) {
|
||||
image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||
}
|
||||
|
||||
next(null, res, image);
|
||||
});
|
||||
},
|
||||
function finish(err, res, image) {
|
||||
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
|
||||
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
return callback(err, res, image);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TestClient.prototype.drain = function(callback) {
|
||||
helper.deleteRedisKeys(this.keysToDelete, callback);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user