Compare commits
181 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
778860c81f | ||
|
|
ee3c56efba | ||
|
|
5dc328724a | ||
|
|
c77ea49594 | ||
|
|
73aa159b98 | ||
|
|
b7d7cffb67 | ||
|
|
eba8db292c | ||
|
|
d5391ef15b | ||
|
|
685cbb1eec | ||
|
|
5842a239fd | ||
|
|
285363aa40 | ||
|
|
92e130d8de | ||
|
|
f674f90eba | ||
|
|
0542b65cbb | ||
|
|
60fa94781e | ||
|
|
38d57533c2 | ||
|
|
e3d6da06a7 | ||
|
|
3af05bb734 | ||
|
|
ffd58cc7ca | ||
|
|
e103c52d27 | ||
|
|
00a71af95d | ||
|
|
2fb8036740 | ||
|
|
6902a8c9b3 | ||
|
|
c19a345f6c | ||
|
|
bdc3ecff5a | ||
|
|
6c4ec29e18 | ||
|
|
412712e62e | ||
|
|
0edcb30f75 | ||
|
|
c0f49b3acb | ||
|
|
b926244085 | ||
|
|
d21136b475 | ||
|
|
8788aaaf25 | ||
|
|
120e800089 | ||
|
|
92dc4b148c | ||
|
|
755dfe6822 | ||
|
|
b787ee1033 | ||
|
|
9ee3612da0 | ||
|
|
fe0b0a9c50 | ||
|
|
dea19f20dd | ||
|
|
f9f70cc6e7 | ||
|
|
23ef1157cb | ||
|
|
8ca4d537ce | ||
|
|
98d5731555 | ||
|
|
5da8929a5d | ||
|
|
e198bafac1 | ||
|
|
dd731399dc | ||
|
|
66fd899ffb | ||
|
|
bca8a33417 | ||
|
|
a11c8d882e | ||
|
|
c5bed48d61 | ||
|
|
98b7d12796 | ||
|
|
93dd8a2213 | ||
|
|
4e4a223f24 | ||
|
|
4a73f3874d | ||
|
|
bc845b2e8d | ||
|
|
83eceb349c | ||
|
|
bb518f0744 | ||
|
|
08ad961123 | ||
|
|
146d494cae | ||
|
|
bb40ecf7c2 | ||
|
|
7de9f64f2a | ||
|
|
0bb6178d49 | ||
|
|
084b3e94a6 | ||
|
|
a0445b5cdd | ||
|
|
1d4ddd373b | ||
|
|
92795963ae | ||
|
|
e634ad6f7d | ||
|
|
ecbae52abe | ||
|
|
e1c5af8602 | ||
|
|
6704a94f36 | ||
|
|
a35403cd91 | ||
|
|
41ead9664b | ||
|
|
e04a9a2579 | ||
|
|
d70af7c9c1 | ||
|
|
d910f5ef3e | ||
|
|
57cba3d511 | ||
|
|
7902b276ad | ||
|
|
f932862ce4 | ||
|
|
ab55b083b4 | ||
|
|
263b3e3682 | ||
|
|
5baad96924 | ||
|
|
68b19c65fe | ||
|
|
3a81302699 | ||
|
|
6b942ecf0b | ||
|
|
1ed0c73525 | ||
|
|
7175b2b05e | ||
|
|
61137610c7 | ||
|
|
b53031972e | ||
|
|
da602eeda0 | ||
|
|
a26025b259 | ||
|
|
25a61c8479 | ||
|
|
e73b64bfed | ||
|
|
b5b8085444 | ||
|
|
388f08a277 | ||
|
|
aa36236ed2 | ||
|
|
a9ca453b17 | ||
|
|
9ab4eb5801 | ||
|
|
26149e7755 | ||
|
|
2d4fd62acf | ||
|
|
e037c8c1b2 | ||
|
|
09687b3811 | ||
|
|
28400f4544 | ||
|
|
7a87b8ebef | ||
|
|
9ff661480b | ||
|
|
ca564bfaad | ||
|
|
dd36877a20 | ||
|
|
1d860fd202 | ||
|
|
e9111ec3bb | ||
|
|
0981ccd0c4 | ||
|
|
077c4ab907 | ||
|
|
efacafaa0d | ||
|
|
1c250bf243 | ||
|
|
c974b91c6b | ||
|
|
ab6bae6a7f | ||
|
|
4d39d30f6e | ||
|
|
499178319d | ||
|
|
7b7e9ffe59 | ||
|
|
2bd3e46a4d | ||
|
|
e44b5eaccd | ||
|
|
26512f6485 | ||
|
|
90b92f0180 | ||
|
|
ebe25761d2 | ||
|
|
f928147559 | ||
|
|
d5c5c7bdbb | ||
|
|
ff147ca3bf | ||
|
|
f745e915d3 | ||
|
|
1e239658d8 | ||
|
|
52f35d74b9 | ||
|
|
57e6e49749 | ||
|
|
b3bbb9d97a | ||
|
|
697749b204 | ||
|
|
a769545e39 | ||
|
|
df40302fd0 | ||
|
|
5bd30b6b5f | ||
|
|
ed84ed8475 | ||
|
|
a444b80c96 | ||
|
|
43c56af0fc | ||
|
|
6db48a24b8 | ||
|
|
1da937d639 | ||
|
|
4924bcc298 | ||
|
|
a05f3d6ee9 | ||
|
|
eec44dd62d | ||
|
|
3f41f19ab9 | ||
|
|
1535820d4f | ||
|
|
b2378939c5 | ||
|
|
7fa8d1e0c9 | ||
|
|
d5fbcfcecb | ||
|
|
9d77aea6be | ||
|
|
634a4c2a01 | ||
|
|
f504807812 | ||
|
|
c7bdabfc65 | ||
|
|
10a602a4f3 | ||
|
|
e3a5c52ebf | ||
|
|
f9c0e29db0 | ||
|
|
e53d823b5a | ||
|
|
31dede5d06 | ||
|
|
69142964c6 | ||
|
|
2eac808e18 | ||
|
|
011b60eeab | ||
|
|
16654c016a | ||
|
|
9b9e6b13b7 | ||
|
|
3b11525cfb | ||
|
|
6823fd8b03 | ||
|
|
ce032fcc96 | ||
|
|
a44477dddc | ||
|
|
3709d1f1d5 | ||
|
|
e958f925d3 | ||
|
|
d923b343fc | ||
|
|
b9d2e297b6 | ||
|
|
30f8234bd0 | ||
|
|
ae8e3f2ef8 | ||
|
|
6d91172630 | ||
|
|
8d4ebc171b | ||
|
|
4d3c21f1bc | ||
|
|
dfcb3b6dc1 | ||
|
|
87b4a37f3f | ||
|
|
4db2c715a0 | ||
|
|
2f51ad9c3f | ||
|
|
ed1f753690 | ||
|
|
bcf3ce71ef | ||
|
|
354c982ea0 |
19
.jshintrc
@@ -82,13 +82,14 @@
|
||||
// "wsh" : false, // Windows Scripting Host
|
||||
// "yui" : false, // Yahoo User Interface
|
||||
|
||||
// Custom Globals
|
||||
"globals" : { // additional predefined global variables
|
||||
"describe": true,
|
||||
"before": true,
|
||||
"after": true,
|
||||
"beforeEach": true,
|
||||
"afterEach": true,
|
||||
"it": true
|
||||
}
|
||||
// Custom predefined global variables
|
||||
"predef": [
|
||||
"-console", // disallows console, use debug
|
||||
"beforeEach",
|
||||
"afterEach",
|
||||
"before",
|
||||
"after",
|
||||
"describe",
|
||||
"it"
|
||||
]
|
||||
}
|
||||
|
||||
99
NEWS.md
@@ -1,5 +1,104 @@
|
||||
# Changelog
|
||||
|
||||
## 2.39.0
|
||||
|
||||
Released 2016-05-05
|
||||
|
||||
Announcements:
|
||||
- Upgrades step-profiler to 0.3.0 to avoid dots in json keys #438
|
||||
- Use a more aggressive cache control header for node status endpoint
|
||||
|
||||
|
||||
## 2.38.1
|
||||
|
||||
Released 2016-05-05
|
||||
|
||||
Announcements:
|
||||
- Fixes problem in turbo-carto dependency
|
||||
- Removes console usages
|
||||
|
||||
|
||||
## 2.38.0
|
||||
|
||||
Released 2016-05-05
|
||||
|
||||
Announcements:
|
||||
- Upgrades turbo-carto to [0.7.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.7.0)
|
||||
|
||||
|
||||
## 2.37.0
|
||||
|
||||
Released 2016-05-03
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.6.0](https://github.com/CartoDB/camshaft/releases/tag/0.6.0)
|
||||
|
||||
|
||||
## 2.36.1
|
||||
|
||||
Released 2016-04-29
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.5.1](https://github.com/CartoDB/camshaft/releases/tag/0.5.1)
|
||||
|
||||
|
||||
## 2.36.0
|
||||
|
||||
Released 2016-04-28
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [1.19.0](https://github.com/CartoDB/Windshaft/releases/tag/1.19.0)
|
||||
|
||||
|
||||
## 2.35.0
|
||||
|
||||
Released 2016-04-27
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [1.18.0](https://github.com/CartoDB/Windshaft/releases/tag/1.18.0)
|
||||
- Appends columns to layers from associated dataviews
|
||||
|
||||
|
||||
## 2.34.1
|
||||
|
||||
Released 2016-04-27
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [1.17.3](https://github.com/CartoDB/Windshaft/releases/tag/1.17.3)
|
||||
|
||||
|
||||
## 2.34.0
|
||||
|
||||
Released 2016-04-27
|
||||
|
||||
Enhancements:
|
||||
- Adds support to return multiple errors in BaseController.sendError #423
|
||||
- Starts using turbo-carto dependency
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [1.17.2](https://github.com/CartoDB/Windshaft/releases/tag/1.17.2)
|
||||
|
||||
|
||||
## 2.33.1
|
||||
|
||||
Released 2016-04-20
|
||||
|
||||
Bug fixes:
|
||||
- Support unneeded schema names in overviews queries #421
|
||||
|
||||
|
||||
## 2.33.0
|
||||
|
||||
Released 2016-04-20
|
||||
|
||||
New features:
|
||||
- Adds experimental support for analysis and dataviews
|
||||
|
||||
Announcements:
|
||||
- Upgrades cartodb-psql to 0.6.1 version.
|
||||
- Upgrades windshaft to [1.17.1](https://github.com/CartoDB/Windshaft/releases/tag/1.17.1)
|
||||
|
||||
|
||||
## 2.32.0
|
||||
|
||||
Released 2016-04-06
|
||||
|
||||
17
app.js
@@ -14,6 +14,11 @@ if ( process.argv[2] ) {
|
||||
ENVIRONMENT = 'development';
|
||||
}
|
||||
|
||||
// jshint undef:false
|
||||
var log = console.log.bind(console);
|
||||
var logError = console.error.bind(console);
|
||||
// jshint undef:true
|
||||
|
||||
var availableEnvironments = {
|
||||
production: true,
|
||||
staging: true,
|
||||
@@ -22,8 +27,8 @@ var availableEnvironments = {
|
||||
|
||||
// sanity check
|
||||
if (!availableEnvironments[ENVIRONMENT]){
|
||||
console.error('node app.js [environment]');
|
||||
console.error('environments: %s', Object.keys(availableEnvironments).join(', '));
|
||||
logError('node app.js [environment]');
|
||||
logError('environments: %s', Object.keys(availableEnvironments).join(', '));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -58,10 +63,10 @@ if ( global.environment.log_filename ) {
|
||||
// See cwd inlog4js.configure call below
|
||||
logdir = path.resolve(__dirname, logdir);
|
||||
if ( ! fs.existsSync(logdir) ) {
|
||||
console.error("Log filename directory does not exist: " + logdir);
|
||||
logError("Log filename directory does not exist: " + logdir);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("Logs will be written to " + global.environment.log_filename);
|
||||
log("Logs will be written to " + global.environment.log_filename);
|
||||
log4js_config.appenders.push(
|
||||
{ type: "file", filename: global.environment.log_filename }
|
||||
);
|
||||
@@ -94,7 +99,7 @@ var listener = server.listen(serverOptions.bind.port, serverOptions.bind.host, b
|
||||
var version = require("./package").version;
|
||||
|
||||
listener.on('listening', function() {
|
||||
console.log(
|
||||
log(
|
||||
"Windshaft tileserver %s started on %s:%s PID=%d (%s)",
|
||||
version, serverOptions.bind.host, serverOptions.bind.port, process.pid, ENVIRONMENT
|
||||
);
|
||||
@@ -111,7 +116,7 @@ process.on('SIGHUP', function() {
|
||||
global.log4js.clearAndShutdownAppenders(function() {
|
||||
global.log4js.configure(log4js_config);
|
||||
global.logger = global.log4js.getLogger();
|
||||
console.log('Log files reloaded');
|
||||
log('Log files reloaded');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -191,6 +191,22 @@ var config = {
|
||||
}
|
||||
}
|
||||
}
|
||||
// anything analyses related
|
||||
,analysis: {
|
||||
// batch configuration
|
||||
batch: {
|
||||
// Inline execution avoid the use of SQL API as batch endpoint
|
||||
// When set to true it will run all analysis queries in series, with a direct connection to the DB
|
||||
// This might be useful for:
|
||||
// - testing
|
||||
// - running an standalone server without any dependency on external services
|
||||
inlineExecution: false,
|
||||
// where the SQL API is running, it will use a custom Host header to specify the username.
|
||||
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
|
||||
// the template to use for adding the host header in the batch api requests
|
||||
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
// Needs to be writable by server user
|
||||
cache_basedir: '/tmp/cdb-tiler-dev/millstone-dev'
|
||||
|
||||
@@ -185,6 +185,22 @@ var config = {
|
||||
}
|
||||
}
|
||||
}
|
||||
// anything analyses related
|
||||
,analysis: {
|
||||
// batch configuration
|
||||
batch: {
|
||||
// Inline execution avoid the use of SQL API as batch endpoint
|
||||
// When set to true it will run all analysis queries in series, with a direct connection to the DB
|
||||
// This might be useful for:
|
||||
// - testing
|
||||
// - running an standalone server without any dependency on external services
|
||||
inlineExecution: false,
|
||||
// where the SQL API is running, it will use a custom Host header to specify the username.
|
||||
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
|
||||
// the template to use for adding the host header in the batch api requests
|
||||
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
// Needs to be writable by server user
|
||||
cache_basedir: '/home/ubuntu/tile_assets/'
|
||||
|
||||
@@ -185,6 +185,22 @@ var config = {
|
||||
}
|
||||
}
|
||||
}
|
||||
// anything analyses related
|
||||
,analysis: {
|
||||
// batch configuration
|
||||
batch: {
|
||||
// Inline execution avoid the use of SQL API as batch endpoint
|
||||
// When set to true it will run all analysis queries in series, with a direct connection to the DB
|
||||
// This might be useful for:
|
||||
// - testing
|
||||
// - running an standalone server without any dependency on external services
|
||||
inlineExecution: false,
|
||||
// where the SQL API is running, it will use a custom Host header to specify the username.
|
||||
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
|
||||
// the template to use for adding the host header in the batch api requests
|
||||
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
// Needs to be writable by server user
|
||||
cache_basedir: '/home/ubuntu/tile_assets/'
|
||||
|
||||
@@ -186,6 +186,22 @@ var config = {
|
||||
}
|
||||
}
|
||||
}
|
||||
// anything analyses related
|
||||
,analysis: {
|
||||
// batch configuration
|
||||
batch: {
|
||||
// Inline execution avoid the use of SQL API as batch endpoint
|
||||
// When set to true it will run all analysis queries in series, with a direct connection to the DB
|
||||
// This might be useful for:
|
||||
// - testing
|
||||
// - running an standalone server without any dependency on external services
|
||||
inlineExecution: true,
|
||||
// where the SQL API is running, it will use a custom Host header to specify the username.
|
||||
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
|
||||
// the template to use for adding the host header in the batch api requests
|
||||
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
// Needs to be writable by server user
|
||||
cache_basedir: '/tmp/cdb-tiler-test/millstone'
|
||||
|
||||
120
docs/Routes.md
@@ -11,9 +11,6 @@ This document list all routes available in Windshaft-cartodb Maps API server.
|
||||
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/:z/:x/:y.(:format) {:user(f),:token(f),:layer(f),:z(f),:x(f),:y(f),:format(f)} (1)`
|
||||
<br/>Notes: Per :layer rendering based on :format [0]
|
||||
|
||||
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
|
||||
<br/>Notes: Map instantiation [0]
|
||||
|
||||
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/attributes/:fid {:user(f),:token(f),:layer(f),:fid(f)} (1)`
|
||||
<br/>Notes: Endpoint for info windows data, alternative for sql api when tables are private [0]
|
||||
|
||||
@@ -23,47 +20,60 @@ This document list all routes available in Windshaft-cartodb Maps API server.
|
||||
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format {:user(f),:token(f),:west(f),:south(f),:east(f),:north(f),:width(f),:height(f),:format(f)} (1)`
|
||||
<br/>Notes: Static Maps API [0]
|
||||
|
||||
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/widget/:widgetName {:user(f),:token(f),:layer(f),:widgetName(f)} (1)`
|
||||
<br/>Notes: By :widgetName per :layer widget [0]
|
||||
|
||||
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/widget/:widgetName/search {:user(f),:token(f),:layer(f),:widgetName(f)} (1)`
|
||||
<br/>Notes: By :widgetName per :layer widget search [0]
|
||||
|
||||
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
|
||||
<br/>Notes: Map instantiation [0]
|
||||
|
||||
1. `POST (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
|
||||
<br/>Notes: Map instantiation [0]
|
||||
|
||||
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/jsonp {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Named maps JSONP instantiation [1]
|
||||
|
||||
1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Instantiate named map [1]
|
||||
|
||||
1. `OPTIONS (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
|
||||
<br/>Notes: CORS [0]
|
||||
|
||||
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/:layer/:z/:x/:y.(:format) {:user(f),:template_id(f),:layer(f),:z(f),:x(f),:y(f),:0(f),:format(f)} (1)`
|
||||
<br/>Notes: Per :layer fixed URL named map tiles [1]
|
||||
|
||||
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/named/:template_id/:width/:height.:format {:user(f),:template_id(f),:width(f),:height(f),:format(f)} (1)`
|
||||
<br/>Notes: Static map for named maps [1]
|
||||
|
||||
1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)`
|
||||
<br/>Notes: Create named map (w/ API KEY) [1]
|
||||
|
||||
1. `PUT (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Update a named map (w/ API KEY) [1]
|
||||
|
||||
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Named map retrieval (w/ API KEY) [1]
|
||||
|
||||
1. `DELETE (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Delete named map (w/ API KEY) [1]
|
||||
|
||||
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)`
|
||||
<br/>Notes: List named maps (w/ API KEY) [1]
|
||||
|
||||
1. `OPTIONS (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: CORS [1]
|
||||
|
||||
1. `GET /health {} (1)`
|
||||
<br/>Notes: Health check
|
||||
|
||||
1. `GET / {} (1)`
|
||||
<br/>Notes: Welcome message
|
||||
|
||||
1. `GET /version {} (1)`
|
||||
<br/>Notes: Return relevant module versions: mapnik, grainstore, etc
|
||||
|
||||
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/jsonp {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Named maps JSONP instantiation [1]
|
||||
|
||||
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Named map retrieval (w/ API KEY) [1]
|
||||
|
||||
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)`
|
||||
<br/>Notes: List named maps (w/ API KEY) [1]
|
||||
|
||||
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/named/:template_id/:width/:height.:format {:user(f),:template_id(f),:width(f),:height(f),:format(f)} (1)`
|
||||
<br/>Notes: Static map for named maps
|
||||
|
||||
1. `GET /health {} (1)`
|
||||
<br/>Notes: Healt check
|
||||
|
||||
1. `OPTIONS (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
|
||||
<br/>Notes: CORS [0]
|
||||
|
||||
1. `OPTIONS (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: CORS [1]
|
||||
|
||||
1. `POST (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
|
||||
<br/>Notes: Map instantiation [0]
|
||||
|
||||
1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)`
|
||||
<br/>Notes: Create named map (w/ API KEY) [1]
|
||||
|
||||
1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Instantiate named map [1]
|
||||
|
||||
1. `PUT (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Update a named map (w/ API KEY) [1]
|
||||
|
||||
1. `DELETE (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Delete named map (w/ API KEY) [1]
|
||||
|
||||
## Optional deprecated routes
|
||||
|
||||
@@ -75,29 +85,29 @@ This document list all routes available in Windshaft-cartodb Maps API server.
|
||||
Something like the following patch should do the trick
|
||||
|
||||
```javascript
|
||||
diff --git a/lib/cartodb/cartodb_windshaft.js b/lib/cartodb/cartodb_windshaft.js
|
||||
index b9429a2..e6cc5f9 100644
|
||||
--- a/lib/cartodb/cartodb_windshaft.js
|
||||
+++ b/lib/cartodb/cartodb_windshaft.js
|
||||
@@ -212,6 +212,20 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
}
|
||||
});
|
||||
diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js
|
||||
index 5f62850..bca377d 100644
|
||||
--- a/lib/cartodb/server.js
|
||||
+++ b/lib/cartodb/server.js
|
||||
@@ -215,6 +215,20 @@ module.exports = function(serverOptions) {
|
||||
* END Routing
|
||||
******************************************************************************************************************/
|
||||
|
||||
+ var format = require('util').format;
|
||||
+ var routesNotes = Object.keys(ws.routes.routes)
|
||||
+ .map(function(method) { return ws.routes.routes[method]; })
|
||||
+ .reduce(function(previous, current) { current.map(function(r) { previous.push(r) }); return previous;}, [])
|
||||
+ .map(function(route) {
|
||||
+ return format("\n1. `%s %s {%s} (%d)`\n<br/>Notes: [DEPRECATED]? ",
|
||||
+ route.method.toUpperCase(),
|
||||
+ route.path,
|
||||
+ route.keys.map(function(k) { return format(':%s(%s)', k.name, k.optional ? 't' : 'f'); } ).join(','),
|
||||
+ route.callbacks.length
|
||||
+ var routesNotes = app._router.stack
|
||||
+ .filter(function(handler) { return !!handler.route; })
|
||||
+ .map(function(handler) {
|
||||
+ return format("\n1. `%s %s {%s} (1)`\n<br/>Notes: [DEPRECATED]? ",
|
||||
+ Object.keys(handler.route.methods)[0].toUpperCase(),
|
||||
+ handler.route.path,
|
||||
+ handler.keys.map(function(k) {
|
||||
+ return format(':%s(%s)', k.name, k.optional ? 't' : 'f');
|
||||
+ }).join(',')
|
||||
+ );
|
||||
+ });
|
||||
+ console.log(routesNotes.join('\n'));
|
||||
+
|
||||
return ws;
|
||||
return app;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -49,8 +49,6 @@ bbox | the bounding box in WGS 84 (EPSG:4326), comma separated values for:
|
||||
width | the width in pixels for the output image
|
||||
height | the height in pixels for the output image
|
||||
format | the format for the image, supported types: `png`, `jpg`
|
||||
|
||||
format | the bounding box in WGS 84 (EPSG:4326), comma separated values for:
|
||||
--- | ---
|
||||
|_ jpg | will have a default quality of 85.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
function OverviewsMetadataApi(pgQueryRunner) {
|
||||
this.pgQueryRunner = pgQueryRunner;
|
||||
this.pgQueryRunner = pgQueryRunner;
|
||||
}
|
||||
|
||||
module.exports = OverviewsMetadataApi;
|
||||
@@ -22,22 +22,25 @@ function prepareSql(sql) {
|
||||
}
|
||||
|
||||
OverviewsMetadataApi.prototype.getOverviewsMetadata = function (username, sql, callback) {
|
||||
var query = 'SELECT * FROM CDB_Overviews(CDB_QueryTablesText($windshaft$' + prepareSql(sql) + '$windshaft$))';
|
||||
// FIXME: Currently using internal function _cdb_schema_name
|
||||
// CDB_Overviews should provide the schema information directly.
|
||||
var query = 'SELECT *, _cdb_schema_name(base_table)' +
|
||||
' FROM CDB_Overviews(CDB_QueryTablesText($windshaft$' + prepareSql(sql) + '$windshaft$))';
|
||||
this.pgQueryRunner.run(username, query, function handleOverviewsRows(err, rows) {
|
||||
if (err){
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
var metadata = {};
|
||||
rows.forEach(function(row) {
|
||||
var metadata = rows.reduce(function(metadata, row){
|
||||
var table = row.base_table;
|
||||
var table_metadata = metadata[table];
|
||||
if ( !table_metadata ) {
|
||||
table_metadata = metadata[table] = {};
|
||||
var schema = row._cdb_schema_name;
|
||||
if ( !metadata[table] ) {
|
||||
metadata[table] = {};
|
||||
}
|
||||
table_metadata[row.z] = { table: row.overview_table };
|
||||
});
|
||||
metadata[table][row.z] = { table: row.overview_table };
|
||||
metadata[table].schema = schema;
|
||||
return metadata;
|
||||
}, {});
|
||||
return callback(null, metadata);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
49
lib/cartodb/backends/analysis-status.js
Normal file
@@ -0,0 +1,49 @@
|
||||
var PSQL = require('cartodb-psql');
|
||||
|
||||
function AnalysisStatusBackend() {
|
||||
}
|
||||
|
||||
module.exports = AnalysisStatusBackend;
|
||||
|
||||
|
||||
AnalysisStatusBackend.prototype.getNodeStatus = function (params, callback) {
|
||||
var nodeId = params.nodeId;
|
||||
|
||||
var statusQuery = 'SELECT node_id, status, updated_at FROM cdb_analysis_catalog where node_id = \'' + nodeId + '\'';
|
||||
|
||||
var pg = new PSQL(dbParamsFromReqParams(params));
|
||||
pg.query(statusQuery, function(err, result) {
|
||||
if (err) {
|
||||
return callback(err, result);
|
||||
}
|
||||
|
||||
result = result || {};
|
||||
|
||||
var rows = result.rows || [];
|
||||
|
||||
return callback(null, rows[0] || {
|
||||
node_id: nodeId,
|
||||
status: 'unknown'
|
||||
});
|
||||
}, true); // use read-only transaction
|
||||
};
|
||||
|
||||
function dbParamsFromReqParams(params) {
|
||||
var dbParams = {};
|
||||
if ( params.dbuser ) {
|
||||
dbParams.user = params.dbuser;
|
||||
}
|
||||
if ( params.dbpassword ) {
|
||||
dbParams.pass = params.dbpassword;
|
||||
}
|
||||
if ( params.dbhost ) {
|
||||
dbParams.host = params.dbhost;
|
||||
}
|
||||
if ( params.dbport ) {
|
||||
dbParams.port = params.dbport;
|
||||
}
|
||||
if ( params.dbname ) {
|
||||
dbParams.dbname = params.dbname;
|
||||
}
|
||||
return dbParams;
|
||||
}
|
||||
19
lib/cartodb/backends/analysis.js
Normal file
@@ -0,0 +1,19 @@
|
||||
var camshaft = require('camshaft');
|
||||
|
||||
function AnalysisBackend(options) {
|
||||
var batchConfig = options.batch || {};
|
||||
batchConfig.endpoint = batchConfig.endpoint || 'http://127.0.0.1:8080/api/v1/sql/job';
|
||||
batchConfig.inlineExecution = batchConfig.inlineExecution || false;
|
||||
batchConfig.hostHeaderTemplate = batchConfig.hostHeaderTemplate || '{{=it.username}}.localhost.lan';
|
||||
this.batchConfig = batchConfig;
|
||||
}
|
||||
|
||||
module.exports = AnalysisBackend;
|
||||
|
||||
AnalysisBackend.prototype.create = function(analysisConfiguration, analysisDefinition, callback) {
|
||||
analysisConfiguration.batch.endpoint = this.batchConfig.endpoint;
|
||||
analysisConfiguration.batch.inlineExecution = this.batchConfig.inlineExecution;
|
||||
analysisConfiguration.batch.hostHeaderTemplate = this.batchConfig.hostHeaderTemplate;
|
||||
|
||||
camshaft.create(analysisConfiguration, analysisDefinition, callback);
|
||||
};
|
||||
280
lib/cartodb/backends/dataview.js
Normal file
@@ -0,0 +1,280 @@
|
||||
var assert = require('assert');
|
||||
|
||||
var _ = require('underscore');
|
||||
var PSQL = require('cartodb-psql');
|
||||
var camshaft = require('camshaft');
|
||||
var step = require('step');
|
||||
|
||||
var Timer = require('../stats/timer');
|
||||
|
||||
var BBoxFilter = require('../models/filter/bbox');
|
||||
var DataviewFactory = require('../models/dataview/factory');
|
||||
|
||||
function DataviewBackend(analysisBackend) {
|
||||
this.analysisBackend = analysisBackend;
|
||||
}
|
||||
|
||||
module.exports = DataviewBackend;
|
||||
|
||||
|
||||
DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, params, callback) {
|
||||
var self = this;
|
||||
|
||||
var timer = new Timer();
|
||||
|
||||
var dataviewName = params.dataviewName;
|
||||
|
||||
var mapConfig;
|
||||
var dataviewDefinition;
|
||||
step(
|
||||
function getMapConfig() {
|
||||
mapConfigProvider.getMapConfig(this);
|
||||
},
|
||||
function _getDataviewDefinition(err, _mapConfig) {
|
||||
assert.ifError(err);
|
||||
|
||||
mapConfig = _mapConfig;
|
||||
|
||||
var _dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
|
||||
if (!_dataviewDefinition) {
|
||||
throw new Error("Dataview '" + dataviewName + "' does not exists");
|
||||
}
|
||||
|
||||
dataviewDefinition = _dataviewDefinition;
|
||||
|
||||
return dataviewDefinition;
|
||||
},
|
||||
function loadAnalysis(err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var analysisConfiguration = {
|
||||
db: {
|
||||
host: params.dbhost,
|
||||
port: params.dbport,
|
||||
dbname: params.dbname,
|
||||
user: params.dbuser,
|
||||
pass: params.dbpassword
|
||||
},
|
||||
batch: {
|
||||
username: user,
|
||||
apiKey: params.api_key
|
||||
}
|
||||
};
|
||||
|
||||
var sourceId = dataviewDefinition.source.id;
|
||||
var analysisDefinition = getAnalysisDefinition(mapConfig.obj().analyses, sourceId);
|
||||
|
||||
var next = this;
|
||||
|
||||
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function(err, analysis) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var sourceId2Node = {};
|
||||
var rootNode = analysis.getRoot();
|
||||
if (rootNode.params && rootNode.params.id) {
|
||||
sourceId2Node[rootNode.params.id] = rootNode;
|
||||
}
|
||||
|
||||
analysis.getSortedNodes().forEach(function(node) {
|
||||
if (node.params && node.params.id) {
|
||||
sourceId2Node[node.params.id] = node;
|
||||
}
|
||||
});
|
||||
|
||||
var node = sourceId2Node[sourceId];
|
||||
|
||||
if (!node) {
|
||||
return next(new Error('Analysis node not found for dataview'));
|
||||
}
|
||||
|
||||
return next(null, node);
|
||||
});
|
||||
},
|
||||
function runDataviewQuery(err, node) {
|
||||
assert.ifError(err);
|
||||
|
||||
var pg = new PSQL(dbParamsFromReqParams(params));
|
||||
|
||||
var ownFilter = +params.own_filter;
|
||||
ownFilter = !!ownFilter;
|
||||
|
||||
var query;
|
||||
if (ownFilter) {
|
||||
query = node.getQuery();
|
||||
} else {
|
||||
var applyFilters = {};
|
||||
applyFilters[dataviewName] = false;
|
||||
query = node.getQuery(applyFilters);
|
||||
}
|
||||
|
||||
if (params.bbox) {
|
||||
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
|
||||
query = bboxFilter.sql(query);
|
||||
}
|
||||
|
||||
|
||||
var overrideParams = _.reduce(_.pick(params, 'start', 'end', 'bins'),
|
||||
function castNumbers(overrides, val, k) {
|
||||
overrides[k] = Number.isFinite(+val) ? +val : val;
|
||||
return overrides;
|
||||
},
|
||||
{ownFilter: ownFilter}
|
||||
);
|
||||
|
||||
var dataview = DataviewFactory.getDataview(query, dataviewDefinition);
|
||||
dataview.getResult(pg, overrideParams, this);
|
||||
},
|
||||
function returnCallback(err, result) {
|
||||
return callback(err, result, timer.getTimes());
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
DataviewBackend.prototype.search = function (mapConfigProvider, user, params, callback) {
|
||||
var self = this;
|
||||
|
||||
var timer = new Timer();
|
||||
|
||||
var dataviewName = params.dataviewName;
|
||||
|
||||
var mapConfig;
|
||||
var dataviewDefinition;
|
||||
step(
|
||||
function getMapConfig() {
|
||||
mapConfigProvider.getMapConfig(this);
|
||||
},
|
||||
function _getDataviewDefinition(err, _mapConfig) {
|
||||
assert.ifError(err);
|
||||
|
||||
mapConfig = _mapConfig;
|
||||
|
||||
var _dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
|
||||
if (!_dataviewDefinition) {
|
||||
throw new Error("Dataview '" + dataviewName + "' does not exists");
|
||||
}
|
||||
|
||||
dataviewDefinition = _dataviewDefinition;
|
||||
|
||||
return dataviewDefinition;
|
||||
},
|
||||
function loadAnalysis(err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var analysisConfiguration = {
|
||||
db: {
|
||||
host: params.dbhost,
|
||||
port: params.dbport,
|
||||
dbname: params.dbname,
|
||||
user: params.dbuser,
|
||||
pass: params.dbpassword
|
||||
},
|
||||
batch: {
|
||||
// TODO load this from configuration
|
||||
endpoint: 'http://127.0.0.1:8080/api/v1/sql/job',
|
||||
username: user,
|
||||
apiKey: params.api_key
|
||||
}
|
||||
};
|
||||
|
||||
var sourceId = dataviewDefinition.source.id;
|
||||
var analysisDefinition = getAnalysisDefinition(mapConfig.obj().analyses, sourceId);
|
||||
|
||||
var next = this;
|
||||
|
||||
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function(err, analysis) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var sourceId2Node = {};
|
||||
var rootNode = analysis.getRoot();
|
||||
if (rootNode.params && rootNode.params.id) {
|
||||
sourceId2Node[rootNode.params.id] = rootNode;
|
||||
}
|
||||
|
||||
analysis.getSortedNodes().forEach(function(node) {
|
||||
if (node.params && node.params.id) {
|
||||
sourceId2Node[node.params.id] = node;
|
||||
}
|
||||
});
|
||||
|
||||
var node = sourceId2Node[sourceId];
|
||||
|
||||
if (!node) {
|
||||
return next(new Error('Analysis node not found for dataview'));
|
||||
}
|
||||
|
||||
return next(null, node);
|
||||
});
|
||||
},
|
||||
function runDataviewQuery(err, node) {
|
||||
assert.ifError(err);
|
||||
|
||||
var pg = new PSQL(dbParamsFromReqParams(params));
|
||||
|
||||
var ownFilter = +params.own_filter;
|
||||
ownFilter = !!ownFilter;
|
||||
|
||||
var query;
|
||||
if (ownFilter) {
|
||||
query = node.getQuery();
|
||||
} else {
|
||||
var applyFilters = {};
|
||||
applyFilters[dataviewName] = false;
|
||||
query = node.getQuery(applyFilters);
|
||||
}
|
||||
|
||||
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, timer.getTimes());
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function getAnalysisDefinition(mapConfigAnalyses, sourceId) {
|
||||
mapConfigAnalyses = mapConfigAnalyses || [];
|
||||
for (var i = 0; i < mapConfigAnalyses.length; i++) {
|
||||
var analysisGraph = new camshaft.reference.AnalysisGraph(mapConfigAnalyses[i]);
|
||||
var nodes = analysisGraph.getNodesWithId();
|
||||
if (nodes.hasOwnProperty(sourceId)) {
|
||||
return mapConfigAnalyses[i];
|
||||
}
|
||||
}
|
||||
throw new Error('There is no associated analysis for the dataview source id');
|
||||
}
|
||||
|
||||
function getDataviewDefinition(mapConfig, dataviewName) {
|
||||
var dataviews = mapConfig.dataviews || {};
|
||||
return dataviews[dataviewName];
|
||||
}
|
||||
|
||||
function dbParamsFromReqParams(params) {
|
||||
var dbParams = {};
|
||||
if ( params.dbuser ) {
|
||||
dbParams.user = params.dbuser;
|
||||
}
|
||||
if ( params.dbpassword ) {
|
||||
dbParams.pass = params.dbpassword;
|
||||
}
|
||||
if ( params.dbhost ) {
|
||||
dbParams.host = params.dbhost;
|
||||
}
|
||||
if ( params.dbport ) {
|
||||
dbParams.port = params.dbport;
|
||||
}
|
||||
if ( params.dbname ) {
|
||||
dbParams.dbname = params.dbname;
|
||||
}
|
||||
return dbParams;
|
||||
}
|
||||
12
lib/cartodb/cache/named_map_provider_cache.js
vendored
@@ -2,19 +2,23 @@ var _ = require('underscore');
|
||||
var dot = require('dot');
|
||||
var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider');
|
||||
var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter');
|
||||
var AnalysisMapConfigAdapter = require('../models/analysis-mapconfig-adapter');
|
||||
var templateName = require('../backends/template_maps').templateName;
|
||||
var queue = require('queue-async');
|
||||
|
||||
var LruCache = require("lru-cache");
|
||||
|
||||
function NamedMapProviderCache(templateMaps, pgConnection, userLimitsApi, overviewsAdapter, turboCartocssAdapter) {
|
||||
function NamedMapProviderCache(templateMaps, pgConnection, metadataBackend, userLimitsApi, overviewsAdapter,
|
||||
turboCartoAdapter) {
|
||||
this.templateMaps = templateMaps;
|
||||
this.pgConnection = pgConnection;
|
||||
this.metadataBackend = metadataBackend;
|
||||
this.userLimitsApi = userLimitsApi;
|
||||
|
||||
this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
|
||||
this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter();
|
||||
this.overviewsAdapter = overviewsAdapter;
|
||||
this.turboCartocssAdapter = turboCartocssAdapter;
|
||||
this.turboCartoAdapter = turboCartoAdapter;
|
||||
|
||||
this.providerCache = new LruCache({ max: 2000 });
|
||||
}
|
||||
@@ -30,10 +34,12 @@ NamedMapProviderCache.prototype.get = function(user, templateId, config, authTok
|
||||
namedMapProviders[providerKey] = new NamedMapMapConfigProvider(
|
||||
this.templateMaps,
|
||||
this.pgConnection,
|
||||
this.metadataBackend,
|
||||
this.userLimitsApi,
|
||||
this.namedLayersAdapter,
|
||||
this.overviewsAdapter,
|
||||
this.turboCartocssAdapter,
|
||||
this.turboCartoAdapter,
|
||||
this.analysisMapConfigAdapter,
|
||||
user,
|
||||
templateId,
|
||||
config,
|
||||
|
||||
@@ -197,7 +197,10 @@ BaseController.prototype.send = function(req, res, body, status, headers) {
|
||||
// jshint maxcomplexity:6
|
||||
|
||||
BaseController.prototype.sendError = function(req, res, err, label) {
|
||||
var allErrors = Array.isArray(err) ? err : [err];
|
||||
label = label || 'UNKNOWN';
|
||||
err = allErrors[0] || new Error(label);
|
||||
allErrors[0] = err;
|
||||
|
||||
var statusCode = findStatusCode(err);
|
||||
|
||||
@@ -208,7 +211,7 @@ BaseController.prototype.sendError = function(req, res, err, label) {
|
||||
statusCode = 200;
|
||||
}
|
||||
|
||||
var errorResponseBody = { errors: [errorMessage(err)] };
|
||||
var errorResponseBody = { errors: allErrors.map(errorMessage) };
|
||||
|
||||
this.send(req, res, errorResponseBody, statusCode);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,9 @@ var BaseController = require('./base');
|
||||
var cors = require('../middleware/cors');
|
||||
var userMiddleware = require('../middleware/user');
|
||||
|
||||
var DataviewBackend = require('../backends/dataview');
|
||||
var AnalysisStatusBackend = require('../backends/analysis-status');
|
||||
|
||||
var MapStoreMapConfigProvider = require('../models/mapconfig/map_store_provider');
|
||||
|
||||
var QueryTables = require('cartodb-query-tables');
|
||||
@@ -22,10 +25,11 @@ var QueryTables = require('cartodb-query-tables');
|
||||
* @param {SurrogateKeysCache} surrogateKeysCache
|
||||
* @param {UserLimitsApi} userLimitsApi
|
||||
* @param {LayergroupAffectedTables} layergroupAffectedTables
|
||||
* @param {AnalysisBackend} analysisBackend
|
||||
* @constructor
|
||||
*/
|
||||
function LayergroupController(authApi, pgConnection, mapStore, tileBackend, previewBackend, attributesBackend,
|
||||
widgetBackend, surrogateKeysCache, userLimitsApi, layergroupAffectedTables) {
|
||||
widgetBackend, surrogateKeysCache, userLimitsApi, layergroupAffectedTables, analysisBackend) {
|
||||
BaseController.call(this, authApi, pgConnection);
|
||||
|
||||
this.pgConnection = pgConnection;
|
||||
@@ -37,6 +41,9 @@ function LayergroupController(authApi, pgConnection, mapStore, tileBackend, prev
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
this.userLimitsApi = userLimitsApi;
|
||||
this.layergroupAffectedTables = layergroupAffectedTables;
|
||||
|
||||
this.dataviewBackend = new DataviewBackend(analysisBackend);
|
||||
this.analysisStatusBackend = new AnalysisStatusBackend();
|
||||
}
|
||||
|
||||
util.inherits(LayergroupController, BaseController);
|
||||
@@ -78,6 +85,100 @@ LayergroupController.prototype.register = function(app) {
|
||||
app.get(app.base_url_mapconfig +
|
||||
'/:token/:layer/widget/:widgetName/search', cors(), userMiddleware,
|
||||
this.widgetSearch.bind(this));
|
||||
|
||||
app.get(app.base_url_mapconfig +
|
||||
'/:token/dataview/:dataviewName', cors(), userMiddleware,
|
||||
this.dataview.bind(this));
|
||||
|
||||
app.get(app.base_url_mapconfig +
|
||||
'/:token/dataview/:dataviewName/search', cors(), userMiddleware,
|
||||
this.dataviewSearch.bind(this));
|
||||
|
||||
app.get(app.base_url_mapconfig +
|
||||
'/:token/analysis/node/:nodeId', cors(), userMiddleware,
|
||||
this.analysisNodeStatus.bind(this));
|
||||
};
|
||||
|
||||
LayergroupController.prototype.analysisNodeStatus = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
step(
|
||||
function setupParams() {
|
||||
self.req2params(req, this);
|
||||
},
|
||||
function retrieveNodeStatus(err) {
|
||||
assert.ifError(err);
|
||||
self.analysisStatusBackend.getNodeStatus(req.params, this);
|
||||
},
|
||||
function finish(err, nodeStatus, stats) {
|
||||
req.profiler.add(stats || {});
|
||||
|
||||
if (err) {
|
||||
self.sendError(req, res, err, 'GET NODE STATUS');
|
||||
} else {
|
||||
self.sendResponse(req, res, nodeStatus, 200, {
|
||||
'Cache-Control': 'public,max-age=5',
|
||||
'Last-Modified': new Date().toUTCString()
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
LayergroupController.prototype.dataview = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
step(
|
||||
function setupParams() {
|
||||
self.req2params(req, this);
|
||||
},
|
||||
function retrieveDataview(err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var mapConfigProvider = new MapStoreMapConfigProvider(
|
||||
self.mapStore, req.context.user, self.userLimitsApi, req.params
|
||||
);
|
||||
self.dataviewBackend.getDataview(mapConfigProvider, req.context.user, req.params, this);
|
||||
},
|
||||
function finish(err, dataview, stats) {
|
||||
req.profiler.add(stats || {});
|
||||
|
||||
if (err) {
|
||||
self.sendError(req, res, err, 'GET DATAVIEW');
|
||||
} else {
|
||||
self.sendResponse(req, res, dataview, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
LayergroupController.prototype.dataviewSearch = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
step(
|
||||
function setupParams() {
|
||||
self.req2params(req, this);
|
||||
},
|
||||
function searchDataview(err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var mapConfigProvider = new MapStoreMapConfigProvider(
|
||||
self.mapStore, req.context.user, self.userLimitsApi, req.params
|
||||
);
|
||||
self.dataviewBackend.search(mapConfigProvider, req.context.user, req.params, this);
|
||||
},
|
||||
function finish(err, searchResult, stats) {
|
||||
req.profiler.add(stats || {});
|
||||
|
||||
if (err) {
|
||||
self.sendError(req, res, err, 'GET DATAVIEW SEARCH');
|
||||
} else {
|
||||
self.sendResponse(req, res, searchResult, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
LayergroupController.prototype.widget = function(req, res) {
|
||||
@@ -95,13 +196,13 @@ LayergroupController.prototype.widget = function(req, res) {
|
||||
);
|
||||
self.widgetBackend.getWidget(mapConfigProvider, req.params, this);
|
||||
},
|
||||
function finish(err, tile, stats) {
|
||||
function finish(err, widget, stats) {
|
||||
req.profiler.add(stats || {});
|
||||
|
||||
if (err) {
|
||||
self.sendError(req, res, err, 'GET WIDGET');
|
||||
} else {
|
||||
self.sendResponse(req, res, tile, 200);
|
||||
self.sendResponse(req, res, widget, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -123,13 +224,13 @@ LayergroupController.prototype.widgetSearch = function(req, res) {
|
||||
);
|
||||
self.widgetBackend.search(mapConfigProvider, req.params, this);
|
||||
},
|
||||
function finish(err, tile, stats) {
|
||||
function finish(err, searchResult, stats) {
|
||||
req.profiler.add(stats || {});
|
||||
|
||||
if (err) {
|
||||
self.sendError(req, res, err, 'GET WIDGET');
|
||||
} else {
|
||||
self.sendResponse(req, res, tile, 200);
|
||||
self.sendResponse(req, res, searchResult, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ var Datasource = windshaft.model.Datasource;
|
||||
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
|
||||
|
||||
var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter');
|
||||
var AnalysisMapConfigAdapter = require('../models/analysis-mapconfig-adapter');
|
||||
var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider');
|
||||
var CreateLayergroupMapConfigProvider = require('../models/mapconfig/create_layergroup_provider');
|
||||
|
||||
@@ -25,15 +26,17 @@ var CreateLayergroupMapConfigProvider = require('../models/mapconfig/create_laye
|
||||
* @param {TemplateMaps} templateMaps
|
||||
* @param {MapBackend} mapBackend
|
||||
* @param metadataBackend
|
||||
* @param {OverviewsMetadataApi} overviewsMetadataApi
|
||||
* @param {SurrogateKeysCache} surrogateKeysCache
|
||||
* @param {UserLimitsApi} userLimitsApi
|
||||
* @param {LayergroupAffectedTables} layergroupAffectedTables
|
||||
* @param {MapConfigOverviewsAdapter} overviewsAdapter
|
||||
* @param {TurboCartoAdapter} turboCartoAdapter
|
||||
* @param {AnalysisBackend} analysisBackend
|
||||
* @constructor
|
||||
*/
|
||||
function MapController(authApi, pgConnection, templateMaps, mapBackend, metadataBackend,
|
||||
surrogateKeysCache, userLimitsApi, layergroupAffectedTables,
|
||||
overviewsAdapter, turboCartoCssAdapter) {
|
||||
overviewsAdapter, turboCartoAdapter, analysisBackend) {
|
||||
|
||||
BaseController.call(this, authApi, pgConnection);
|
||||
|
||||
@@ -44,8 +47,9 @@ function MapController(authApi, pgConnection, templateMaps, mapBackend, metadata
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
this.userLimitsApi = userLimitsApi;
|
||||
this.layergroupAffectedTables = layergroupAffectedTables;
|
||||
this.turboCartoCssAdapter = turboCartoCssAdapter;
|
||||
this.turboCartoAdapter = turboCartoAdapter;
|
||||
|
||||
this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter(analysisBackend);
|
||||
this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
|
||||
this.overviewsAdapter = overviewsAdapter;
|
||||
}
|
||||
@@ -128,15 +132,43 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
|
||||
var self = this;
|
||||
|
||||
var mapConfig;
|
||||
var analysesResults = [];
|
||||
|
||||
step(
|
||||
function setupParams(){
|
||||
self.req2params(req, this);
|
||||
},
|
||||
prepareConfigFn,
|
||||
function beforeLayergroupCreate(err, requestMapConfig) {
|
||||
function prepareAnalysisLayers(err, requestMapConfig) {
|
||||
assert.ifError(err);
|
||||
var analysisConfiguration = {
|
||||
db: {
|
||||
host: req.params.dbhost,
|
||||
port: req.params.dbport,
|
||||
dbname: req.params.dbname,
|
||||
user: req.params.dbuser,
|
||||
pass: req.params.dbpassword
|
||||
},
|
||||
batch: {
|
||||
username: req.context.user,
|
||||
apiKey: req.params.api_key
|
||||
}
|
||||
};
|
||||
|
||||
var filters = {};
|
||||
if (req.params.filters) {
|
||||
try {
|
||||
filters = JSON.parse(req.params.filters);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
self.analysisMapConfigAdapter.getMapConfig(analysisConfiguration, requestMapConfig, filters, this);
|
||||
},
|
||||
function beforeLayergroupCreate(err, requestMapConfig, _analysesResults) {
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
analysesResults = _analysesResults;
|
||||
self.namedLayersAdapter.getLayers(req.context.user, requestMapConfig.layers, self.pgConnection,
|
||||
function(err, layers, datasource) {
|
||||
if (err) {
|
||||
@@ -165,11 +197,11 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
|
||||
return next(null, requestMapConfig, datasource);
|
||||
});
|
||||
},
|
||||
function parseTurboCartoCss(err, requestMapConfig, datasource) {
|
||||
function parseTurboCarto(err, requestMapConfig, datasource) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
self.turboCartoCssAdapter.getLayers(req.context.user, requestMapConfig.layers, function (err, layers) {
|
||||
self.turboCartoAdapter.getLayers(req.context.user, requestMapConfig.layers, function (err, layers) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -192,7 +224,7 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
|
||||
},
|
||||
function afterLayergroupCreate(err, layergroup) {
|
||||
assert.ifError(err);
|
||||
self.afterLayergroupCreate(req, res, mapConfig, layergroup, this);
|
||||
self.afterLayergroupCreate(req, res, mapConfig, analysesResults, layergroup, this);
|
||||
},
|
||||
function finish(err, layergroup) {
|
||||
if (err) {
|
||||
@@ -227,10 +259,12 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
|
||||
mapConfigProvider = new NamedMapMapConfigProvider(
|
||||
self.templateMaps,
|
||||
self.pgConnection,
|
||||
self.metadataBackend,
|
||||
self.userLimitsApi,
|
||||
self.namedLayersAdapter,
|
||||
self.overviewsAdapter,
|
||||
self.turboCartoCssAdapter,
|
||||
self.turboCartoAdapter,
|
||||
self.analysisMapConfigAdapter,
|
||||
cdbuser,
|
||||
req.params.template_id,
|
||||
templateParams,
|
||||
@@ -250,7 +284,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
|
||||
},
|
||||
function afterLayergroupCreate(err, layergroup) {
|
||||
assert.ifError(err);
|
||||
self.afterLayergroupCreate(req, res, mapConfig, layergroup, this);
|
||||
self.afterLayergroupCreate(req, res, mapConfig, [], layergroup, this);
|
||||
},
|
||||
function finishTemplateInstantiation(err, layergroup) {
|
||||
if (err) {
|
||||
@@ -259,7 +293,9 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
|
||||
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
|
||||
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
|
||||
|
||||
addWidgetsUrl(req.context.user, layergroup);
|
||||
addWidgetsUrl(cdbuser, layergroup);
|
||||
addDataviewsUrls(cdbuser, layergroup, mapConfig.obj());
|
||||
addAnalysesMetadata(cdbuser, layergroup, mapConfigProvider.analysesResults);
|
||||
|
||||
res.set('X-Layergroup-Id', layergroup.layergroupid);
|
||||
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, mapConfigProvider.getTemplateName()));
|
||||
@@ -271,7 +307,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
|
||||
};
|
||||
|
||||
|
||||
MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, layergroup, callback) {
|
||||
MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, analysesResults, layergroup, callback) {
|
||||
var self = this;
|
||||
|
||||
var username = req.context.user;
|
||||
@@ -336,6 +372,8 @@ MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, la
|
||||
|
||||
// TODO this should take into account several URL patterns
|
||||
addWidgetsUrl(username, layergroup);
|
||||
addDataviewsUrls(username, layergroup, mapconfig.obj());
|
||||
addAnalysesMetadata(username, layergroup, analysesResults, true);
|
||||
if (req.method === 'GET') {
|
||||
var ttl = global.environment.varnish.layergroupTtl || 86400;
|
||||
res.set('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
|
||||
@@ -354,6 +392,44 @@ MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, la
|
||||
);
|
||||
};
|
||||
|
||||
function addAnalysesMetadata(username, layergroup, analysesResults, includeQuery) {
|
||||
includeQuery = includeQuery || false;
|
||||
analysesResults = analysesResults || [];
|
||||
layergroup.metadata.analyses = [];
|
||||
|
||||
analysesResults.forEach(function(analysis) {
|
||||
var nodes = analysis.getSortedNodes();
|
||||
layergroup.metadata.analyses.push({
|
||||
nodes: nodes.reduce(function(nodesIdMap, node) {
|
||||
if (node.params.id) {
|
||||
var nodeResource = layergroup.layergroupid + '/analysis/node/' + node.id();
|
||||
nodesIdMap[node.params.id] = {
|
||||
status: node.getStatus(),
|
||||
url: getUrls(username, nodeResource)
|
||||
};
|
||||
if (includeQuery) {
|
||||
nodesIdMap[node.params.id].query = node.getQuery();
|
||||
}
|
||||
}
|
||||
|
||||
return nodesIdMap;
|
||||
}, {})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addDataviewsUrls(username, layergroup, mapConfig) {
|
||||
layergroup.metadata.dataviews = layergroup.metadata.dataviews || {};
|
||||
var dataviews = mapConfig.dataviews || {};
|
||||
|
||||
Object.keys(dataviews).forEach(function(dataviewName) {
|
||||
var resource = layergroup.layergroupid + '/dataview/' + dataviewName;
|
||||
layergroup.metadata.dataviews[dataviewName] = {
|
||||
url: getUrls(username, resource)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function addWidgetsUrl(username, layergroup) {
|
||||
|
||||
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers)) {
|
||||
|
||||
220
lib/cartodb/models/analysis-mapconfig-adapter.js
Normal file
@@ -0,0 +1,220 @@
|
||||
var queue = require('queue-async');
|
||||
var debug = require('debug')('windshaft:analysis');
|
||||
|
||||
var camshaft = require('camshaft');
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
|
||||
function AnalysisMapConfigAdapter(analysisBackend) {
|
||||
this.analysisBackend = analysisBackend;
|
||||
}
|
||||
|
||||
module.exports = AnalysisMapConfigAdapter;
|
||||
|
||||
var SKIP_COLUMNS = {
|
||||
'the_geom': true,
|
||||
'the_geom_webmercator': true
|
||||
};
|
||||
|
||||
function skipColumns(columnNames) {
|
||||
return columnNames
|
||||
.filter(function(columnName) { return !SKIP_COLUMNS[columnName]; });
|
||||
}
|
||||
|
||||
var layerQueryTemplate = dot.template([
|
||||
'SELECT {{=it._columns}}',
|
||||
'FROM ({{=it._query}}) _cdb_analysis_query'
|
||||
].join('\n'));
|
||||
|
||||
function layerQuery(query, columnNames) {
|
||||
var _columns = ['ST_Transform(the_geom, 3857) the_geom_webmercator'].concat(skipColumns(columnNames));
|
||||
return layerQueryTemplate({ _query: query, _columns: _columns.join(', ') });
|
||||
}
|
||||
|
||||
function appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId) {
|
||||
var analyses = requestMapConfig.analyses || [];
|
||||
|
||||
requestMapConfig.analyses = analyses.map(function(analysisDefinition) {
|
||||
var analysisGraph = new camshaft.reference.AnalysisGraph(analysisDefinition);
|
||||
var definition = analysisDefinition;
|
||||
Object.keys(dataviewsFiltersBySourceId).forEach(function(sourceId) {
|
||||
definition = analysisGraph.getDefinitionWith(sourceId, {filters: dataviewsFiltersBySourceId[sourceId] });
|
||||
});
|
||||
|
||||
return definition;
|
||||
});
|
||||
|
||||
return requestMapConfig;
|
||||
}
|
||||
|
||||
function shouldAdaptLayers(requestMapConfig) {
|
||||
return Array.isArray(requestMapConfig.layers) &&
|
||||
Array.isArray(requestMapConfig.analyses) && requestMapConfig.analyses.length > 0;
|
||||
}
|
||||
|
||||
var DATAVIEW_TYPE_2_FILTER_TYPE = {
|
||||
aggregation: 'category',
|
||||
histogram: 'range'
|
||||
};
|
||||
function getFilter(dataview, params) {
|
||||
var type = dataview.type;
|
||||
|
||||
return {
|
||||
type: DATAVIEW_TYPE_2_FILTER_TYPE[type],
|
||||
column: dataview.options.column,
|
||||
params: params
|
||||
};
|
||||
}
|
||||
|
||||
AnalysisMapConfigAdapter.prototype.getMapConfig = function(analysisConfiguration, requestMapConfig, filters, callback) {
|
||||
// jshint maxcomplexity:7
|
||||
var self = this;
|
||||
filters = filters || {};
|
||||
|
||||
if (!shouldAdaptLayers(requestMapConfig)) {
|
||||
return callback(null, requestMapConfig);
|
||||
}
|
||||
|
||||
var dataviewsFilters = filters.dataviews || {};
|
||||
debug(dataviewsFilters);
|
||||
var dataviews = requestMapConfig.dataviews || {};
|
||||
|
||||
var errors = getDataviewsErrors(dataviews);
|
||||
if (errors.length > 0) {
|
||||
return callback(errors);
|
||||
}
|
||||
|
||||
var dataviewsFiltersBySourceId = Object.keys(dataviewsFilters).reduce(function(bySourceId, dataviewName) {
|
||||
var dataview = dataviews[dataviewName];
|
||||
if (dataview) {
|
||||
var sourceId = dataview.source.id;
|
||||
if (!bySourceId.hasOwnProperty(sourceId)) {
|
||||
bySourceId[sourceId] = {};
|
||||
}
|
||||
|
||||
bySourceId[sourceId][dataviewName] = getFilter(dataview, dataviewsFilters[dataviewName]);
|
||||
}
|
||||
return bySourceId;
|
||||
}, {});
|
||||
|
||||
debug(dataviewsFiltersBySourceId);
|
||||
|
||||
debug('mapconfig input', JSON.stringify(requestMapConfig, null, 4));
|
||||
|
||||
requestMapConfig = appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId);
|
||||
|
||||
function createAnalysis(analysisDefinition, done) {
|
||||
self.analysisBackend.create(analysisConfiguration, analysisDefinition, done);
|
||||
}
|
||||
|
||||
var analysesQueue = queue(requestMapConfig.analyses.length);
|
||||
requestMapConfig.analyses.forEach(function(analysis) {
|
||||
analysesQueue.defer(createAnalysis, analysis);
|
||||
});
|
||||
|
||||
analysesQueue.awaitAll(function(err, analysesResults) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var sourceId2Node = analysesResults.reduce(function(sourceId2Query, analysis) {
|
||||
var rootNode = analysis.getRoot();
|
||||
if (rootNode.params && rootNode.params.id) {
|
||||
sourceId2Query[rootNode.params.id] = rootNode;
|
||||
}
|
||||
|
||||
analysis.getSortedNodes().forEach(function(node) {
|
||||
if (node.params && node.params.id) {
|
||||
sourceId2Query[node.params.id] = node;
|
||||
}
|
||||
});
|
||||
|
||||
return sourceId2Query;
|
||||
}, {});
|
||||
|
||||
var missingNodesErrors = [];
|
||||
|
||||
requestMapConfig.layers = requestMapConfig.layers.map(function(layer, layerIndex) {
|
||||
if (getLayerSourceId(layer)) {
|
||||
var layerSourceId = getLayerSourceId(layer);
|
||||
var layerNode = sourceId2Node[layerSourceId];
|
||||
if (layerNode) {
|
||||
layer.options.sql = layerQuery(layerNode.getQuery(), layerNode.getColumns());
|
||||
var layerDataviews = getLayerDataviews(layer, dataviews);
|
||||
layer.options.columns = layerDataviews.reduce(function(columns, dataview) {
|
||||
return columns.concat(getDataviewColumns(dataview));
|
||||
}, []);
|
||||
} else {
|
||||
missingNodesErrors.push(
|
||||
new Error('Missing analysis node.id="' + layerSourceId +'" for layer='+layerIndex)
|
||||
);
|
||||
}
|
||||
}
|
||||
return layer;
|
||||
});
|
||||
|
||||
debug('mapconfig output', JSON.stringify(requestMapConfig, null, 4));
|
||||
|
||||
if (missingNodesErrors.length > 0) {
|
||||
return callback(missingNodesErrors);
|
||||
}
|
||||
|
||||
return callback(null, requestMapConfig, analysesResults);
|
||||
});
|
||||
};
|
||||
|
||||
function getLayerSourceId(layer) {
|
||||
return layer.options.source && layer.options.source.id;
|
||||
}
|
||||
|
||||
function getDataviewSourceId(dataview) {
|
||||
return dataview.source && dataview.source.id;
|
||||
}
|
||||
|
||||
function getLayerDataviews(layer, dataviews) {
|
||||
var layerDataviews = [];
|
||||
|
||||
var layerSourceId = getLayerSourceId(layer);
|
||||
if (layerSourceId) {
|
||||
var dataviewsList = getDataviewsList(dataviews);
|
||||
dataviewsList.forEach(function(dataview) {
|
||||
if (getDataviewSourceId(dataview) === layerSourceId) {
|
||||
layerDataviews.push(dataview);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return layerDataviews;
|
||||
}
|
||||
|
||||
function getDataviewColumns(dataview) {
|
||||
var columns = [];
|
||||
var options = dataview.options;
|
||||
['column', 'aggregationColumn'].forEach(function(opt) {
|
||||
if (options.hasOwnProperty(opt)) {
|
||||
columns.push(options[opt]);
|
||||
}
|
||||
});
|
||||
return columns;
|
||||
}
|
||||
|
||||
function getDataviewsList(dataviews) {
|
||||
return Object.keys(dataviews).map(function(dataviewKey) { return dataviews[dataviewKey]; });
|
||||
}
|
||||
|
||||
function getDataviewsErrors(dataviews) {
|
||||
var errors = [];
|
||||
|
||||
Object.keys(dataviews).forEach(function(dataviewName) {
|
||||
var dataview = dataviews[dataviewName];
|
||||
if (!dataview.hasOwnProperty('source') || !dataview.source.id) {
|
||||
errors.push(new Error('Dataview "' + dataviewName + '" is missing `source.id` attribute'));
|
||||
}
|
||||
|
||||
if (!dataview.type) {
|
||||
errors.push(new Error('Dataview "' + dataviewName + '" is missing `type` attribute'));
|
||||
}
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
278
lib/cartodb/models/dataview/aggregation.js
Normal file
@@ -0,0 +1,278 @@
|
||||
var _ = require('underscore');
|
||||
var BaseWidget = require('./base');
|
||||
var debug = require('debug')('windshaft:widget:aggregation');
|
||||
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
|
||||
var summaryQueryTpl = dot.template([
|
||||
'summary AS (',
|
||||
' SELECT',
|
||||
' count(1) AS count,',
|
||||
' sum(CASE WHEN {{=it._column}} IS NULL THEN 1 ELSE 0 END) AS nulls_count',
|
||||
' FROM ({{=it._query}}) _cdb_aggregation_nulls',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var rankedCategoriesQueryTpl = dot.template([
|
||||
'categories AS(',
|
||||
' SELECT {{=it._column}} AS category, {{=it._aggregation}} AS value,',
|
||||
' row_number() OVER (ORDER BY {{=it._aggregation}} desc) as rank',
|
||||
' FROM ({{=it._query}}) _cdb_aggregation_all',
|
||||
' GROUP BY {{=it._column}}',
|
||||
' ORDER BY 2 DESC',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var categoriesSummaryQueryTpl = dot.template([
|
||||
'categories_summary AS(',
|
||||
' SELECT count(1) categories_count, max(value) max_val, min(value) min_val',
|
||||
' FROM categories',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var rankedAggregationQueryTpl = dot.template([
|
||||
'SELECT CAST(category AS text), value, false as agg, nulls_count, min_val, max_val, count, categories_count',
|
||||
' FROM categories, summary, categories_summary',
|
||||
' WHERE rank < {{=it._limit}}',
|
||||
'UNION ALL',
|
||||
'SELECT \'Other\' category, sum(value), true as agg, nulls_count, min_val, max_val, count, categories_count',
|
||||
' FROM categories, summary, categories_summary',
|
||||
' WHERE rank >= {{=it._limit}}',
|
||||
'GROUP BY nulls_count, min_val, max_val, count, categories_count'
|
||||
].join('\n'));
|
||||
|
||||
var aggregationQueryTpl = dot.template([
|
||||
'SELECT CAST({{=it._column}} AS text) AS category, {{=it._aggregation}} AS value, false as agg,',
|
||||
' nulls_count, min_val, max_val, count, categories_count',
|
||||
'FROM ({{=it._query}}) _cdb_aggregation_all, summary, categories_summary',
|
||||
'GROUP BY category, nulls_count, min_val, max_val, count, categories_count',
|
||||
'ORDER BY value DESC'
|
||||
].join('\n'));
|
||||
|
||||
var CATEGORIES_LIMIT = 6;
|
||||
|
||||
var VALID_OPERATIONS = {
|
||||
count: [],
|
||||
sum: ['aggregationColumn']
|
||||
};
|
||||
|
||||
var TYPE = 'aggregation';
|
||||
|
||||
/**
|
||||
{
|
||||
type: 'aggregation',
|
||||
options: {
|
||||
column: 'name',
|
||||
aggregation: 'count' // it could be, e.g., sum if column is numeric
|
||||
}
|
||||
}
|
||||
*/
|
||||
function Aggregation(query, options) {
|
||||
if (!_.isString(options.column)) {
|
||||
throw new Error('Aggregation expects `column` in widget options');
|
||||
}
|
||||
|
||||
if (!_.isString(options.aggregation)) {
|
||||
throw new Error('Aggregation expects `aggregation` operation in widget options');
|
||||
}
|
||||
|
||||
if (!VALID_OPERATIONS[options.aggregation]) {
|
||||
throw new Error("Aggregation does not support '" + options.aggregation + "' operation");
|
||||
}
|
||||
|
||||
var requiredOptions = VALID_OPERATIONS[options.aggregation];
|
||||
var missingOptions = _.difference(requiredOptions, Object.keys(options));
|
||||
if (missingOptions.length > 0) {
|
||||
throw new Error(
|
||||
"Aggregation '" + options.aggregation + "' is missing some options: " + missingOptions.join(',')
|
||||
);
|
||||
}
|
||||
|
||||
BaseWidget.apply(this);
|
||||
|
||||
this.query = query;
|
||||
this.column = options.column;
|
||||
this.aggregation = options.aggregation;
|
||||
this.aggregationColumn = options.aggregationColumn;
|
||||
}
|
||||
|
||||
Aggregation.prototype = new BaseWidget();
|
||||
Aggregation.prototype.constructor = Aggregation;
|
||||
|
||||
module.exports = Aggregation;
|
||||
|
||||
Aggregation.prototype.sql = function(psql, filters, override, callback) {
|
||||
if (!callback) {
|
||||
callback = override;
|
||||
override = {};
|
||||
}
|
||||
|
||||
var _query = this.query;
|
||||
|
||||
var aggregationSql;
|
||||
if (!!override.ownFilter) {
|
||||
aggregationSql = [
|
||||
"WITH",
|
||||
[
|
||||
summaryQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column
|
||||
}),
|
||||
rankedCategoriesQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column,
|
||||
_aggregation: this.getAggregationSql()
|
||||
}),
|
||||
categoriesSummaryQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column
|
||||
})
|
||||
].join(',\n'),
|
||||
aggregationQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column,
|
||||
_aggregation: this.getAggregationSql(),
|
||||
_limit: CATEGORIES_LIMIT
|
||||
})
|
||||
].join('\n');
|
||||
} else {
|
||||
aggregationSql = [
|
||||
"WITH",
|
||||
[
|
||||
summaryQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column
|
||||
}),
|
||||
rankedCategoriesQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column,
|
||||
_aggregation: this.getAggregationSql()
|
||||
}),
|
||||
categoriesSummaryQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column
|
||||
})
|
||||
].join(',\n'),
|
||||
rankedAggregationQueryTpl({
|
||||
_query: _query,
|
||||
_column: this.column,
|
||||
_limit: CATEGORIES_LIMIT
|
||||
})
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
debug(aggregationSql);
|
||||
|
||||
return callback(null, aggregationSql);
|
||||
};
|
||||
|
||||
var aggregationFnQueryTpl = dot.template('{{=it._aggregationFn}}({{=it._aggregationColumn}})');
|
||||
Aggregation.prototype.getAggregationSql = function() {
|
||||
return aggregationFnQueryTpl({
|
||||
_aggregationFn: this.aggregation,
|
||||
_aggregationColumn: this.aggregationColumn || 1
|
||||
});
|
||||
};
|
||||
|
||||
Aggregation.prototype.format = function(result) {
|
||||
var categories = [];
|
||||
var count = 0;
|
||||
var nulls = 0;
|
||||
var minValue = 0;
|
||||
var maxValue = 0;
|
||||
var categoriesCount = 0;
|
||||
|
||||
|
||||
if (result.rows.length) {
|
||||
var firstRow = result.rows[0];
|
||||
count = firstRow.count;
|
||||
nulls = firstRow.nulls_count;
|
||||
minValue = firstRow.min_val;
|
||||
maxValue = firstRow.max_val;
|
||||
categoriesCount = firstRow.categories_count;
|
||||
|
||||
result.rows.forEach(function(row) {
|
||||
categories.push(_.omit(row, 'count', 'nulls_count', 'min_val', 'max_val', 'categories_count'));
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
count: count,
|
||||
nulls: nulls,
|
||||
min: minValue,
|
||||
max: maxValue,
|
||||
categoriesCount: categoriesCount,
|
||||
categories: categories
|
||||
};
|
||||
};
|
||||
|
||||
var filterCategoriesQueryTpl = dot.template([
|
||||
'SELECT {{=it._column}} AS category, {{=it._value}} AS value',
|
||||
'FROM ({{=it._query}}) _cdb_aggregation_search',
|
||||
'WHERE CAST({{=it._column}} as text) ILIKE {{=it._userQuery}}',
|
||||
'GROUP BY {{=it._column}}'
|
||||
].join('\n'));
|
||||
|
||||
var searchQueryTpl = dot.template([
|
||||
'WITH',
|
||||
'search_unfiltered AS (',
|
||||
' {{=it._searchUnfiltered}}',
|
||||
'),',
|
||||
'search_filtered AS (',
|
||||
' {{=it._searchFiltered}}',
|
||||
'),',
|
||||
'search_union AS (',
|
||||
' SELECT * FROM search_unfiltered',
|
||||
' UNION ALL',
|
||||
' SELECT * FROM search_filtered',
|
||||
')',
|
||||
'SELECT category, sum(value) AS value',
|
||||
'FROM search_union',
|
||||
'GROUP BY category',
|
||||
'ORDER BY value desc'
|
||||
].join('\n'));
|
||||
|
||||
|
||||
Aggregation.prototype.search = function(psql, userQuery, callback) {
|
||||
var self = this;
|
||||
|
||||
var _userQuery = psql.escapeLiteral('%' + userQuery + '%');
|
||||
|
||||
// TODO unfiltered will be wrong as filters are already applied at this point
|
||||
var query = searchQueryTpl({
|
||||
_searchUnfiltered: filterCategoriesQueryTpl({
|
||||
_query: this.query,
|
||||
_column: this.column,
|
||||
_value: '0',
|
||||
_userQuery: _userQuery
|
||||
}),
|
||||
_searchFiltered: filterCategoriesQueryTpl({
|
||||
_query: this.query,
|
||||
_column: this.column,
|
||||
_value: 'count(1)',
|
||||
_userQuery: _userQuery
|
||||
})
|
||||
});
|
||||
|
||||
psql.query(query, function(err, result) {
|
||||
if (err) {
|
||||
return callback(err, result);
|
||||
}
|
||||
|
||||
return callback(null, {type: self.getType(), categories: result.rows });
|
||||
}, true); // use read-only transaction
|
||||
};
|
||||
|
||||
Aggregation.prototype.getType = function() {
|
||||
return TYPE;
|
||||
};
|
||||
|
||||
Aggregation.prototype.toString = function() {
|
||||
return JSON.stringify({
|
||||
_type: TYPE,
|
||||
_query: this.query,
|
||||
_column: this.column,
|
||||
_aggregation: this.aggregation
|
||||
});
|
||||
};
|
||||
26
lib/cartodb/models/dataview/base.js
Normal file
@@ -0,0 +1,26 @@
|
||||
function BaseDataview() {}
|
||||
|
||||
module.exports = BaseDataview;
|
||||
|
||||
BaseDataview.prototype.getResult = function(psql, override, callback) {
|
||||
var self = this;
|
||||
this.sql(psql, override, function(err, query) {
|
||||
psql.query(query, function(err, result) {
|
||||
|
||||
if (err) {
|
||||
return callback(err, result);
|
||||
}
|
||||
|
||||
result = self.format(result, override);
|
||||
result.type = self.getType();
|
||||
|
||||
return callback(null, result);
|
||||
|
||||
}, true); // use read-only transaction
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
BaseDataview.prototype.search = function(psql, userQuery, callback) {
|
||||
return callback(null, this.format({ rows: [] }));
|
||||
};
|
||||
18
lib/cartodb/models/dataview/factory.js
Normal file
@@ -0,0 +1,18 @@
|
||||
var dataviews = require('./');
|
||||
|
||||
var DataviewFactory = {
|
||||
dataviews: Object.keys(dataviews).reduce(function(allDataviews, dataviewClassName) {
|
||||
allDataviews[dataviewClassName.toLowerCase()] = dataviews[dataviewClassName];
|
||||
return allDataviews;
|
||||
}, {}),
|
||||
|
||||
getDataview: function(query, dataviewDefinition) {
|
||||
var type = dataviewDefinition.type;
|
||||
if (!this.dataviews[type]) {
|
||||
throw new Error('Invalid dataview type: "' + type + '"');
|
||||
}
|
||||
return new this.dataviews[type](query, dataviewDefinition.options);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = DataviewFactory;
|
||||
104
lib/cartodb/models/dataview/formula.js
Normal file
@@ -0,0 +1,104 @@
|
||||
var _ = require('underscore');
|
||||
var BaseWidget = require('./base');
|
||||
var debug = require('debug')('windshaft:widget:formula');
|
||||
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
|
||||
var formulaQueryTpl = dot.template([
|
||||
'SELECT',
|
||||
'{{=it._operation}}({{=it._column}}) AS result,',
|
||||
'(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls WHERE {{=it._column}} IS NULL) AS nulls_count',
|
||||
'FROM ({{=it._query}}) _cdb_formula'
|
||||
].join('\n'));
|
||||
|
||||
var VALID_OPERATIONS = {
|
||||
count: true,
|
||||
avg: true,
|
||||
sum: true,
|
||||
min: true,
|
||||
max: true
|
||||
};
|
||||
|
||||
var TYPE = 'formula';
|
||||
|
||||
/**
|
||||
{
|
||||
type: 'formula',
|
||||
options: {
|
||||
column: 'name',
|
||||
operation: 'count' // count, sum, avg
|
||||
}
|
||||
}
|
||||
*/
|
||||
function Formula(query, options) {
|
||||
if (!_.isString(options.operation)) {
|
||||
throw new Error('Formula expects `operation` in widget options');
|
||||
}
|
||||
|
||||
if (!VALID_OPERATIONS[options.operation]) {
|
||||
throw new Error("Formula does not support '" + options.operation + "' operation");
|
||||
}
|
||||
|
||||
if (options.operation !== 'count' && !_.isString(options.column)) {
|
||||
throw new Error('Formula expects `column` in widget options');
|
||||
}
|
||||
|
||||
BaseWidget.apply(this);
|
||||
|
||||
this.query = query;
|
||||
this.column = options.column || '1';
|
||||
this.operation = options.operation;
|
||||
}
|
||||
|
||||
Formula.prototype = new BaseWidget();
|
||||
Formula.prototype.constructor = Formula;
|
||||
|
||||
module.exports = Formula;
|
||||
|
||||
Formula.prototype.sql = function(psql, filters, override, callback) {
|
||||
if (!callback) {
|
||||
callback = override;
|
||||
override = {};
|
||||
}
|
||||
|
||||
var _query = this.query;
|
||||
var formulaSql = formulaQueryTpl({
|
||||
_query: _query,
|
||||
_operation: this.operation,
|
||||
_column: this.column
|
||||
});
|
||||
|
||||
debug(formulaSql);
|
||||
|
||||
return callback(null, formulaSql);
|
||||
};
|
||||
|
||||
Formula.prototype.format = function(result) {
|
||||
var formattedResult = {
|
||||
operation: this.operation,
|
||||
result: 0,
|
||||
nulls: 0
|
||||
};
|
||||
|
||||
if (result.rows.length) {
|
||||
formattedResult.operation = this.operation;
|
||||
formattedResult.result = result.rows[0].result;
|
||||
formattedResult.nulls = result.rows[0].nulls_count;
|
||||
}
|
||||
|
||||
return formattedResult;
|
||||
};
|
||||
|
||||
Formula.prototype.getType = function() {
|
||||
return TYPE;
|
||||
};
|
||||
|
||||
Formula.prototype.toString = function() {
|
||||
return JSON.stringify({
|
||||
_type: TYPE,
|
||||
_query: this.query,
|
||||
_column: this.column,
|
||||
_operation: this.operation
|
||||
});
|
||||
};
|
||||
297
lib/cartodb/models/dataview/histogram.js
Normal file
@@ -0,0 +1,297 @@
|
||||
var _ = require('underscore');
|
||||
var BaseWidget = require('./base');
|
||||
var debug = require('debug')('windshaft:dataview:histogram');
|
||||
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
|
||||
var columnTypeQueryTpl = dot.template(
|
||||
'SELECT pg_typeof({{=it.column}})::oid FROM ({{=it.query}}) _cdb_histogram_column_type limit 1'
|
||||
);
|
||||
var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})");
|
||||
|
||||
var BIN_MIN_NUMBER = 6;
|
||||
var BIN_MAX_NUMBER = 48;
|
||||
|
||||
var basicsQueryTpl = dot.template([
|
||||
'basics AS (',
|
||||
' SELECT',
|
||||
' max({{=it._column}}) AS max_val, min({{=it._column}}) AS min_val,',
|
||||
' avg({{=it._column}}) AS avg_val, count(1) AS total_rows',
|
||||
' FROM ({{=it._query}}) _cdb_basics',
|
||||
')'
|
||||
].join(' \n'));
|
||||
|
||||
var overrideBasicsQueryTpl = dot.template([
|
||||
'basics AS (',
|
||||
' SELECT',
|
||||
' max({{=it._end}}) AS max_val, min({{=it._start}}) AS min_val,',
|
||||
' avg({{=it._column}}) AS avg_val, count(1) AS total_rows',
|
||||
' FROM ({{=it._query}}) _cdb_basics',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var iqrQueryTpl = dot.template([
|
||||
'iqrange AS (',
|
||||
' SELECT max(quartile_max) - min(quartile_max) AS iqr',
|
||||
' FROM (',
|
||||
' SELECT quartile, max(_cdb_iqr_column) AS quartile_max from (',
|
||||
' SELECT {{=it._column}} AS _cdb_iqr_column, ntile(4) over (order by {{=it._column}}',
|
||||
' ) AS quartile',
|
||||
' FROM ({{=it._query}}) _cdb_rank) _cdb_quartiles',
|
||||
' WHERE quartile = 1 or quartile = 3',
|
||||
' GROUP BY quartile',
|
||||
' ) _cdb_iqr',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var binsQueryTpl = dot.template([
|
||||
'bins AS (',
|
||||
' SELECT CASE WHEN total_rows = 0 OR iqr = 0',
|
||||
' THEN 1',
|
||||
' ELSE GREATEST(',
|
||||
' LEAST({{=it._minBins}}, CAST(total_rows AS INT)),',
|
||||
' LEAST(',
|
||||
' CAST(((max_val - min_val) / (2 * iqr * power(total_rows, 1/3))) AS INT),',
|
||||
' {{=it._maxBins}}',
|
||||
' )',
|
||||
' )',
|
||||
' END AS bins_number',
|
||||
' FROM basics, iqrange, ({{=it._query}}) _cdb_bins',
|
||||
' LIMIT 1',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var overrideBinsQueryTpl = dot.template([
|
||||
'bins AS (',
|
||||
' SELECT {{=it._bins}} AS bins_number',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var nullsQueryTpl = dot.template([
|
||||
'nulls AS (',
|
||||
' SELECT',
|
||||
' count(*) AS nulls_count',
|
||||
' FROM ({{=it._query}}) _cdb_histogram_nulls',
|
||||
' WHERE {{=it._column}} IS NULL',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var histogramQueryTpl = dot.template([
|
||||
'SELECT',
|
||||
' (max_val - min_val) / cast(bins_number as float) AS bin_width,',
|
||||
' bins_number,',
|
||||
' nulls_count,',
|
||||
' avg_val,',
|
||||
' CASE WHEN min_val = max_val',
|
||||
' THEN 0',
|
||||
' ELSE GREATEST(1, LEAST(WIDTH_BUCKET({{=it._column}}, min_val, max_val, bins_number), bins_number)) - 1',
|
||||
' END AS bin,',
|
||||
' min({{=it._column}})::numeric AS min,',
|
||||
' max({{=it._column}})::numeric AS max,',
|
||||
' avg({{=it._column}})::numeric AS avg,',
|
||||
' count(*) AS freq',
|
||||
'FROM ({{=it._query}}) _cdb_histogram, basics, nulls, bins',
|
||||
'WHERE {{=it._column}} IS NOT NULL',
|
||||
'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val',
|
||||
'ORDER BY bin'
|
||||
].join('\n'));
|
||||
|
||||
|
||||
var TYPE = 'histogram';
|
||||
|
||||
/**
|
||||
{
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'name',
|
||||
bins: 10 // OPTIONAL
|
||||
}
|
||||
}
|
||||
*/
|
||||
function Histogram(query, options) {
|
||||
if (!_.isString(options.column)) {
|
||||
throw new Error('Histogram expects `column` in widget options');
|
||||
}
|
||||
|
||||
this.query = query;
|
||||
this.column = options.column;
|
||||
this.bins = options.bins;
|
||||
|
||||
this._columnType = null;
|
||||
}
|
||||
|
||||
Histogram.prototype = new BaseWidget();
|
||||
Histogram.prototype.constructor = Histogram;
|
||||
|
||||
module.exports = Histogram;
|
||||
|
||||
var DATE_OIDS = {
|
||||
1082: true,
|
||||
1114: true,
|
||||
1184: true
|
||||
};
|
||||
|
||||
Histogram.prototype.sql = function(psql, override, callback) {
|
||||
if (!callback) {
|
||||
callback = override;
|
||||
override = {};
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
var _column = this.column;
|
||||
|
||||
var columnTypeQuery = columnTypeQueryTpl({
|
||||
column: _column, query: this.query
|
||||
});
|
||||
|
||||
if (this._columnType === null) {
|
||||
psql.query(columnTypeQuery, function(err, result) {
|
||||
// assume numeric, will fail later
|
||||
self._columnType = 'numeric';
|
||||
if (!err && !!result.rows[0]) {
|
||||
var pgType = result.rows[0].pg_typeof;
|
||||
if (DATE_OIDS.hasOwnProperty(pgType)) {
|
||||
self._columnType = 'date';
|
||||
}
|
||||
}
|
||||
self.sql(psql, override, callback);
|
||||
}, true); // use read-only transaction
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this._columnType === 'date') {
|
||||
_column = columnCastTpl({column: _column});
|
||||
}
|
||||
|
||||
var _query = this.query;
|
||||
|
||||
var basicsQuery, binsQuery;
|
||||
|
||||
if (override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins')) {
|
||||
debug('overriding with %j', override);
|
||||
basicsQuery = overrideBasicsQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column,
|
||||
_start: override.start,
|
||||
_end: override.end
|
||||
});
|
||||
|
||||
binsQuery = [
|
||||
overrideBinsQueryTpl({
|
||||
_bins: override.bins
|
||||
})
|
||||
].join(',\n');
|
||||
} else {
|
||||
basicsQuery = basicsQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column
|
||||
});
|
||||
|
||||
if (override && _.has(override, 'bins')) {
|
||||
binsQuery = [
|
||||
overrideBinsQueryTpl({
|
||||
_bins: override.bins
|
||||
})
|
||||
].join(',\n');
|
||||
} else {
|
||||
binsQuery = [
|
||||
iqrQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column
|
||||
}),
|
||||
binsQueryTpl({
|
||||
_query: _query,
|
||||
_minBins: BIN_MIN_NUMBER,
|
||||
_maxBins: BIN_MAX_NUMBER
|
||||
})
|
||||
].join(',\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var histogramSql = [
|
||||
"WITH",
|
||||
[
|
||||
basicsQuery,
|
||||
binsQuery,
|
||||
nullsQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column
|
||||
})
|
||||
].join(',\n'),
|
||||
histogramQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column
|
||||
})
|
||||
].join('\n');
|
||||
|
||||
debug(histogramSql);
|
||||
|
||||
return callback(null, histogramSql);
|
||||
};
|
||||
|
||||
Histogram.prototype.format = function(result, override) {
|
||||
override = override || {};
|
||||
var buckets = [];
|
||||
|
||||
var binsCount = getBinsCount(override);
|
||||
var width = getWidth(override);
|
||||
var binsStart = getBinStart(override);
|
||||
var nulls = 0;
|
||||
var avg;
|
||||
|
||||
if (result.rows.length) {
|
||||
var firstRow = result.rows[0];
|
||||
binsCount = firstRow.bins_number;
|
||||
width = firstRow.bin_width || width;
|
||||
avg = firstRow.avg_val;
|
||||
nulls = firstRow.nulls_count;
|
||||
binsStart = override.hasOwnProperty('start') ? override.start : firstRow.min;
|
||||
|
||||
buckets = result.rows.map(function(row) {
|
||||
return _.omit(row, 'bins_number', 'bin_width', 'nulls_count', 'avg_val');
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
bin_width: width,
|
||||
bins_count: binsCount,
|
||||
bins_start: binsStart,
|
||||
nulls: nulls,
|
||||
avg: avg,
|
||||
bins: buckets
|
||||
};
|
||||
};
|
||||
|
||||
function getBinStart(override) {
|
||||
return override.start || 0;
|
||||
}
|
||||
|
||||
function getBinsCount(override) {
|
||||
return override.bins || 0;
|
||||
}
|
||||
|
||||
function getWidth(override) {
|
||||
var width = 0;
|
||||
var binsCount = override.bins;
|
||||
|
||||
if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) {
|
||||
width = (override.end - override.start) / binsCount;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
Histogram.prototype.getType = function() {
|
||||
return TYPE;
|
||||
};
|
||||
|
||||
Histogram.prototype.toString = function() {
|
||||
return JSON.stringify({
|
||||
_type: TYPE,
|
||||
_column: this.column,
|
||||
_query: this.query
|
||||
});
|
||||
};
|
||||
6
lib/cartodb/models/dataview/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
Aggregation: require('./aggregation'),
|
||||
Formula: require('./formula'),
|
||||
Histogram: require('./histogram'),
|
||||
List: require('./list')
|
||||
};
|
||||
66
lib/cartodb/models/dataview/list.js
Normal file
@@ -0,0 +1,66 @@
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
|
||||
var BaseWidget = require('./base');
|
||||
|
||||
var TYPE = 'list';
|
||||
|
||||
var listSqlTpl = dot.template('select {{=it._columns}} from ({{=it._query}}) as _cdb_list');
|
||||
|
||||
/**
|
||||
{
|
||||
type: 'list',
|
||||
options: {
|
||||
columns: ['name', 'description']
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
function List(query, options) {
|
||||
options = options || {};
|
||||
|
||||
if (!Array.isArray(options.columns)) {
|
||||
throw new Error('List expects `columns` array in widget options');
|
||||
}
|
||||
|
||||
BaseWidget.apply(this);
|
||||
|
||||
this.query = query;
|
||||
this.columns = options.columns;
|
||||
}
|
||||
|
||||
List.prototype = new BaseWidget();
|
||||
List.prototype.constructor = List;
|
||||
|
||||
module.exports = List;
|
||||
|
||||
List.prototype.sql = function(psql, filters, override, callback) {
|
||||
if (!callback) {
|
||||
callback = override;
|
||||
}
|
||||
|
||||
var listSql = listSqlTpl({
|
||||
_query: this.query,
|
||||
_columns: this.columns.join(', ')
|
||||
});
|
||||
|
||||
return callback(null, listSql);
|
||||
};
|
||||
|
||||
List.prototype.format = function(result) {
|
||||
return {
|
||||
rows: result.rows
|
||||
};
|
||||
};
|
||||
|
||||
List.prototype.getType = function() {
|
||||
return TYPE;
|
||||
};
|
||||
|
||||
List.prototype.toString = function() {
|
||||
return JSON.stringify({
|
||||
_type: TYPE,
|
||||
_query: this.query,
|
||||
_columns: this.columns.join(', ')
|
||||
});
|
||||
};
|
||||
122
lib/cartodb/models/filter/bbox.js
Normal file
@@ -0,0 +1,122 @@
|
||||
var debug = require('debug')('windshaft:filter:bbox');
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
|
||||
var filterQueryTpl = dot.template([
|
||||
'SELECT * FROM ({{=it._sql}}) _cdb_bbox_filter',
|
||||
'WHERE {{=it._filters}}'
|
||||
].join('\n'));
|
||||
|
||||
var bboxFilterTpl = dot.template(
|
||||
'{{=it._column}} && ST_Transform(ST_MakeEnvelope({{=it._bbox}}, 4326), {{=it._srid}})'
|
||||
);
|
||||
|
||||
var LATITUDE_MAX_VALUE = 85.0511287798066;
|
||||
var LONGITUDE_LOWER_BOUND = -180;
|
||||
var LONGITUDE_UPPER_BOUND = 180;
|
||||
var LONGITUDE_RANGE = LONGITUDE_UPPER_BOUND - LONGITUDE_LOWER_BOUND;
|
||||
|
||||
/**
|
||||
Definition
|
||||
{
|
||||
"type”: "bbox",
|
||||
"options": {
|
||||
"column": "the_geom_webmercator",
|
||||
"srid": 3857
|
||||
}
|
||||
}
|
||||
|
||||
Params
|
||||
{
|
||||
“bbox”: "west,south,east,north"
|
||||
}
|
||||
*/
|
||||
function BBox(filterDefinition, filterParams) {
|
||||
var bbox = filterParams.bbox;
|
||||
|
||||
if (!bbox) {
|
||||
throw new Error('BBox filter expects to have a bbox param');
|
||||
}
|
||||
|
||||
var bboxElements = bbox.split(',').map(function(e) { return +e; });
|
||||
|
||||
validateBboxElements(bboxElements);
|
||||
|
||||
this.column = filterDefinition.column || 'the_geom_webmercator';
|
||||
this.srid = filterDefinition.srid || 3857;
|
||||
|
||||
// Latitudes must be within max extent
|
||||
var south = Math.max(bboxElements[1], -LATITUDE_MAX_VALUE);
|
||||
var north = Math.min(bboxElements[3], LATITUDE_MAX_VALUE);
|
||||
|
||||
// Longitudes crossing 180º need another approach
|
||||
var adjustedLongitudeRange = adjustLongitudeRange([bboxElements[0], bboxElements[2]]);
|
||||
var west = adjustedLongitudeRange[0];
|
||||
var east = adjustedLongitudeRange[1];
|
||||
|
||||
this.bboxes = getBoundingBoxes(west, south, east, north);
|
||||
}
|
||||
|
||||
function getBoundingBoxes(west, south, east, north) {
|
||||
var bboxes = [];
|
||||
|
||||
if (east - west >= 360) {
|
||||
bboxes.push([-180, south, 180, north]);
|
||||
} else if (west >= -180 && east <= 180) {
|
||||
bboxes.push([west, south, east, north]);
|
||||
} else {
|
||||
bboxes.push([west, south, 180, north]);
|
||||
bboxes.push([-180, south, east % 180, north]);
|
||||
}
|
||||
|
||||
return bboxes;
|
||||
}
|
||||
|
||||
function validateBboxElements(bboxElements) {
|
||||
var isNumericBbox = bboxElements
|
||||
.map(function(n) { return Number.isFinite(n); })
|
||||
.reduce(function(allFinite, isFinite) {
|
||||
if (!allFinite) {
|
||||
return false;
|
||||
}
|
||||
return isFinite;
|
||||
}, true);
|
||||
|
||||
if (bboxElements.length !== 4 || !isNumericBbox) {
|
||||
throw new Error('Invalid bbox filter, expected format="west,south,east,north"');
|
||||
}
|
||||
}
|
||||
|
||||
function adjustLongitudeRange(we) {
|
||||
var west = we[0];
|
||||
west -= LONGITUDE_LOWER_BOUND;
|
||||
west = west - (LONGITUDE_RANGE * Math.floor(west / LONGITUDE_RANGE)) + LONGITUDE_LOWER_BOUND;
|
||||
|
||||
var longitudeRange = Math.min(we[1] - we[0], 360);
|
||||
|
||||
return [west, west + longitudeRange];
|
||||
}
|
||||
|
||||
module.exports = BBox;
|
||||
|
||||
module.exports.adjustLongitudeRange = adjustLongitudeRange;
|
||||
module.exports.LATITUDE_MAX_VALUE = LATITUDE_MAX_VALUE;
|
||||
module.exports.LONGITUDE_MAX_VALUE = LONGITUDE_UPPER_BOUND;
|
||||
|
||||
|
||||
BBox.prototype.sql = function(rawSql) {
|
||||
var bboxSql = filterQueryTpl({
|
||||
_sql: rawSql,
|
||||
_filters: this.bboxes.map(function(bbox) {
|
||||
return bboxFilterTpl({
|
||||
_column: this.column,
|
||||
_bbox: bbox.join(','),
|
||||
_srid: this.srid
|
||||
});
|
||||
}.bind(this)).join(' OR ')
|
||||
});
|
||||
|
||||
debug(bboxSql);
|
||||
|
||||
return bboxSql;
|
||||
};
|
||||
@@ -11,14 +11,16 @@ var QueryTables = require('cartodb-query-tables');
|
||||
* @constructor
|
||||
* @type {NamedMapMapConfigProvider}
|
||||
*/
|
||||
function NamedMapMapConfigProvider(templateMaps, pgConnection, userLimitsApi,
|
||||
namedLayersAdapter, overviewsAdapter, turboCartoCssAdapter,
|
||||
function NamedMapMapConfigProvider(templateMaps, pgConnection, metadataBackend, userLimitsApi,
|
||||
namedLayersAdapter, overviewsAdapter, turboCartoAdapter, analysisMapConfigAdapter,
|
||||
owner, templateId, config, authToken, params) {
|
||||
this.templateMaps = templateMaps;
|
||||
this.pgConnection = pgConnection;
|
||||
this.metadataBackend = metadataBackend;
|
||||
this.userLimitsApi = userLimitsApi;
|
||||
this.namedLayersAdapter = namedLayersAdapter;
|
||||
this.turboCartoCssAdapter = turboCartoCssAdapter;
|
||||
this.turboCartoAdapter = turboCartoAdapter;
|
||||
this.analysisMapConfigAdapter = analysisMapConfigAdapter;
|
||||
this.overviewsAdapter = overviewsAdapter;
|
||||
|
||||
this.owner = owner;
|
||||
@@ -39,6 +41,7 @@ function NamedMapMapConfigProvider(templateMaps, pgConnection, userLimitsApi,
|
||||
this.mapConfig = null;
|
||||
this.rendererParams = null;
|
||||
this.context = {};
|
||||
this.analysesResults = [];
|
||||
}
|
||||
|
||||
module.exports = NamedMapMapConfigProvider;
|
||||
@@ -53,15 +56,29 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
|
||||
var mapConfig = null;
|
||||
var datasource = null;
|
||||
var rendererParams;
|
||||
var apiKey;
|
||||
|
||||
step(
|
||||
function getTemplate() {
|
||||
self.getTemplate(this);
|
||||
},
|
||||
function prepareParams(err, tpl) {
|
||||
function prepareDbParams(err, tpl) {
|
||||
assert.ifError(err);
|
||||
self.template = tpl;
|
||||
|
||||
rendererParams = _.extend({}, self.params, {
|
||||
user: self.owner
|
||||
});
|
||||
self.setDBParams(self.owner, rendererParams, this);
|
||||
},
|
||||
function getUserApiKey(err) {
|
||||
assert.ifError(err);
|
||||
self.metadataBackend.getUserMapKey(self.owner, this);
|
||||
},
|
||||
function prepareParams(err, _apiKey) {
|
||||
assert.ifError(err);
|
||||
|
||||
self.template = tpl;
|
||||
apiKey = _apiKey;
|
||||
|
||||
var templateParams = {};
|
||||
if (self.config) {
|
||||
@@ -78,9 +95,36 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
|
||||
assert.ifError(err);
|
||||
return self.templateMaps.instance(self.template, templateParams);
|
||||
},
|
||||
function prepareLayergroup(err, _mapConfig) {
|
||||
function prepareAnalysisLayers(err, requestMapConfig) {
|
||||
assert.ifError(err);
|
||||
var analysisConfiguration = {
|
||||
db: {
|
||||
host: rendererParams.dbhost,
|
||||
port: rendererParams.dbport,
|
||||
dbname: rendererParams.dbname,
|
||||
user: rendererParams.dbuser,
|
||||
pass: rendererParams.dbpassword
|
||||
},
|
||||
batch: {
|
||||
username: self.owner,
|
||||
apiKey: apiKey
|
||||
}
|
||||
};
|
||||
|
||||
var filters = {};
|
||||
if (self.params.filters) {
|
||||
try {
|
||||
filters = JSON.parse(self.params.filters);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
self.analysisMapConfigAdapter.getMapConfig(analysisConfiguration, requestMapConfig, filters, this);
|
||||
},
|
||||
function prepareLayergroup(err, _mapConfig, analysesResults) {
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
self.analysesResults = analysesResults || [];
|
||||
self.namedLayersAdapter.getLayers(self.owner, _mapConfig.layers, self.pgConnection,
|
||||
function(err, layers, datasource) {
|
||||
if (err) {
|
||||
@@ -110,11 +154,11 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
|
||||
return next(null, _mapConfig, datasource);
|
||||
});
|
||||
},
|
||||
function parseTurboCartoCss(err, _mapConfig, datasource) {
|
||||
function parseTurboCarto(err, _mapConfig, datasource) {
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
|
||||
self.turboCartoCssAdapter.getLayers(self.owner, _mapConfig.layers, function (err, layers) {
|
||||
self.turboCartoAdapter.getLayers(self.owner, _mapConfig.layers, function (err, layers) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -126,17 +170,10 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
|
||||
return next(null, _mapConfig, datasource);
|
||||
});
|
||||
},
|
||||
function beforeLayergroupCreate(err, _mapConfig, _datasource) {
|
||||
function prepareContextLimits(err, _mapConfig, _datasource) {
|
||||
assert.ifError(err);
|
||||
mapConfig = _mapConfig;
|
||||
datasource = _datasource;
|
||||
rendererParams = _.extend({}, self.params, {
|
||||
user: self.owner
|
||||
});
|
||||
self.setDBParams(self.owner, rendererParams, this);
|
||||
},
|
||||
function prepareContextLimits(err) {
|
||||
assert.ifError(err);
|
||||
self.userLimitsApi.getRenderLimits(self.owner, this);
|
||||
},
|
||||
function cacheAndReturnMapConfig(err, renderLimits) {
|
||||
|
||||
@@ -3,6 +3,7 @@ var bodyParser = require('body-parser');
|
||||
var RedisPool = require('redis-mpool');
|
||||
var cartodbRedis = require('cartodb-redis');
|
||||
var _ = require('underscore');
|
||||
var debug = require('debug')('windshaft:cartodb');
|
||||
|
||||
var controller = require('./controllers');
|
||||
|
||||
@@ -27,13 +28,15 @@ var NamedMapProviderCache = require('./cache/named_map_provider_cache');
|
||||
var PgQueryRunner = require('./backends/pg_query_runner');
|
||||
var PgConnection = require('./backends/pg_connection');
|
||||
|
||||
var AnalysisBackend = require('./backends/analysis');
|
||||
|
||||
var timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png';
|
||||
var timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});
|
||||
|
||||
var MapConfigOverviewsAdapter = require('./models/mapconfig_overviews_adapter');
|
||||
|
||||
var TurboCartocssParser = require('./utils/style/turbo-cartocss-parser');
|
||||
var TurboCartocssAdapter = require('./utils/style/turbo-cartocss-adapter');
|
||||
var TurboCartoParser = require('./utils/style/turbo-carto-parser');
|
||||
var TurboCartoAdapter = require('./utils/style/turbo-carto-adapter');
|
||||
|
||||
module.exports = function(serverOptions) {
|
||||
// Make stats client globally accessible
|
||||
@@ -140,21 +143,23 @@ module.exports = function(serverOptions) {
|
||||
var tileBackend = new windshaft.backend.Tile(rendererCache);
|
||||
var mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend);
|
||||
var mapBackend = new windshaft.backend.Map(rendererCache, mapStore, mapValidatorBackend);
|
||||
var analysisBackend = new AnalysisBackend(serverOptions.analysis);
|
||||
|
||||
var layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();
|
||||
app.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
|
||||
|
||||
var overviewsAdapter = new MapConfigOverviewsAdapter(overviewsMetadataApi);
|
||||
|
||||
var turboCartoCssParser = new TurboCartocssParser(pgQueryRunner);
|
||||
var turboCartocssAdapter = new TurboCartocssAdapter(turboCartoCssParser);
|
||||
var turboCartoParser = new TurboCartoParser(pgQueryRunner);
|
||||
var turboCartoAdapter = new TurboCartoAdapter(turboCartoParser);
|
||||
|
||||
var namedMapProviderCache = new NamedMapProviderCache(
|
||||
templateMaps,
|
||||
pgConnection,
|
||||
metadataBackend,
|
||||
userLimitsApi,
|
||||
overviewsAdapter,
|
||||
turboCartocssAdapter
|
||||
turboCartoAdapter
|
||||
);
|
||||
|
||||
['update', 'delete'].forEach(function(eventType) {
|
||||
@@ -180,7 +185,8 @@ module.exports = function(serverOptions) {
|
||||
new windshaft.backend.Widget(),
|
||||
surrogateKeysCache,
|
||||
userLimitsApi,
|
||||
layergroupAffectedTablesCache
|
||||
layergroupAffectedTablesCache,
|
||||
analysisBackend
|
||||
).register(app);
|
||||
|
||||
new controller.Map(
|
||||
@@ -193,7 +199,8 @@ module.exports = function(serverOptions) {
|
||||
userLimitsApi,
|
||||
layergroupAffectedTablesCache,
|
||||
overviewsAdapter,
|
||||
turboCartocssAdapter
|
||||
turboCartoAdapter,
|
||||
analysisBackend
|
||||
).register(app);
|
||||
|
||||
new controller.NamedMaps(
|
||||
@@ -225,7 +232,7 @@ function validateOptions(opts) {
|
||||
|
||||
// Be nice and warn if configured mapnik version is != instaled mapnik version
|
||||
if (mapnik.versions.mapnik !== opts.grainstore.mapnik_version) {
|
||||
console.warn('WARNING: detected mapnik version (' + mapnik.versions.mapnik + ')' +
|
||||
debug('WARNING: detected mapnik version (' + mapnik.versions.mapnik + ')' +
|
||||
' != configured mapnik version (' + opts.grainstore.mapnik_version + ')');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ var _ = require('underscore');
|
||||
var OverviewsQueryRewriter = require('./utils/overviews_query_rewriter');
|
||||
|
||||
var overviewsQueryRewriter = new OverviewsQueryRewriter({
|
||||
zoom_level: 'CDB_ZoomFromScale(!scale_denominator!)'
|
||||
zoom_level: 'CDB_ZoomFromScale(!scale_denominator!)'
|
||||
});
|
||||
|
||||
var rendererConfig = _.defaults(global.environment.renderer || {}, {
|
||||
@@ -24,73 +24,89 @@ rendererConfig.mapnik.queryRewriter = overviewsQueryRewriter;
|
||||
|
||||
// Perform keyword substitution in statsd
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/153
|
||||
if ( global.environment.statsd ) {
|
||||
if ( global.environment.statsd.prefix ) {
|
||||
if (global.environment.statsd) {
|
||||
if (global.environment.statsd.prefix) {
|
||||
var host_token = os.hostname().split('.').reverse().join('.');
|
||||
global.environment.statsd.prefix = global.environment.statsd.prefix.replace(/:host/, host_token);
|
||||
}
|
||||
}
|
||||
|
||||
var analysisConfig = _.defaults(global.environment.analysis || {}, {
|
||||
batch: {
|
||||
inlineExecution: false,
|
||||
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
|
||||
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
bind: {
|
||||
port: global.environment.port,
|
||||
host: global.environment.host
|
||||
},
|
||||
// This is for inline maps and table maps
|
||||
base_url: global.environment.base_url_legacy || '/tiles/:table',
|
||||
// This is for inline maps and table maps
|
||||
base_url: global.environment.base_url_legacy || '/tiles/:table',
|
||||
|
||||
/// @deprecated with Windshaft-0.17.0
|
||||
///base_url_notable: '/tiles',
|
||||
/// @deprecated with Windshaft-0.17.0
|
||||
///base_url_notable: '/tiles',
|
||||
|
||||
// This is for Detached maps
|
||||
//
|
||||
// "maps" is the official, while
|
||||
// "tiles/layergroup" is for backward compatibility up to 1.6.x
|
||||
//
|
||||
base_url_mapconfig: global.environment.base_url_detached || '(?:/maps|/tiles/layergroup)',
|
||||
// This is for Detached maps
|
||||
//
|
||||
// "maps" is the official, while
|
||||
// "tiles/layergroup" is for backward compatibility up to 1.6.x
|
||||
//
|
||||
base_url_mapconfig: global.environment.base_url_detached || '(?:/maps|/tiles/layergroup)',
|
||||
|
||||
base_url_templated: global.environment.base_url_templated || '(?:/maps/named|/tiles/template)',
|
||||
base_url_templated: global.environment.base_url_templated || '(?:/maps/named|/tiles/template)',
|
||||
|
||||
grainstore: {
|
||||
map: {
|
||||
grainstore: {
|
||||
map: {
|
||||
// TODO: allow to specify in configuration
|
||||
srid: 3857
|
||||
},
|
||||
datasource: global.environment.postgres,
|
||||
cachedir: global.environment.millstone.cache_basedir,
|
||||
mapnik_version: global.environment.mapnik_version,
|
||||
mapnik_tile_format: global.environment.mapnik_tile_format || 'png',
|
||||
default_layergroup_ttl: global.environment.mapConfigTTL || 7200
|
||||
},
|
||||
statsd: global.environment.statsd,
|
||||
renderCache: {
|
||||
ttl: rendererConfig.cache_ttl,
|
||||
statsInterval: rendererConfig.statsInterval
|
||||
},
|
||||
renderer: {
|
||||
mapnik: _.defaults(rendererConfig.mapnik, {
|
||||
geojson: {
|
||||
dbPoolParams: {
|
||||
size: 16,
|
||||
idleTimeout: 3000,
|
||||
reapInterval: 1000
|
||||
},
|
||||
clipByBox2d: false,
|
||||
}
|
||||
}),
|
||||
torque: rendererConfig.torque,
|
||||
http: rendererConfig.http
|
||||
},
|
||||
// Do not send unwatch on release. See http://github.com/CartoDB/Windshaft-cartodb/issues/161
|
||||
redis: _.extend(global.environment.redis, {unwatchOnRelease: false}),
|
||||
enable_cors: global.environment.enable_cors,
|
||||
varnish_host: global.environment.varnish.host,
|
||||
varnish_port: global.environment.varnish.port,
|
||||
varnish_http_port: global.environment.varnish.http_port,
|
||||
varnish_secret: global.environment.varnish.secret,
|
||||
varnish_purge_enabled: global.environment.varnish.purge_enabled,
|
||||
fastly: global.environment.fastly || {},
|
||||
cache_enabled: global.environment.cache_enabled,
|
||||
log_format: global.environment.log_format,
|
||||
useProfiler: global.environment.useProfiler
|
||||
datasource: global.environment.postgres,
|
||||
cachedir: global.environment.millstone.cache_basedir,
|
||||
mapnik_version: global.environment.mapnik_version,
|
||||
mapnik_tile_format: global.environment.mapnik_tile_format || 'png',
|
||||
default_layergroup_ttl: global.environment.mapConfigTTL || 7200
|
||||
},
|
||||
statsd: global.environment.statsd,
|
||||
renderCache: {
|
||||
ttl: rendererConfig.cache_ttl,
|
||||
statsInterval: rendererConfig.statsInterval
|
||||
},
|
||||
renderer: {
|
||||
mapnik: _.defaults(rendererConfig.mapnik, {
|
||||
geojson: {
|
||||
dbPoolParams: {
|
||||
size: 16,
|
||||
idleTimeout: 3000,
|
||||
reapInterval: 1000
|
||||
},
|
||||
clipByBox2d: false
|
||||
}
|
||||
}),
|
||||
torque: rendererConfig.torque,
|
||||
http: rendererConfig.http
|
||||
},
|
||||
|
||||
analysis: {
|
||||
batch: {
|
||||
inlineExecution: analysisConfig.batch.inlineExecution,
|
||||
endpoint: analysisConfig.batch.endpoint,
|
||||
hostHeaderTemplate: analysisConfig.batch.hostHeaderTemplate
|
||||
}
|
||||
},
|
||||
// Do not send unwatch on release. See http://github.com/CartoDB/Windshaft-cartodb/issues/161
|
||||
redis: _.extend(global.environment.redis, {unwatchOnRelease: false}),
|
||||
enable_cors: global.environment.enable_cors,
|
||||
varnish_host: global.environment.varnish.host,
|
||||
varnish_port: global.environment.varnish.port,
|
||||
varnish_http_port: global.environment.varnish.http_port,
|
||||
varnish_secret: global.environment.varnish.secret,
|
||||
varnish_purge_enabled: global.environment.varnish.purge_enabled,
|
||||
fastly: global.environment.fastly || {},
|
||||
cache_enabled: global.environment.cache_enabled,
|
||||
log_format: global.environment.log_format,
|
||||
useProfiler: global.environment.useProfiler
|
||||
};
|
||||
|
||||
32
lib/cartodb/stats/timer.js
Normal file
@@ -0,0 +1,32 @@
|
||||
function Timer() {
|
||||
this.times = {};
|
||||
}
|
||||
|
||||
module.exports = Timer;
|
||||
|
||||
Timer.prototype.start = function(label) {
|
||||
this.timeIt(label, 'start');
|
||||
};
|
||||
|
||||
Timer.prototype.end = function(label) {
|
||||
this.timeIt(label, 'end');
|
||||
};
|
||||
|
||||
Timer.prototype.timeIt = function(label, what) {
|
||||
this.times[label] = this.times[label] || {};
|
||||
this.times[label][what] = Date.now();
|
||||
};
|
||||
|
||||
Timer.prototype.getTimes = function() {
|
||||
var self = this;
|
||||
var times = {};
|
||||
|
||||
Object.keys(this.times).forEach(function(label) {
|
||||
var stat = self.times[label];
|
||||
if (stat.start && stat.end) {
|
||||
times[label] = Math.max(0, stat.end - stat.start);
|
||||
}
|
||||
});
|
||||
|
||||
return times;
|
||||
};
|
||||
@@ -22,7 +22,7 @@ function overviews_view_for_table(table, overviews_metadata, indent) {
|
||||
|
||||
indent = indent || ' ';
|
||||
for (var z in overviews_metadata) {
|
||||
if (overviews_metadata.hasOwnProperty(z)) {
|
||||
if (overviews_metadata.hasOwnProperty(z) && z !== 'schema') {
|
||||
sorted_overviews.push([z, overviews_metadata[z].table]);
|
||||
}
|
||||
}
|
||||
@@ -39,11 +39,11 @@ function overviews_view_for_table(table, overviews_metadata, indent) {
|
||||
overview_layers.push(["_vovw_z > " + z_lo, table]);
|
||||
|
||||
selects = overview_layers.map(function(condition_table) {
|
||||
condition = condition_table[0];
|
||||
ov_table = TableNameParser.parse(condition_table[1]);
|
||||
ov_table.schema = ov_table.schema || parsed_table.schema;
|
||||
var ov_identifier = TableNameParser.table_identifier(ov_table);
|
||||
return indent + "SELECT * FROM " + ov_identifier + ", _vovw_scale WHERE " + condition;
|
||||
condition = condition_table[0];
|
||||
ov_table = TableNameParser.parse(condition_table[1]);
|
||||
ov_table.schema = ov_table.schema || parsed_table.schema;
|
||||
var ov_identifier = TableNameParser.table_identifier(ov_table);
|
||||
return indent + "SELECT * FROM " + ov_identifier + ", _vovw_scale WHERE " + condition;
|
||||
});
|
||||
|
||||
return selects.join("\n"+indent+"UNION ALL\n");
|
||||
@@ -90,13 +90,13 @@ function replace_table_in_query(sql, old_table_name, replacement) {
|
||||
return '';
|
||||
}
|
||||
} else {
|
||||
// to match a table name without schema
|
||||
// name should not begin right after a dot (i.e. have a explicit schema)
|
||||
// nor be part of another name
|
||||
// since the pattern matches the first character of the table
|
||||
// it must be put back in the replacement text
|
||||
replacement = '$01'+replacement;
|
||||
return '([^\.a-z0-9_]|^)';
|
||||
// to match a table name without schema
|
||||
// name should not begin right after a dot (i.e. have a explicit schema)
|
||||
// nor be part of another name
|
||||
// since the pattern matches the first character of the table
|
||||
// it must be put back in the replacement text
|
||||
replacement = '$01'+replacement;
|
||||
return '([^\.a-z0-9_]|^)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,8 +127,16 @@ function overviews_query(query, overviews, zoom_level_expression) {
|
||||
if (overviews.hasOwnProperty(table)) {
|
||||
var table_overviews = overviews[table];
|
||||
var table_view = overviews_view_name(table);
|
||||
var schema = table_overviews.schema;
|
||||
replacement = "(\n" + overviews_view_for_table(table, table_overviews) + "\n ) AS " + table_view;
|
||||
replaced_query = replace_table_in_query(replaced_query, table, replacement);
|
||||
var parsed_table = TableNameParser.parse(table);
|
||||
if (!parsed_table.schema && schema) {
|
||||
// replace also the qualified table name, if the table wasn't qualified
|
||||
parsed_table.schema = schema;
|
||||
table = TableNameParser.table_identifier(parsed_table);
|
||||
replaced_query = replace_table_in_query(replaced_query, table, replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( replaced_query !== query ) {
|
||||
@@ -168,18 +176,19 @@ OverviewsQueryRewriter.prototype.query = function(query, data) {
|
||||
};
|
||||
|
||||
OverviewsQueryRewriter.prototype.is_supported_query = function(sql) {
|
||||
var basic_query = /\s*SELECT\s+[\*a-z0-9_,\s]+?\s+FROM\s+((\"[^"]+\"|[a-z0-9_]+)\.)?(\"[^"]+\"|[a-z0-9_]+)\s*;?\s*/i;
|
||||
var unwrapped_query = new RegExp("^"+basic_query.source+"$", 'i');
|
||||
// queries for named maps are wrapped like this:
|
||||
var wrapped_query = new RegExp(
|
||||
"^\\s*SELECT\\s+\\*\\s+FROM\\s+\\(" +
|
||||
basic_query.source +
|
||||
"\\)\\s+AS\\s+wrapped_query\\s+WHERE\\s+\\d+=1\\s*$",
|
||||
'i'
|
||||
);
|
||||
return !!(sql.match(unwrapped_query) || sql.match(wrapped_query));
|
||||
var basic_query =
|
||||
/\s*SELECT\s+[\*a-z0-9_,\s]+?\s+FROM\s+((\"[^"]+\"|[a-z0-9_]+)\.)?(\"[^"]+\"|[a-z0-9_]+)\s*;?\s*/i;
|
||||
var unwrapped_query = new RegExp("^"+basic_query.source+"$", 'i');
|
||||
// queries for named maps are wrapped like this:
|
||||
var wrapped_query = new RegExp(
|
||||
"^\\s*SELECT\\s+\\*\\s+FROM\\s+\\(" +
|
||||
basic_query.source +
|
||||
"\\)\\s+AS\\s+wrapped_query\\s+WHERE\\s+\\d+=1\\s*$",
|
||||
'i'
|
||||
);
|
||||
return !!(sql.match(unwrapped_query) || sql.match(wrapped_query));
|
||||
};
|
||||
|
||||
OverviewsQueryRewriter.prototype.overviews_metadata = function(data) {
|
||||
return data && data.overviews;
|
||||
return data && data.overviews;
|
||||
};
|
||||
|
||||
@@ -2,26 +2,26 @@
|
||||
|
||||
var queue = require('queue-async');
|
||||
|
||||
function TurboCartocssAdapter(turboCartocssParser) {
|
||||
this.turboCartocssParser = turboCartocssParser;
|
||||
function TurboCartoAdapter(turboCartoParser) {
|
||||
this.turboCartoParser = turboCartoParser;
|
||||
}
|
||||
|
||||
module.exports = TurboCartocssAdapter;
|
||||
module.exports = TurboCartoAdapter;
|
||||
|
||||
TurboCartocssAdapter.prototype.getLayers = function (username, layers, callback) {
|
||||
TurboCartoAdapter.prototype.getLayers = function (username, layers, callback) {
|
||||
var self = this;
|
||||
|
||||
if (!layers || layers.length === 0) {
|
||||
return callback(null, layers);
|
||||
}
|
||||
|
||||
var parseCartoCssQueue = queue(layers.length);
|
||||
var parseCartoQueue = queue(layers.length);
|
||||
|
||||
layers.forEach(function(layer) {
|
||||
parseCartoCssQueue.defer(self._parseCartoCss.bind(self), username, layer);
|
||||
parseCartoQueue.defer(self._parseCartoCss.bind(self), username, layer);
|
||||
});
|
||||
|
||||
parseCartoCssQueue.awaitAll(function (err, layers) {
|
||||
parseCartoQueue.awaitAll(function (err, layers) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -30,15 +30,15 @@ TurboCartocssAdapter.prototype.getLayers = function (username, layers, callback)
|
||||
});
|
||||
};
|
||||
|
||||
TurboCartocssAdapter.prototype._parseCartoCss = function (username, layer, callback) {
|
||||
TurboCartoAdapter.prototype._parseCartoCss = function (username, layer, callback) {
|
||||
if (isNotLayerToParseCartocss(layer)) {
|
||||
return process.nextTick(function () {
|
||||
callback(null, layer);
|
||||
});
|
||||
}
|
||||
|
||||
this.turboCartocssParser.process(username, layer.options.cartocss, layer.options.sql, function (err, cartocss) {
|
||||
// Ignore turbo-cartocss errors and continue
|
||||
this.turboCartoParser.process(username, layer.options.cartocss, layer.options.sql, function (err, cartocss) {
|
||||
// Ignore turbo-carto errors and continue
|
||||
if (!err && cartocss) {
|
||||
layer.options.cartocss = cartocss;
|
||||
}
|
||||
15
lib/cartodb/utils/style/turbo-carto-parser.js
Normal file
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
var turboCarto = require('turbo-carto');
|
||||
var PostgresDatasource = require('./postgres-datasource');
|
||||
|
||||
function TurboCartoParser (pgQueryRunner) {
|
||||
this.pgQueryRunner = pgQueryRunner;
|
||||
}
|
||||
|
||||
module.exports = TurboCartoParser;
|
||||
|
||||
TurboCartoParser.prototype.process = function (username, cartocss, sql, callback) {
|
||||
var datasource = new PostgresDatasource(this.pgQueryRunner, username, sql);
|
||||
turboCarto(cartocss, datasource, callback);
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var turboCartoCss = require('turbo-cartocss');
|
||||
var PostgresDatasource = require('./postgres-datasource');
|
||||
|
||||
function TurboCartocssParser (pgQueryRunner) {
|
||||
this.pgQueryRunner = pgQueryRunner;
|
||||
}
|
||||
|
||||
module.exports = TurboCartocssParser;
|
||||
|
||||
TurboCartocssParser.prototype.process = function (username, cartocss, sql, callback) {
|
||||
var datasource = new PostgresDatasource(this.pgQueryRunner, username, sql);
|
||||
turboCartoCss(cartocss, datasource, callback);
|
||||
};
|
||||
1061
npm-shrinkwrap.json
generated
117
package.json
@@ -1,60 +1,61 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "2.32.0",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
],
|
||||
"url": "https://github.com/CartoDB/Windshaft-cartodb",
|
||||
"license": "BSD-3-Clause",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/CartoDB/Windshaft-cartodb.git"
|
||||
},
|
||||
"author": "Vizzuality <contact@vizzuality.com> (http://vizzuality.com)",
|
||||
"contributors": [
|
||||
"Simon Tokumine <simon@vizzuality.com>",
|
||||
"Javi Santana <jsantana@vizzuality.com>",
|
||||
"Sandro Santilli <strk@vizzuality.com>"
|
||||
],
|
||||
"dependencies": {
|
||||
"express": "~4.13.3",
|
||||
"body-parser": "~1.14.0",
|
||||
"debug": "~2.2.0",
|
||||
"step-profiler": "~0.2.1",
|
||||
"node-statsd": "~0.0.7",
|
||||
"underscore" : "~1.6.0",
|
||||
"dot": "~1.0.2",
|
||||
"windshaft": "1.17.0",
|
||||
"step": "~0.0.6",
|
||||
"queue-async": "~1.0.7",
|
||||
"request": "~2.62.0",
|
||||
"cartodb-redis": "~0.13.0",
|
||||
"cartodb-psql": "~0.4.0",
|
||||
"fastly-purge": "~1.0.1",
|
||||
"redis-mpool": "~0.4.0",
|
||||
"lru-cache": "2.6.5",
|
||||
"lzma": "~1.3.7",
|
||||
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb",
|
||||
"cartodb-query-tables": "~0.1.0",
|
||||
"turbo-cartocss": "0.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"istanbul": "~0.3.6",
|
||||
"mocha": "~1.21.4",
|
||||
"nock": "~2.11.0",
|
||||
"jshint": "~2.6.0",
|
||||
"redis": "~0.8.6",
|
||||
"strftime": "~0.8.2",
|
||||
"semver": "~1.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "make pre-install",
|
||||
"test": "make test-all"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8 <0.11",
|
||||
"npm": ">=2.14.16"
|
||||
}
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "2.39.0",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
],
|
||||
"url": "https://github.com/CartoDB/Windshaft-cartodb",
|
||||
"license": "BSD-3-Clause",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/CartoDB/Windshaft-cartodb.git"
|
||||
},
|
||||
"author": "Vizzuality <contact@vizzuality.com> (http://vizzuality.com)",
|
||||
"contributors": [
|
||||
"Simon Tokumine <simon@vizzuality.com>",
|
||||
"Javi Santana <jsantana@vizzuality.com>",
|
||||
"Sandro Santilli <strk@vizzuality.com>"
|
||||
],
|
||||
"dependencies": {
|
||||
"body-parser": "~1.14.0",
|
||||
"camshaft": "0.6.0",
|
||||
"cartodb-psql": "~0.6.1",
|
||||
"cartodb-query-tables": "~0.1.0",
|
||||
"cartodb-redis": "~0.13.0",
|
||||
"debug": "~2.2.0",
|
||||
"dot": "~1.0.2",
|
||||
"express": "~4.13.3",
|
||||
"fastly-purge": "~1.0.1",
|
||||
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb",
|
||||
"lru-cache": "2.6.5",
|
||||
"lzma": "~1.3.7",
|
||||
"node-statsd": "~0.0.7",
|
||||
"queue-async": "~1.0.7",
|
||||
"redis-mpool": "~0.4.0",
|
||||
"request": "~2.62.0",
|
||||
"step": "~0.0.6",
|
||||
"step-profiler": "~0.3.0",
|
||||
"turbo-carto": "0.7.1",
|
||||
"underscore": "~1.6.0",
|
||||
"windshaft": "1.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"istanbul": "~0.4.3",
|
||||
"jshint": "~2.6.0",
|
||||
"mocha": "~1.21.4",
|
||||
"nock": "~2.11.0",
|
||||
"redis": "~0.8.6",
|
||||
"semver": "~1.1.4",
|
||||
"strftime": "~0.8.2"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "make pre-install",
|
||||
"test": "make test-all"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8 <0.11",
|
||||
"npm": ">=2.14.16"
|
||||
}
|
||||
}
|
||||
|
||||
148
test/acceptance/analysis/analysis-layers-dataviews.js
Normal file
@@ -0,0 +1,148 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var TestClient = require('../../support/test-client');
|
||||
var dot = require('dot');
|
||||
|
||||
describe('analysis-layers-dataviews', function() {
|
||||
|
||||
var multitypeStyleTemplate = dot.template([
|
||||
"#points['mapnik::geometry_type'=1] {",
|
||||
" marker-fill-opacity: {{=it._opacity}};",
|
||||
" marker-line-color: #FFF;",
|
||||
" marker-line-width: 0.5;",
|
||||
" marker-line-opacity: {{=it._opacity}};",
|
||||
" marker-placement: point;",
|
||||
" marker-type: ellipse;",
|
||||
" marker-width: 8;",
|
||||
" marker-fill: {{=it._color}};",
|
||||
" marker-allow-overlap: true;",
|
||||
"}",
|
||||
"#lines['mapnik::geometry_type'=2] {",
|
||||
" line-color: {{=it._color}};",
|
||||
" line-width: 2;",
|
||||
" line-opacity: {{=it._opacity}};",
|
||||
"}",
|
||||
"#polygons['mapnik::geometry_type'=3] {",
|
||||
" polygon-fill: {{=it._color}};",
|
||||
" polygon-opacity: {{=it._opacity}};",
|
||||
" line-color: #FFF;",
|
||||
" line-width: 0.5;",
|
||||
" line-opacity: {{=it._opacity}};",
|
||||
"}"
|
||||
].join('\n'));
|
||||
|
||||
|
||||
function cartocss(color, opacity) {
|
||||
return multitypeStyleTemplate({
|
||||
_color: color || '#F11810',
|
||||
_opacity: Number.isFinite(opacity) ? opacity : 1
|
||||
});
|
||||
}
|
||||
|
||||
function createMapConfig(layers, dataviews, analysis) {
|
||||
return {
|
||||
version: '1.5.0',
|
||||
layers: layers,
|
||||
dataviews: dataviews || {},
|
||||
analyses: analysis || []
|
||||
};
|
||||
}
|
||||
|
||||
var DEFAULT_MULTITYPE_STYLE = cartocss();
|
||||
|
||||
var mapConfig = createMapConfig(
|
||||
[
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"source": {
|
||||
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
|
||||
},
|
||||
"cartocss": DEFAULT_MULTITYPE_STYLE,
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
{
|
||||
pop_max_histogram: {
|
||||
source: {
|
||||
id: '2570e105-7b37-40d2-bdf4-1af889598745'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'pop_max'
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
{
|
||||
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from populated_places_simple_reduced"
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
it('should get histogram dataview', function(done) {
|
||||
var testClient = new TestClient(mapConfig, 1234);
|
||||
|
||||
testClient.getDataview('pop_max_histogram', function(err, dataview) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.equal(dataview.type, 'histogram');
|
||||
assert.equal(dataview.bins_start, 0);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should get a filtered histogram dataview', function(done) {
|
||||
var testClient = new TestClient(mapConfig, 1234);
|
||||
|
||||
var params = {
|
||||
filters: {
|
||||
dataviews: {
|
||||
pop_max_histogram: {
|
||||
min: 2e6
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
testClient.getDataview('pop_max_histogram', params, function(err, dataview) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.equal(dataview.type, 'histogram');
|
||||
assert.equal(dataview.bins_start, 2008000);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip the filter when sending own_filter=0 for histogram dataview', function(done) {
|
||||
var testClient = new TestClient(mapConfig, 1234);
|
||||
|
||||
var params = {
|
||||
filters: {
|
||||
dataviews: {
|
||||
pop_max_histogram: {
|
||||
min: 2e6
|
||||
}
|
||||
}
|
||||
},
|
||||
own_filter: 0
|
||||
};
|
||||
|
||||
testClient.getDataview('pop_max_histogram', params, function(err, dataview) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.equal(dataview.type, 'histogram');
|
||||
assert.equal(dataview.bins_start, 0);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
81
test/acceptance/analysis/analysis-layers-geojson.js
Normal file
@@ -0,0 +1,81 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var TestClient = require('../../support/test-client');
|
||||
|
||||
describe('analysis-layers-dataviews-geojson', function() {
|
||||
|
||||
function createMapConfig(layers, dataviews, analysis) {
|
||||
return {
|
||||
version: '1.5.0',
|
||||
layers: layers,
|
||||
dataviews: dataviews || {},
|
||||
analyses: analysis || []
|
||||
};
|
||||
}
|
||||
|
||||
var CARTOCSS = [
|
||||
"#points {",
|
||||
" marker-fill-opacity: 1.0;",
|
||||
" marker-line-color: #FFF;",
|
||||
" marker-line-width: 0.5;",
|
||||
" marker-line-opacity: 1.0;",
|
||||
" marker-placement: point;",
|
||||
" marker-type: ellipse;",
|
||||
" marker-width: 8;",
|
||||
" marker-fill: red;",
|
||||
" marker-allow-overlap: true;",
|
||||
"}"
|
||||
].join('\n');
|
||||
|
||||
var mapConfig = createMapConfig(
|
||||
[
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"source": {
|
||||
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
|
||||
},
|
||||
"cartocss": CARTOCSS,
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
{
|
||||
pop_max_histogram: {
|
||||
source: {
|
||||
id: '2570e105-7b37-40d2-bdf4-1af889598745'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'pop_max'
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
{
|
||||
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from populated_places_simple_reduced"
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
it('should get pop_max column from dataview', function(done) {
|
||||
var testClient = new TestClient(mapConfig, 1234);
|
||||
|
||||
testClient.getTile(0, 0, 0, {format: 'geojson', layers: 0}, function(err, res, geojson) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(Array.isArray(geojson.features));
|
||||
assert.ok(geojson.features.length > 0);
|
||||
var feature = geojson.features[0];
|
||||
assert.ok(feature.properties.hasOwnProperty('pop_max'), 'Missing pop_max property');
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
672
test/acceptance/analysis/analysis-layers-use-cases.js
Normal file
@@ -0,0 +1,672 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var TestClient = require('../../support/test-client');
|
||||
var dot = require('dot');
|
||||
var debug = require('debug')('windshaft:cartodb:test');
|
||||
|
||||
describe('analysis-layers use cases', function() {
|
||||
|
||||
|
||||
var multitypeStyleTemplate = dot.template([
|
||||
"#points['mapnik::geometry_type'=1] {",
|
||||
" marker-fill-opacity: {{=it._opacity}};",
|
||||
" marker-line-color: #FFF;",
|
||||
" marker-line-width: 0.5;",
|
||||
" marker-line-opacity: {{=it._opacity}};",
|
||||
" marker-placement: point;",
|
||||
" marker-type: ellipse;",
|
||||
" marker-width: 8;",
|
||||
" marker-fill: {{=it._color}};",
|
||||
" marker-allow-overlap: true;",
|
||||
"}",
|
||||
"#lines['mapnik::geometry_type'=2] {",
|
||||
" line-color: {{=it._color}};",
|
||||
" line-width: 2;",
|
||||
" line-opacity: {{=it._opacity}};",
|
||||
"}",
|
||||
"#polygons['mapnik::geometry_type'=3] {",
|
||||
" polygon-fill: {{=it._color}};",
|
||||
" polygon-opacity: {{=it._opacity}};",
|
||||
" line-color: #FFF;",
|
||||
" line-width: 0.5;",
|
||||
" line-opacity: {{=it._opacity}};",
|
||||
"}"
|
||||
].join('\n'));
|
||||
|
||||
|
||||
function cartocss(color, opacity) {
|
||||
return multitypeStyleTemplate({
|
||||
_color: color || '#F11810',
|
||||
_opacity: Number.isFinite(opacity) ? opacity : 1
|
||||
});
|
||||
}
|
||||
|
||||
function mapConfig(layers, dataviews, analysis) {
|
||||
return {
|
||||
version: '1.5.0',
|
||||
layers: layers,
|
||||
dataviews: dataviews || {},
|
||||
analysis: analysis || []
|
||||
};
|
||||
}
|
||||
|
||||
function analysisDef(analysis) {
|
||||
return JSON.stringify(analysis);
|
||||
}
|
||||
|
||||
var DEFAULT_MULTITYPE_STYLE = cartocss();
|
||||
|
||||
var TILE_ANALYSIS_TABLES = { z: 14, x: 8023, y: 6177 };
|
||||
|
||||
var useCases = [
|
||||
{
|
||||
desc: '1 mapnik layer',
|
||||
mapConfig: {
|
||||
version: '1.5.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: "select * from analysis_rent_listings",
|
||||
cartocss: DEFAULT_MULTITYPE_STYLE,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
desc: '2 mapnik layers',
|
||||
mapConfig: mapConfig([
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: "select * from analysis_banks",
|
||||
cartocss: cartocss('#2167AB'),
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: "select * from analysis_rent_listings",
|
||||
cartocss: DEFAULT_MULTITYPE_STYLE,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
{
|
||||
desc: 'rent listings + buffer over atm-machines',
|
||||
mapConfig: mapConfig([
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: "select * from analysis_rent_listings",
|
||||
cartocss: DEFAULT_MULTITYPE_STYLE,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'analysis',
|
||||
options: {
|
||||
def: analysisDef({
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from analysis_banks"
|
||||
}
|
||||
},
|
||||
"radius": 250
|
||||
}
|
||||
}),
|
||||
cartocss: cartocss('black', 0.5)
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
{
|
||||
desc: 'rent listings + point-in-polygon from buffer atm-machines and rent listings',
|
||||
mapConfig: mapConfig([
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: "select * from analysis_rent_listings",
|
||||
cartocss: DEFAULT_MULTITYPE_STYLE,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'analysis',
|
||||
options: {
|
||||
def: analysisDef({
|
||||
"type": "point-in-polygon",
|
||||
"params": {
|
||||
"pointsSource": {
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from analysis_rent_listings"
|
||||
}
|
||||
},
|
||||
"polygonsSource": {
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from analysis_banks"
|
||||
}
|
||||
},
|
||||
"radius": 250
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
cartocss: cartocss('green', 1.0)
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
{
|
||||
desc: 'point-in-polygon from buffer atm-machines and rent listings + rent listings',
|
||||
mapConfig: mapConfig([
|
||||
{
|
||||
type: 'analysis',
|
||||
options: {
|
||||
def: analysisDef({
|
||||
"type": "point-in-polygon",
|
||||
"params": {
|
||||
"pointsSource": {
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from analysis_rent_listings"
|
||||
}
|
||||
},
|
||||
"polygonsSource": {
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from analysis_banks"
|
||||
}
|
||||
},
|
||||
"radius": 250
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
cartocss: cartocss('green', 1.0)
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: "select * from analysis_rent_listings",
|
||||
cartocss: DEFAULT_MULTITYPE_STYLE,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
{
|
||||
desc: 'buffer + point-in-polygon from buffer atm-machines and rent listings + rent listings',
|
||||
mapConfig: mapConfig([
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: "select * from analysis_rent_listings",
|
||||
cartocss: DEFAULT_MULTITYPE_STYLE,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'analysis',
|
||||
options: {
|
||||
def: analysisDef({
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from analysis_banks"
|
||||
}
|
||||
},
|
||||
"radius": 300
|
||||
}
|
||||
}),
|
||||
cartocss: cartocss('magenta', 0.5)
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'analysis',
|
||||
options: {
|
||||
def: analysisDef({
|
||||
"type": "point-in-polygon",
|
||||
"params": {
|
||||
"pointsSource": {
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from analysis_rent_listings"
|
||||
}
|
||||
},
|
||||
"polygonsSource": {
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from analysis_banks"
|
||||
}
|
||||
},
|
||||
"radius": 300
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
cartocss: cartocss('green', 1.0)
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
{
|
||||
skip: true,
|
||||
desc: 'buffer + point-in-polygon from buffer atm-machines and rent listings + rent listings',
|
||||
mapConfig: mapConfig([
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
"source": { id: "a" },
|
||||
"cartocss": DEFAULT_MULTITYPE_STYLE,
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
"source": { id: "b1" },
|
||||
"cartocss": cartocss('green', 1.0),
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
"source": { id: "b2" },
|
||||
"cartocss": cartocss('magenta', 0.5),
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
id: "b2",
|
||||
options: {
|
||||
def: analysisDef({
|
||||
"type": "count-in-polygon",
|
||||
"id": "a0",
|
||||
"params": {
|
||||
"columnName": 'count_airbnb',
|
||||
"pointsSource": {
|
||||
"type": "source",
|
||||
"params": {
|
||||
query: "select * from analysis_rent_listings"
|
||||
},
|
||||
dataviews: {
|
||||
price_histogram: {
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'price'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"polygonsSource": {
|
||||
"id": "b1",
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"id": "b0",
|
||||
"type": "source",
|
||||
"params": {
|
||||
query: "select * from analysis_banks"
|
||||
}
|
||||
},
|
||||
"radius": 250
|
||||
},
|
||||
dataviews: {
|
||||
bank_category: {
|
||||
type: 'aggregation',
|
||||
options: {
|
||||
column: 'bank'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
dataviews: {
|
||||
count_histogram: {
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'count_airbnb'
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
cartocss: cartocss('green', 1.0)
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
{
|
||||
skip: true,
|
||||
desc: 'I. Distribution centers',
|
||||
mapConfig: mapConfig(
|
||||
// layers
|
||||
[
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
"source": { id: "b0" },
|
||||
"cartocss": [
|
||||
"#distribution_centers {",
|
||||
" marker-fill-opacity: 1.0;",
|
||||
" marker-line-color: #FFF;",
|
||||
" marker-line-width: 0.5;",
|
||||
" marker-line-opacity: 0.7;",
|
||||
" marker-placement: point;",
|
||||
" marker-type: ellipse;",
|
||||
" marker-width: 8;",
|
||||
" marker-fill: blue;",
|
||||
" marker-allow-overlap: true;",
|
||||
"}"
|
||||
].join('\n'),
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
"source": { id: "a0" },
|
||||
"cartocss": [
|
||||
"#shops {",
|
||||
" marker-fill-opacity: 1.0;",
|
||||
" marker-line-color: #FFF;",
|
||||
" marker-line-width: 0.5;",
|
||||
" marker-line-opacity: 0.7;",
|
||||
" marker-placement: point;",
|
||||
" marker-type: ellipse;",
|
||||
" marker-width: 8;",
|
||||
" marker-fill: red;",
|
||||
" marker-allow-overlap: true;",
|
||||
"}"
|
||||
].join('\n'),
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
"source": { id: "a1" },
|
||||
"cartocss": [
|
||||
"#routing {",
|
||||
" line-color: ramp([routing_time], colorbrewer(Reds));",
|
||||
" line-width: ramp([routing_time], 2, 8);",
|
||||
" line-opacity: 1.0;",
|
||||
"}"
|
||||
].join('\n'),
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
// dataviews
|
||||
{
|
||||
distribution_center_name_category: {
|
||||
source: { id: 'b0' },
|
||||
type: 'aggregation',
|
||||
options: {
|
||||
column: 'name'
|
||||
}
|
||||
},
|
||||
time_histogram: {
|
||||
source: { id: 'a1' },
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'routing_time'
|
||||
}
|
||||
},
|
||||
distance_histogram: {
|
||||
source: { id: 'a1' },
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'routing_distance'
|
||||
}
|
||||
}
|
||||
},
|
||||
// analysis
|
||||
[
|
||||
{
|
||||
id: 'a1',
|
||||
type: 'routing-n-to-n',
|
||||
params: {
|
||||
// distanceColumn: 'routing_distance',
|
||||
// timeColumn: 'routing_time',
|
||||
originSource: {
|
||||
id: 'b0',
|
||||
type: 'source',
|
||||
params: {
|
||||
query: 'select * from distribution_centers'
|
||||
}
|
||||
},
|
||||
destinationSource: {
|
||||
id: 'a0',
|
||||
type: 'source',
|
||||
params: {
|
||||
query: 'select * from shops'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
skip: true,
|
||||
desc: 'II. Population analysis',
|
||||
mapConfig: mapConfig(
|
||||
// layers
|
||||
[
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
"source": { id: "a2" },
|
||||
"cartocss": [
|
||||
"#count_in_polygon {",
|
||||
" polygon-opacity: 1.0",
|
||||
" line-color: #FFF;",
|
||||
" line-width: 0.5;",
|
||||
" line-opacity: 0.7",
|
||||
" polygon-fill: ramp([estimated_people], colorbrewer(Reds));",
|
||||
"}"
|
||||
].join('\n'),
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
"source": { id: "a0" },
|
||||
"cartocss": DEFAULT_MULTITYPE_STYLE,
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
// dataviews
|
||||
{
|
||||
total_population_formula: {
|
||||
"source": { id: "a3" },
|
||||
type: 'formula',
|
||||
options: {
|
||||
column: 'total_population',
|
||||
operation: 'sum'
|
||||
}
|
||||
},
|
||||
people_histogram: { // this injects a range filter at `a2` node output
|
||||
"source": { id: "a2" },
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'estimated_people'
|
||||
}
|
||||
},
|
||||
subway_line_category: { // this injects a category filter at `a0` node output
|
||||
"source": { id: "a0" },
|
||||
type: 'aggregation',
|
||||
options: {
|
||||
column: 'subway_line'
|
||||
}
|
||||
}
|
||||
},
|
||||
// analysis
|
||||
[
|
||||
{
|
||||
id: 'a3',
|
||||
// this will union the polygons, produce just one polygon, and calculate the total population for it
|
||||
type: 'total-population',
|
||||
params: {
|
||||
columnName: 'total_population',
|
||||
source: {
|
||||
id: 'a2',
|
||||
type: 'estimated-population',
|
||||
params: {
|
||||
columnName: 'estimated_people',
|
||||
source: {
|
||||
id: 'a1',
|
||||
type: 'trade-area',
|
||||
params: {
|
||||
source: {
|
||||
"id": "a0",
|
||||
"type": "source",
|
||||
"params": {
|
||||
query: "select * from subway_stops"
|
||||
}
|
||||
},
|
||||
kind: 'walk',
|
||||
time: 300
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
{
|
||||
skip: true,
|
||||
desc: 'III. Point in polygon',
|
||||
mapConfig: mapConfig(
|
||||
// layers
|
||||
[
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
"source": { id: "a1" },
|
||||
"cartocss": [
|
||||
"#count_in_polygon {",
|
||||
" polygon-opacity: 1.0",
|
||||
" line-color: #FFF;",
|
||||
" line-width: 0.5;",
|
||||
" line-opacity: 0.7",
|
||||
" polygon-fill: ramp([count_people], colorbrewer(Reds));",
|
||||
"}"
|
||||
].join('\n'),
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
// dataviews
|
||||
{
|
||||
age_histogram: {
|
||||
"source": { id: "a0" },
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'age'
|
||||
}
|
||||
},
|
||||
income_histogram: {
|
||||
"source": { id: "a0" },
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'income'
|
||||
}
|
||||
},
|
||||
gender_category: {
|
||||
"source": { id: "a0" },
|
||||
type: 'aggregation',
|
||||
options: {
|
||||
column: 'gender'
|
||||
}
|
||||
}
|
||||
},
|
||||
// analysis
|
||||
[
|
||||
{
|
||||
"id": "a1",
|
||||
"type": "count-in-polygon",
|
||||
"params": {
|
||||
"columnName": 'count_people',
|
||||
"pointsSource": {
|
||||
"id": 'a0',
|
||||
"type": "source",
|
||||
"params": {
|
||||
query: "select the_geom, age, gender, income from people"
|
||||
}
|
||||
},
|
||||
"polygonsSource": {
|
||||
"id": "b0",
|
||||
"type": "source",
|
||||
"params": {
|
||||
query: "select * from postal_codes"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
useCases.forEach(function(useCase, imageIdx) {
|
||||
if (!!useCase.skip) {
|
||||
debug(JSON.stringify(useCase.mapConfig, null, 4));
|
||||
}
|
||||
it.skip('should implement use case: "' + useCase.desc + '"', function(done) {
|
||||
|
||||
var testClient = new TestClient(useCase.mapConfig, 1234);
|
||||
|
||||
var tile = useCase.tile || TILE_ANALYSIS_TABLES;
|
||||
|
||||
testClient.getTile(tile.z, tile.x, tile.y, function(err, res, image) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
image.save('/tmp/tests/' + imageIdx + '---' + useCase.desc.replace(/\s/g, '-') + '.png');
|
||||
|
||||
assert.equal(image.width(), 256);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
330
test/acceptance/analysis/analysis-layers.js
Normal file
@@ -0,0 +1,330 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var TestClient = require('../../support/test-client');
|
||||
var dot = require('dot');
|
||||
|
||||
describe('analysis-layers', function() {
|
||||
|
||||
var IMAGE_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
var multitypeStyleTemplate = dot.template([
|
||||
"#points['mapnik::geometry_type'=1] {",
|
||||
" marker-fill-opacity: {{=it._opacity}};",
|
||||
" marker-line-color: #FFF;",
|
||||
" marker-line-width: 0.5;",
|
||||
" marker-line-opacity: {{=it._opacity}};",
|
||||
" marker-placement: point;",
|
||||
" marker-type: ellipse;",
|
||||
" marker-width: 8;",
|
||||
" marker-fill: {{=it._color}};",
|
||||
" marker-allow-overlap: true;",
|
||||
"}",
|
||||
"#lines['mapnik::geometry_type'=2] {",
|
||||
" line-color: {{=it._color}};",
|
||||
" line-width: 2;",
|
||||
" line-opacity: {{=it._opacity}};",
|
||||
"}",
|
||||
"#polygons['mapnik::geometry_type'=3] {",
|
||||
" polygon-fill: {{=it._color}};",
|
||||
" polygon-opacity: {{=it._opacity}};",
|
||||
" line-color: #FFF;",
|
||||
" line-width: 0.5;",
|
||||
" line-opacity: {{=it._opacity}};",
|
||||
"}"
|
||||
].join('\n'));
|
||||
|
||||
|
||||
function cartocss(color, opacity) {
|
||||
return multitypeStyleTemplate({
|
||||
_color: color || '#F11810',
|
||||
_opacity: Number.isFinite(opacity) ? opacity : 1
|
||||
});
|
||||
}
|
||||
|
||||
function mapConfig(layers, dataviews, analysis) {
|
||||
return {
|
||||
version: '1.5.0',
|
||||
layers: layers,
|
||||
dataviews: dataviews || {},
|
||||
analyses: analysis || []
|
||||
};
|
||||
}
|
||||
|
||||
var DEFAULT_MULTITYPE_STYLE = cartocss();
|
||||
|
||||
var TILE_ANALYSIS_TABLES = { z: 0, x: 0, y: 0 };
|
||||
|
||||
var useCases = [
|
||||
{
|
||||
desc: 'basic source-id mapnik layer',
|
||||
fixture: 'basic-source-id-mapnik-layer.png',
|
||||
mapConfig: mapConfig(
|
||||
[
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"source": {
|
||||
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
|
||||
},
|
||||
"cartocss": DEFAULT_MULTITYPE_STYLE,
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
{},
|
||||
[
|
||||
{
|
||||
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from populated_places_simple_reduced"
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
desc: 'buffer over source',
|
||||
fixture: 'buffer-over-source.png',
|
||||
tile: { z: 7, x: 61, y: 47 },
|
||||
mapConfig: mapConfig(
|
||||
[
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"source": {
|
||||
"id": "HEAD"
|
||||
},
|
||||
"cartocss": DEFAULT_MULTITYPE_STYLE,
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
{},
|
||||
[
|
||||
{
|
||||
"id": "HEAD",
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from populated_places_simple_reduced"
|
||||
}
|
||||
},
|
||||
"radius": 50000
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
useCases.forEach(function(useCase) {
|
||||
it('should implement use case: "' + useCase.desc + '"', function(done) {
|
||||
|
||||
var testClient = new TestClient(useCase.mapConfig, 1234);
|
||||
|
||||
var tile = useCase.tile || TILE_ANALYSIS_TABLES;
|
||||
|
||||
testClient.getTile(tile.z, tile.x, tile.y, function(err, res, image) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
// To generate images use:
|
||||
// image.save('/tmp/' + useCase.desc.replace(/\s/g, '-') + '.png');
|
||||
|
||||
var fixturePath = './test/fixtures/analysis/' + useCase.fixture;
|
||||
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, function(err) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT fail for non-authenticated requests when it is just source', function(done) {
|
||||
var useCase = useCases[0];
|
||||
|
||||
// No API key here
|
||||
var testClient = new TestClient(useCase.mapConfig);
|
||||
|
||||
testClient.getLayergroup(function(err, layergroupResult) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.equal(layergroupResult.metadata.layers.length, 1);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail for non-authenticated requests that has a node other than "source"', function(done) {
|
||||
var useCase = useCases[1];
|
||||
|
||||
// No API key here
|
||||
var testClient = new TestClient(useCase.mapConfig);
|
||||
|
||||
var PERMISSION_DENIED_RESPONSE = {
|
||||
status: 403,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
testClient.getLayergroup(PERMISSION_DENIED_RESPONSE, function(err, layergroupResult) {
|
||||
assert.ok(!err, err);
|
||||
assert.deepEqual(
|
||||
layergroupResult.errors,
|
||||
["Analysis requires authentication with API key: permission denied."]
|
||||
);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should retrieve enough metadata about analyses', function(done) {
|
||||
var useCase = useCases[1];
|
||||
|
||||
// No API key here
|
||||
var testClient = new TestClient(useCase.mapConfig, 1234);
|
||||
|
||||
testClient.getLayergroup(function(err, layergroupResult) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(
|
||||
Array.isArray(layergroupResult.metadata.analyses),
|
||||
'Missing "analyses" array metadata from: ' + JSON.stringify(layergroupResult)
|
||||
);
|
||||
var analyses = layergroupResult.metadata.analyses;
|
||||
assert.equal(analyses.length, 1, 'Invalid number of analyses in metadata');
|
||||
var nodes = analyses[0].nodes;
|
||||
var nodesIds = Object.keys(nodes);
|
||||
assert.deepEqual(nodesIds, ['2570e105-7b37-40d2-bdf4-1af889598745', 'HEAD']);
|
||||
nodesIds.forEach(function(nodeId) {
|
||||
var node = nodes[nodeId];
|
||||
assert.ok(node.hasOwnProperty('url'), 'Missing "url" attribute in node');
|
||||
assert.ok(node.hasOwnProperty('status'), 'Missing "status" attribute in node');
|
||||
assert.ok(node.hasOwnProperty('query'), 'Missing "status" attribute in node');
|
||||
});
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have analysis metadata when dataviews point to source node', function(done) {
|
||||
var useCase = {
|
||||
desc: 'basic source-id mapnik layer',
|
||||
fixture: 'basic-source-id-mapnik-layer.png',
|
||||
mapConfig: mapConfig(
|
||||
[
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"sql": "select * from populated_places_simple_reduced",
|
||||
"cartocss": DEFAULT_MULTITYPE_STYLE,
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"source": {
|
||||
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
|
||||
},
|
||||
"cartocss": DEFAULT_MULTITYPE_STYLE,
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"sql": "select * from populated_places_simple_reduced",
|
||||
"cartocss": DEFAULT_MULTITYPE_STYLE,
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
{
|
||||
pop_max_histogram: {
|
||||
source: {
|
||||
id: '2570e105-7b37-40d2-bdf4-1af889598745'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'pop_max'
|
||||
}
|
||||
},
|
||||
scalerank_histogram: {
|
||||
source: {
|
||||
id: '2570e105-7b37-40d2-bdf4-1af889598745'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'scalerank'
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
{
|
||||
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from populated_places_simple_reduced"
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
};
|
||||
|
||||
var testClient = new TestClient(useCase.mapConfig, 1234);
|
||||
|
||||
testClient.getLayergroup(function(err, layergroupResult) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(
|
||||
Array.isArray(layergroupResult.metadata.analyses),
|
||||
'Missing "analyses" array metadata from: ' + JSON.stringify(layergroupResult)
|
||||
);
|
||||
var analyses = layergroupResult.metadata.analyses;
|
||||
assert.equal(analyses.length, 1, 'Invalid number of analyses in metadata');
|
||||
var nodes = analyses[0].nodes;
|
||||
|
||||
var nodesIds = Object.keys(nodes);
|
||||
assert.deepEqual(nodesIds, ['2570e105-7b37-40d2-bdf4-1af889598745']);
|
||||
nodesIds.forEach(function(nodeId) {
|
||||
var node = nodes[nodeId];
|
||||
assert.ok(node.hasOwnProperty('url'), 'Missing "url" attribute in node');
|
||||
assert.ok(node.hasOwnProperty('status'), 'Missing "status" attribute in node');
|
||||
assert.ok(node.hasOwnProperty('query'), 'Missing "status" attribute in node');
|
||||
});
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should response with custom cache headers for node status endpoints', function(done) {
|
||||
var useCase = useCases[1];
|
||||
|
||||
// No API key here
|
||||
var testClient = new TestClient(useCase.mapConfig, 1234);
|
||||
|
||||
testClient.getNodeStatus('HEAD', function(err, response, nodeStatus) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.equal(nodeStatus.status, 'ready');
|
||||
|
||||
var headers = response.headers;
|
||||
|
||||
assert.equal(headers['cache-control'], 'public,max-age=5');
|
||||
|
||||
var lastModified = new Date(headers['last-modified']);
|
||||
var tenSecondsInMs = 1e5;
|
||||
assert.ok(Date.now() - lastModified.getTime() < tenSecondsInMs);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
67
test/acceptance/analysis/error-cases.js
Normal file
@@ -0,0 +1,67 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var TestClient = require('../../support/test-client');
|
||||
|
||||
describe('analysis-layers error cases', function() {
|
||||
function createMapConfig(layers, dataviews, analysis) {
|
||||
return {
|
||||
version: '1.5.0',
|
||||
layers: layers,
|
||||
dataviews: dataviews || {},
|
||||
analyses: analysis || []
|
||||
};
|
||||
}
|
||||
|
||||
var ERROR_RESPONSE = {
|
||||
status: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
it('should handle missing analysis nodes for layers', function(done) {
|
||||
var mapConfig = createMapConfig(
|
||||
[
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"source": {
|
||||
"id": "INVALID-SOURCE-ID"
|
||||
},
|
||||
"cartocss": '#polygons { polygon-fill: red; }',
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
{},
|
||||
[
|
||||
{
|
||||
"id": "HEAD",
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from populated_places_simple_reduced"
|
||||
}
|
||||
},
|
||||
"radius": 50000
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
var testClient = new TestClient(mapConfig, 1234);
|
||||
|
||||
testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroupResult) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.equal(layergroupResult.errors.length, 1);
|
||||
assert.equal(layergroupResult.errors[0], 'Missing analysis node.id="INVALID-SOURCE-ID" for layer=0');
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
245
test/acceptance/analysis/named-maps.js
Normal file
@@ -0,0 +1,245 @@
|
||||
var assert = require('../../support/assert');
|
||||
var step = require('step');
|
||||
|
||||
var helper = require('../../support/test_helper');
|
||||
|
||||
var CartodbWindshaft = require('../../../lib/cartodb/server');
|
||||
var serverOptions = require('../../../lib/cartodb/server_options');
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
var LayergroupToken = require('../../../lib/cartodb/models/layergroup_token');
|
||||
|
||||
describe('named-maps analysis', function() {
|
||||
|
||||
var IMAGE_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
var username = 'localhost';
|
||||
var widgetsTemplateName = 'widgets-template';
|
||||
|
||||
var layergroupid;
|
||||
var layergroup;
|
||||
var keysToDelete;
|
||||
|
||||
beforeEach(function(done) {
|
||||
keysToDelete = {};
|
||||
|
||||
var widgetsTemplate = {
|
||||
version: '0.0.1',
|
||||
name: widgetsTemplateName,
|
||||
layergroup: {
|
||||
version: '1.5.0',
|
||||
layers: [
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"source": {
|
||||
"id": "HEAD"
|
||||
},
|
||||
"cartocss": '#buffer { polygon-fill: red; }',
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
dataviews: {
|
||||
pop_max_histogram: {
|
||||
source: {
|
||||
id: 'HEAD'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'pop_max'
|
||||
}
|
||||
}
|
||||
},
|
||||
analyses: [
|
||||
{
|
||||
"id": "HEAD",
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from populated_places_simple_reduced"
|
||||
}
|
||||
},
|
||||
"radius": 50000
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var template_params = {};
|
||||
|
||||
step(
|
||||
function createTemplate()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: username,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(widgetsTemplate)
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function instantiateTemplate(err, res) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.deepEqual(JSON.parse(res.body), { template_id: widgetsTemplateName });
|
||||
var next = this;
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/map/named/' + widgetsTemplateName,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: username,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(template_params)
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
next(null, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function finish(err, res) {
|
||||
assert.ifError(err);
|
||||
|
||||
layergroup = JSON.parse(res.body);
|
||||
assert.ok(layergroup.hasOwnProperty('layergroupid'), "Missing 'layergroupid' from: " + res.body);
|
||||
layergroupid = layergroup.layergroupid;
|
||||
|
||||
assert.ok(
|
||||
Array.isArray(layergroup.metadata.analyses),
|
||||
'Missing "analyses" array metadata from: ' + res.body
|
||||
);
|
||||
var analyses = layergroup.metadata.analyses;
|
||||
assert.equal(analyses.length, 1, 'Invalid number of analyses in metadata');
|
||||
var nodes = analyses[0].nodes;
|
||||
var nodesIds = Object.keys(nodes);
|
||||
assert.deepEqual(nodesIds, ['2570e105-7b37-40d2-bdf4-1af889598745', 'HEAD']);
|
||||
nodesIds.forEach(function(nodeId) {
|
||||
var node = nodes[nodeId];
|
||||
assert.ok(node.hasOwnProperty('url'), 'Missing "url" attribute in node');
|
||||
assert.ok(node.hasOwnProperty('status'), 'Missing "status" attribute in node');
|
||||
assert.ok(!node.hasOwnProperty('query'), 'Unexpected "query" attribute in node');
|
||||
});
|
||||
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(layergroup.layergroupid).token] = 0;
|
||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
|
||||
return done();
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
step(
|
||||
function deleteTemplate(err) {
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/map/named/' + widgetsTemplateName + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
host: username
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 204
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function deleteRedisKeys(err) {
|
||||
assert.ifError(err);
|
||||
helper.deleteRedisKeys(keysToDelete, done);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to retrieve images from analysis', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/map/' + layergroupid + '/6/31/24.png',
|
||||
method: 'GET',
|
||||
encoding: 'binary',
|
||||
headers: {
|
||||
host: username
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'image/png'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var fixturePath = './test/fixtures/analysis/named-map-buffer.png';
|
||||
assert.imageBufferIsSimilarToFile(res.body, fixturePath, IMAGE_TOLERANCE_PER_MIL, function(err) {
|
||||
assert.ok(!err, err);
|
||||
done();
|
||||
});
|
||||
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to retrieve dataviews from analysis', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/map/' + layergroupid + '/dataview/pop_max_histogram',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: username
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var dataview = JSON.parse(res.body);
|
||||
assert.equal(dataview.type, 'histogram');
|
||||
assert.equal(dataview.bins_start, 0);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -48,63 +48,64 @@ describe('overviews metadata', function() {
|
||||
|
||||
it("layers with and without overviews", function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.0.0',
|
||||
layers: [overviews_layer, non_overviews_layer]
|
||||
};
|
||||
var layergroup = {
|
||||
version: '1.0.0',
|
||||
layers: [overviews_layer, non_overviews_layer]
|
||||
};
|
||||
|
||||
var layergroup_url = '/api/v1/map';
|
||||
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);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid);
|
||||
expected_token = parsedBody.layergroupid;
|
||||
next(null, res);
|
||||
});
|
||||
},
|
||||
function do_get_mapconfig(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
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);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid);
|
||||
expected_token = parsedBody.layergroupid;
|
||||
next(null, res);
|
||||
});
|
||||
},
|
||||
function do_get_mapconfig(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
|
||||
var mapStore = new windshaft.storage.MapStore({
|
||||
pool: redisPool,
|
||||
expire_time: 500000
|
||||
});
|
||||
mapStore.load(LayergroupToken.parse(expected_token).token, function(err, mapConfig) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(non_overviews_layer, mapConfig._cfg.layers[1]);
|
||||
assert.equal(mapConfig._cfg.layers[0].type, 'cartodb');
|
||||
assert.ok(mapConfig._cfg.layers[0].options.query_rewrite_data);
|
||||
var expected_data = {
|
||||
overviews: {
|
||||
test_table_overviews: {
|
||||
1: { table: '_vovw_1_test_table_overviews' },
|
||||
2: { table: '_vovw_2_test_table_overviews' }
|
||||
}
|
||||
}
|
||||
};
|
||||
assert.deepEqual(mapConfig._cfg.layers[0].options.query_rewrite_data, expected_data);
|
||||
});
|
||||
var mapStore = new windshaft.storage.MapStore({
|
||||
pool: redisPool,
|
||||
expire_time: 500000
|
||||
});
|
||||
mapStore.load(LayergroupToken.parse(expected_token).token, function(err, mapConfig) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(non_overviews_layer, mapConfig._cfg.layers[1]);
|
||||
assert.equal(mapConfig._cfg.layers[0].type, 'cartodb');
|
||||
assert.ok(mapConfig._cfg.layers[0].options.query_rewrite_data);
|
||||
var expected_data = {
|
||||
overviews: {
|
||||
test_table_overviews: {
|
||||
schema: 'public',
|
||||
1: { table: '_vovw_1_test_table_overviews' },
|
||||
2: { table: '_vovw_2_test_table_overviews' }
|
||||
}
|
||||
}
|
||||
};
|
||||
assert.deepEqual(mapConfig._cfg.layers[0].options.query_rewrite_data, expected_data);
|
||||
});
|
||||
|
||||
next(err);
|
||||
},
|
||||
function finish(err) {
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(expected_token).token] = 0;
|
||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
next(err);
|
||||
},
|
||||
function finish(err) {
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(expected_token).token] = 0;
|
||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,116 +59,117 @@ describe('overviews metadata for named maps', function() {
|
||||
};
|
||||
|
||||
it("should add overviews data to layers", function(done) {
|
||||
step(
|
||||
function postTemplate()
|
||||
{
|
||||
var next = this;
|
||||
step(
|
||||
function postTemplate()
|
||||
{
|
||||
var next = this;
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template)
|
||||
}, {}, function(res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function checkTemplate(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template)
|
||||
}, {}, function(res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function checkTemplate(err, res) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.deepEqual(JSON.parse(res.body), {
|
||||
template_id: templateId
|
||||
});
|
||||
next(null);
|
||||
},
|
||||
function instantiateTemplate(err) {
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.deepEqual(JSON.parse(res.body), {
|
||||
template_id: templateId
|
||||
});
|
||||
next(null);
|
||||
},
|
||||
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);
|
||||
});
|
||||
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 checkInstanciation(err, res) {
|
||||
assert.ifError(err);
|
||||
},
|
||||
function checkInstanciation(err, res) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
var next = this;
|
||||
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.equal(res.statusCode, 200);
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
|
||||
assert.ok(parsedBody.layergroupid);
|
||||
assert.ok(parsedBody.last_updated);
|
||||
assert.ok(parsedBody.layergroupid);
|
||||
assert.ok(parsedBody.last_updated);
|
||||
|
||||
next(null, parsedBody.layergroupid);
|
||||
},
|
||||
next(null, parsedBody.layergroupid);
|
||||
},
|
||||
|
||||
function checkMapconfig(err, layergroupId)
|
||||
{
|
||||
assert.ifError(err);
|
||||
function checkMapconfig(err, layergroupId)
|
||||
{
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
var next = this;
|
||||
|
||||
var mapStore = new windshaft.storage.MapStore({
|
||||
pool: redisPool,
|
||||
expire_time: 500000
|
||||
});
|
||||
mapStore.load(LayergroupToken.parse(layergroupId).token, function(err, mapConfig) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(non_overviews_layer, mapConfig._cfg.layers[1]);
|
||||
assert.equal(mapConfig._cfg.layers[0].type, 'cartodb');
|
||||
assert.ok(mapConfig._cfg.layers[0].options.query_rewrite_data);
|
||||
var expected_data = {
|
||||
overviews: {
|
||||
test_table_overviews: {
|
||||
1: { table: '_vovw_1_test_table_overviews' },
|
||||
2: { table: '_vovw_2_test_table_overviews' }
|
||||
}
|
||||
}
|
||||
};
|
||||
assert.deepEqual(mapConfig._cfg.layers[0].options.query_rewrite_data, expected_data);
|
||||
});
|
||||
var mapStore = new windshaft.storage.MapStore({
|
||||
pool: redisPool,
|
||||
expire_time: 500000
|
||||
});
|
||||
mapStore.load(LayergroupToken.parse(layergroupId).token, function(err, mapConfig) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(non_overviews_layer, mapConfig._cfg.layers[1]);
|
||||
assert.equal(mapConfig._cfg.layers[0].type, 'cartodb');
|
||||
assert.ok(mapConfig._cfg.layers[0].options.query_rewrite_data);
|
||||
var expected_data = {
|
||||
overviews: {
|
||||
test_table_overviews: {
|
||||
schema: 'public',
|
||||
1: { table: '_vovw_1_test_table_overviews' },
|
||||
2: { table: '_vovw_2_test_table_overviews' }
|
||||
}
|
||||
}
|
||||
};
|
||||
assert.deepEqual(mapConfig._cfg.layers[0].options.query_rewrite_data, expected_data);
|
||||
});
|
||||
|
||||
next(err);
|
||||
},
|
||||
function deleteTemplate(err) {
|
||||
assert.ifError(err);
|
||||
next(err);
|
||||
},
|
||||
function deleteTemplate(err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
var next = this;
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/named/' + templateId + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: { host: 'localhost' }
|
||||
}, {}, function (res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function checkDeleteTemplate(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 204);
|
||||
assert.ok(!res.body);
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/named/' + templateId + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: { host: 'localhost' }
|
||||
}, {}, function (res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function checkDeleteTemplate(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 204);
|
||||
assert.ok(!res.body);
|
||||
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var assert = require('../support/assert');
|
||||
var TestClient = require('../support/test-client');
|
||||
var assert = require('../../support/assert');
|
||||
var TestClient = require('../../support/test-client');
|
||||
|
||||
var IMAGE_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
@@ -38,7 +38,7 @@ function makeMapconfig(cartocss) {
|
||||
};
|
||||
}
|
||||
|
||||
describe('turbo-cartocss for anonymous maps', function() {
|
||||
describe('turbo-carto for anonymous maps', function() {
|
||||
describe('parsing ramp function with colorbrewer for greens and mapnik renderer', function () {
|
||||
beforeEach(function () {
|
||||
var turboCartocss = '#layer { marker-fill: ramp([price], colorbrewer(Greens)); }';
|
||||
@@ -49,8 +49,8 @@ describe('turbo-cartocss for anonymous maps', function() {
|
||||
this.testClient.drain(done);
|
||||
});
|
||||
|
||||
it('should get a tile with turbo-cartocss parsed properly', function (done) {
|
||||
var fixturePath = 'test_turbo_cartocss_greens_13_4011_3088.png';
|
||||
it('should get a tile with turbo-carto parsed properly', function (done) {
|
||||
var fixturePath = 'test_turbo_carto_greens_13_4011_3088.png';
|
||||
this.testClient.getTile(13, 4011, 3088, imageCompareFn(fixturePath, done));
|
||||
});
|
||||
});
|
||||
@@ -65,8 +65,8 @@ describe('turbo-cartocss for anonymous maps', function() {
|
||||
this.testClient.drain(done);
|
||||
});
|
||||
|
||||
it('should get a tile with turbo-cartocss parsed properly', function (done) {
|
||||
var fixtureFileName = 'test_turbo_cartocss_reds_13_4011_3088.png';
|
||||
it('should get a tile with turbo-carto parsed properly', function (done) {
|
||||
var fixtureFileName = 'test_turbo_carto_reds_13_4011_3088.png';
|
||||
this.testClient.getTile(13, 4011, 3088, imageCompareFn(fixtureFileName, done));
|
||||
});
|
||||
});
|
||||
@@ -115,12 +115,12 @@ describe('turbo-cartocss for anonymous maps', function() {
|
||||
this.testClient.drain(done);
|
||||
});
|
||||
|
||||
it('should get a tile with turbo-cartocss parsed properly', function (done) {
|
||||
it('should get a tile with turbo-carto parsed properly', function (done) {
|
||||
var z = 2;
|
||||
var x = 2;
|
||||
var y = 1;
|
||||
|
||||
var pngFixture = 'torque/populated_places_simple_reduced-turbo-cartocss-' + [z, x, y].join('.') + '.png';
|
||||
var pngFixture = 'torque/populated_places_simple_reduced-turbo-carto-' + [z, x, y].join('.') + '.png';
|
||||
|
||||
this.testClient.getTile(z, x, y, { layers: 0, format: 'torque.png' }, imageCompareFn(pngFixture, done));
|
||||
});
|
||||
@@ -1,14 +1,14 @@
|
||||
var assert = require('../support/assert');
|
||||
var assert = require('../../support/assert');
|
||||
var step = require('step');
|
||||
var LayergroupToken = require('../../lib/cartodb/models/layergroup_token');
|
||||
var testHelper = require(__dirname + '/../support/test_helper');
|
||||
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
var LayergroupToken = require('../../../lib/cartodb/models/layergroup_token');
|
||||
var testHelper = require('../../support/test_helper');
|
||||
var CartodbWindshaft = require('../../../lib/cartodb/server');
|
||||
var serverOptions = require('../../../lib/cartodb/server_options');
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
var mapnik = require('windshaft').mapnik;
|
||||
var IMAGE_TOLERANCE_PER_MIL = 10;
|
||||
|
||||
describe('turbo-cartocss for named maps', function() {
|
||||
describe('turbo-carto for named maps', function() {
|
||||
|
||||
var keysToDelete;
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('turbo-cartocss for named maps', function() {
|
||||
testHelper.deleteRedisKeys(keysToDelete, done);
|
||||
});
|
||||
|
||||
var templateId = 'turbo-cartocss-template-1';
|
||||
var templateId = 'turbo-carto-template-1';
|
||||
|
||||
var template = {
|
||||
version: '0.0.1',
|
||||
@@ -65,7 +65,7 @@ describe('turbo-cartocss for named maps', function() {
|
||||
var templateParamsReds = { color: 'Reds' };
|
||||
var templateParamsBlues = { color: 'Blues' };
|
||||
|
||||
it('should create a template with turbo-cartocss parsed properly', function (done) {
|
||||
it('should create a template with turbo-carto parsed properly', function (done) {
|
||||
step(
|
||||
function postTemplate() {
|
||||
var next = this;
|
||||
@@ -144,7 +144,7 @@ describe('turbo-cartocss for named maps', function() {
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.equal(res.headers['content-type'], 'image/png');
|
||||
|
||||
var fixturePath = './test/fixtures/turbo-cartocss-named-maps-reds.png';
|
||||
var fixturePath = './test/fixtures/turbo-carto-named-maps-reds.png';
|
||||
var image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||
|
||||
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, next);
|
||||
@@ -203,7 +203,7 @@ describe('turbo-cartocss for named maps', function() {
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.equal(res.headers['content-type'], 'image/png');
|
||||
|
||||
var fixturePath = './test/fixtures/turbo-cartocss-named-maps-blues.png';
|
||||
var fixturePath = './test/fixtures/turbo-carto-named-maps-blues.png';
|
||||
var image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||
|
||||
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, next);
|
||||
@@ -1,7 +1,7 @@
|
||||
require('../support/test_helper');
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../support/assert');
|
||||
var TestClient = require('../support/test-client');
|
||||
var assert = require('../../support/assert');
|
||||
var TestClient = require('../../support/test-client');
|
||||
|
||||
function makeMapconfig(cartocss) {
|
||||
return {
|
||||
@@ -31,7 +31,7 @@ function makeMapconfig(cartocss) {
|
||||
};
|
||||
}
|
||||
|
||||
describe('turbo-cartocss regressions', function() {
|
||||
describe('turbo-carto regressions', function() {
|
||||
|
||||
var cartocss = [
|
||||
"/** simple visualization */",
|
||||
@@ -114,6 +114,34 @@ describe('histogram widgets', function() {
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it("should expose a filtered histogram using dataviews for filtering", function(done) {
|
||||
var params = {
|
||||
filters: {
|
||||
dataviews: {
|
||||
country_places_histogram: { min: 4000000 }
|
||||
}
|
||||
}
|
||||
};
|
||||
var testClient = new TestClient(histogramMapConfig);
|
||||
testClient.getWidget('country_places_histogram', params, function(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var histogram = JSON.parse(res.body);
|
||||
// notice min value
|
||||
assert.deepEqual(histogram.bins[0], {
|
||||
bin: 0,
|
||||
freq: 62,
|
||||
min: 4000000,
|
||||
max: 9276403,
|
||||
avg: 5815009.596774193
|
||||
});
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ describe('named-maps widgets', function() {
|
||||
}
|
||||
);
|
||||
},
|
||||
function fetchTile(err, res) {
|
||||
function finish(err, res) {
|
||||
assert.ifError(err);
|
||||
|
||||
layergroup = JSON.parse(res.body);
|
||||
|
||||
BIN
test/fixtures/analysis/basic-source-id-mapnik-layer.png
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
test/fixtures/analysis/buffer-over-source.png
vendored
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
test/fixtures/analysis/named-map-buffer.png
vendored
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 356 B After Width: | Height: | Size: 356 B |
|
Before Width: | Height: | Size: 349 B After Width: | Height: | Size: 349 B |
@@ -73,12 +73,13 @@ describe('MapConfigOverviewsAdapter', function() {
|
||||
assert.equal(layers[0].options.cartocss_version, cartocss_version);
|
||||
assert.ok(layers[0].options.query_rewrite_data);
|
||||
var expected_data = {
|
||||
overviews: {
|
||||
test_table_overviews: {
|
||||
1: { table: '_vovw_1_test_table_overviews' },
|
||||
2: { table: '_vovw_2_test_table_overviews' }
|
||||
overviews: {
|
||||
test_table_overviews: {
|
||||
schema: 'public',
|
||||
1: { table: '_vovw_1_test_table_overviews' },
|
||||
2: { table: '_vovw_2_test_table_overviews' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
assert.deepEqual(layers[0].options.query_rewrite_data, expected_data);
|
||||
done();
|
||||
|
||||
@@ -40,8 +40,9 @@ describe('OverviewsMetadataApi', function() {
|
||||
|
||||
assert.deepEqual(result, {
|
||||
'test_table_overviews': {
|
||||
1: { table: '_vovw_1_test_table_overviews' },
|
||||
2: { table: '_vovw_2_test_table_overviews' }
|
||||
schema: 'public',
|
||||
1: { table: '_vovw_1_test_table_overviews' },
|
||||
2: { table: '_vovw_2_test_table_overviews' }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -71,6 +71,9 @@ if test x"$PREPARE_PGSQL" = xyes; then
|
||||
dropdb "${TEST_DB}"
|
||||
createdb -Ttemplate_postgis -EUTF8 "${TEST_DB}" || die "Could not create test database"
|
||||
|
||||
curl -L -s https://raw.githubusercontent.com/CartoDB/camshaft/master/test/fixtures/cdb_analysis_catalog.sql -o sql/cdb_analysis_catalog.sql
|
||||
cat sql/cdb_analysis_catalog.sql | psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
||||
|
||||
cat sql/windshaft.test.sql sql/gadm4.sql |
|
||||
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
|
||||
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
|
||||
|
||||
@@ -332,3 +332,298 @@ INSERT INTO _vovw_2_test_table_overviews VALUES
|
||||
|
||||
INSERT INTO _vovw_1_test_table_overviews VALUES
|
||||
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.314252', 1, 'Hawai', 'Calle de Pérez Galdós 9, Madrid, Spain', '0101000020E610000000000000000020C00000000000004440', '0101000020110F000076491621312319C122D4663F1DCC5241');
|
||||
|
||||
|
||||
-- analysis tables -----------------------------------------------
|
||||
|
||||
ALTER TABLE cdb_analysis_catalog OWNER TO :TESTUSER;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 804 (class 1259 OID 13870252)
|
||||
-- Name: analysis_banks; Type: TABLE; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE analysis_banks (
|
||||
cartodb_id bigint NOT NULL,
|
||||
the_geom geometry(Geometry,4326),
|
||||
the_geom_webmercator geometry(Geometry,3857),
|
||||
bank text
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE analysis_banks OWNER TO :TESTUSER;
|
||||
|
||||
--
|
||||
-- TOC entry 803 (class 1259 OID 13870250)
|
||||
-- Name: analysis_banks_cartodb_id_seq; Type: SEQUENCE; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
|
||||
--
|
||||
|
||||
CREATE SEQUENCE analysis_banks_cartodb_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE analysis_banks_cartodb_id_seq OWNER TO :TESTUSER;
|
||||
|
||||
--
|
||||
-- TOC entry 5784 (class 0 OID 0)
|
||||
-- Dependencies: 803
|
||||
-- Name: analysis_banks_cartodb_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
|
||||
--
|
||||
|
||||
ALTER SEQUENCE analysis_banks_cartodb_id_seq OWNED BY analysis_banks.cartodb_id;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 802 (class 1259 OID 13870235)
|
||||
-- Name: analysis_rent_listings; Type: TABLE; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE analysis_rent_listings (
|
||||
cartodb_id bigint NOT NULL,
|
||||
the_geom geometry(Geometry,4326),
|
||||
the_geom_webmercator geometry(Geometry,3857),
|
||||
price double precision
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE analysis_rent_listings OWNER TO :TESTUSER;
|
||||
|
||||
--
|
||||
-- TOC entry 801 (class 1259 OID 13870233)
|
||||
-- Name: analysis_rent_listings_cartodb_id_seq; Type: SEQUENCE; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
|
||||
--
|
||||
|
||||
CREATE SEQUENCE analysis_rent_listings_cartodb_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE analysis_rent_listings_cartodb_id_seq OWNER TO :TESTUSER;
|
||||
|
||||
--
|
||||
-- TOC entry 5786 (class 0 OID 0)
|
||||
-- Dependencies: 801
|
||||
-- Name: analysis_rent_listings_cartodb_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
|
||||
--
|
||||
|
||||
ALTER SEQUENCE analysis_rent_listings_cartodb_id_seq OWNED BY analysis_rent_listings.cartodb_id;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5612 (class 2604 OID 13870258)
|
||||
-- Name: cartodb_id; Type: DEFAULT; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY analysis_banks ALTER COLUMN cartodb_id SET DEFAULT nextval('analysis_banks_cartodb_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5611 (class 2604 OID 13870241)
|
||||
-- Name: cartodb_id; Type: DEFAULT; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY analysis_rent_listings ALTER COLUMN cartodb_id SET DEFAULT nextval('analysis_rent_listings_cartodb_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5778 (class 0 OID 13870252)
|
||||
-- Dependencies: 804
|
||||
-- Data for Name: analysis_banks; Type: TABLE DATA; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
|
||||
--
|
||||
|
||||
COPY analysis_banks (cartodb_id, the_geom, the_geom_webmercator, bank) FROM stdin;
|
||||
1 0101000020E61000000AEC3F6BE5A80DC09EF022E20C364440 0101000020110F0000AD24852BA63019C13215440E02CC5241 BBVA
|
||||
2 0101000020E61000005AB3C72EE6A40DC02A499181E3364440 0101000020110F0000DE3E9A22412D19C1B059CF80F1CC5241 Santander
|
||||
3 0101000020E6100000FD52FCA1E1960DC0DD48F7ADB9354440 0101000020110F00008C1CE860592119C12B37FC3BA5CB5241 BBVA
|
||||
4 0101000020E61000008A52C823B69F0DC02579E6E0B4354440 0101000020110F0000DC41553AD92819C170BBE0E09FCB5241 Santander
|
||||
5 0101000020E6100000A6C9EAA399B00DC00A31DF64BD354440 0101000020110F000041C09F2D313719C1EDC9C960A9CB5241 Santander
|
||||
6 0101000020E6100000D2AD3EB570970DC01F85791E1B354440 0101000020110F00001D0E73E4D22119C14730F65AF4CA5241 BBVA
|
||||
7 0101000020E6100000B2F9AF3A6FA40DC0BA91F3D44C364440 0101000020110F0000B071A11BDC2C19C16DD5026649CC5241 BBVA
|
||||
8 0101000020E61000000F1B8134EE8E0DC067D8E65807374440 0101000020110F000033B46EB0981A19C10322017E19CD5241 BBVA
|
||||
9 0101000020E6100000F12A179193880DC07EBCEDABC2354440 0101000020110F0000C11C4B2F331519C1B2B8FD43AFCB5241 BBVA
|
||||
10 0101000020E61000004B602775E0A50DC0037374A875364440 0101000020110F00004B5097B1152E19C1481948F276CC5241 BBVA
|
||||
11 0101000020E6100000F796A5DDEE880DC05337CD960A364440 0101000020110F0000ACE39CB9801519C1F843077FFFCB5241 Santander
|
||||
12 0101000020E610000041BC5F214A920DC0AFCE2B5507354440 0101000020110F000027682406731D19C15145B148DECA5241 BBVA
|
||||
13 0101000020E6100000CC2EFD14A0AF0DC00550C5AE47354440 0101000020110F000095956F3A5D3619C11CACED1026CB5241 BBVA
|
||||
14 0101000020E6100000B113F5FFF28E0DC01075C9419A354440 0101000020110F00005E9EE8C29C1A19C1DB38342E82CB5241 BBVA
|
||||
15 0101000020E6100000997EFF432F990DC0E6D8677BF1364440 0101000020110F00007E0667274E2319C1ACE7B01801CD5241 Santander
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5787 (class 0 OID 0)
|
||||
-- Dependencies: 803
|
||||
-- Name: analysis_banks_cartodb_id_seq; Type: SEQUENCE SET; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('analysis_banks_cartodb_id_seq', 15, true);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5776 (class 0 OID 13870235)
|
||||
-- Dependencies: 802
|
||||
-- Data for Name: analysis_rent_listings; Type: TABLE DATA; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
|
||||
--
|
||||
|
||||
COPY analysis_rent_listings (cartodb_id, the_geom, the_geom_webmercator, price) FROM stdin;
|
||||
1 0101000020E61000006300CEA9C4920DC0FCB8FEFB04354440 0101000020110F0000EA3B5C17DB1D19C135CF17AADBCA5241 81
|
||||
2 0101000020E6100000F49AD93201910DC0E37AB970F8354440 0101000020110F00009BD483A95B1C19C11FC4D53FEBCB5241 169
|
||||
3 0101000020E610000096F9FBAA0F880DC03974BF03B8354440 0101000020110F00006891BA29C31419C1055F8260A3CB5241 163
|
||||
4 0101000020E61000009F08C1D17C930DC0651427FA68364440 0101000020110F00006C35BB7E771E19C1837381CC68CC5241 127
|
||||
5 0101000020E61000000A7B7928EAB40DC0425EE239C4364440 0101000020110F00000C6ADB3EDB3A19C14FDB889ACECC5241 58
|
||||
6 0101000020E610000095B232CF20A00DC0584C660044354440 0101000020110F0000979587D2332919C177DEB3F521CB5241 184
|
||||
7 0101000020E61000001E8C81FF328F0DC031AE47DFB2364440 0101000020110F00006634761DD31A19C13723EB3DBBCC5241 109
|
||||
8 0101000020E61000008A014F8FD79D0DC0B1E8EA3376364440 0101000020110F000021A20DC5422719C15A18E08D77CC5241 87
|
||||
9 0101000020E61000006ECAA9C393970DC0F71C39950A364440 0101000020110F0000A3016DAAF02119C19C71447DFFCB5241 132
|
||||
10 0101000020E6100000FB434A9D57A60DC0F9FD273C3F354440 0101000020110F00008E7BC3E47A2E19C1798082A41CCB5241 134
|
||||
11 0101000020E6100000C597512EFE8C0DC032DE0DCDFD344440 0101000020110F0000F51B6C6AF31819C15512CCA6D3CA5241 167
|
||||
12 0101000020E610000025E84904589F0DC00BC88CB0BA364440 0101000020110F000024C7054A892819C1C84EBBF6C3CC5241 135
|
||||
13 0101000020E61000002917E524E69E0DC0EE4915018C354440 0101000020110F00008011BC93282819C153F8E34772CB5241 160
|
||||
14 0101000020E6100000617E8939B1A40DC05EF963BEF0344440 0101000020110F000071896E28142D19C160872616C5CA5241 185
|
||||
15 0101000020E6100000B20B809602A30DC0514274FE37354440 0101000020110F00007D63FC6AA62B19C1DC88B19014CB5241 94
|
||||
16 0101000020E6100000191978861B9C0DC05C902E6C45354440 0101000020110F0000EBC9ACA6C92519C10A76818B23CB5241 172
|
||||
17 0101000020E610000098D6FCD1F2AC0DC01ED4EEFCAE354440 0101000020110F0000FA7E3A3C173419C1DFFAA34E99CB5241 154
|
||||
18 0101000020E6100000F9C7CB37DC970DC0B7E914C94E354440 0101000020110F0000795D5C332E2219C1FAAE47FD2DCB5241 183
|
||||
19 0101000020E61000002D0263B27B940DC09D26F27AC4354440 0101000020110F0000C4616AF64F1F19C1D2868548B1CB5241 77
|
||||
20 0101000020E6100000BD682E39DA8D0DC0DA3ADC937A364440 0101000020110F000038D83D4CAE1919C143A35B6F7CCC5241 196
|
||||
21 0101000020E6100000165B41DEB2A90DC0D0D1F568A4354440 0101000020110F00008B9179A8543119C1EAADBA818DCB5241 74
|
||||
22 0101000020E6100000DECD65EC4A8D0DC08D309F6B1F354440 0101000020110F000063E4D797341919C1A1034827F9CA5241 164
|
||||
23 0101000020E6100000D14DF59EC1AD0DC086C17713C7354440 0101000020110F0000E8ED02DFC63419C15FACD82DB4CB5241 180
|
||||
24 0101000020E61000002A41F69FA7900DC063FF9C14AC364440 0101000020110F0000DDF34D960F1C19C1070119AAB3CC5241 62
|
||||
25 0101000020E61000001E390026BA8B0DC0B506A19336354440 0101000020110F00005620FE36E01719C109E9F5FB12CB5241 103
|
||||
26 0101000020E61000001856B89F9E890DC0AE3A805FA7364440 0101000020110F0000F4E717FF151619C102619069AECC5241 132
|
||||
27 0101000020E61000006244348FA7AD0DC093992086E1354440 0101000020110F0000204AB0BCB03419C10E2515AFD1CB5241 64
|
||||
28 0101000020E6100000C249D99ADA970DC0D7CF322454364440 0101000020110F00005303A5D42C2219C1463AA98D51CC5241 142
|
||||
29 0101000020E61000001E36C089F2930DC0098310FAF2344440 0101000020110F0000E9293E79DB1E19C134C7D593C7CA5241 149
|
||||
30 0101000020E610000084DC236A48970DC08AA0D30E37354440 0101000020110F0000A4E5D3ABB02119C12A57638513CB5241 190
|
||||
31 0101000020E610000068C42C3D6DAE0DC0E316EF8262354440 0101000020110F000007FF5AA0583519C1CEF97EFE43CB5241 132
|
||||
32 0101000020E6100000F0FCDE54BCA70DC03311579A9F354440 0101000020110F0000CE2C83DAA92F19C120A8E62488CB5241 180
|
||||
33 0101000020E6100000B5A43D9E809B0DC059B84FF6F1364440 0101000020110F000035B5A016462519C15C07D2A101CD5241 160
|
||||
34 0101000020E6100000D4D36C80629E0DC0249F8DFC8B354440 0101000020110F0000F241EAC5B82719C1904ED64272CB5241 95
|
||||
35 0101000020E61000008BEA878AF1A50DC05DA77AA5B9354440 0101000020110F0000BBD0E633242E19C15D7E8432A5CB5241 108
|
||||
36 0101000020E610000022D3CD1F62B40DC08AC22B852E364440 0101000020110F0000C8E240B6673A19C1DCF9E99427CC5241 168
|
||||
37 0101000020E6100000EBAC4CB637950DC0E989CFA0D8364440 0101000020110F0000883BDDA4EF1F19C15478CE5DE5CC5241 80
|
||||
38 0101000020E61000007CEFD63A8A960DC006B8BDD1C8364440 0101000020110F00003728B0250F2119C169FD71BAD3CC5241 168
|
||||
39 0101000020E6100000756B387CCFA70DC084C36711F7354440 0101000020110F0000CEB4ED1EBA2F19C13F1EE7B7E9CB5241 56
|
||||
40 0101000020E6100000A47B2BAC4E960DC0820DC4B760354440 0101000020110F00008BCCAF90DC2019C1AE4248FE41CB5241 162
|
||||
41 0101000020E6100000D5645E0C31990DC0F535DB974F364440 0101000020110F00007E8BFFAA4F2319C1B884AA7A4CCC5241 102
|
||||
42 0101000020E6100000E3B6E0EA41B40DC02ADE56A899364440 0101000020110F0000FFC4D55B4C3A19C1A602351C9FCC5241 174
|
||||
43 0101000020E61000006C513CE4E3B00DC010B3C38659364440 0101000020110F00002817653D703719C14258A88F57CC5241 97
|
||||
44 0101000020E61000006B6DF64366A10DC0C980E41EB2364440 0101000020110F0000CFE47B3B482A19C1EFCB4567BACC5241 63
|
||||
45 0101000020E61000007031674EA8960DC073B9E40B22354440 0101000020110F0000A608EEB0282119C159033215FCCA5241 159
|
||||
46 0101000020E610000002328782CB890DC0A6462A1EAF364440 0101000020110F000008823D1E3C1619C14C749B0DB7CC5241 141
|
||||
47 0101000020E61000009694069312A70DC09438822A79354440 0101000020110F0000922DC0AD192F19C113F51A445DCB5241 185
|
||||
48 0101000020E610000081F9078F75AE0DC069F4C3ADCD364440 0101000020110F00003F393EB15F3519C1D4F16926D9CC5241 113
|
||||
49 0101000020E6100000E348615820AC0DC0D165849A50364440 0101000020110F0000ACEC8F7A643319C14D783D9B4DCC5241 124
|
||||
50 0101000020E6100000378F35768FAD0DC085A048F310364440 0101000020110F0000BFF95B459C3419C12A94C89706CC5241 62
|
||||
51 0101000020E6100000F25E60BD14B40DC0FA9EFA964F354440 0101000020110F0000EB4840FD253A19C1FF36F6E22ECB5241 109
|
||||
52 0101000020E6100000B73BFD3FB6AF0DC0A3D844C97B364440 0101000020110F0000AD1F370E703619C1B9278EC87DCC5241 149
|
||||
53 0101000020E6100000FF1181969EB10DC0136E39E5A0364440 0101000020110F0000D10A15CD0E3819C1828B712FA7CC5241 176
|
||||
54 0101000020E6100000426F39BD459A0DC0A5505E4CB7354440 0101000020110F000074615DA93A2419C106D4EF93A2CB5241 90
|
||||
55 0101000020E6100000E2B0F14972A80DC0AFA4D0BF84354440 0101000020110F00008817D563443019C1202D06306ACB5241 162
|
||||
56 0101000020E6100000ADE43CA9F79A0DC0C24A1417D9364440 0101000020110F0000C4944EC5D12419C1D0B5C2E1E5CC5241 80
|
||||
57 0101000020E6100000AD6B99A9818B0DC07E06699A33364440 0101000020110F00007EE2C43DB01719C12BBF9D402DCC5241 172
|
||||
58 0101000020E610000098272DE67B960DC08EC657CCF4344440 0101000020110F0000352CE4F9022119C16660F49BC9CA5241 177
|
||||
59 0101000020E6100000DEAA210E56A90DC0B45845C72B354440 0101000020110F00003689FED4053119C1F0FE50F006CB5241 172
|
||||
60 0101000020E61000004CAD1CF03B8F0DC07A4C4F1EED364440 0101000020110F000048EE2CB5DA1A19C1E236513AFCCC5241 122
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5788 (class 0 OID 0)
|
||||
-- Dependencies: 801
|
||||
-- Name: analysis_rent_listings_cartodb_id_seq; Type: SEQUENCE SET; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('analysis_rent_listings_cartodb_id_seq', 60, true);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5618 (class 2606 OID 13870260)
|
||||
-- Name: analysis_banks_pkey; Type: CONSTRAINT; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY analysis_banks
|
||||
ADD CONSTRAINT analysis_banks_pkey PRIMARY KEY (cartodb_id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5614 (class 2606 OID 13870243)
|
||||
-- Name: analysis_rent_listings_pkey; Type: CONSTRAINT; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY analysis_rent_listings
|
||||
ADD CONSTRAINT analysis_rent_listings_pkey PRIMARY KEY (cartodb_id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5619 (class 1259 OID 13870261)
|
||||
-- Name: analysis_banks_the_geom_idx; Type: INDEX; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX analysis_banks_the_geom_idx ON analysis_banks USING gist (the_geom);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5620 (class 1259 OID 13870262)
|
||||
-- Name: analysis_banks_the_geom_webmercator_idx; Type: INDEX; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX analysis_banks_the_geom_webmercator_idx ON analysis_banks USING gist (the_geom_webmercator);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5615 (class 1259 OID 13870244)
|
||||
-- Name: analysis_rent_listings_the_geom_idx; Type: INDEX; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX analysis_rent_listings_the_geom_idx ON analysis_rent_listings USING gist (the_geom);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5616 (class 1259 OID 13870245)
|
||||
-- Name: analysis_rent_listings_the_geom_webmercator_idx; Type: INDEX; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX analysis_rent_listings_the_geom_webmercator_idx ON analysis_rent_listings USING gist (the_geom_webmercator);
|
||||
|
||||
--
|
||||
-- TOC entry 5783 (class 0 OID 0)
|
||||
-- Dependencies: 804
|
||||
-- Name: analysis_banks; Type: ACL; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
|
||||
--
|
||||
|
||||
REVOKE ALL ON TABLE analysis_banks FROM PUBLIC;
|
||||
REVOKE ALL ON TABLE analysis_banks FROM :TESTUSER;
|
||||
GRANT ALL ON TABLE analysis_banks TO :TESTUSER;
|
||||
GRANT SELECT ON TABLE analysis_banks TO :PUBLICUSER;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 5785 (class 0 OID 0)
|
||||
-- Dependencies: 802
|
||||
-- Name: analysis_rent_listings; Type: ACL; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
|
||||
--
|
||||
|
||||
REVOKE ALL ON TABLE analysis_rent_listings FROM PUBLIC;
|
||||
REVOKE ALL ON TABLE analysis_rent_listings FROM :TESTUSER;
|
||||
GRANT ALL ON TABLE analysis_rent_listings TO :TESTUSER;
|
||||
GRANT SELECT ON TABLE analysis_rent_listings TO :PUBLICUSER;
|
||||
|
||||
|
||||
-- Completed on 2016-02-29 12:50:53 CET
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
--
|
||||
GRANT SELECT, UPDATE, INSERT, DELETE ON cdb_analysis_catalog TO :TESTUSER;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
var qs = require('querystring');
|
||||
var step = require('step');
|
||||
var urlParser = require('url');
|
||||
|
||||
var mapnik = require('windshaft').mapnik;
|
||||
|
||||
@@ -12,9 +13,9 @@ var helper = require('./test_helper');
|
||||
|
||||
var CartodbWindshaft = require('../../lib/cartodb/server');
|
||||
var serverOptions = require('../../lib/cartodb/server_options');
|
||||
serverOptions.analysis.batch.inlineExecution = true;
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
|
||||
function TestClient(mapConfig, apiKey) {
|
||||
this.mapConfig = mapConfig;
|
||||
this.apiKey = apiKey;
|
||||
@@ -117,6 +118,111 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) {
|
||||
);
|
||||
};
|
||||
|
||||
TestClient.prototype.getDataview = function(dataviewName, params, callback) {
|
||||
var self = this;
|
||||
|
||||
if (!callback) {
|
||||
callback = params;
|
||||
params = {};
|
||||
}
|
||||
|
||||
var extraParams = {};
|
||||
if (this.apiKey) {
|
||||
extraParams.api_key = this.apiKey;
|
||||
}
|
||||
if (params && params.filters) {
|
||||
extraParams.filters = JSON.stringify(params.filters);
|
||||
}
|
||||
|
||||
var url = '/api/v1/map';
|
||||
if (Object.keys(extraParams).length > 0) {
|
||||
url += '?' + qs.stringify(extraParams);
|
||||
}
|
||||
|
||||
var layergroupId;
|
||||
step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(self.mapConfig)
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var expectedDataviewsURLS = {
|
||||
http: "/api/v1/map/" + parsedBody.layergroupid + "/dataview/" + dataviewName
|
||||
};
|
||||
assert.ok(parsedBody.metadata.dataviews[dataviewName]);
|
||||
assert.ok(
|
||||
parsedBody.metadata.dataviews[dataviewName].url.http.match(expectedDataviewsURLS.http)
|
||||
);
|
||||
return next(null, parsedBody.layergroupid);
|
||||
}
|
||||
);
|
||||
},
|
||||
function getDataviewResult(err, _layergroupId) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
layergroupId = _layergroupId;
|
||||
|
||||
var urlParams = {
|
||||
own_filter: params.hasOwnProperty('own_filter') ? params.own_filter : 1
|
||||
};
|
||||
if (params && params.bbox) {
|
||||
urlParams.bbox = params.bbox;
|
||||
}
|
||||
if (self.apiKey) {
|
||||
urlParams.api_key = self.apiKey;
|
||||
}
|
||||
url = '/api/v1/map/' + layergroupId + '/dataview/' + dataviewName + '?' + qs.stringify(urlParams);
|
||||
|
||||
assert.response(server,
|
||||
{
|
||||
url: url,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
next(null, JSON.parse(res.body));
|
||||
}
|
||||
);
|
||||
},
|
||||
function finish(err, res) {
|
||||
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
|
||||
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
return callback(err, res);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TestClient.prototype.getTile = function(z, x, y, params, callback) {
|
||||
var self = this;
|
||||
|
||||
@@ -198,7 +304,7 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) {
|
||||
}
|
||||
};
|
||||
|
||||
var isPng = format === 'png' || format === 'torque.png';
|
||||
var isPng = format.match(/png$/);
|
||||
|
||||
if (isPng) {
|
||||
request.encoding = 'binary';
|
||||
@@ -208,13 +314,150 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) {
|
||||
assert.response(server, request, expectedResponse, function(res, err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var image;
|
||||
var obj;
|
||||
|
||||
if (isPng) {
|
||||
image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||
obj = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||
} else {
|
||||
obj = JSON.parse(res.body);
|
||||
}
|
||||
|
||||
next(null, res, image);
|
||||
next(null, res, obj);
|
||||
});
|
||||
},
|
||||
function finish(err, res, image) {
|
||||
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
|
||||
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
return callback(err, res, image);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TestClient.prototype.getLayergroup = function(expectedResponse, callback) {
|
||||
var self = this;
|
||||
|
||||
if (!callback) {
|
||||
callback = expectedResponse;
|
||||
expectedResponse = {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var url = '/api/v1/map';
|
||||
|
||||
if (this.apiKey) {
|
||||
url += '?' + qs.stringify({api_key: this.apiKey});
|
||||
}
|
||||
|
||||
assert.response(server,
|
||||
{
|
||||
url: url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(self.mapConfig)
|
||||
},
|
||||
expectedResponse,
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
|
||||
if (parsedBody.layergroupid) {
|
||||
self.keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
||||
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
}
|
||||
|
||||
return callback(null, parsedBody);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TestClient.prototype.getNodeStatus = function(nodeName, callback) {
|
||||
var self = this;
|
||||
|
||||
var url = '/api/v1/map';
|
||||
|
||||
if (this.apiKey) {
|
||||
url += '?' + qs.stringify({api_key: this.apiKey});
|
||||
}
|
||||
|
||||
var layergroupId;
|
||||
var nodes = {};
|
||||
step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(self.mapConfig)
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
|
||||
nodes = parsedBody.metadata.analyses.reduce(function(nodes, analysis) {
|
||||
return Object.keys(analysis.nodes).reduce(function(nodes, nodeName) {
|
||||
var node = analysis.nodes[nodeName];
|
||||
nodes[nodeName] = node.url.http;
|
||||
return nodes;
|
||||
}, nodes);
|
||||
}, nodes);
|
||||
|
||||
return next(null, parsedBody.layergroupid);
|
||||
}
|
||||
);
|
||||
},
|
||||
function getNodeStatusResult(err, _layergroupId) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
layergroupId = _layergroupId;
|
||||
|
||||
url = urlParser.parse(nodes[nodeName]).path;
|
||||
|
||||
if (self.apiKey) {
|
||||
url += '?' + qs.stringify({api_key: self.apiKey});
|
||||
}
|
||||
|
||||
var request = {
|
||||
url: url,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
}
|
||||
};
|
||||
|
||||
var expectedResponse = {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
assert.response(server, request, expectedResponse, function(res, err) {
|
||||
assert.ifError(err);
|
||||
next(null, res, JSON.parse(res.body));
|
||||
});
|
||||
},
|
||||
function finish(err, res, image) {
|
||||
|
||||
@@ -3,11 +3,11 @@ require('../../support/test_helper');
|
||||
var assert = require('assert');
|
||||
var OverviewsQueryRewriter = require('../../../lib/cartodb/utils/overviews_query_rewriter');
|
||||
var overviewsQueryRewriter = new OverviewsQueryRewriter({
|
||||
zoom_level: 'ZoomLevel()'
|
||||
zoom_level: 'ZoomLevel()'
|
||||
});
|
||||
|
||||
function normalize_whitespace(txt) {
|
||||
return txt.replace(/\s+/g, " ").trim();
|
||||
return txt.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
// compare SQL statements ignoring whitespace
|
||||
@@ -17,410 +17,462 @@ function assertSameSql(sql1, sql2) {
|
||||
|
||||
describe('Overviews query rewriter', function() {
|
||||
|
||||
it('does not alter queries if no overviews data is present', function(done){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql);
|
||||
assert.equal(overviews_sql, sql);
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, {});
|
||||
assert.equal(overviews_sql, sql);
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, { overviews: {} });
|
||||
assert.equal(overviews_sql, sql);
|
||||
done();
|
||||
});
|
||||
it('does not alter queries if no overviews data is present', function(){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql);
|
||||
assert.equal(overviews_sql, sql);
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, {});
|
||||
assert.equal(overviews_sql, sql);
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, { overviews: {} });
|
||||
assert.equal(overviews_sql, sql);
|
||||
});
|
||||
|
||||
|
||||
it('does not alter queries which don\'t use overviews', function(done){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table2: {
|
||||
0: { table: 'table2_ov0' },
|
||||
1: { table: 'table2_ov1' },
|
||||
4: { table: 'table2_ov4' }
|
||||
it('does not alter queries which don\'t use overviews', function(){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table2: {
|
||||
0: { table: 'table2_ov0' },
|
||||
1: { table: 'table2_ov1' },
|
||||
4: { table: 'table2_ov4' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
done();
|
||||
});
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
});
|
||||
|
||||
// jshint multistr:true
|
||||
// jshint multistr:true
|
||||
|
||||
it('generates query with single overview layer for level 0', function(done){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
0: { table: 'table1_ov0' }
|
||||
it('generates query with single overview layer for level 0', function(){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
0: { table: 'table1_ov0' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 0\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 0\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
it('generates query with single overview layer for level >0', function(done){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
2: { table: 'table1_ov2' }
|
||||
it('generates query with single overview layer for level >0', function(){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
2: { table: 'table1_ov2' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
it('generates query with multiple overview layers for all levels up to N', function(done){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
0: { table: 'table1_ov0' },
|
||||
1: { table: 'table1_ov1' },
|
||||
2: { table: 'table1_ov2' },
|
||||
3: { table: 'table1_ov3' }
|
||||
it('generates query with multiple overview layers for all levels up to N', function(){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
0: { table: 'table1_ov0' },
|
||||
1: { table: 'table1_ov1' },
|
||||
2: { table: 'table1_ov2' },
|
||||
3: { table: 'table1_ov3' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov1, _vovw_scale WHERE _vovw_z = 1\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z = 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov3, _vovw_scale WHERE _vovw_z = 3\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 3\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov1, _vovw_scale WHERE _vovw_z = 1\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z = 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov3, _vovw_scale WHERE _vovw_z = 3\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 3\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
it('generates query with multiple overview layers for random levels', function(done){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
0: { table: 'table1_ov0' },
|
||||
1: { table: 'table1_ov1' },
|
||||
6: { table: 'table1_ov6' }
|
||||
it('generates query with multiple overview layers for random levels', function(){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
0: { table: 'table1_ov0' },
|
||||
1: { table: 'table1_ov1' },
|
||||
6: { table: 'table1_ov6' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov1, _vovw_scale WHERE _vovw_z = 1\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov6, _vovw_scale WHERE _vovw_z > 1 AND _vovw_z <= 6\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 6\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov1, _vovw_scale WHERE _vovw_z = 1\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov6, _vovw_scale WHERE _vovw_z > 1 AND _vovw_z <= 6\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 6\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
it('generates query for a table with explicit schema', function(done){
|
||||
var sql = "SELECT * FROM public.table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
'public.table1': {
|
||||
2: { table: 'table1_ov2' }
|
||||
it('generates query for a table with explicit schema', function(){
|
||||
var sql = "SELECT * FROM public.table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
'public.table1': {
|
||||
2: { table: 'table1_ov2' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM public.table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM public.table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM public.table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM public.table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
it('generates query for a table with explicit schema in the overviews info', function(done){
|
||||
var sql = "SELECT * FROM public.table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
'public.table1': {
|
||||
2: { table: 'table1_ov2' }
|
||||
it('generates query for a table with explicit schema in the overviews info', function(){
|
||||
var sql = "SELECT * FROM public.table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
'public.table1': {
|
||||
2: { table: 'table1_ov2' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM public.table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM public.table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM public.table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM public.table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
it('generates query for a table that needs quoting with explicit schema', function(done){
|
||||
var sql = "SELECT * FROM public.\"table 1\"";
|
||||
var data = {
|
||||
overviews: {
|
||||
'public."table 1"': {
|
||||
2: { table: '"table 1_ov2"' }
|
||||
it('uses schema name from overviews', function(){
|
||||
var sql = "SELECT * FROM public.table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
'table1': {
|
||||
schema: 'public',
|
||||
2: { table: 'table1_ov2' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM public.\"table 1_ov2\", _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM public.\"table 1\", _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS \"_vovw_table 1\"\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
it('generates query for a table with explicit schema that needs quoting', function(done){
|
||||
var sql = "SELECT * FROM \"user-1\".table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
'"user-1".table1': {
|
||||
2: { table: 'table1_ov2' }
|
||||
it('ignores schema name from overviews if not necessary', function(){
|
||||
var sql = "SELECT * FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
'table1': {
|
||||
schema: 'public',
|
||||
2: { table: 'table1_ov2' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM \"user-1\".table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM \"user-1\".table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it('generates query for a table with explicit schema both needing quoting', function(done){
|
||||
var sql = "SELECT * FROM \"user-1\".\"table 1\"";
|
||||
var data = {
|
||||
overviews: {
|
||||
'"user-1"."table 1"': {
|
||||
2: { table: '"table 1_ov2"' }
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
it('uses redundant schema information', function(){
|
||||
var sql = "SELECT * FROM public.table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
'public.table1': {
|
||||
schema: 'public',
|
||||
2: { table: 'table1_ov2' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM \"user-1\".\"table 1_ov2\", _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM \"user-1\".\"table 1\", _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS \"_vovw_table 1\"\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM public.table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM public.table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
|
||||
it('generates query using overviews for queries with selected columns', function(done){
|
||||
var sql = "SELECT column1, column2, column3 FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
2: { table: 'table1_ov2' }
|
||||
it('generates query for a table that needs quoting with explicit schema', function(){
|
||||
var sql = "SELECT * FROM public.\"table 1\"";
|
||||
var data = {
|
||||
overviews: {
|
||||
'public."table 1"': {
|
||||
2: { table: '"table 1_ov2"' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT column1, column2, column3 FROM (\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM public.\"table 1_ov2\", _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM public.\"table 1\", _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS \"_vovw_table 1\"\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
it('generates query using overviews for queries with a semicolon', function(done){
|
||||
var sql = "SELECT column1, column2, column3 FROM table1;";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
2: { table: 'table1_ov2' }
|
||||
it('generates query for a table with explicit schema that needs quoting', function(){
|
||||
var sql = "SELECT * FROM \"user-1\".table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
'"user-1".table1': {
|
||||
2: { table: 'table1_ov2' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM \"user-1\".table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM \"user-1\".table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT column1, column2, column3 FROM (\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1;\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
|
||||
it('generates query using overviews for queries with extra whitespace', function(done){
|
||||
var sql = " SELECT column1,column2, column3 FROM table1 ";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
2: { table: 'table1_ov2' }
|
||||
it('generates query for a table with explicit schema both needing quoting', function(){
|
||||
var sql = "SELECT * FROM \"user-1\".\"table 1\"";
|
||||
var data = {
|
||||
overviews: {
|
||||
'"user-1"."table 1"': {
|
||||
2: { table: '"table 1_ov2"' }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT column1,column2, column3 FROM (\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (\
|
||||
SELECT * FROM \"user-1\".\"table 1_ov2\", _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM \"user-1\".\"table 1\", _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS \"_vovw_table 1\"\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
it('does not alter queries which have not the simple supported form', function(done){
|
||||
var sql = "SELECT * FROM table1 WHERE column1='x'";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
2: { table: 'table1_ov2' }
|
||||
|
||||
it('generates query using overviews for queries with selected columns', function(){
|
||||
var sql = "SELECT column1, column2, column3 FROM table1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
2: { table: 'table1_ov2' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT column1, column2, column3 FROM (\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
sql = "SELECT * FROM table1 JOIN table2 ON (table1.col1=table2.col1)";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "SELECT a+b AS c FROM table1";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "SELECT f(a) AS b FROM table1";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "SELECT * FROM table1 AS x";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "WITH a AS (1) SELECT * FROM table1";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "SELECT * FROM table1 WHERE a=1";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "\
|
||||
SELECT table1.* FROM table1 \
|
||||
JOIN areas ON ST_Intersects(table1.the_geom, areas.the_geom) \
|
||||
WHERE areas.name='A' \
|
||||
";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "SELECT table1.*, column1, column2, column3 FROM table1";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('generates overviews for wrapped query', function(done){
|
||||
var sql = "SELECT * FROM (SELECT * FROM table1) AS wrapped_query WHERE 1=1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
0: { table: 'table1_ov0' },
|
||||
1: { table: 'table1_ov1' },
|
||||
2: { table: 'table1_ov2' }
|
||||
it('generates query using overviews for queries with a semicolon', function(){
|
||||
var sql = "SELECT column1, column2, column3 FROM table1;";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
2: { table: 'table1_ov2' }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (SELECT * FROM (\
|
||||
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov1, _vovw_scale WHERE _vovw_z = 1\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z = 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1) AS wrapped_query WHERE 1=1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
done();
|
||||
});
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT column1, column2, column3 FROM (\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1;\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
it('generates query using overviews for queries with extra whitespace', function(){
|
||||
var sql = " SELECT column1,column2, column3 FROM table1 ";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
2: { table: 'table1_ov2' }
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT column1,column2, column3 FROM (\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
|
||||
it('does not alter queries which have not the simple supported form', function(){
|
||||
var sql = "SELECT * FROM table1 WHERE column1='x'";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
2: { table: 'table1_ov2' }
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "SELECT * FROM table1 JOIN table2 ON (table1.col1=table2.col1)";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "SELECT a+b AS c FROM table1";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "SELECT f(a) AS b FROM table1";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "SELECT * FROM table1 AS x";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "WITH a AS (1) SELECT * FROM table1";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "SELECT * FROM table1 WHERE a=1";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "\
|
||||
SELECT table1.* FROM table1 \
|
||||
JOIN areas ON ST_Intersects(table1.the_geom, areas.the_geom) \
|
||||
WHERE areas.name='A' \
|
||||
";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
|
||||
sql = "SELECT table1.*, column1, column2, column3 FROM table1";
|
||||
overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
assert.equal(overviews_sql, sql);
|
||||
});
|
||||
|
||||
it('generates overviews for wrapped query', function(){
|
||||
var sql = "SELECT * FROM (SELECT * FROM table1) AS wrapped_query WHERE 1=1";
|
||||
var data = {
|
||||
overviews: {
|
||||
table1: {
|
||||
0: { table: 'table1_ov0' },
|
||||
1: { table: 'table1_ov1' },
|
||||
2: { table: 'table1_ov2' }
|
||||
}
|
||||
}
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data);
|
||||
var expected_sql = "\
|
||||
WITH\
|
||||
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
|
||||
SELECT * FROM (SELECT * FROM (\
|
||||
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov1, _vovw_scale WHERE _vovw_z = 1\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z = 2\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
|
||||
) AS _vovw_table1) AS wrapped_query WHERE 1=1\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
});
|
||||
});
|
||||
|
||||