Compare commits
156 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef9ec5b262 | ||
|
|
f9b8152f21 | ||
|
|
e0ff8b4320 | ||
|
|
6a699ba51b | ||
|
|
1899fd3813 | ||
|
|
bd7c99f94f | ||
|
|
6ba9e50da7 | ||
|
|
21a2d9e82f | ||
|
|
0f20cdaae1 | ||
|
|
5d813b6e43 | ||
|
|
a842acfdb4 | ||
|
|
1e65804a1b | ||
|
|
acf0b082b4 | ||
|
|
2c6305bcd4 | ||
|
|
bd153a0c87 | ||
|
|
fb6987e91a | ||
|
|
74c036483a | ||
|
|
65e10bc20d | ||
|
|
89a1e69bec | ||
|
|
564884797d | ||
|
|
dd1ee56648 | ||
|
|
c54c3754ef | ||
|
|
d72a5075b9 | ||
|
|
6dde5fc6f1 | ||
|
|
880ef63720 | ||
|
|
b75150e91e | ||
|
|
006e21379f | ||
|
|
03850fb31c | ||
|
|
7df0fb456b | ||
|
|
1fe1b5fc4d | ||
|
|
95d179835c | ||
|
|
7c52f504e5 | ||
|
|
44bbfe3ba6 | ||
|
|
57258a9cd3 | ||
|
|
cd25150056 | ||
|
|
c9d50c412d | ||
|
|
732f891850 | ||
|
|
8ef260972d | ||
|
|
de30ab99ef | ||
|
|
de6c651b60 | ||
|
|
b466937d68 | ||
|
|
d338b5ca37 | ||
|
|
9740b65fe7 | ||
|
|
27e2d0baa5 | ||
|
|
4039221b4b | ||
|
|
5bb58429b3 | ||
|
|
204d246a8c | ||
|
|
e0f49ca8f5 | ||
|
|
07cdb37deb | ||
|
|
5c3182e168 | ||
|
|
31263b7b22 | ||
|
|
925328c43b | ||
|
|
a36b93a473 | ||
|
|
14cf3c1093 | ||
|
|
534808038e | ||
|
|
83c7d38d42 | ||
|
|
964bfef6e7 | ||
|
|
657fb97d58 | ||
|
|
2b36e8c68b | ||
|
|
ed101e30fa | ||
|
|
9a3bd51664 | ||
|
|
6576fa5ca0 | ||
|
|
45fc2dd07a | ||
|
|
e7a6ddb4ff | ||
|
|
f719813c52 | ||
|
|
9c4adef0c2 | ||
|
|
9f831b2c40 | ||
|
|
dfa057d979 | ||
|
|
7ffac651b6 | ||
|
|
98ab237bf7 | ||
|
|
63e4bcebef | ||
|
|
593a72a967 | ||
|
|
6c8f38a241 | ||
|
|
a910f1442e | ||
|
|
df14afb55f | ||
|
|
732a2d7742 | ||
|
|
78f4cf3155 | ||
|
|
8f763d655d | ||
|
|
64329c3fac | ||
|
|
c8c22a787e | ||
|
|
6a6ec4300b | ||
|
|
44ba5aa568 | ||
|
|
01658c33fd | ||
|
|
efafd4cb3e | ||
|
|
d25740ed51 | ||
|
|
3bb4ad86ff | ||
|
|
b169c96f1c | ||
|
|
843f4b8e28 | ||
|
|
e5b75abc76 | ||
|
|
83777540d0 | ||
|
|
0e28348e16 | ||
|
|
7b7bee2901 | ||
|
|
2a2f703abc | ||
|
|
e85f4e4129 | ||
|
|
bcad6dbe22 | ||
|
|
e9f88a78d5 | ||
|
|
f601ed3806 | ||
|
|
085d26f1b2 | ||
|
|
559546d333 | ||
|
|
da8d92b78e | ||
|
|
de5498c1b2 | ||
|
|
2134bf898a | ||
|
|
e27ed7e79d | ||
|
|
efad5b20e8 | ||
|
|
f27d5ba7d1 | ||
|
|
09a67871fb | ||
|
|
cec9994add | ||
|
|
d45d0018d2 | ||
|
|
410cbd082c | ||
|
|
fd875c41c7 | ||
|
|
750798d0a3 | ||
|
|
04faaea10d | ||
|
|
74831c9b7f | ||
|
|
5471a218eb | ||
|
|
79f2a8dde9 | ||
|
|
5c0b7487f7 | ||
|
|
c021f5ebdc | ||
|
|
3a3baf3c85 | ||
|
|
b8d320c434 | ||
|
|
515e482886 | ||
|
|
ea0805b017 | ||
|
|
ec0e90e8ce | ||
|
|
d4de54f292 | ||
|
|
9124a26a45 | ||
|
|
18603ad24f | ||
|
|
70ac0587db | ||
|
|
230b1bb3db | ||
|
|
23ef884e9b | ||
|
|
bb24b1dfcc | ||
|
|
324e614902 | ||
|
|
20c8d07a46 | ||
|
|
fe9f4939d5 | ||
|
|
a89131c043 | ||
|
|
1f6bb6839a | ||
|
|
1e70717554 | ||
|
|
3cf378e045 | ||
|
|
12ad4420aa | ||
|
|
70000f9df1 | ||
|
|
eaff11ef6e | ||
|
|
6c3b546648 | ||
|
|
384f4f74e0 | ||
|
|
898bac5b04 | ||
|
|
cd08ad693f | ||
|
|
c2577d1d25 | ||
|
|
13075460ac | ||
|
|
cca0848e6d | ||
|
|
150658d58d | ||
|
|
6be1a77a29 | ||
|
|
fb683e438d | ||
|
|
2196a89ec3 | ||
|
|
bafeceeb6e | ||
|
|
eeafad7cd9 | ||
|
|
94b7353fbf | ||
|
|
60cd91f144 | ||
|
|
1d199f8713 | ||
|
|
42b79e5bc6 |
22
NEWS.md
22
NEWS.md
@@ -1,5 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## 6.4.0
|
||||
Released 2018-09-24
|
||||
|
||||
- Upgrades Camshaft to [0.62.3](https://github.com/CartoDB/camshaft/releases/tag/0.61.11):
|
||||
- Build query from node's cache to compute output columns when building analysis
|
||||
- Adds metadata columns for street level geocoding
|
||||
- Remove use of `step` module to handle asynchronous code, now it's defined as development dependency.
|
||||
- Bug Fixes: (#1020)
|
||||
- Fix bug in date-wrapper regarding columns with spaces
|
||||
- Fix bug in aggregation-query regarding columns with spaces
|
||||
- Upgrades Windshaft to [4.10.0](https://github.com/CartoDB/Windshaft/blob/4.10.0/NEWS.md#version-4100)
|
||||
- `pg-mvt`:
|
||||
- Now matches the behaviour of the `mapnik` renderer for MVTs.
|
||||
- Removed undocummented filtering by `layer.options.columns`.
|
||||
- Implement timeout in getTile.
|
||||
- Several bugfixes.
|
||||
- Dependency updates: Fixed a bug in Mapnik MVT renderer and cleanup in `tilelive-mapnik`.
|
||||
- [MapConfig 1.8.0 released](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.8.0.md) with new options for MVTs:
|
||||
- Add **`vector_extent`** option in MapConfig to setup the layer extent.
|
||||
- Add **`vector_simplify_extent`** option in MapConfig to configure the simplification process.
|
||||
- Remove use of `step` module to handle asynchronous code, now it's defined as development dependency.
|
||||
|
||||
## 6.3.0
|
||||
Released 2018-07-26
|
||||
|
||||
|
||||
16
carto-package.json
Normal file
16
carto-package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "carto_windshaft",
|
||||
"current_version": {
|
||||
"requires": {
|
||||
"node": ">=6.9.2 <10.0.0",
|
||||
"yarn": ">=0.27.5 <1.0.0",
|
||||
"mapnik": ">=3.0.15"
|
||||
},
|
||||
"works_with": {
|
||||
"redis": ">=3.0.0",
|
||||
"postgresql": ">=9.5.0",
|
||||
"postgis": ">=2.2.0.0",
|
||||
"carto_postgresql_ext": ">=0.19.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,8 +152,17 @@ function prepareAdapterMapConfig (mapConfigAdapter) {
|
||||
}
|
||||
};
|
||||
|
||||
mapConfigAdapter.getMapConfig(user, requestMapConfig, params, context, (err, requestMapConfig) => {
|
||||
mapConfigAdapter.getMapConfig(user,
|
||||
requestMapConfig,
|
||||
params,
|
||||
context,
|
||||
(err, requestMapConfig, stats = { overviewsAddedToMapconfig : false }) => {
|
||||
req.profiler.done('anonymous.getMapConfig');
|
||||
|
||||
stats.mapType = 'anonymous';
|
||||
req.profiler.add(stats);
|
||||
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
@@ -147,10 +147,6 @@ function incrementErrorMetrics (statsClient) {
|
||||
|
||||
function tileError () {
|
||||
return function tileErrorMiddleware (err, req, res, next) {
|
||||
if (err.message === 'Tile does not exist' && req.params.format === 'mvt') {
|
||||
res.statusCode = 204;
|
||||
return next();
|
||||
}
|
||||
|
||||
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
|
||||
let errMsg = err.message ? ( '' + err.message ) : ( '' + err );
|
||||
|
||||
@@ -165,8 +165,12 @@ function getTemplate (
|
||||
params
|
||||
);
|
||||
|
||||
mapConfigProvider.getMapConfig((err, mapConfig, rendererParams) => {
|
||||
mapConfigProvider.getMapConfig((err, mapConfig, rendererParams, context, stats = {}) => {
|
||||
req.profiler.done('named.getMapConfig');
|
||||
|
||||
stats.mapType = 'named';
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
var assert = require('assert');
|
||||
var _ = require('underscore');
|
||||
var PSQL = require('cartodb-psql');
|
||||
var step = require('step');
|
||||
var BBoxFilter = require('../models/filter/bbox');
|
||||
var DataviewFactory = require('../models/dataview/factory');
|
||||
var DataviewFactoryWithOverviews = require('../models/dataview/overviews/factory');
|
||||
@@ -21,53 +19,76 @@ function DataviewBackend(analysisBackend) {
|
||||
module.exports = DataviewBackend;
|
||||
|
||||
DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, params, callback) {
|
||||
const dataviewName = params.dataviewName;
|
||||
|
||||
var dataviewName = params.dataviewName;
|
||||
step(
|
||||
function getMapConfig() {
|
||||
mapConfigProvider.getMapConfig(this);
|
||||
},
|
||||
function runDataviewQuery(err, mapConfig) {
|
||||
assert.ifError(err);
|
||||
mapConfigProvider.getMapConfig(function (err, mapConfig) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
|
||||
if (!dataviewDefinition) {
|
||||
throw new Error("Dataview '" + dataviewName + "' does not exists");
|
||||
}
|
||||
var dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
|
||||
if (!dataviewDefinition) {
|
||||
const error = new Error(`Dataview '${dataviewName}' does not exist`);
|
||||
error.type = 'dataview';
|
||||
error.http_status = 400;
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
if (!validFilterParams(params)) {
|
||||
const error = new Error('Both own_filter and no_filters cannot be sent in the same request');
|
||||
error.type = 'dataview';
|
||||
error.http_status = 400;
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
var pg;
|
||||
var overrideParams;
|
||||
var dataview;
|
||||
|
||||
try {
|
||||
pg = new PSQL(dbParamsFromReqParams(params));
|
||||
var query = getQueryWithFilters(dataviewDefinition, params);
|
||||
var queryRewriteData = getQueryRewriteData(mapConfig, dataviewDefinition, params);
|
||||
var dataviewFactory = DataviewFactoryWithOverviews.getFactory(overviewsQueryRewriter, queryRewriteData, {
|
||||
bbox: params.bbox
|
||||
});
|
||||
dataview = dataviewFactory.getDataview(query, dataviewDefinition);
|
||||
var ownFilter = +params.own_filter;
|
||||
var noFilters = +params.no_filters;
|
||||
if (Number.isFinite(ownFilter) && Number.isFinite(noFilters)) {
|
||||
err = new Error();
|
||||
err.message = 'Both own_filter and no_filters cannot be sent in the same request';
|
||||
err.type = 'dataview';
|
||||
err.http_status = 400;
|
||||
overrideParams = getOverrideParams(params, !!ownFilter);
|
||||
} catch (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
dataview.getResult(pg, overrideParams, function (err, dataviewResult, stats = {}) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var pg = new PSQL(dbParamsFromReqParams(params));
|
||||
|
||||
var query = getDataviewQuery(dataviewDefinition, ownFilter, noFilters);
|
||||
if (params.bbox) {
|
||||
var bboxFilter = new BBoxFilter({column: 'the_geom_webmercator', srid: 3857}, {bbox: params.bbox});
|
||||
query = bboxFilter.sql(query);
|
||||
}
|
||||
|
||||
var queryRewriteData = getQueryRewriteData(mapConfig, dataviewDefinition, params);
|
||||
|
||||
var dataviewFactory = DataviewFactoryWithOverviews.getFactory(
|
||||
overviewsQueryRewriter, queryRewriteData, { bbox: params.bbox }
|
||||
);
|
||||
|
||||
var dataview = dataviewFactory.getDataview(query, dataviewDefinition);
|
||||
dataview.getResult(pg, getOverrideParams(params, !!ownFilter), this);
|
||||
},
|
||||
function returnCallback(err, result) {
|
||||
return callback(err, result);
|
||||
}
|
||||
);
|
||||
return callback(null, dataviewResult, stats);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function validFilterParams (params) {
|
||||
var ownFilter = +params.own_filter;
|
||||
var noFilters = +params.no_filters;
|
||||
|
||||
return !(Number.isFinite(ownFilter) && Number.isFinite(noFilters));
|
||||
}
|
||||
|
||||
function getQueryWithFilters (dataviewDefinition, params) {
|
||||
var ownFilter = +params.own_filter;
|
||||
var noFilters = +params.no_filters;
|
||||
var query = getDataviewQuery(dataviewDefinition, ownFilter, noFilters);
|
||||
|
||||
if (params.bbox) {
|
||||
var bboxFilter = new BBoxFilter({column: 'the_geom_webmercator', srid: 3857}, {bbox: params.bbox});
|
||||
query = bboxFilter.sql(query);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
function getDataviewQuery(dataviewDefinition, ownFilter, noFilters) {
|
||||
if (noFilters) {
|
||||
return dataviewDefinition.sql.no_filters;
|
||||
@@ -129,41 +150,56 @@ function getOverrideParams(params, ownFilter) {
|
||||
}
|
||||
|
||||
DataviewBackend.prototype.search = function (mapConfigProvider, user, dataviewName, params, callback) {
|
||||
step(
|
||||
function getMapConfig() {
|
||||
mapConfigProvider.getMapConfig(this);
|
||||
},
|
||||
function runDataviewSearchQuery(err, mapConfig) {
|
||||
assert.ifError(err);
|
||||
|
||||
var dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
|
||||
if (!dataviewDefinition) {
|
||||
throw new Error("Dataview '" + dataviewName + "' does not exists");
|
||||
}
|
||||
|
||||
var pg = new PSQL(dbParamsFromReqParams(params));
|
||||
|
||||
var ownFilter = +params.own_filter;
|
||||
ownFilter = !!ownFilter;
|
||||
|
||||
var query = (ownFilter) ? dataviewDefinition.sql.own_filter_on : dataviewDefinition.sql.own_filter_off;
|
||||
|
||||
if (params.bbox) {
|
||||
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
|
||||
query = bboxFilter.sql(query);
|
||||
}
|
||||
|
||||
var userQuery = params.q;
|
||||
|
||||
var dataview = DataviewFactory.getDataview(query, dataviewDefinition);
|
||||
dataview.search(pg, userQuery, this);
|
||||
},
|
||||
function returnCallback(err, result) {
|
||||
return callback(err, result);
|
||||
mapConfigProvider.getMapConfig(function (err, mapConfig) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
|
||||
var dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
|
||||
if (!dataviewDefinition) {
|
||||
const error = new Error(`Dataview '${dataviewName}' does not exist`);
|
||||
error.type = 'dataview';
|
||||
error.http_status = 400;
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
var pg;
|
||||
var query;
|
||||
var dataview;
|
||||
var userQuery = params.q;
|
||||
|
||||
try {
|
||||
pg = new PSQL(dbParamsFromReqParams(params));
|
||||
query = getQueryWithOwnFilters(dataviewDefinition, params);
|
||||
dataview = DataviewFactory.getDataview(query, dataviewDefinition);
|
||||
} catch (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
dataview.search(pg, userQuery, function (err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
return callback(null, result);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function getQueryWithOwnFilters (dataviewDefinition, params) {
|
||||
var ownFilter = +params.own_filter;
|
||||
ownFilter = !!ownFilter;
|
||||
|
||||
var query = (ownFilter) ? dataviewDefinition.sql.own_filter_on : dataviewDefinition.sql.own_filter_off;
|
||||
|
||||
if (params.bbox) {
|
||||
var bboxFilter = new BBoxFilter({ column: 'the_geom', srid: 4326 }, { bbox: params.bbox });
|
||||
query = bboxFilter.sql(query);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
function getDataviewDefinition(mapConfig, dataviewName) {
|
||||
var dataviews = mapConfig.dataviews || {};
|
||||
return dataviews[dataviewName];
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var _ = require('underscore');
|
||||
var step = require('step');
|
||||
var AnalysisFilter = require('../models/filter/analysis');
|
||||
|
||||
function FilterStatsBackends(pgQueryRunner) {
|
||||
@@ -24,36 +23,29 @@ function getEstimatedRows(pgQueryRunner, username, query, callback) {
|
||||
}
|
||||
|
||||
FilterStatsBackends.prototype.getFilterStats = function (username, unfiltered_query, filters, callback) {
|
||||
var stats = {};
|
||||
var self = this;
|
||||
step(
|
||||
function getUnfilteredRows() {
|
||||
getEstimatedRows(self.pgQueryRunner, username, unfiltered_query, this);
|
||||
},
|
||||
function receiveUnfilteredRows(err, rows) {
|
||||
if (err){
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
stats.unfiltered_rows = rows;
|
||||
this(null, rows);
|
||||
},
|
||||
function getFilteredRows() {
|
||||
if ( filters && !_.isEmpty(filters)) {
|
||||
var analysisFilter = new AnalysisFilter(filters);
|
||||
var query = analysisFilter.sql(unfiltered_query);
|
||||
getEstimatedRows(self.pgQueryRunner, username, query, this);
|
||||
} else {
|
||||
this(null, null);
|
||||
}
|
||||
},
|
||||
function receiveFilteredRows(err, rows) {
|
||||
if (err){
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
stats.filtered_rows = rows;
|
||||
callback(null, stats);
|
||||
}
|
||||
);
|
||||
var stats = {};
|
||||
|
||||
getEstimatedRows(this.pgQueryRunner, username, unfiltered_query, (err, rows) => {
|
||||
if (err){
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
stats.unfiltered_rows = rows;
|
||||
|
||||
if (!filters || _.isEmpty(filters)) {
|
||||
return callback(null, stats);
|
||||
}
|
||||
|
||||
var analysisFilter = new AnalysisFilter(filters);
|
||||
var query = analysisFilter.sql(unfiltered_query);
|
||||
|
||||
getEstimatedRows(this.pgQueryRunner, username, query, (err, rows) => {
|
||||
if (err){
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
stats.filtered_rows = rows;
|
||||
return callback(null, stats);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -148,7 +148,7 @@ function _columnStats(ctx, columns) {
|
||||
Object.keys(columns).forEach(name => {
|
||||
aggr = aggr.concat(
|
||||
columnAggregations(columns[name])
|
||||
.map(fn => `${fn}(${name}) AS ${name}_${fn}`)
|
||||
.map(fn => `${fn}("${name}") AS "${name}_${fn}"`)
|
||||
);
|
||||
if (columns[name].type === 'string') {
|
||||
const topN = ctx.metaOptions.columnStats.topCategories || 1024;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
var assert = require('assert');
|
||||
var crypto = require('crypto');
|
||||
var debug = require('debug')('windshaft:templates');
|
||||
var step = require('step');
|
||||
var _ = require('underscore');
|
||||
var dot = require('dot');
|
||||
|
||||
@@ -69,27 +67,19 @@ TemplateMaps.prototype._userTemplateLimit = function() {
|
||||
* @param callback - function to pass results too.
|
||||
*/
|
||||
TemplateMaps.prototype._redisCmd = function(redisFunc, redisArgs, callback) {
|
||||
var redisClient;
|
||||
var that = this;
|
||||
var db = that.db_signatures;
|
||||
this.redis_pool.acquire(this.db_signatures, (err, redisClient) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
step(
|
||||
function getRedisClient() {
|
||||
that.redis_pool.acquire(db, this);
|
||||
},
|
||||
function executeQuery(err, data) {
|
||||
assert.ifError(err);
|
||||
redisClient = data;
|
||||
redisArgs.push(this);
|
||||
redisClient[redisFunc.toUpperCase()].apply(redisClient, redisArgs);
|
||||
},
|
||||
function releaseRedisClient(err, data) {
|
||||
if ( ! _.isUndefined(redisClient) ) {
|
||||
that.redis_pool.release(db, redisClient);
|
||||
}
|
||||
callback(err, data);
|
||||
}
|
||||
);
|
||||
redisClient[redisFunc.toUpperCase()](...redisArgs, (err, data) => {
|
||||
this.redis_pool.release(this.db_signatures, redisClient);
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null, data);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var _reValidNameIdentifier = /^[a-z0-9][0-9a-z_\-]*$/i;
|
||||
@@ -184,6 +174,37 @@ function templateDefaults(template) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the if the user reaches the templetes limit
|
||||
*
|
||||
* @param userTemplatesKey user templat key in Redis
|
||||
* @param owner cartodb username of the template owner
|
||||
* @param callback returns error if the user reaches the limit
|
||||
*/
|
||||
TemplateMaps.prototype._checkUserTemplatesLimit = function(userTemplatesKey, owner, callback) {
|
||||
const limit = this._userTemplateLimit();
|
||||
|
||||
if(!limit) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
this._redisCmd('HLEN', [userTemplatesKey], (err, numberOfTemplates) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (numberOfTemplates >= limit) {
|
||||
const limitReachedError = new Error(
|
||||
`User '${owner}' reached limit on number of templates (${numberOfTemplates}/${limit})`
|
||||
);
|
||||
limitReachedError.http_status = 409;
|
||||
return callback(limitReachedError);
|
||||
}
|
||||
|
||||
return callback();
|
||||
});
|
||||
};
|
||||
|
||||
//--------------- PUBLIC API -------------------------------------
|
||||
|
||||
// Add a template
|
||||
@@ -199,52 +220,41 @@ function templateDefaults(template) {
|
||||
// Return template identifier (only valid for given user)
|
||||
//
|
||||
TemplateMaps.prototype.addTemplate = function(owner, template, callback) {
|
||||
var self = this;
|
||||
|
||||
template = templateDefaults(template);
|
||||
|
||||
var invalidError = this._checkInvalidTemplate(template);
|
||||
if ( invalidError ) {
|
||||
if (invalidError) {
|
||||
return callback(invalidError);
|
||||
}
|
||||
|
||||
var templateName = template.name;
|
||||
var userTemplatesKey = this.key_usr_tpl({ owner:owner });
|
||||
var limit = this._userTemplateLimit();
|
||||
var userTemplatesKey = this.key_usr_tpl({ owner });
|
||||
|
||||
step(
|
||||
function checkLimit() {
|
||||
if ( ! limit ) {
|
||||
return 0;
|
||||
}
|
||||
self._redisCmd('HLEN', [ userTemplatesKey ], this);
|
||||
},
|
||||
function installTemplateIfDoesNotExist(err, numberOfTemplates) {
|
||||
assert.ifError(err);
|
||||
if ( limit && numberOfTemplates >= limit ) {
|
||||
var limitReachedError = new Error("User '" + owner + "' reached limit on number of templates (" +
|
||||
numberOfTemplates + "/" + limit + ")");
|
||||
limitReachedError.http_status = 409;
|
||||
throw limitReachedError;
|
||||
}
|
||||
self._redisCmd('HSETNX', [ userTemplatesKey, templateName, JSON.stringify(template) ], this);
|
||||
},
|
||||
function validateInstallation(err, wasSet) {
|
||||
assert.ifError(err);
|
||||
if ( ! wasSet ) {
|
||||
throw new Error("Template '" + templateName + "' of user '" + owner + "' already exists");
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
function finish(err) {
|
||||
if (!err) {
|
||||
self.emit('add', owner, templateName, template);
|
||||
}
|
||||
|
||||
callback(err, templateName, template);
|
||||
this._checkUserTemplatesLimit(userTemplatesKey, owner, err => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
|
||||
let templateString;
|
||||
try {
|
||||
templateString = JSON.stringify(template);
|
||||
} catch (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
this._redisCmd('HSETNX', [userTemplatesKey, template.name, templateString], (err, wasSet) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!wasSet) {
|
||||
var templateExistsError = new Error(`Template '${template.name}' of user '${owner}' already exists`);
|
||||
return callback(templateExistsError);
|
||||
}
|
||||
|
||||
this.emit('add', owner, template.name, template);
|
||||
return callback(null, template.name, template);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Delete a template
|
||||
@@ -257,26 +267,18 @@ TemplateMaps.prototype.addTemplate = function(owner, template, callback) {
|
||||
// @param callback function(err)
|
||||
//
|
||||
TemplateMaps.prototype.delTemplate = function(owner, tpl_id, callback) {
|
||||
var self = this;
|
||||
step(
|
||||
function deleteTemplate() {
|
||||
self._redisCmd('HDEL', [ self.key_usr_tpl({ owner:owner }), tpl_id ], this);
|
||||
},
|
||||
function handleDeletion(err, deleted) {
|
||||
assert.ifError(err);
|
||||
if (!deleted) {
|
||||
throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist");
|
||||
}
|
||||
return true;
|
||||
},
|
||||
function finish(err) {
|
||||
if (!err) {
|
||||
self.emit('delete', owner, tpl_id);
|
||||
}
|
||||
|
||||
callback(err);
|
||||
this._redisCmd('HDEL', [ this.key_usr_tpl({ owner:owner }), tpl_id ], (err, deleted) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
|
||||
if (!deleted) {
|
||||
return callback(new Error(`Template '${tpl_id}' of user '${owner}' does not exist`));
|
||||
}
|
||||
|
||||
this.emit('delete', owner, tpl_id);
|
||||
return callback();
|
||||
});
|
||||
};
|
||||
|
||||
// Update a template
|
||||
@@ -296,56 +298,58 @@ TemplateMaps.prototype.delTemplate = function(owner, tpl_id, callback) {
|
||||
// @param callback function(err)
|
||||
//
|
||||
TemplateMaps.prototype.updTemplate = function(owner, tpl_id, template, callback) {
|
||||
|
||||
var self = this;
|
||||
|
||||
template = templateDefaults(template);
|
||||
|
||||
var invalidError = this._checkInvalidTemplate(template);
|
||||
|
||||
if ( invalidError ) {
|
||||
if (invalidError) {
|
||||
return callback(invalidError);
|
||||
}
|
||||
|
||||
var templateName = template.name;
|
||||
|
||||
if ( tpl_id !== templateName ) {
|
||||
return callback(new Error("Cannot update name of a map template ('" + tpl_id + "' != '" + templateName + "')"));
|
||||
if (tpl_id !== template.name) {
|
||||
return callback(new Error(`Cannot update name of a map template ('${tpl_id}' != '${template.name}')`));
|
||||
}
|
||||
|
||||
var userTemplatesKey = this.key_usr_tpl({ owner:owner });
|
||||
var userTemplatesKey = this.key_usr_tpl({ owner });
|
||||
|
||||
var previousTemplate = null;
|
||||
this._redisCmd('HGET', [userTemplatesKey, tpl_id], (err, beforeUpdateTemplate) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
step(
|
||||
function getExistingTemplate() {
|
||||
self._redisCmd('HGET', [ userTemplatesKey, tpl_id ], this);
|
||||
},
|
||||
function updateTemplate(err, _currentTemplate) {
|
||||
assert.ifError(err);
|
||||
if (!_currentTemplate) {
|
||||
throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist");
|
||||
if (!beforeUpdateTemplate) {
|
||||
return callback(new Error(`Template '${tpl_id}' of user '${owner}' does not exist`));
|
||||
}
|
||||
|
||||
let templateString;
|
||||
try {
|
||||
templateString = JSON.stringify(template);
|
||||
} catch (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
this._redisCmd('HSET', [userTemplatesKey, template.name, templateString], (err, didSetNewField) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
previousTemplate = _currentTemplate;
|
||||
self._redisCmd('HSET', [ userTemplatesKey, templateName, JSON.stringify(template) ], this);
|
||||
},
|
||||
function handleTemplateUpdate(err, didSetNewField) {
|
||||
assert.ifError(err);
|
||||
|
||||
if (didSetNewField) {
|
||||
debug('New template created on update operation');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
function finish(err) {
|
||||
if (!err) {
|
||||
if (self.fingerPrint(JSON.parse(previousTemplate)) !== self.fingerPrint(template)) {
|
||||
self.emit('update', owner, templateName, template);
|
||||
}
|
||||
|
||||
let beforeUpdateTemplateObject;
|
||||
try {
|
||||
beforeUpdateTemplateObject = JSON.parse(beforeUpdateTemplate);
|
||||
} catch (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
callback(err, template);
|
||||
}
|
||||
);
|
||||
if (this.fingerPrint(beforeUpdateTemplateObject) !== this.fingerPrint(template)) {
|
||||
this.emit('update', owner, template.name, template);
|
||||
}
|
||||
|
||||
return callback(null, template);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// List user templates
|
||||
@@ -370,19 +374,20 @@ TemplateMaps.prototype.listTemplates = function(owner, callback) {
|
||||
// Return full template definition
|
||||
//
|
||||
TemplateMaps.prototype.getTemplate = function(owner, tpl_id, callback) {
|
||||
var self = this;
|
||||
step(
|
||||
function getTemplate() {
|
||||
self._redisCmd('HGET', [ self.key_usr_tpl({owner:owner}), tpl_id ], this);
|
||||
},
|
||||
function parseTemplate(err, tpl_val) {
|
||||
assert.ifError(err);
|
||||
return JSON.parse(tpl_val);
|
||||
},
|
||||
function finish(err, tpl) {
|
||||
callback(err, tpl);
|
||||
this._redisCmd('HGET', [this.key_usr_tpl({owner:owner}), tpl_id], (err, template) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
|
||||
let templateObject;
|
||||
try {
|
||||
templateObject = JSON.parse(template);
|
||||
} catch (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
return callback(null, templateObject);
|
||||
});
|
||||
};
|
||||
|
||||
TemplateMaps.prototype.isAuthorized = function(template, authTokens) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
var step = require('step');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param metadataBackend
|
||||
@@ -41,45 +39,38 @@ UserLimitsBackend.prototype.getRenderLimits = function (username, apiKey, callba
|
||||
};
|
||||
|
||||
UserLimitsBackend.prototype.getTimeoutRenderLimit = function (username, apiKey, callback) {
|
||||
var self = this;
|
||||
|
||||
step(
|
||||
function isAuthorized() {
|
||||
var next = this;
|
||||
|
||||
if (!apiKey) {
|
||||
return next(null, false);
|
||||
}
|
||||
|
||||
self.metadataBackend.getUserMapKey(username, function (err, userApiKey) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
return next(null, userApiKey === apiKey);
|
||||
});
|
||||
},
|
||||
function getUserTimeoutRenderLimits(err, authorized) {
|
||||
var next = this;
|
||||
isAuthorized(this.metadataBackend, username, apiKey, (err, authorized) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
this.metadataBackend.getUserTimeoutRenderLimits(username, (err, timeoutRenderLimit) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self.metadataBackend.getUserTimeoutRenderLimits(username, function (err, timeoutRenderLimit) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
next(null, {
|
||||
render: authorized ? timeoutRenderLimit.render : timeoutRenderLimit.renderPublic
|
||||
});
|
||||
});
|
||||
},
|
||||
callback
|
||||
);
|
||||
return callback(
|
||||
null,
|
||||
{ render: authorized ? timeoutRenderLimit.render : timeoutRenderLimit.renderPublic }
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function isAuthorized(metadataBackend, username, apiKey, callback) {
|
||||
if (!apiKey) {
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
metadataBackend.getUserMapKey(username, function (err, userApiKey) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
return callback(null, userApiKey === apiKey);
|
||||
});
|
||||
}
|
||||
|
||||
UserLimitsBackend.prototype.preprareRateLimit = function () {
|
||||
if (this.options.limits.rateLimitsEnabled) {
|
||||
this.metadataBackend.loadRateLimitsScript();
|
||||
|
||||
@@ -117,17 +117,19 @@ const dimensionNames = (ctx, table) => {
|
||||
let dimensions = aggregateDimensions(ctx);
|
||||
if (table) {
|
||||
return sep(Object.keys(dimensions).map(
|
||||
dimension_name => `${table}.${dimension_name}`
|
||||
dimension_name => `${table}."${dimension_name}"`
|
||||
));
|
||||
}
|
||||
return sep(Object.keys(dimensions));
|
||||
return sep(Object.keys(dimensions).map(dimension_name => {
|
||||
return `"${dimension_name}"`;
|
||||
}));
|
||||
};
|
||||
|
||||
const dimensionDefs = ctx => {
|
||||
let dimensions = aggregateDimensions(ctx);
|
||||
return sep(Object.keys(dimensions).map(dimension_name => {
|
||||
const expression = dimensions[dimension_name];
|
||||
return `${expression} AS ${dimension_name}`;
|
||||
return `"${expression}" AS "${dimension_name}"`;
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -274,23 +276,24 @@ const spatialFilter = `
|
||||
// * This queries are used for rendering and the_geom is omitted in the results for better performance
|
||||
// * If the MVT extent or tile buffer was 0 or a multiple of the resolution we could use directly
|
||||
// the bbox for them, but in general we need to find the nearest cell limits inside the bbox.
|
||||
// * bbox coordinates can have an error in the last digites; we apply a small correction before
|
||||
// applying CEIL or FLOOR to compensate for this.
|
||||
// * bbox coordinates can have an error in the last digits; we apply a small correction before
|
||||
// applying CEIL or FLOOR to compensate for this, so that coordinates closer than a small (`eps`)
|
||||
// fraction of the cell size to a cell limit are moved to the exact limit.
|
||||
const sqlParams = (ctx) => `
|
||||
_cdb_res AS (
|
||||
SELECT
|
||||
${gridResolution(ctx)} AS res,
|
||||
!bbox! AS bbox,
|
||||
(2*2.220446049250313e-16::double precision) AS eps
|
||||
(1E-6::double precision) AS eps
|
||||
),
|
||||
_cdb_params AS (
|
||||
SELECT
|
||||
res,
|
||||
bbox,
|
||||
CEIL((ST_XMIN(bbox) - eps*ABS(ST_XMIN(bbox)))/res)*res AS xmin,
|
||||
FLOOR((ST_XMAX(bbox) + eps*ABS(ST_XMAX(bbox)))/res)*res AS xmax,
|
||||
CEIL((ST_YMIN(bbox) - eps*ABS(ST_YMIN(bbox)))/res)*res AS ymin,
|
||||
FLOOR((ST_YMAX(bbox) + eps*ABS(ST_YMAX(bbox)))/res)*res AS ymax
|
||||
CEIL((ST_XMIN(bbox) - eps*res)/res)*res AS xmin,
|
||||
FLOOR((ST_XMAX(bbox) + eps*res)/res)*res AS xmax,
|
||||
CEIL((ST_YMIN(bbox) - eps*res)/res)*res AS ymin,
|
||||
FLOOR((ST_YMAX(bbox) + eps*res)/res)*res AS ymax
|
||||
FROM _cdb_res
|
||||
)
|
||||
`;
|
||||
@@ -379,7 +382,7 @@ const aggregationQueryTemplates = {
|
||||
)
|
||||
SELECT
|
||||
_cdb_clusters.cartodb_id,
|
||||
the_geom, the_geom_webmercator
|
||||
the_geom_webmercator
|
||||
${dimensionNames(ctx, '_cdb_clusters')}
|
||||
${aggregateColumnNames(ctx, '_cdb_clusters')}
|
||||
FROM
|
||||
|
||||
@@ -21,7 +21,7 @@ function getPGTypeName (pgType) {
|
||||
|
||||
module.exports = class BaseDataview {
|
||||
getResult (psql, override, callback) {
|
||||
this.sql(psql, override, (err, query) => {
|
||||
this.sql(psql, override, (err, query, flags = null) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -34,8 +34,20 @@ module.exports = class BaseDataview {
|
||||
result = this.format(result, override);
|
||||
result.type = this.getType();
|
||||
|
||||
return callback(null, result);
|
||||
//Overviews logging
|
||||
const stats = {};
|
||||
|
||||
if (flags && flags.usesOverviews !== undefined) {
|
||||
stats.usesOverviews = flags.usesOverviews;
|
||||
} else {
|
||||
stats.usesOverviews = false;
|
||||
}
|
||||
|
||||
if (this.getType) {
|
||||
stats.dataviewType = this.getType();
|
||||
}
|
||||
|
||||
return callback(null, result, stats);
|
||||
}, true); // use read-only transaction
|
||||
});
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ Aggregation.prototype.sql = function(psql, override, callback) {
|
||||
|
||||
debug(aggregationSql);
|
||||
|
||||
return callback(null, aggregationSql);
|
||||
return callback(null, aggregationSql, { usesOverviews: true });
|
||||
};
|
||||
|
||||
var aggregationFnQueryTpl = {
|
||||
|
||||
@@ -74,5 +74,5 @@ Formula.prototype.sql = function (psql, override, callback) {
|
||||
|
||||
debug(formulaSql);
|
||||
|
||||
return callback(null, formulaSql);
|
||||
return callback(null, formulaSql, { usesOverviews: true });
|
||||
};
|
||||
|
||||
@@ -178,7 +178,7 @@ Histogram.prototype.sql = function(psql, override, callback) {
|
||||
|
||||
var histogramSql = this._buildQuery(override);
|
||||
|
||||
return callback(null, histogramSql);
|
||||
return callback(null, histogramSql, { usesOverviews: true });
|
||||
};
|
||||
|
||||
Histogram.prototype._buildQuery = function (override) {
|
||||
|
||||
@@ -11,16 +11,21 @@ MapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfig, param
|
||||
var i = 0;
|
||||
var tasksLeft = this.adapters.length;
|
||||
|
||||
function next(err, _requestMapConfig) {
|
||||
let mapConfigStats = {};
|
||||
|
||||
function next(err, _requestMapConfig, adapterStats = {}) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
mapConfigStats = Object.assign(mapConfigStats, adapterStats);
|
||||
|
||||
if (tasksLeft-- === 0) {
|
||||
return callback(null, _requestMapConfig);
|
||||
return callback(null, _requestMapConfig, mapConfigStats);
|
||||
}
|
||||
var nextAdapter = self.adapters[i++];
|
||||
nextAdapter.getMapConfig(user, _requestMapConfig, params, context, next);
|
||||
}
|
||||
|
||||
next(null, requestMapConfig);
|
||||
next(null, requestMapConfig, mapConfigStats);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
var step = require('step');
|
||||
var queue = require('queue-async');
|
||||
var _ = require('underscore');
|
||||
|
||||
@@ -9,93 +8,127 @@ function MapConfigOverviewsAdapter(overviewsMetadataBackend, filterStatsBackend)
|
||||
|
||||
module.exports = MapConfigOverviewsAdapter;
|
||||
|
||||
MapConfigOverviewsAdapter.prototype.getMapConfig = function(user, requestMapConfig, params, context, callback) {
|
||||
var self = this;
|
||||
|
||||
var layers = requestMapConfig.layers;
|
||||
var analysesResults = context.analysesResults;
|
||||
|
||||
if (!layers || layers.length === 0) {
|
||||
return callback(null, requestMapConfig);
|
||||
}
|
||||
|
||||
var augmentLayersQueue = queue(layers.length);
|
||||
|
||||
function augmentLayer(layer, done) {
|
||||
if ( layer.type !== 'mapnik' && layer.type !== 'cartodb' ) {
|
||||
return done(null, layer);
|
||||
}
|
||||
self.overviewsMetadataBackend.getOverviewsMetadata(user, layer.options.sql, function(err, metadata){
|
||||
if (err) {
|
||||
done(err, layer);
|
||||
} else {
|
||||
var query_rewrite_data = { overviews: metadata };
|
||||
step(
|
||||
function collectFiltersData() {
|
||||
var filters, unfiltered_query;
|
||||
if ( layer.options.source && analysesResults && !layer.options.sql_wrap) {
|
||||
var sourceId = layer.options.source.id;
|
||||
var node = _.find(analysesResults, function(a){ return a.rootNode.params.id === sourceId; });
|
||||
if ( node ) {
|
||||
node = node.rootNode;
|
||||
filters = node.getFilters();
|
||||
var filters_disabler = Object.keys(filters).reduce(
|
||||
function(disabler, filter_id){ disabler[filter_id] = false; return disabler; },
|
||||
{}
|
||||
);
|
||||
unfiltered_query = node.getQuery(filters_disabler);
|
||||
query_rewrite_data.filters = filters;
|
||||
query_rewrite_data.unfiltered_query = unfiltered_query;
|
||||
}
|
||||
}
|
||||
this(null, filters, unfiltered_query);
|
||||
},
|
||||
function collectStatsData(err, filters, unfiltered_query) {
|
||||
var next_step = this;
|
||||
if ( filters ) {
|
||||
self.filterStatsBackend.getFilterStats(
|
||||
user,
|
||||
unfiltered_query, filters,
|
||||
function(err, stats) {
|
||||
if ( !err ) {
|
||||
query_rewrite_data.filter_stats = stats;
|
||||
}
|
||||
return next_step(err);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return next_step(null);
|
||||
}
|
||||
},
|
||||
function addDataToLayer(err) {
|
||||
if ( !err && !_.isEmpty(metadata) ) {
|
||||
layer = _.extend({}, layer);
|
||||
layer.options = _.extend({}, layer.options, { query_rewrite_data: query_rewrite_data });
|
||||
}
|
||||
done(null, layer);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function layersAugmentQueueFinish(err, layers) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
MapConfigOverviewsAdapter.prototype.getMapConfig = function (user, requestMapConfig, params, context, callback) {
|
||||
var layers = requestMapConfig.layers;
|
||||
var analysesResults = context.analysesResults;
|
||||
|
||||
if (!layers || layers.length === 0) {
|
||||
return callback(new Error('Missing layers array from layergroup config'));
|
||||
return callback(null, requestMapConfig);
|
||||
}
|
||||
|
||||
requestMapConfig.layers = layers;
|
||||
var augmentLayersQueue = queue(layers.length);
|
||||
|
||||
return callback(null, requestMapConfig);
|
||||
}
|
||||
layers.forEach(layer => augmentLayersQueue.defer(this._augmentLayer.bind(this), user, layer, analysesResults));
|
||||
|
||||
layers.forEach(function(layer) {
|
||||
augmentLayersQueue.defer(augmentLayer, layer);
|
||||
});
|
||||
augmentLayersQueue.awaitAll(layersAugmentQueueFinish);
|
||||
augmentLayersQueue.awaitAll(function layersAugmentQueueFinish (err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
const layers = results.map(result => result.layer);
|
||||
const overviewsAddedToMapconfig = results.some(result => result.overviewsAddedToMapconfig);
|
||||
|
||||
if (!layers || layers.length === 0) {
|
||||
return callback(new Error('Missing layers array from layergroup config'));
|
||||
}
|
||||
|
||||
requestMapConfig.layers = layers;
|
||||
|
||||
const stats = { overviewsAddedToMapconfig };
|
||||
|
||||
return callback(null, requestMapConfig, stats);
|
||||
});
|
||||
};
|
||||
|
||||
MapConfigOverviewsAdapter.prototype._augmentLayer = function (user, layer, analysesResults, callback) {
|
||||
let overviewsAddedToMapconfig = false;
|
||||
if (layer.type !== 'mapnik' && layer.type !== 'cartodb') {
|
||||
return callback(null, { layer, overviewsAddedToMapconfig });
|
||||
}
|
||||
|
||||
this.overviewsMetadataBackend.getOverviewsMetadata(user, layer.options.sql, (err, metadata) => {
|
||||
if (err) {
|
||||
return callback(err, { layer, overviewsAddedToMapconfig });
|
||||
}
|
||||
|
||||
if (_.isEmpty(metadata)) {
|
||||
return callback(null, { layer, overviewsAddedToMapconfig });
|
||||
}
|
||||
|
||||
var filters = getFilters(analysesResults, layer);
|
||||
|
||||
overviewsAddedToMapconfig = true;
|
||||
|
||||
if (!filters) {
|
||||
layer.options = Object.assign({}, layer.options, getQueryRewriteData(layer, analysesResults, {
|
||||
overviews: metadata
|
||||
}));
|
||||
|
||||
return callback(null, { layer, overviewsAddedToMapconfig });
|
||||
}
|
||||
|
||||
var unfilteredQuery = getUnfilteredQuery(analysesResults, layer);
|
||||
|
||||
this.filterStatsBackend.getFilterStats(user, unfilteredQuery, filters, function (err, stats) {
|
||||
if (err) {
|
||||
return callback(null, { layer, overviewsAddedToMapconfig });
|
||||
}
|
||||
|
||||
layer.options = Object.assign({}, layer.options, getQueryRewriteData(layer, analysesResults, {
|
||||
overviews: metadata,
|
||||
filter_stats: stats
|
||||
}));
|
||||
|
||||
return callback(null, { layer, overviewsAddedToMapconfig });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function getRootNode (analysesResults, sourceId) {
|
||||
var node = _.find(analysesResults, function (a) {
|
||||
return a.rootNode.params.id === sourceId;
|
||||
});
|
||||
|
||||
return node ? node.rootNode : undefined;
|
||||
}
|
||||
|
||||
function getFilters (analysesResults, layer) {
|
||||
if (layer.options.source && analysesResults && !layer.options.sql_wrap) {
|
||||
var sourceId = layer.options.source.id;
|
||||
var node = getRootNode(analysesResults, sourceId);
|
||||
|
||||
if (node) {
|
||||
return node.getFilters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getUnfilteredQuery (analysesResults, layer) {
|
||||
if (layer.options.source && analysesResults && !layer.options.sql_wrap) {
|
||||
var sourceId = layer.options.source.id;
|
||||
var node = getRootNode(analysesResults, sourceId);
|
||||
|
||||
if (node) {
|
||||
var filters = node.getFilters();
|
||||
var filters_disabler = Object.keys(filters).reduce(function (disabler, filter_id) {
|
||||
disabler[filter_id] = false;
|
||||
return disabler;
|
||||
}, {});
|
||||
|
||||
return node.getQuery(filters_disabler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getQueryRewriteData (layer, analysesResults, extend = {}) {
|
||||
var queryRewriteData = {};
|
||||
|
||||
if (layer.options.source && analysesResults && !layer.options.sql_wrap) {
|
||||
queryRewriteData.filters = getFilters(analysesResults, layer);
|
||||
queryRewriteData.unfiltered_query = getUnfilteredQuery(analysesResults, layer);
|
||||
}
|
||||
|
||||
queryRewriteData = Object.assign({}, queryRewriteData, extend);
|
||||
|
||||
return { query_rewrite_data: queryRewriteData };
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ module.exports = class NamedMapMapConfigProvider extends BaseMapConfigProvider {
|
||||
const { user, rendererParams } = this;
|
||||
|
||||
this.mapConfigAdapter.getMapConfig(
|
||||
user, requestMapConfig, rendererParams, context, (err, mapConfig) => {
|
||||
user, requestMapConfig, rendererParams, context, (err, mapConfig, stats = {}) => {
|
||||
if (err) {
|
||||
this.err = err;
|
||||
return callback(err);
|
||||
@@ -108,7 +108,7 @@ module.exports = class NamedMapMapConfigProvider extends BaseMapConfigProvider {
|
||||
this.mapConfig = (mapConfig === null) ? null : new MapConfig(mapConfig, context.datasource);
|
||||
this.analysesResults = context.analysesResults || [];
|
||||
|
||||
return callback(null, this.mapConfig, this.rendererParams, this.context);
|
||||
return callback(null, this.mapConfig, this.rendererParams, this.context, stats);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ const DATE_OIDS = Object.freeze({
|
||||
function wrapDates(originalQuery, fields) {
|
||||
return `
|
||||
SELECT
|
||||
${fields.map(field => _isDateType(field) ? _castColumnToEpoch(field.name) : `${field.name}`).join(',')}
|
||||
${fields.map(field => _isDateType(field) ? _castColumnToEpoch(field.name) : `"${field.name}"`).join(',')}
|
||||
FROM
|
||||
(${originalQuery}) _cdb_epoch_transformation `;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "6.3.0",
|
||||
"version": "6.4.0",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
@@ -27,7 +27,7 @@
|
||||
"@carto/fqdn-sync": "0.2.2",
|
||||
"basic-auth": "2.0.0",
|
||||
"body-parser": "1.18.3",
|
||||
"camshaft": "0.62.1",
|
||||
"camshaft": "0.62.3",
|
||||
"cartodb-psql": "0.11.0",
|
||||
"cartodb-query-tables": "0.3.0",
|
||||
"cartodb-redis": "2.0.1",
|
||||
@@ -45,11 +45,10 @@
|
||||
"redis-mpool": "0.5.0",
|
||||
"request": "2.87.0",
|
||||
"semver": "5.5.0",
|
||||
"step": "1.0.0",
|
||||
"step-profiler": "0.3.0",
|
||||
"turbo-carto": "0.20.4",
|
||||
"underscore": "1.6.0",
|
||||
"windshaft": "4.8.3",
|
||||
"windshaft": "4.10.0",
|
||||
"yargs": "11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -59,6 +58,7 @@
|
||||
"moment": "2.22.1",
|
||||
"nock": "9.2.6",
|
||||
"redis": "2.8.0",
|
||||
"step": "1.0.0",
|
||||
"strftime": "0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -171,7 +171,7 @@ describe('aggregation', function () {
|
||||
//
|
||||
const POINTS_SQL_GRID = `
|
||||
WITH params AS (
|
||||
SELECT CDB_XYZ_Resolution(1) AS l -- cell size for Z=1 res=1
|
||||
SELECT CDB_XYZ_Resolution($Z)*$resolution AS l -- cell size for Z, resolution
|
||||
)
|
||||
SELECT
|
||||
row_number() OVER () AS cartodb_id,
|
||||
@@ -1640,7 +1640,7 @@ describe('aggregation', function () {
|
||||
});
|
||||
});
|
||||
|
||||
['centroid', 'point-sample', 'point-grid'].forEach(placement => {
|
||||
['centroid', 'point-sample', 'point-grid', 'default'].forEach(placement => {
|
||||
it(`cartodb_id should be present in ${placement} aggregation`, function(done) {
|
||||
this.mapConfig = createVectorMapConfig([
|
||||
{
|
||||
@@ -1648,8 +1648,8 @@ describe('aggregation', function () {
|
||||
options: {
|
||||
sql: POINTS_SQL_1,
|
||||
aggregation: {
|
||||
placement: placement,
|
||||
threshold: 1
|
||||
threshold: 1,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
},
|
||||
cartocss: '#layer { marker-width: 1; }',
|
||||
cartocss_version: '2.3.0',
|
||||
@@ -1673,71 +1673,72 @@ describe('aggregation', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only require the_geom_webmercator for aggregation', function (done) {
|
||||
this.mapConfig = createVectorMapConfig([
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: POINTS_SQL_ONLY_WEBMERCATOR,
|
||||
aggregation: {
|
||||
threshold: 1
|
||||
it(`should only require the_geom_webmercator for ${placement} aggregation`, function (done) {
|
||||
this.mapConfig = createVectorMapConfig([
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: POINTS_SQL_ONLY_WEBMERCATOR,
|
||||
aggregation: {
|
||||
threshold: 1,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
]);
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
|
||||
this.testClient.getLayergroup((err, body) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
this.testClient.getLayergroup((err, body) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.equal(typeof body.metadata, 'object');
|
||||
assert.ok(Array.isArray(body.metadata.layers));
|
||||
assert.equal(typeof body.metadata, 'object');
|
||||
assert.ok(Array.isArray(body.metadata.layers));
|
||||
|
||||
body.metadata.layers.forEach(layer => assert.ok(layer.meta.aggregation.mvt));
|
||||
body.metadata.layers.forEach(layer => assert.ok(!layer.meta.aggregation.png));
|
||||
body.metadata.layers.forEach(layer => assert.ok(layer.meta.aggregation.mvt));
|
||||
body.metadata.layers.forEach(layer => assert.ok(!layer.meta.aggregation.png));
|
||||
|
||||
done();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('aggregation should work with attributes', function (done) {
|
||||
this.mapConfig = createVectorMapConfig([
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: POINTS_SQL_1,
|
||||
cartocss: '#layer { marker-width: 7; }',
|
||||
cartocss_version: '2.3.0',
|
||||
aggregation: {
|
||||
threshold: 1
|
||||
},
|
||||
attributes: {
|
||||
id: 'cartodb_id',
|
||||
columns: [
|
||||
'value'
|
||||
]
|
||||
it(`${placement} aggregation should work with attributes`, function (done) {
|
||||
this.mapConfig = createVectorMapConfig([
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: POINTS_SQL_1,
|
||||
cartocss: '#layer { marker-width: 7; }',
|
||||
cartocss_version: '2.3.0',
|
||||
aggregation: {
|
||||
threshold: 1
|
||||
},
|
||||
attributes: {
|
||||
id: 'cartodb_id',
|
||||
columns: [
|
||||
'value'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
]);
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
|
||||
this.testClient.getLayergroup((err, body) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
this.testClient.getLayergroup((err, body) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.equal(typeof body.metadata, 'object');
|
||||
assert.ok(Array.isArray(body.metadata.layers));
|
||||
assert.equal(typeof body.metadata, 'object');
|
||||
assert.ok(Array.isArray(body.metadata.layers));
|
||||
|
||||
body.metadata.layers.forEach(layer => assert.ok(layer.meta.aggregation.mvt));
|
||||
body.metadata.layers.forEach(layer => assert.ok(layer.meta.aggregation.png));
|
||||
body.metadata.layers.forEach(layer => assert.ok(layer.meta.aggregation.mvt));
|
||||
body.metadata.layers.forEach(layer => assert.ok(layer.meta.aggregation.png));
|
||||
|
||||
done();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2257,15 +2258,13 @@ describe('aggregation', function () {
|
||||
sql: POINTS_SQL_0,
|
||||
aggregation: {
|
||||
threshold: 1,
|
||||
resolution: 1
|
||||
resolution: 1,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if (placement !== 'default') {
|
||||
this.mapConfig.layers[0].options.aggregation.placement = placement;
|
||||
}
|
||||
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
|
||||
@@ -2312,15 +2311,13 @@ describe('aggregation', function () {
|
||||
sql: POINTS_SQL_1,
|
||||
resolution: 1,
|
||||
aggregation: {
|
||||
threshold: 1
|
||||
threshold: 1,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if (placement !== 'default') {
|
||||
this.mapConfig.layers[0].options.aggregation.placement = placement;
|
||||
}
|
||||
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
|
||||
@@ -2375,6 +2372,9 @@ describe('aggregation', function () {
|
||||
});
|
||||
|
||||
it(`for ${placement} each aggr. cell is in a single tile`, function (done) {
|
||||
const z = 1;
|
||||
const resolution = 1;
|
||||
const query = POINTS_SQL_GRID.replace('$Z', z).replace('$resolution', resolution);
|
||||
this.mapConfig = {
|
||||
version: '1.6.0',
|
||||
buffersize: { 'mvt': 0 },
|
||||
@@ -2383,37 +2383,37 @@ describe('aggregation', function () {
|
||||
type: 'cartodb',
|
||||
|
||||
options: {
|
||||
sql: POINTS_SQL_GRID,
|
||||
sql: query,
|
||||
aggregation: {
|
||||
threshold: 1,
|
||||
resolution: 1
|
||||
resolution: resolution,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if (placement !== 'default') {
|
||||
this.mapConfig.layers[0].options.aggregation.placement = placement;
|
||||
}
|
||||
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
|
||||
this.testClient.getTile(1, 0, 0, { format: 'mvt' }, (err, res, mvt) => {
|
||||
const c = Math.pow(2, z - 1) - 1; // center tile coordinates
|
||||
|
||||
this.testClient.getTile(z, c + 0, c + 0, { format: 'mvt' }, (err, res, mvt) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const tile00 = JSON.parse(mvt.toGeoJSONSync(0));
|
||||
this.testClient.getTile(1, 0, 1, { format: 'mvt' }, (err, res, mvt) => {
|
||||
this.testClient.getTile(z, c + 0, c + 1, { format: 'mvt' }, (err, res, mvt) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const tile01 = JSON.parse(mvt.toGeoJSONSync(0));
|
||||
this.testClient.getTile(1, 1, 0, { format: 'mvt' }, (err, res, mvt) => {
|
||||
this.testClient.getTile(z, c + 1, c + 0, { format: 'mvt' }, (err, res, mvt) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const tile10 = JSON.parse(mvt.toGeoJSONSync(0));
|
||||
this.testClient.getTile(1, 1, 1, { format: 'mvt' }, (err, res, mvt) => {
|
||||
this.testClient.getTile(z, c + 1, c + 1, { format: 'mvt' }, (err, res, mvt) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
@@ -2457,6 +2457,11 @@ describe('aggregation', function () {
|
||||
it(`for ${placement} no partially aggregated cells`, function (done) {
|
||||
// Use level 1 with resolution 2 tiles and buffersize 1 (half the cell size)
|
||||
// Only the cells completely inside the buffer are aggregated
|
||||
const z = 1;
|
||||
const resolution = 2;
|
||||
// space the test points by half the resolution:
|
||||
const query = POINTS_SQL_GRID.replace('$Z', z).replace('$resolution', resolution/2);
|
||||
|
||||
this.mapConfig = {
|
||||
version: '1.6.0',
|
||||
buffersize: { 'mvt': 1 },
|
||||
@@ -2465,37 +2470,37 @@ describe('aggregation', function () {
|
||||
type: 'cartodb',
|
||||
|
||||
options: {
|
||||
sql: POINTS_SQL_GRID,
|
||||
sql: query,
|
||||
aggregation: {
|
||||
threshold: 1,
|
||||
resolution: 2
|
||||
resolution: resolution,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if (placement !== 'default') {
|
||||
this.mapConfig.layers[0].options.aggregation.placement = placement;
|
||||
}
|
||||
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
|
||||
this.testClient.getTile(1, 0, 0, { format: 'mvt' }, (err, res, mvt) => {
|
||||
const c = Math.pow(2, z - 1) - 1; // center tile coordinates
|
||||
|
||||
this.testClient.getTile(z, c, c, { format: 'mvt' }, (err, res, mvt) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const tile00 = JSON.parse(mvt.toGeoJSONSync(0));
|
||||
this.testClient.getTile(1, 0, 1, { format: 'mvt' }, (err, res, mvt) => {
|
||||
this.testClient.getTile(z, c, c + 1, { format: 'mvt' }, (err, res, mvt) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const tile01 = JSON.parse(mvt.toGeoJSONSync(0));
|
||||
this.testClient.getTile(1, 1, 0, { format: 'mvt' }, (err, res, mvt) => {
|
||||
this.testClient.getTile(z, c + 1, c, { format: 'mvt' }, (err, res, mvt) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const tile10 = JSON.parse(mvt.toGeoJSONSync(0));
|
||||
this.testClient.getTile(1, 1, 1, { format: 'mvt' }, (err, res, mvt) => {
|
||||
this.testClient.getTile(z, c + 1, c + 1, { format: 'mvt' }, (err, res, mvt) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
@@ -2531,6 +2536,117 @@ describe('aggregation', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it(`for ${placement} includes complete cells in buffer`, function (done) {
|
||||
if (!usePostGIS && placement !== 'point-grid') {
|
||||
// Mapnik seem to filter query results by its (inaccurate) bbox,
|
||||
// which makes some aggregated clusters get lost here.
|
||||
// The point-grid placement is resilient to this problem because the result
|
||||
// coordinates are moved to cluster cell centers, so they're well within
|
||||
// bbox limits.
|
||||
this.testClient = new TestClient({});
|
||||
return done();
|
||||
}
|
||||
|
||||
// use buffersize coincident with resolution, the buffer should include neighbour cells
|
||||
const z = 2;
|
||||
const resolution = 1;
|
||||
const query = POINTS_SQL_GRID.replace('$Z', z).replace('$resolution', resolution);
|
||||
|
||||
this.mapConfig = {
|
||||
version: '1.6.0',
|
||||
buffersize: { 'mvt': 1 },
|
||||
layers: [
|
||||
{
|
||||
type: 'cartodb',
|
||||
|
||||
options: {
|
||||
sql: query,
|
||||
aggregation: {
|
||||
threshold: 1,
|
||||
resolution: resolution,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
|
||||
const c = Math.pow(2, z - 1) - 1; // center tile coordinates
|
||||
|
||||
this.testClient.getTile(z, c, c, { format: 'mvt' }, (err, res, mvt) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const tile00 = JSON.parse(mvt.toGeoJSONSync(0));
|
||||
this.testClient.getTile(z, c, c + 1, { format: 'mvt' }, (err, res, mvt) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const tile01 = JSON.parse(mvt.toGeoJSONSync(0));
|
||||
this.testClient.getTile(z, c + 1, c, { format: 'mvt' }, (err, res, mvt) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const tile10 = JSON.parse(mvt.toGeoJSONSync(0));
|
||||
this.testClient.getTile(z, c + 1, c + 1, { format: 'mvt' }, (err, res, mvt) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const tile11 = JSON.parse(mvt.toGeoJSONSync(0));
|
||||
|
||||
const tile00Expected = [
|
||||
{ _cdb_feature_count: 2, cartodb_id: 1 },
|
||||
{ _cdb_feature_count: 2, cartodb_id: 2 },
|
||||
{ _cdb_feature_count: 2, cartodb_id: 4 },
|
||||
{ _cdb_feature_count: 2, cartodb_id: 5 },
|
||||
{ _cdb_feature_count: 1, cartodb_id: 7 },
|
||||
{ _cdb_feature_count: 1, cartodb_id: 8 }
|
||||
];
|
||||
const tile10Expected = [
|
||||
{ _cdb_feature_count: 2, cartodb_id: 1 },
|
||||
{ _cdb_feature_count: 2, cartodb_id: 2 },
|
||||
{ _cdb_feature_count: 1, cartodb_id: 3 },
|
||||
{ _cdb_feature_count: 2, cartodb_id: 4 },
|
||||
{ _cdb_feature_count: 2, cartodb_id: 5 },
|
||||
{ _cdb_feature_count: 1, cartodb_id: 6 },
|
||||
{ _cdb_feature_count: 1, cartodb_id: 7 },
|
||||
{ _cdb_feature_count: 1, cartodb_id: 8 },
|
||||
{ _cdb_feature_count: 1, cartodb_id: 9 }
|
||||
];
|
||||
const tile01Expected = [
|
||||
{ _cdb_feature_count: 2, cartodb_id: 1 },
|
||||
{ _cdb_feature_count: 2, cartodb_id: 2 },
|
||||
{ _cdb_feature_count: 2, cartodb_id: 4 },
|
||||
{ _cdb_feature_count: 2, cartodb_id: 5 }
|
||||
];
|
||||
const tile11Expected = [
|
||||
{ _cdb_feature_count: 2, cartodb_id: 1 },
|
||||
{ _cdb_feature_count: 2, cartodb_id: 2 },
|
||||
{ _cdb_feature_count: 1, cartodb_id: 3 },
|
||||
{ _cdb_feature_count: 2, cartodb_id: 4 },
|
||||
{ _cdb_feature_count: 2, cartodb_id: 5 },
|
||||
{ _cdb_feature_count: 1, cartodb_id: 6 }
|
||||
];
|
||||
const tile00Actual = tile00.features.map(f => f.properties);
|
||||
const tile10Actual = tile10.features.map(f => f.properties);
|
||||
const tile01Actual = tile01.features.map(f => f.properties);
|
||||
const tile11Actual = tile11.features.map(f => f.properties);
|
||||
const orderById = (a, b) => a.cartodb_id - b.cartodb_id;
|
||||
assert.deepEqual(tile00Actual.sort(orderById), tile00Expected);
|
||||
assert.deepEqual(tile10Actual.sort(orderById), tile10Expected);
|
||||
assert.deepEqual(tile01Actual.sort(orderById), tile01Expected);
|
||||
assert.deepEqual(tile11Actual.sort(orderById), tile11Expected);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it(`for ${placement} points aggregated into corner cluster`, function (done) {
|
||||
// this test will fail due to !bbox! lack of accuracy if strict cell filtering is in place
|
||||
this.mapConfig = {
|
||||
@@ -2544,15 +2660,13 @@ describe('aggregation', function () {
|
||||
sql: POINTS_SQL_CELL,
|
||||
aggregation: {
|
||||
threshold: 1,
|
||||
resolution: 1
|
||||
resolution: 1,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if (placement !== 'default') {
|
||||
this.mapConfig.layers[0].options.aggregation.placement = placement;
|
||||
}
|
||||
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
|
||||
@@ -2584,15 +2698,13 @@ describe('aggregation', function () {
|
||||
sql: POINTS_SQL_CELL_INNER,
|
||||
aggregation: {
|
||||
threshold: 1,
|
||||
resolution: 1
|
||||
resolution: 1,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if (placement !== 'default') {
|
||||
this.mapConfig.layers[0].options.aggregation.placement = placement;
|
||||
}
|
||||
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
|
||||
@@ -2627,15 +2739,13 @@ describe('aggregation', function () {
|
||||
sql: POINTS_SQL_PAIRS,
|
||||
aggregation: {
|
||||
threshold: 1,
|
||||
resolution: 1
|
||||
resolution: 1,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if (placement !== 'default') {
|
||||
this.mapConfig.layers[0].options.aggregation.placement = placement;
|
||||
}
|
||||
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
this.testClient.getLayergroup((err, body) => {
|
||||
@@ -2665,7 +2775,8 @@ describe('aggregation', function () {
|
||||
sql: POINTS_SQL_PAIRS,
|
||||
aggregation: {
|
||||
threshold: 1,
|
||||
resolution: 1
|
||||
resolution: 1,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
},
|
||||
metadata: {
|
||||
aggrFeatureCount: 10
|
||||
@@ -2674,9 +2785,6 @@ describe('aggregation', function () {
|
||||
}
|
||||
]
|
||||
};
|
||||
if (placement !== 'default') {
|
||||
this.mapConfig.layers[0].options.aggregation.placement = placement;
|
||||
}
|
||||
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
this.testClient.getLayergroup((err, body) => {
|
||||
@@ -2706,7 +2814,8 @@ describe('aggregation', function () {
|
||||
sql: POINTS_SQL_PAIRS,
|
||||
aggregation: {
|
||||
threshold: 1,
|
||||
resolution: 1
|
||||
resolution: 1,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
},
|
||||
metadata: {
|
||||
aggrFeatureCount: 0
|
||||
@@ -2715,9 +2824,6 @@ describe('aggregation', function () {
|
||||
}
|
||||
]
|
||||
};
|
||||
if (placement !== 'default') {
|
||||
this.mapConfig.layers[0].options.aggregation.placement = placement;
|
||||
}
|
||||
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
this.testClient.getLayergroup((err, body) => {
|
||||
@@ -2747,7 +2853,8 @@ describe('aggregation', function () {
|
||||
sql: POINTS_SQL_PAIRS,
|
||||
aggregation: {
|
||||
threshold: 1,
|
||||
resolution: 1
|
||||
resolution: 1,
|
||||
placement: placement !== 'default' ? placement : undefined
|
||||
},
|
||||
metadata: {
|
||||
featureCount: true
|
||||
@@ -2756,9 +2863,6 @@ describe('aggregation', function () {
|
||||
}
|
||||
]
|
||||
};
|
||||
if (placement !== 'default') {
|
||||
this.mapConfig.layers[0].options.aggregation.placement = placement;
|
||||
}
|
||||
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
this.testClient.getLayergroup((err, body) => {
|
||||
|
||||
@@ -48,11 +48,12 @@ describe('dataviews using tables without overviews', function() {
|
||||
|
||||
it("should expose a formula", function(done) {
|
||||
var testClient = new TestClient(nonOverviewsMapConfig);
|
||||
testClient.getDataview('country_places_count', { own_filter: 0 }, function(err, formula_result) {
|
||||
testClient.getDataview('country_places_count', { own_filter: 0 }, function(err, formula_result, headers) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert.deepEqual(formula_result, { operation: 'count', result: 7313, nulls: 0, type: 'formula' });
|
||||
assert(getUsesOverviewsFromHeaders(headers) === false); //Overviews logging
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
@@ -257,7 +258,7 @@ describe('dataviews using tables with overviews', function() {
|
||||
|
||||
it("should expose a sum formula", function(done) {
|
||||
var testClient = new TestClient(overviewsMapConfig);
|
||||
testClient.getDataview('test_sum', { own_filter: 0 }, function(err, formula_result) {
|
||||
testClient.getDataview('test_sum', { own_filter: 0 }, function(err, formula_result, headers) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
@@ -269,6 +270,8 @@ describe('dataviews using tables with overviews', function() {
|
||||
"nulls":0,
|
||||
"type":"formula"
|
||||
});
|
||||
assert.ok(getUsesOverviewsFromHeaders(headers)); //Overviews logging
|
||||
assert(getDataviewTypeFromHeaders(headers) === 'formula'); //Overviews logging
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
@@ -276,7 +279,7 @@ describe('dataviews using tables with overviews', function() {
|
||||
|
||||
it("should expose an avg formula", function(done) {
|
||||
var testClient = new TestClient(overviewsMapConfig);
|
||||
testClient.getDataview('test_avg', { own_filter: 0 }, function(err, formula_result) {
|
||||
testClient.getDataview('test_avg', { own_filter: 0 }, function(err, formula_result, headers) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
@@ -288,6 +291,8 @@ describe('dataviews using tables with overviews', function() {
|
||||
"infinities": 0,
|
||||
"nans": 0
|
||||
});
|
||||
assert.ok(getUsesOverviewsFromHeaders(headers)); //Overviews logging
|
||||
assert(getDataviewTypeFromHeaders(headers) === 'formula'); //Overviews logging
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
@@ -295,7 +300,7 @@ describe('dataviews using tables with overviews', function() {
|
||||
|
||||
it("should expose a count formula", function(done) {
|
||||
var testClient = new TestClient(overviewsMapConfig);
|
||||
testClient.getDataview('test_count', { own_filter: 0 }, function(err, formula_result) {
|
||||
testClient.getDataview('test_count', { own_filter: 0 }, function(err, formula_result, headers) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
@@ -307,6 +312,8 @@ describe('dataviews using tables with overviews', function() {
|
||||
"infinities": 0,
|
||||
"nans": 0
|
||||
});
|
||||
assert.ok(getUsesOverviewsFromHeaders(headers)); //Overviews logging
|
||||
assert(getDataviewTypeFromHeaders(headers) === 'formula'); //Overviews logging
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
@@ -374,13 +381,16 @@ describe('dataviews using tables with overviews', function() {
|
||||
|
||||
it("should expose a histogram", function (done) {
|
||||
var testClient = new TestClient(overviewsMapConfig);
|
||||
testClient.getDataview('test_histogram', function (err, histogram) {
|
||||
testClient.getDataview('test_histogram', function (err, histogram, headers) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert.ok(histogram);
|
||||
assert.equal(histogram.type, 'histogram');
|
||||
assert.ok(Array.isArray(histogram.bins));
|
||||
assert.ok(getUsesOverviewsFromHeaders(headers)); //Overviews logging
|
||||
assert(getDataviewTypeFromHeaders(headers) === 'histogram'); //Overviews logging
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
@@ -462,7 +472,7 @@ describe('dataviews using tables with overviews', function() {
|
||||
|
||||
it("should expose a filtered sum formula", function (done) {
|
||||
var testClient = new TestClient(overviewsMapConfig);
|
||||
testClient.getDataview('test_sum', params, function (err, formula_result) {
|
||||
testClient.getDataview('test_sum', params, function (err, formula_result, headers) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
@@ -474,13 +484,14 @@ describe('dataviews using tables with overviews', function() {
|
||||
"nans": 0,
|
||||
"type":"formula"
|
||||
});
|
||||
assert.ok(getUsesOverviewsFromHeaders(headers)); //Overviews logging
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it("should expose a filtered avg formula", function(done) {
|
||||
var testClient = new TestClient(overviewsMapConfig);
|
||||
testClient.getDataview('test_avg', params, function(err, formula_result) {
|
||||
testClient.getDataview('test_avg', params, function(err, formula_result, headers) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
@@ -492,6 +503,7 @@ describe('dataviews using tables with overviews', function() {
|
||||
"nans": 0,
|
||||
"type":"formula"
|
||||
});
|
||||
assert.ok(getUsesOverviewsFromHeaders(headers)); //Overviews logging
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
@@ -499,7 +511,7 @@ describe('dataviews using tables with overviews', function() {
|
||||
|
||||
it("should expose a filtered count formula", function(done) {
|
||||
var testClient = new TestClient(overviewsMapConfig);
|
||||
testClient.getDataview('test_count', params, function(err, formula_result) {
|
||||
testClient.getDataview('test_count', params, function(err, formula_result, headers) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
@@ -511,6 +523,7 @@ describe('dataviews using tables with overviews', function() {
|
||||
"nulls":0,
|
||||
"type":"formula"
|
||||
});
|
||||
assert.ok(getUsesOverviewsFromHeaders(headers)); //Overviews logging
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
@@ -586,10 +599,11 @@ describe('dataviews using tables with overviews', function() {
|
||||
|
||||
it("should expose an aggregation dataview filtering special float values out", function (done) {
|
||||
var testClient = new TestClient(overviewsMapConfig);
|
||||
testClient.getDataview('test_categories_special_values', params, function (err, dataview) {
|
||||
testClient.getDataview('test_categories_special_values', params, function (err, dataview, headers) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.deepEqual(dataview, {
|
||||
aggregation: 'sum',
|
||||
count: 5,
|
||||
@@ -602,6 +616,10 @@ describe('dataviews using tables with overviews', function() {
|
||||
categories: [ { category: 'Hawai', value: 6, agg: false } ],
|
||||
type: 'aggregation'
|
||||
});
|
||||
|
||||
assert.ok(getUsesOverviewsFromHeaders(headers)); //Overviews logging
|
||||
assert(getDataviewTypeFromHeaders(headers) === 'aggregation'); //Overviews logging
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
@@ -647,3 +665,11 @@ describe('dataviews using tables with overviews', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getUsesOverviewsFromHeaders(headers) {
|
||||
return headers && headers['x-tiler-profiler'] && JSON.parse(headers['x-tiler-profiler']).usesOverviews;
|
||||
}
|
||||
|
||||
function getDataviewTypeFromHeaders(headers) {
|
||||
return headers && headers['x-tiler-profiler'] && JSON.parse(headers['x-tiler-profiler']).dataviewType;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('health checks', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('not err if disabled file does not exists', function(done) {
|
||||
it('not err if disabled file does not exist', function(done) {
|
||||
global.environment.disabled_file = '/tmp/ftreftrgtrccre';
|
||||
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
@@ -61,6 +61,7 @@ return function () {
|
||||
};
|
||||
|
||||
const testClient = new TestClient(template, apikeyToken);
|
||||
testClient.keysToDelete['map_tpl|localhost'] = 0;
|
||||
|
||||
testClient.getNamedTile(templateName, 0, 0, 0, 'mvt', {}, (err, res, tile) => {
|
||||
if (err) {
|
||||
@@ -194,356 +195,5 @@ return function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
if (usePostGIS){
|
||||
describe('use only needed columns', onlyNeededColumns);
|
||||
}else{
|
||||
describe.skip('use only needed columns', onlyNeededColumns);
|
||||
}
|
||||
|
||||
function onlyNeededColumns() {
|
||||
function getFeatureByCartodbId(features, cartodbId) {
|
||||
for (var i = 0, len = features.length; i < len; i++) {
|
||||
if (features[i].properties.cartodb_id === cartodbId) {
|
||||
return features[i];
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
var options = { format: 'mvt', layer: 0 };
|
||||
|
||||
afterEach(function (done) {
|
||||
if (this.testClient) {
|
||||
this.testClient.drain(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('with aggregation widget, interactivity and cartocss columns', function (done) {
|
||||
var widgetMapConfig = {
|
||||
version: '1.5.0',
|
||||
layers: [{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: 'select * from populated_places_simple_reduced',
|
||||
cartocss:
|
||||
'#layer0 { marker-fill: red; marker-width: 10; [name="Madrid"] { marker-fill: green; } }',
|
||||
cartocss_version: '2.0.1',
|
||||
widgets: {
|
||||
adm0name: {
|
||||
type: 'aggregation',
|
||||
options: {
|
||||
column: 'adm0name',
|
||||
aggregation: 'sum',
|
||||
aggregationColumn: 'pop_max'
|
||||
}
|
||||
}
|
||||
},
|
||||
interactivity: "cartodb_id,pop_min"
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
this.testClient = new TestClient(widgetMapConfig);
|
||||
this.testClient.getTile(0, 0, 0, options, function (err, res, MVT) {
|
||||
var geojsonTile = JSON.parse(MVT.toGeoJSONSync(0));
|
||||
assert.ok(!err, err);
|
||||
assert.deepEqual(getFeatureByCartodbId(geojsonTile.features, 1109).properties, {
|
||||
cartodb_id: 1109,
|
||||
name: 'Mardin',
|
||||
adm0name: 'Turkey',
|
||||
pop_max: 71373,
|
||||
pop_min: 57586
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not duplicate columns', function (done) {
|
||||
var widgetMapConfig = {
|
||||
version: '1.5.0',
|
||||
layers: [{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: 'select * from populated_places_simple_reduced',
|
||||
cartocss: ['#layer0 {',
|
||||
'marker-fill: red;',
|
||||
'marker-width: 10;',
|
||||
'[name="Madrid"] { marker-fill: green; } ',
|
||||
'[pop_max>100000] { marker-fill: black; } ',
|
||||
'}'].join('\n'),
|
||||
cartocss_version: '2.3.0',
|
||||
widgets: {
|
||||
adm0name: {
|
||||
type: 'aggregation',
|
||||
options: {
|
||||
column: 'adm0name',
|
||||
aggregation: 'sum',
|
||||
aggregationColumn: 'pop_max'
|
||||
}
|
||||
}
|
||||
},
|
||||
interactivity: "cartodb_id,pop_max"
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
this.testClient = new TestClient(widgetMapConfig);
|
||||
this.testClient.getTile(0, 0, 0, options, function (err, res, MVT) {
|
||||
var geojsonTile = JSON.parse(MVT.toGeoJSONSync(0));
|
||||
assert.ok(!err, err);
|
||||
assert.deepEqual(getFeatureByCartodbId(geojsonTile.features, 1109).properties, {
|
||||
cartodb_id: 1109,
|
||||
name: 'Mardin',
|
||||
adm0name: 'Turkey',
|
||||
pop_max: 71373
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('with formula widget, no interactivity and no cartocss columns', function (done) {
|
||||
var formulaWidgetMapConfig = {
|
||||
version: '1.5.0',
|
||||
layers: [{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: 'select * from populated_places_simple_reduced where pop_max > 0 and pop_max < 600000',
|
||||
cartocss: '#layer0 { marker-fill: red; marker-width: 10; }',
|
||||
cartocss_version: '2.0.1',
|
||||
interactivity: 'cartodb_id',
|
||||
widgets: {
|
||||
pop_max_f: {
|
||||
type: 'formula',
|
||||
options: {
|
||||
column: 'pop_max',
|
||||
operation: 'count'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
this.testClient = new TestClient(formulaWidgetMapConfig);
|
||||
this.testClient.getTile(0, 0, 0, options, function (err, res, MVT) {
|
||||
var geojsonTile = JSON.parse(MVT.toGeoJSONSync(0));
|
||||
assert.ok(!err, err);
|
||||
assert.deepEqual(getFeatureByCartodbId(geojsonTile.features, 1109).properties, {
|
||||
cartodb_id: 1109,
|
||||
pop_max: 71373
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('with cartocss with multiple expressions', function (done) {
|
||||
var formulaWidgetMapConfig = {
|
||||
version: '1.5.0',
|
||||
layers: [{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: 'select * from populated_places_simple_reduced where pop_max > 0 and pop_max < 600000',
|
||||
cartocss: '#layer0 { marker-fill: red; marker-width: 10; }' +
|
||||
'#layer0 { marker-width: 14; [name="Madrid"] { marker-width: 20; } }' +
|
||||
'#layer0[pop_max>1000] { marker-width: 14; [name="Madrid"] { marker-width: 20; } }' +
|
||||
'#layer0[adm0name=~".*Turkey*"] { marker-width: 14; [name="Madrid"] { marker-width: 20; } }',
|
||||
cartocss_version: '2.0.1',
|
||||
interactivity: 'cartodb_id'
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
this.testClient = new TestClient(formulaWidgetMapConfig);
|
||||
this.testClient.getTile(0, 0, 0, options, function (err, res, MVT) {
|
||||
var geojsonTile = JSON.parse(MVT.toGeoJSONSync(0));
|
||||
assert.ok(!err, err);
|
||||
assert.deepEqual(getFeatureByCartodbId(geojsonTile.features, 1109).properties, {
|
||||
cartodb_id: 1109,
|
||||
pop_max: 71373,
|
||||
name: "Mardin",
|
||||
adm0name: "Turkey"
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var skipOnPostGIS = usePostGIS ? it.skip: it;
|
||||
skipOnPostGIS('should work with mapnik substitution tokens', function (done) {
|
||||
var cartocss = [
|
||||
"#layer {",
|
||||
" line-width: 2;",
|
||||
" line-color: #3B3B58;",
|
||||
" line-opacity: 1;",
|
||||
" polygon-opacity: 0.7;",
|
||||
" polygon-fill: ramp([points_count], (#E5F5F9,#99D8C9,#2CA25F))",
|
||||
"}"
|
||||
].join('\n');
|
||||
|
||||
var sql = [
|
||||
'WITH hgrid AS (',
|
||||
' SELECT CDB_HexagonGrid(',
|
||||
' ST_Expand(!bbox!, greatest(!pixel_width!,!pixel_height!) * 100),',
|
||||
' greatest(!pixel_width!,!pixel_height!) * 100',
|
||||
' ) as cell',
|
||||
')',
|
||||
'SELECT',
|
||||
' hgrid.cell as the_geom_webmercator,',
|
||||
' count(1) as points_count,',
|
||||
' count(1)/power(100 * CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!)), 2) as points_density,',
|
||||
' 1 as cartodb_id',
|
||||
'FROM hgrid, (SELECT * FROM populated_places_simple_reduced) i',
|
||||
'where ST_Intersects(i.the_geom_webmercator, hgrid.cell)',
|
||||
'GROUP BY hgrid.cell'
|
||||
].join('\n');
|
||||
|
||||
var mapConfig = {
|
||||
"version": "1.4.0",
|
||||
"layers": [
|
||||
{
|
||||
"type": 'mapnik',
|
||||
"options": {
|
||||
"cartocss_version": '2.3.0',
|
||||
"sql": sql,
|
||||
"cartocss": cartocss
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.testClient = new TestClient(mapConfig);
|
||||
this.testClient.getTile(0, 0, 0, options, function (err, res, MVT) {
|
||||
var geojsonTile = JSON.parse(MVT.toGeoJSONSync(0));
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(geojsonTile);
|
||||
assert.equal(geojsonTile.features.length, 5);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip empty and null columns for geojson tiles', function (done) {
|
||||
|
||||
var mapConfig = {
|
||||
"analyses": [
|
||||
{
|
||||
"id": "a0",
|
||||
"params": {
|
||||
"query": "SELECT * FROM test_table"
|
||||
},
|
||||
"type": "source"
|
||||
}
|
||||
],
|
||||
"dataviews": {
|
||||
"4e7b0e07-6d21-4b83-9adb-6d7e17eea6ca": {
|
||||
"options": {
|
||||
"aggregationColumn": null,
|
||||
"column": "cartodb_id",
|
||||
"operation": "avg"
|
||||
},
|
||||
"source": {
|
||||
"id": "a0"
|
||||
},
|
||||
"type": "formula"
|
||||
},
|
||||
"74f590f8-625c-4e95-922f-34ad3e9919c0": {
|
||||
"options": {
|
||||
"aggregation": "sum",
|
||||
"aggregationColumn": "cartodb_id",
|
||||
"column": "name"
|
||||
},
|
||||
"source": {
|
||||
"id": "a0"
|
||||
},
|
||||
"type": "aggregation"
|
||||
},
|
||||
"98a75757-3006-400a-b028-fb613a6c0b69": {
|
||||
"options": {
|
||||
"aggregationColumn": null,
|
||||
"column": "cartodb_id",
|
||||
"operation": "sum"
|
||||
},
|
||||
"source": {
|
||||
"id": "a0"
|
||||
},
|
||||
"type": "formula"
|
||||
},
|
||||
"ebbc97b2-87d2-4895-9e1f-2f012df3679d": {
|
||||
"options": {
|
||||
"aggregationColumn": null,
|
||||
"bins": "12",
|
||||
"column": "cartodb_id"
|
||||
},
|
||||
"source": {
|
||||
"id": "a0"
|
||||
},
|
||||
"type": "histogram"
|
||||
},
|
||||
"ebc0653f-3581-469c-8b31-c969e440a865": {
|
||||
"options": {
|
||||
"aggregationColumn": null,
|
||||
"column": "cartodb_id",
|
||||
"operation": "avg"
|
||||
},
|
||||
"source": {
|
||||
"id": "a0"
|
||||
},
|
||||
"type": "formula"
|
||||
}
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"options": {
|
||||
"subdomains": "abcd",
|
||||
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png"
|
||||
},
|
||||
"type": "http"
|
||||
},
|
||||
{
|
||||
"options": {
|
||||
"attributes": {
|
||||
"columns": [
|
||||
"name",
|
||||
"address"
|
||||
],
|
||||
"id": "cartodb_id"
|
||||
},
|
||||
"cartocss": "#layer { marker-width: 10; marker-fill: red; }",
|
||||
"cartocss_version": "2.3.0",
|
||||
"interactivity": "cartodb_id",
|
||||
"layer_name": "wadus",
|
||||
"source": {
|
||||
"id": "a0"
|
||||
}
|
||||
},
|
||||
"type": "cartodb"
|
||||
},
|
||||
{
|
||||
"options": {
|
||||
"subdomains": "abcd",
|
||||
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png"
|
||||
},
|
||||
"type": "http"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.testClient = new TestClient(mapConfig);
|
||||
this.testClient.getTile(0, 0, 0, options, function (err, res, MVT) {
|
||||
var geojsonTile = JSON.parse(MVT.toGeoJSONSync(0));
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(geojsonTile);
|
||||
assert.equal(geojsonTile.features.length, 5);
|
||||
|
||||
assert.deepEqual(Object.keys(geojsonTile.features[0].properties), ['cartodb_id', 'name']);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ describe('overviews metadata', function() {
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid);
|
||||
expected_token = parsedBody.layergroupid;
|
||||
@@ -113,6 +114,83 @@ describe('overviews metadata', function() {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Overviews Flags', function () {
|
||||
it("Overviews used", function (done) {
|
||||
var layergroup = {
|
||||
version: '1.0.0',
|
||||
layers: [overviews_layer, non_overviews_layer]
|
||||
};
|
||||
|
||||
var layergroup_url = '/api/v1/map';
|
||||
|
||||
var expected_token;
|
||||
step(
|
||||
function do_post() {
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: { host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function (res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
|
||||
const headers = JSON.parse(res.headers['x-tiler-profiler']);
|
||||
|
||||
assert.ok(headers.overviewsAddedToMapconfig);
|
||||
assert.equal(headers.mapType, 'anonymous');
|
||||
|
||||
const parsedBody = JSON.parse(res.body);
|
||||
expected_token = parsedBody.layergroupid;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(expected_token).token] = 0;
|
||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
it("Overviews NOT used", function (done) {
|
||||
var layergroup = {
|
||||
version: '1.0.0',
|
||||
layers: [non_overviews_layer]
|
||||
};
|
||||
|
||||
var layergroup_url = '/api/v1/map';
|
||||
|
||||
var expected_token;
|
||||
step(
|
||||
function do_post() {
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: { host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function (res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
|
||||
const headers = JSON.parse(res.headers['x-tiler-profiler']);
|
||||
|
||||
assert.equal(headers.overviewsAddedToMapconfig, false);
|
||||
assert.equal(headers.mapType, 'anonymous');
|
||||
|
||||
const parsedBody = JSON.parse(res.body);
|
||||
expected_token = parsedBody.layergroupid;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(expected_token).token] = 0;
|
||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('overviews metadata with filters', function() {
|
||||
|
||||
@@ -176,4 +176,129 @@ describe('overviews metadata for named maps', function() {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Overviews Flags', function() {
|
||||
it("Overviews used", 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 instantiateTemplate(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'
|
||||
}
|
||||
}, {},
|
||||
function (res, err) {
|
||||
return next(err, res);
|
||||
});
|
||||
|
||||
},
|
||||
function checkFlags(err, res) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
|
||||
const headers = JSON.parse(res.headers['x-tiler-profiler']);
|
||||
|
||||
assert.ok(headers.overviewsAddedToMapconfig);
|
||||
assert.equal(headers.mapType, 'named');
|
||||
|
||||
next();
|
||||
},
|
||||
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("Overviews NOT used", function (done) {
|
||||
|
||||
const nonOverviewsTemplateId = 'non-overviews-template';
|
||||
|
||||
var nonOverviewsTemplate = {
|
||||
version: '0.0.1',
|
||||
name: nonOverviewsTemplateId,
|
||||
auth: { method: 'open' },
|
||||
layergroup: {
|
||||
version: '1.0.0',
|
||||
layers: [non_overviews_layer]
|
||||
}
|
||||
};
|
||||
|
||||
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(nonOverviewsTemplate)
|
||||
}, {}, function (res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function instantiateTemplate(err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/named/' + nonOverviewsTemplateId,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}, {},
|
||||
function (res, err) {
|
||||
return next(err, res);
|
||||
});
|
||||
|
||||
},
|
||||
function checkFlags(err, res) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
|
||||
const headers = JSON.parse(res.headers['x-tiler-profiler']);
|
||||
|
||||
assert.equal(headers.overviewsAddedToMapconfig, false);
|
||||
assert.equal(headers.mapType, 'named');
|
||||
|
||||
next();
|
||||
},
|
||||
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -509,16 +509,15 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
next(null, JSON.parse(res.body));
|
||||
next(null, JSON.parse(res.body), res.headers);
|
||||
}
|
||||
);
|
||||
},
|
||||
function finish(err, dataview) {
|
||||
function finish(err, dataview, headers = null) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null, dataview);
|
||||
return callback(null, dataview, headers);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
18
test/unit/cartodb/utils/date-wrapper.test.js
Normal file
18
test/unit/cartodb/utils/date-wrapper.test.js
Normal file
@@ -0,0 +1,18 @@
|
||||
var assert = require('assert');
|
||||
var dateWrapper = require('../../../../lib/cartodb/utils/date-wrapper');
|
||||
|
||||
describe('date-wrapper', function() {
|
||||
|
||||
it('should wrap property fields with spaces', function() {
|
||||
const actual = dateWrapper.wrapDates(
|
||||
'select * from table',
|
||||
[{name: 'a'}, {name: 'b c'}]
|
||||
);
|
||||
const expected = `
|
||||
SELECT
|
||||
"a","b c"
|
||||
FROM
|
||||
(select * from table) _cdb_epoch_transformation `;
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
});
|
||||
102
yarn.lock
102
yarn.lock
@@ -6,20 +6,19 @@
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@carto/fqdn-sync/-/fqdn-sync-0.2.2.tgz#cd7c645ed66690a09249b554d254a0f53963b2dc"
|
||||
|
||||
"@carto/mapnik@3.6.2-carto.10":
|
||||
version "3.6.2-carto.10"
|
||||
resolved "https://registry.yarnpkg.com/@carto/mapnik/-/mapnik-3.6.2-carto.10.tgz#a97c951dcdac09d0eb35b3ea71e5eeaa206c1af6"
|
||||
"@carto/mapnik@3.6.2-carto.11":
|
||||
version "3.6.2-carto.11"
|
||||
resolved "https://registry.yarnpkg.com/@carto/mapnik/-/mapnik-3.6.2-carto.11.tgz#f2f0bc4d0051080169267c5c729b90c6bc934661"
|
||||
dependencies:
|
||||
mapnik-vector-tile cartodb/mapnik-vector-tile#v1.6.1-carto.1
|
||||
mapnik-vector-tile cartodb/mapnik-vector-tile#v1.6.1-carto.2
|
||||
nan "2.10.0"
|
||||
node-pre-gyp "0.10.0"
|
||||
protozero "1.5.1"
|
||||
|
||||
"@carto/tilelive-bridge@cartodb/tilelive-bridge#2.5.1-cdb9":
|
||||
version "2.5.1-cdb9"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/5129e43223cb55daed31373c7a36c98eb6178fc1"
|
||||
"@carto/tilelive-bridge@github:cartodb/tilelive-bridge#2.5.1-cdb10":
|
||||
version "2.5.1-cdb10"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/118ac7e7f6582ac7be0fc0246ea2afd1a7795a43"
|
||||
dependencies:
|
||||
"@carto/mapnik" "3.6.2-carto.10"
|
||||
"@carto/mapnik" "3.6.2-carto.11"
|
||||
"@mapbox/sphericalmercator" "~1.0.1"
|
||||
mapnik-pool "~0.1.3"
|
||||
|
||||
@@ -27,13 +26,13 @@
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@mapbox/sphericalmercator/-/sphericalmercator-1.0.5.tgz#70237b9774095ed1cfdbcea7a8fd1fc82b2691f2"
|
||||
|
||||
abaculus@cartodb/abaculus#2.0.3-cdb10:
|
||||
version "2.0.3-cdb10"
|
||||
resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/90d537028bb8af8a35e7a40c46493066dd8a76b3"
|
||||
"abaculus@github:cartodb/abaculus#2.0.3-cdb11":
|
||||
version "2.0.3-cdb11"
|
||||
resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/b2d4dce48c50fefc5b06f21c6365d82ccf75358d"
|
||||
dependencies:
|
||||
"@carto/mapnik" "3.6.2-carto.10"
|
||||
"@carto/mapnik" "3.6.2-carto.11"
|
||||
d3-queue "^2.0.2"
|
||||
sphericalmercator "1.0.x"
|
||||
sphericalmercator "1.0.5"
|
||||
|
||||
abbrev@1, abbrev@1.0.x:
|
||||
version "1.0.9"
|
||||
@@ -265,9 +264,9 @@ camelcase@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
|
||||
|
||||
camshaft@0.62.1:
|
||||
version "0.62.1"
|
||||
resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.62.1.tgz#75f8734c4089aaeae3b9067eb94d3c669cebbcaf"
|
||||
camshaft@0.62.3:
|
||||
version "0.62.3"
|
||||
resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.62.3.tgz#f76c4dff45d00f7750675d0b573ae12975c9138f"
|
||||
dependencies:
|
||||
async "^1.5.2"
|
||||
bunyan "1.8.1"
|
||||
@@ -276,7 +275,7 @@ camshaft@0.62.1:
|
||||
dot "^1.0.3"
|
||||
request "2.85.0"
|
||||
|
||||
canvas@cartodb/node-canvas#1.6.2-cdb2:
|
||||
"canvas@github:cartodb/node-canvas#1.6.2-cdb2":
|
||||
version "1.6.2-cdb2"
|
||||
resolved "https://codeload.github.com/cartodb/node-canvas/tar.gz/8acf04557005c633f9e68524488a2657c04f3766"
|
||||
dependencies:
|
||||
@@ -294,17 +293,17 @@ carto@0.16.3:
|
||||
semver "^5.1.0"
|
||||
yargs "^4.2.0"
|
||||
|
||||
carto@cartodb/carto#0.15.1-cdb3:
|
||||
version "0.15.1-cdb3"
|
||||
resolved "https://codeload.github.com/cartodb/carto/tar.gz/945f5efb74fd1af1f5e1f69f409f9567f94fb5a7"
|
||||
carto@cartodb/carto#master:
|
||||
version "0.15.1"
|
||||
resolved "https://codeload.github.com/cartodb/carto/tar.gz/31abb8bee02df605521247b0223d508320f7d4c8"
|
||||
dependencies:
|
||||
mapnik-reference "~6.0.2"
|
||||
optimist "~0.6.0"
|
||||
underscore "1.8.3"
|
||||
|
||||
carto@cartodb/carto#master:
|
||||
version "0.15.1"
|
||||
resolved "https://codeload.github.com/cartodb/carto/tar.gz/31abb8bee02df605521247b0223d508320f7d4c8"
|
||||
"carto@github:cartodb/carto#0.15.1-cdb3":
|
||||
version "0.15.1-cdb3"
|
||||
resolved "https://codeload.github.com/cartodb/carto/tar.gz/945f5efb74fd1af1f5e1f69f409f9567f94fb5a7"
|
||||
dependencies:
|
||||
mapnik-reference "~6.0.2"
|
||||
optimist "~0.6.0"
|
||||
@@ -879,6 +878,10 @@ generic-pool@2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff"
|
||||
|
||||
generic-pool@2.5.4:
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.5.4.tgz#38c6188513e14030948ec6e5cf65523d9779299b"
|
||||
|
||||
generic-pool@~2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.1.1.tgz#af04dc2c325cfcb975023fa52bfce9617a7435fd"
|
||||
@@ -887,7 +890,7 @@ generic-pool@~2.2.0, generic-pool@~2.2.1:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.2.2.tgz#7a89f491d575b42f9f069a0e8e2c6dbaa3c241be"
|
||||
|
||||
generic-pool@~2.4.0, generic-pool@~2.4.1:
|
||||
generic-pool@~2.4.1:
|
||||
version "2.4.6"
|
||||
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.6.tgz#f1b55e572167dba2fe75d5aa91ebb1e9f72642d7"
|
||||
|
||||
@@ -1429,9 +1432,9 @@ mapnik-reference@~8.5.3:
|
||||
dependencies:
|
||||
semver "^5.1.0"
|
||||
|
||||
mapnik-vector-tile@cartodb/mapnik-vector-tile#v1.6.1-carto.1:
|
||||
version "1.6.1-carto.1"
|
||||
resolved "https://codeload.github.com/cartodb/mapnik-vector-tile/tar.gz/0111f7117946179d62ec7a6eba2f4e9fb355d05e"
|
||||
mapnik-vector-tile@cartodb/mapnik-vector-tile#v1.6.1-carto.2:
|
||||
version "1.6.1-carto.2"
|
||||
resolved "https://codeload.github.com/cartodb/mapnik-vector-tile/tar.gz/e7ca5471f9e5de81243e6035e70444321fc0a82f"
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
@@ -1480,14 +1483,14 @@ mime@1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
|
||||
|
||||
mime@2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369"
|
||||
|
||||
mime@~1.2.11:
|
||||
version "1.2.11"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10"
|
||||
|
||||
mime@~1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
|
||||
mimic-fn@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
|
||||
@@ -1990,10 +1993,6 @@ propagate@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/propagate/-/propagate-1.0.0.tgz#00c2daeedda20e87e3782b344adba1cddd6ad709"
|
||||
|
||||
protozero@1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/protozero/-/protozero-1.5.1.tgz#5a27df6fb6e1ed743f510812ae76c082f5b16638"
|
||||
|
||||
proxy-addr@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
|
||||
@@ -2398,7 +2397,7 @@ speedometer@~0.1.2:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d"
|
||||
|
||||
sphericalmercator@1.0.5, sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4:
|
||||
sphericalmercator@1.0.5, sphericalmercator@~1.0.1:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.5.tgz#ddc5a049e360e000d0fad9fc22c4071882584980"
|
||||
|
||||
@@ -2581,15 +2580,13 @@ through@2:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
||||
tilelive-mapnik@cartodb/tilelive-mapnik#0.6.18-cdb14:
|
||||
version "0.6.18-cdb14"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/6d06f728833d3e34d1adcd05567b3f4379f547bb"
|
||||
"tilelive-mapnik@github:cartodb/tilelive-mapnik#0.6.18-cdb15":
|
||||
version "0.6.18-cdb15"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/b6c1404a9e37fc60e545d977081867a86eb42b2b"
|
||||
dependencies:
|
||||
"@carto/mapnik" "3.6.2-carto.10"
|
||||
generic-pool "~2.4.0"
|
||||
mime "~1.6.0"
|
||||
sphericalmercator "~1.0.4"
|
||||
step "~0.0.5"
|
||||
"@carto/mapnik" "3.6.2-carto.11"
|
||||
generic-pool "2.5.4"
|
||||
mime "2.3.1"
|
||||
|
||||
tilelive@5.12.3:
|
||||
version "5.12.3"
|
||||
@@ -2762,13 +2759,13 @@ window-size@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"
|
||||
|
||||
windshaft@4.8.3:
|
||||
version "4.8.3"
|
||||
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.8.3.tgz#9cc0ae9cea6b4e716858f4c15c138c1e9c158b9e"
|
||||
windshaft@4.10.0:
|
||||
version "4.10.0"
|
||||
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.10.0.tgz#ed9e1117b7d9ab035c93dbb4f11c1b7046a43edf"
|
||||
dependencies:
|
||||
"@carto/mapnik" "3.6.2-carto.10"
|
||||
"@carto/tilelive-bridge" cartodb/tilelive-bridge#2.5.1-cdb9
|
||||
abaculus cartodb/abaculus#2.0.3-cdb10
|
||||
"@carto/mapnik" "3.6.2-carto.11"
|
||||
"@carto/tilelive-bridge" cartodb/tilelive-bridge#2.5.1-cdb10
|
||||
abaculus cartodb/abaculus#2.0.3-cdb11
|
||||
canvas cartodb/node-canvas#1.6.2-cdb2
|
||||
carto cartodb/carto#0.15.1-cdb3
|
||||
cartodb-psql "0.11.0"
|
||||
@@ -2780,9 +2777,8 @@ windshaft@4.8.3:
|
||||
request "2.87.0"
|
||||
semver "5.5.0"
|
||||
sphericalmercator "1.0.5"
|
||||
step "1.0.0"
|
||||
tilelive "5.12.3"
|
||||
tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb14
|
||||
tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb15
|
||||
torque.js "2.16.2"
|
||||
underscore "1.6.0"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user