Compare commits
195 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd3fdb7f16 | ||
|
|
775af6feee | ||
|
|
adf5c17e0d | ||
|
|
beb2d96a32 | ||
|
|
2a4ae88bc0 | ||
|
|
b76098ba45 | ||
|
|
c095027f8e | ||
|
|
9d1db19907 | ||
|
|
260e321537 | ||
|
|
073603b527 | ||
|
|
17b259cf31 | ||
|
|
8f0f0026e9 | ||
|
|
59dae2b545 | ||
|
|
4670f69ead | ||
|
|
16fbd25a34 | ||
|
|
2d75985cb3 | ||
|
|
f963fb321e | ||
|
|
10feea0d48 | ||
|
|
b6b9b0ac36 | ||
|
|
5551e85853 | ||
|
|
1f0fa5031b | ||
|
|
263294a3f5 | ||
|
|
f9df30f70b | ||
|
|
61d31ec054 | ||
|
|
c8917bfc4c | ||
|
|
36b69a05e5 | ||
|
|
c8d2f66467 | ||
|
|
7416bb0e56 | ||
|
|
9182d0132d | ||
|
|
9be9357ade | ||
|
|
7f414f8adf | ||
|
|
3af9549939 | ||
|
|
41f248d731 | ||
|
|
18a517b7bf | ||
|
|
5150204389 | ||
|
|
3b16e7729d | ||
|
|
76d27c9fce | ||
|
|
4ce6e41000 | ||
|
|
33260cdbd9 | ||
|
|
85d81ba7fd | ||
|
|
7e8a3ca21f | ||
|
|
e9e4dc1f5c | ||
|
|
17c30e165a | ||
|
|
c45c6ceb15 | ||
|
|
d73c2c465f | ||
|
|
d4fc53939b | ||
|
|
4becb65bec | ||
|
|
f64e16c790 | ||
|
|
1772011627 | ||
|
|
5b8f785e2b | ||
|
|
a0e3b77006 | ||
|
|
908070ecd7 | ||
|
|
7c6a58cd30 | ||
|
|
b0990a1132 | ||
|
|
c6988cdb88 | ||
|
|
e0d304b033 | ||
|
|
e4a9f2d64c | ||
|
|
0236fe3ca9 | ||
|
|
1bed8623a2 | ||
|
|
df7d957914 | ||
|
|
30c4b00f33 | ||
|
|
ab27886460 | ||
|
|
31e18d04d7 | ||
|
|
8155484510 | ||
|
|
b61f1d2b53 | ||
|
|
2e274b936a | ||
|
|
bf3e311b57 | ||
|
|
6a7613de6b | ||
|
|
ee46549e04 | ||
|
|
377f3d4aff | ||
|
|
752d47d71e | ||
|
|
367157b80c | ||
|
|
53542f1cd6 | ||
|
|
7a8f156abf | ||
|
|
c60cc57a0d | ||
|
|
8de6ec9a21 | ||
|
|
44b6f4be7e | ||
|
|
280be1751c | ||
|
|
701a73a2c5 | ||
|
|
b578eada07 | ||
|
|
8100f155dc | ||
|
|
9f1a014004 | ||
|
|
e35e0e157c | ||
|
|
3aff328af3 | ||
|
|
ffb086045a | ||
|
|
c0786dfa6f | ||
|
|
ddc33fa52b | ||
|
|
9f2d6a5d41 | ||
|
|
64e884a092 | ||
|
|
17ec174683 | ||
|
|
3666cbee94 | ||
|
|
25de018f7d | ||
|
|
6597851b48 | ||
|
|
0399131968 | ||
|
|
86836e7f89 | ||
|
|
df346b11d3 | ||
|
|
27f74b3fe2 | ||
|
|
87dec64ad1 | ||
|
|
54c787162a | ||
|
|
6e92e699dc | ||
|
|
7950f43db3 | ||
|
|
d300677315 | ||
|
|
bd4d29dd14 | ||
|
|
18a84433f4 | ||
|
|
768ebf0ef2 | ||
|
|
1c20cb5478 | ||
|
|
279587ea11 | ||
|
|
25df193390 | ||
|
|
9ce81693bd | ||
|
|
16765e092f | ||
|
|
237e1257c4 | ||
|
|
665859b17d | ||
|
|
b5a6d6974c | ||
|
|
26bab029f4 | ||
|
|
ed7bb07b03 | ||
|
|
c87277ad01 | ||
|
|
62be259a90 | ||
|
|
80798f984b | ||
|
|
e32409880c | ||
|
|
7b6eb2940e | ||
|
|
87ad8df22f | ||
|
|
9fe20036a1 | ||
|
|
2b0d8d43bd | ||
|
|
3af340d384 | ||
|
|
20c1ad8d87 | ||
|
|
8c351c7c46 | ||
|
|
6647e986d9 | ||
|
|
77f691520c | ||
|
|
dfaa6ec024 | ||
|
|
41c574f5df | ||
|
|
10e901dcaa | ||
|
|
1d3626b4e1 | ||
|
|
bc419a51d6 | ||
|
|
d0980a2872 | ||
|
|
74719e48d9 | ||
|
|
d7c6b45438 | ||
|
|
551cfd87ee | ||
|
|
ba29873a8e | ||
|
|
d9e2bb4537 | ||
|
|
16d7b15d67 | ||
|
|
b9da97fedd | ||
|
|
bdf3b0393a | ||
|
|
a18f701466 | ||
|
|
71c7d8a90c | ||
|
|
99e766d952 | ||
|
|
fc78a0ed36 | ||
|
|
d4d398f583 | ||
|
|
be766ec803 | ||
|
|
57bb8dbbe3 | ||
|
|
c539d4fbbd | ||
|
|
a7dddcebe8 | ||
|
|
a9275845ff | ||
|
|
e5bf9efdb9 | ||
|
|
85073345ec | ||
|
|
80d5b29902 | ||
|
|
f55b748d20 | ||
|
|
870468ddf7 | ||
|
|
b3107916ce | ||
|
|
9cf856ab78 | ||
|
|
bbd047a940 | ||
|
|
d3dfb0a7ff | ||
|
|
5a8f9db79c | ||
|
|
fbe20386b6 | ||
|
|
6245b40015 | ||
|
|
3e959d8dc0 | ||
|
|
564df920d1 | ||
|
|
7e2c467a4f | ||
|
|
3a42305408 | ||
|
|
09616777e6 | ||
|
|
c2a5569b8c | ||
|
|
38bea2108b | ||
|
|
ab0777b45f | ||
|
|
962f94387b | ||
|
|
de5600b4fd | ||
|
|
188a202f02 | ||
|
|
19f39b87f5 | ||
|
|
c759f314f9 | ||
|
|
6c98f14c64 | ||
|
|
f2348e1b24 | ||
|
|
4b5a10fe61 | ||
|
|
163fa58b5a | ||
|
|
2bb03225cb | ||
|
|
b3bbb6af01 | ||
|
|
28d711e1f4 | ||
|
|
410fdb8343 | ||
|
|
21c2f3bdd1 | ||
|
|
baae080318 | ||
|
|
81c0796056 | ||
|
|
6ea5c5f414 | ||
|
|
7e37705843 | ||
|
|
425b5e6b4a | ||
|
|
8942c72fb2 | ||
|
|
9acb980b82 | ||
|
|
7d623faf4b | ||
|
|
0f2401b0cc |
17
.travis.yml
@@ -1,24 +1,17 @@
|
||||
dist: trusty # only environment that supports Postgres 9.5 at this time
|
||||
sudo: required
|
||||
addons:
|
||||
postgresql: "9.5"
|
||||
apt:
|
||||
packages:
|
||||
- postgresql-plpython-9.5
|
||||
- pkg-config
|
||||
- libcairo2-dev
|
||||
- libjpeg8-dev
|
||||
- libgif-dev
|
||||
- libpango1.0-dev
|
||||
|
||||
before_install:
|
||||
- npm install -g npm@2
|
||||
- lsb_release -a
|
||||
- sudo mv /etc/apt/sources.list.d/pgdg-source.list* /tmp
|
||||
- sudo apt-get -qq purge postgis* postgresql*
|
||||
- sudo rm -Rf /var/lib/postgresql /etc/postgresql
|
||||
- sudo apt-add-repository --yes ppa:cartodb/postgresql-9.5
|
||||
- sudo apt-add-repository --yes ppa:cartodb/gis
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install -q postgresql-9.5-postgis-2.2
|
||||
- sudo apt-get install -q postgresql-plpython-9.5
|
||||
- echo -e "local\tall\tall\ttrust\nhost\tall\tall\t127.0.0.1/32\ttrust\nhost\tall\tall\t::1/128\ttrust" |sudo tee /etc/postgresql/9.5/main/pg_hba.conf
|
||||
- sudo service postgresql restart
|
||||
- createdb template_postgis
|
||||
- createuser publicuser
|
||||
- psql -c "CREATE EXTENSION postgis" template_postgis
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
1. Test (make clean all check), fix if broken before proceeding
|
||||
2. Ensure proper version in package.json
|
||||
2. Ensure proper version in package.json
|
||||
3. Ensure NEWS section exists for the new version, review it, add release date
|
||||
4. Recreate npm-shrinkwrap.json with: `npm install --no-shrinkwrap && npm shrinkwrap`
|
||||
5. Commit package.json, npm-shrinwrap.json, NEWS
|
||||
|
||||
286
NEWS.md
@@ -1,5 +1,291 @@
|
||||
# Changelog
|
||||
|
||||
## 2.78.0
|
||||
Released 2016-09-29
|
||||
|
||||
New features:
|
||||
- Add metadata about processed turbo-carto rules.
|
||||
|
||||
Announcements:
|
||||
- Upgrades turbo-carto to [0.17.1](https://github.com/CartoDB/turbo-carto/releases/tag/0.17.1).
|
||||
|
||||
|
||||
## 2.77.1
|
||||
|
||||
Released 2016-09-28
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.44.1](https://github.com/CartoDB/camshaft/releases/tag/0.44.1).
|
||||
|
||||
|
||||
## 2.77.0
|
||||
|
||||
Released 2016-09-26
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.44.0](https://github.com/CartoDB/camshaft/releases/tag/0.44.0).
|
||||
- Adds a new configuration for camshaft: logger stream.
|
||||
|
||||
|
||||
## 2.76.0
|
||||
|
||||
Released 2016-09-15
|
||||
|
||||
New features:
|
||||
- Allow to use `--config /path/to/config.js` to specify configuration file.
|
||||
- Environment will be loaded from config file if `environment` key is present, otherwise it keeps current behaviour.
|
||||
|
||||
Bug fixes:
|
||||
- Allow to use absolute paths for log files #570.
|
||||
|
||||
|
||||
## 2.75.0
|
||||
|
||||
Released 2016-09-14
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.43.0](https://github.com/CartoDB/camshaft/releases/tag/0.43.0).
|
||||
|
||||
|
||||
## 2.74.1
|
||||
|
||||
Released 2016-09-07
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.42.1](https://github.com/CartoDB/camshaft/releases/tag/0.42.1).
|
||||
|
||||
|
||||
## 2.74.0
|
||||
|
||||
Released 2016-09-06
|
||||
|
||||
Enhancements:
|
||||
- Layers in previews can be shown or hidden using `preview_layers` property in template map
|
||||
|
||||
|
||||
## 2.73.1
|
||||
|
||||
Released 2016-09-06
|
||||
|
||||
Bug fixes:
|
||||
- Fixes missing column in fixture table `cdb_analysis_catalog`.
|
||||
|
||||
|
||||
## 2.73.0
|
||||
|
||||
Released 2016-09-06
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.42.0](https://github.com/CartoDB/camshaft/releases/tag/0.42.0).
|
||||
|
||||
|
||||
## 2.72.0
|
||||
|
||||
Released 2016-08-23
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.41.0](https://github.com/CartoDB/camshaft/releases/tag/0.41.0).
|
||||
|
||||
|
||||
## 2.71.0
|
||||
|
||||
Released 2016-08-17
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [2.5.0](https://github.com/CartoDB/windshaft/releases/tag/2.5.0).
|
||||
|
||||
|
||||
## 2.70.0
|
||||
|
||||
Released 2016-08-16
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.40.0](https://github.com/CartoDB/camshaft/releases/tag/0.40.0).
|
||||
|
||||
|
||||
## 2.69.1
|
||||
|
||||
Released 2016-08-12
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [2.4.2](https://github.com/CartoDB/windshaft/releases/tag/2.4.2).
|
||||
|
||||
|
||||
## 2.69.0
|
||||
|
||||
Released 2016-08-11
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.39.0](https://github.com/CartoDB/camshaft/releases/tag/0.39.0).
|
||||
|
||||
|
||||
## 2.68.0
|
||||
|
||||
Released 2016-07-21
|
||||
|
||||
Announcements:
|
||||
- Upgrades turbo-carto to [0.16.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.16.0).
|
||||
|
||||
|
||||
## 2.67.1
|
||||
|
||||
Released 2016-07-21
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.38.1](https://github.com/CartoDB/camshaft/releases/tag/0.38.1).
|
||||
|
||||
|
||||
## 2.67.0
|
||||
|
||||
Released 2016-07-21
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.38.0](https://github.com/CartoDB/camshaft/releases/tag/0.38.0).
|
||||
|
||||
|
||||
## 2.66.2
|
||||
|
||||
Released 2016-07-20
|
||||
|
||||
Announcements:
|
||||
- Upgrades turbo-carto to [0.15.1](https://github.com/CartoDB/turbo-carto/releases/tag/0.15.1).
|
||||
|
||||
|
||||
## 2.66.1
|
||||
|
||||
Released 2016-07-20
|
||||
|
||||
Announcements:
|
||||
- Upgrades turbo-carto to [0.15.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.15.0).
|
||||
|
||||
|
||||
## 2.66.0
|
||||
|
||||
Released 2016-07-18
|
||||
|
||||
Announcements:
|
||||
- Available new endpoint to check user analyses.
|
||||
- Upgrades camshaft to [0.37.1](https://github.com/CartoDB/camshaft/releases/tag/0.37.1).
|
||||
|
||||
|
||||
## 2.65.0
|
||||
|
||||
Released 2016-07-15
|
||||
|
||||
Announcements:
|
||||
- Upgrades cartodb-redis to 0.13.1.
|
||||
- Upgrades camshaft to [0.37.0](https://github.com/CartoDB/camshaft/releases/tag/0.37.0).
|
||||
|
||||
|
||||
## 2.64.0
|
||||
|
||||
Released 2016-07-12
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.36.0](https://github.com/CartoDB/camshaft/releases/tag/0.36.0).
|
||||
|
||||
|
||||
## 2.63.0
|
||||
|
||||
Released 2016-07-11
|
||||
|
||||
Enhancements:
|
||||
- Return last error message for failed nodes on map creation.
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.35.0](https://github.com/CartoDB/camshaft/releases/tag/0.35.0).
|
||||
- Upgrades lzma to 2.3.2.
|
||||
|
||||
|
||||
## 2.62.0
|
||||
|
||||
Released 2016-07-07
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.34.0](https://github.com/CartoDB/camshaft/releases/tag/0.34.0).
|
||||
|
||||
|
||||
## 2.61.2
|
||||
|
||||
Released 2016-07-07
|
||||
|
||||
Announcements:
|
||||
- Limit analysis creation concurrency.
|
||||
- Upgrades camshaft to [0.33.3](https://github.com/CartoDB/camshaft/releases/tag/0.33.3).
|
||||
|
||||
|
||||
## 2.61.1
|
||||
|
||||
Released 2016-07-06
|
||||
|
||||
Enhancements:
|
||||
- Dataviews use mapconfig to store/retrieve their queries instead of instantiating analyses again.
|
||||
|
||||
|
||||
## 2.61.0
|
||||
|
||||
Released 2016-07-06
|
||||
|
||||
Enhancements:
|
||||
- More clear turbo-carto error messages: no context in message.
|
||||
- Return multiple turbo-carto errors #541.
|
||||
|
||||
Announcements:
|
||||
- Upgrades turbo-carto to [0.14.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.14.0).
|
||||
- Upgrades camshaft to [0.33.2](https://github.com/CartoDB/camshaft/releases/tag/0.33.2).
|
||||
|
||||
|
||||
## 2.60.0
|
||||
|
||||
Released 2016-07-05
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.32.0](https://github.com/CartoDB/camshaft/releases/tag/0.32.0).
|
||||
|
||||
|
||||
## 2.59.1
|
||||
|
||||
Released 2016-07-05
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.31.0](https://github.com/CartoDB/camshaft/releases/tag/0.31.0).
|
||||
|
||||
|
||||
## 2.59.0
|
||||
|
||||
Released 2016-07-05
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.30.0](https://github.com/CartoDB/camshaft/releases/tag/0.30.0).
|
||||
|
||||
|
||||
## 2.58.0
|
||||
|
||||
Released 2016-07-05
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.29.2](https://github.com/CartoDB/camshaft/releases/tag/0.29.2).
|
||||
|
||||
Bug fixes:
|
||||
- Return full list of nodes in response metadata.
|
||||
|
||||
|
||||
## 2.57.0
|
||||
|
||||
Released 2016-07-04
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.28.1](https://github.com/CartoDB/camshaft/releases/tag/0.28.1).
|
||||
|
||||
|
||||
## 2.56.0
|
||||
|
||||
Released 2016-07-04
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.27.0](https://github.com/CartoDB/camshaft/releases/tag/0.27.0).
|
||||
|
||||
|
||||
## 2.55.0
|
||||
|
||||
Released 2016-07-04
|
||||
|
||||
82
app.js
@@ -5,20 +5,35 @@ var fs = require('fs');
|
||||
|
||||
var _ = require('underscore');
|
||||
|
||||
var ENVIRONMENT;
|
||||
if ( process.argv[2] ) {
|
||||
ENVIRONMENT = process.argv[2];
|
||||
} else if ( process.env.NODE_ENV ) {
|
||||
ENVIRONMENT = process.env.NODE_ENV;
|
||||
} else {
|
||||
ENVIRONMENT = 'development';
|
||||
}
|
||||
|
||||
// jshint undef:false
|
||||
var log = console.log.bind(console);
|
||||
var logError = console.error.bind(console);
|
||||
// jshint undef:true
|
||||
|
||||
var argv = require('yargs')
|
||||
.usage('Usage: $0 <environment> [options]')
|
||||
.help('h')
|
||||
.example(
|
||||
'$0 production -c /etc/sql-api/config.js',
|
||||
'start server in production environment with /etc/sql-api/config.js as config file'
|
||||
)
|
||||
.alias('h', 'help')
|
||||
.alias('c', 'config')
|
||||
.nargs('c', 1)
|
||||
.describe('c', 'Load configuration from path')
|
||||
.argv;
|
||||
|
||||
var environmentArg = argv._[0] || process.env.NODE_ENV || 'development';
|
||||
var configurationFile = path.resolve(argv.config || './config/environments/' + environmentArg + '.js');
|
||||
if (!fs.existsSync(configurationFile)) {
|
||||
logError('Configuration file "%s" does not exist', configurationFile);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
global.environment = require(configurationFile);
|
||||
var ENVIRONMENT = argv._[0] || process.env.NODE_ENV || global.environment.environment;
|
||||
process.env.NODE_ENV = ENVIRONMENT;
|
||||
|
||||
var availableEnvironments = {
|
||||
production: true,
|
||||
staging: true,
|
||||
@@ -33,16 +48,6 @@ if (!availableEnvironments[ENVIRONMENT]){
|
||||
}
|
||||
|
||||
process.env.NODE_ENV = ENVIRONMENT;
|
||||
|
||||
// set environment specific variables
|
||||
global.environment = require('./config/environments/' + ENVIRONMENT);
|
||||
|
||||
global.log4js = require('log4js');
|
||||
var log4js_config = {
|
||||
appenders: [],
|
||||
replaceConsole: true
|
||||
};
|
||||
|
||||
if (global.environment.uv_threadpool_size) {
|
||||
process.env.UV_THREADPOOL_SIZE = global.environment.uv_threadpool_size;
|
||||
}
|
||||
@@ -58,25 +63,31 @@ var agentOptions = _.defaults(global.environment.httpAgent || {}, {
|
||||
http.globalAgent = new http.Agent(agentOptions);
|
||||
https.globalAgent = new https.Agent(agentOptions);
|
||||
|
||||
|
||||
global.log4js = require('log4js');
|
||||
var log4jsConfig = {
|
||||
appenders: [],
|
||||
replaceConsole: true
|
||||
};
|
||||
|
||||
if ( global.environment.log_filename ) {
|
||||
var logdir = path.dirname(global.environment.log_filename);
|
||||
// See cwd inlog4js.configure call below
|
||||
logdir = path.resolve(__dirname, logdir);
|
||||
if ( ! fs.existsSync(logdir) ) {
|
||||
logError("Log filename directory does not exist: " + logdir);
|
||||
process.exit(1);
|
||||
}
|
||||
log("Logs will be written to " + global.environment.log_filename);
|
||||
log4js_config.appenders.push(
|
||||
{ type: "file", filename: global.environment.log_filename }
|
||||
);
|
||||
var logFilename = path.resolve(global.environment.log_filename);
|
||||
var logDirectory = path.dirname(logFilename);
|
||||
if (!fs.existsSync(logDirectory)) {
|
||||
logError("Log filename directory does not exist: " + logDirectory);
|
||||
process.exit(1);
|
||||
}
|
||||
log("Logs will be written to " + logFilename);
|
||||
log4jsConfig.appenders.push(
|
||||
{ type: "file", absolute: true, filename: logFilename }
|
||||
);
|
||||
} else {
|
||||
log4js_config.appenders.push(
|
||||
{ type: "console", layout: { type:'basic' } }
|
||||
);
|
||||
log4jsConfig.appenders.push(
|
||||
{ type: "console", layout: { type:'basic' } }
|
||||
);
|
||||
}
|
||||
|
||||
global.log4js.configure(log4js_config, { cwd: __dirname });
|
||||
global.log4js.configure(log4jsConfig);
|
||||
global.logger = global.log4js.getLogger();
|
||||
|
||||
global.environment.api_hostname = require('os').hostname().split('.')[0];
|
||||
@@ -99,6 +110,7 @@ var listener = server.listen(serverOptions.bind.port, serverOptions.bind.host, b
|
||||
var version = require("./package").version;
|
||||
|
||||
listener.on('listening', function() {
|
||||
log('Using configuration file "%s"', configurationFile);
|
||||
log(
|
||||
"Windshaft tileserver %s started on %s:%s PID=%d (%s)",
|
||||
version, serverOptions.bind.host, serverOptions.bind.port, process.pid, ENVIRONMENT
|
||||
@@ -114,7 +126,7 @@ setInterval(function() {
|
||||
|
||||
process.on('SIGHUP', function() {
|
||||
global.log4js.clearAndShutdownAppenders(function() {
|
||||
global.log4js.configure(log4js_config);
|
||||
global.log4js.configure(log4jsConfig);
|
||||
global.logger = global.log4js.getLogger();
|
||||
log('Log files reloaded');
|
||||
});
|
||||
|
||||
@@ -62,6 +62,7 @@ var config = {
|
||||
extent: "-180,-90,180,90",
|
||||
srid: 4326,
|
||||
*/
|
||||
// max number of rows to return when querying data, 0 means no limit
|
||||
row_limit: 65535,
|
||||
simplify_geometries: true,
|
||||
use_overviews: true, // use overviews to retrieve raster
|
||||
@@ -209,6 +210,12 @@ var config = {
|
||||
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'
|
||||
},
|
||||
logger: {
|
||||
// If filename is given logs comming from analysis client will be written
|
||||
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
|
||||
// Log file will be re-opened on receiving the HUP signal
|
||||
filename: '/tmp/analysis.log'
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
|
||||
@@ -56,6 +56,7 @@ var config = {
|
||||
host: '127.0.0.1',
|
||||
port: 6432,
|
||||
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
|
||||
// max number of rows to return when querying data, 0 means no limit
|
||||
row_limit: 65535,
|
||||
/*
|
||||
* Set persist_connection to false if you want
|
||||
@@ -203,6 +204,12 @@ var config = {
|
||||
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'
|
||||
},
|
||||
logger: {
|
||||
// If filename is given logs comming from analysis client will be written
|
||||
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
|
||||
// Log file will be re-opened on receiving the HUP signal
|
||||
filename: 'logs/analysis.log'
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
|
||||
@@ -56,6 +56,7 @@ var config = {
|
||||
host: '127.0.0.1',
|
||||
port: 6432,
|
||||
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
|
||||
// max number of rows to return when querying data, 0 means no limit
|
||||
row_limit: 65535,
|
||||
simplify_geometries: true,
|
||||
use_overviews: true, // use overviews to retrieve raster
|
||||
@@ -203,6 +204,12 @@ var config = {
|
||||
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'
|
||||
},
|
||||
logger: {
|
||||
// If filename is given logs comming from analysis client will be written
|
||||
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
|
||||
// Log file will be re-opened on receiving the HUP signal
|
||||
filename: 'logs/analysis.log'
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
|
||||
@@ -56,6 +56,7 @@ var config = {
|
||||
host: '127.0.0.1',
|
||||
port: 5432,
|
||||
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
|
||||
// max number of rows to return when querying data, 0 means no limit
|
||||
row_limit: 65535,
|
||||
simplify_geometries: true,
|
||||
use_overviews: true, // use overviews to retrieve raster
|
||||
@@ -204,6 +205,12 @@ var config = {
|
||||
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'
|
||||
},
|
||||
logger: {
|
||||
// If filename is given logs comming from analysis client will be written
|
||||
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
|
||||
// Log file will be re-opened on receiving the HUP signal
|
||||
filename: 'node-windshaft.log'
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Maps API
|
||||
|
||||
The CartoDB Maps API allows you to generate maps based on data hosted in your CartoDB account and apply custom SQL and CartoCSS to the data. The API generates a XYZ-based URL to fetch Web Mercator projected tiles, using web clients such as [Leaflet](http://leafletjs.com), [Google Maps](https://developers.google.com/maps/), or [OpenLayers](http://openlayers.org/).
|
||||
The CARTO Maps API allows you to generate maps based on data hosted in your CARTO account and apply custom SQL and CartoCSS to the data. The API generates a XYZ-based URL to fetch Web Mercator projected tiles, using web clients such as [Leaflet](http://leafletjs.com), [Google Maps](https://developers.google.com/maps/), or [OpenLayers](http://openlayers.org/).
|
||||
|
||||
You can create two types of maps with the Maps API:
|
||||
|
||||
- **Anonymous Maps**
|
||||
You can create maps using your CartoDB public data. Any client can change the read-only SQL and CartoCSS parameters that generate the map tiles. These maps can be created from a JavaScript application alone and no authenticated calls are needed. See [this CartoDB.js example](/cartodb-platform/cartodb-js/getting-started/).
|
||||
You can create maps using your CARTO public data. Any client can change the read-only SQL and CartoCSS parameters that generate the map tiles. These maps can be created from a JavaScript application alone and no authenticated calls are needed. See [this CARTO.js example](/carto-engine/carto-js/getting-started/).
|
||||
|
||||
- **Named Maps**
|
||||
There are also maps that have access to your private data. These maps require an owner to setup and modify any SQL and CartoCSS parameters and are not modifiable without new setup calls.
|
||||
|
||||
@@ -28,7 +28,7 @@ POST /api/v1/map
|
||||
}
|
||||
```
|
||||
|
||||
See [MapConfig File Formats](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/) for details.
|
||||
See [MapConfig File Formats](http://docs.carto.com/carto-engine/maps-api/mapconfig/) for details.
|
||||
|
||||
#### Response
|
||||
|
||||
@@ -36,7 +36,7 @@ The response includes:
|
||||
|
||||
Attributes | Description
|
||||
--- | ---
|
||||
layergroupid | The ID for that map, used to compose the URL for the tiles. The final URL is: `https://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png`
|
||||
layergroupid | The ID for that map, used to compose the URL for the tiles. The final URL is: `https://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png`
|
||||
updated_at | The ISO date of the last time the data involved in the query was updated.
|
||||
metadata | Includes information about the layers.
|
||||
cdn_url | URLs to fetch the data using the best CDN for your zone.
|
||||
@@ -46,7 +46,7 @@ cdn_url | URLs to fetch the data using the best CDN for your zone.
|
||||
#### Call
|
||||
|
||||
```bash
|
||||
curl 'https://{username}.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json
|
||||
curl 'https://{username}.carto.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json
|
||||
```
|
||||
|
||||
#### Response
|
||||
@@ -79,7 +79,7 @@ When you have a layergroup, there are several resources for retrieving layergoup
|
||||
These tiles will get just the Mapnik layers. To get individual layers, see the following section.
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
|
||||
https://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
|
||||
```
|
||||
|
||||
#### Individual layers
|
||||
@@ -89,7 +89,7 @@ The MapConfig specification holds the layers definition in a 0-based index. Laye
|
||||
Individual layers can be accessed using that 0-based index. For UTF grid tiles:
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer}/{z}/{x}/{y}.grid.json
|
||||
https://{username}.carto.com/api/v1/map/{layergroupid}/{layer}/{z}/{x}/{y}.grid.json
|
||||
```
|
||||
|
||||
In this case, `layer` as 0 returns the UTF grid tiles/attributes for layer 0, the only layer in the example MapConfig.
|
||||
@@ -97,13 +97,13 @@ In this case, `layer` as 0 returns the UTF grid tiles/attributes for layer 0, th
|
||||
If the MapConfig had a Torque layer at index 1 it could be possible to request it with:
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/{layergroupid}/1/{z}/{x}/{y}.torque.json
|
||||
https://{username}.carto.com/api/v1/map/{layergroupid}/1/{z}/{x}/{y}.torque.json
|
||||
```
|
||||
|
||||
#### Attributes defined in `attributes` section
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer}/attributes/{feature_id}
|
||||
https://{username}.carto.com/api/v1/map/{layergroupid}/{layer}/attributes/{feature_id}
|
||||
```
|
||||
|
||||
Which returns JSON with the attributes defined, like:
|
||||
@@ -115,7 +115,7 @@ Which returns JSON with the attributes defined, like:
|
||||
#### Blending and layer selection
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer_filter}/{z}/{x}/{y}.png
|
||||
https://{username}.carto.com/api/v1/map/{layergroupid}/{layer_filter}/{z}/{x}/{y}.png
|
||||
```
|
||||
|
||||
Note: currently format is limited to `png`.
|
||||
@@ -127,7 +127,7 @@ Note: currently format is limited to `png`.
|
||||
Using `all` as `layer_filter` will blend all layers in the layergroup
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/{layergroupid}/all/{z}/{x}/{y}.png
|
||||
https://{username}.carto.com/api/v1/map/{layergroupid}/all/{z}/{x}/{y}.png
|
||||
```
|
||||
|
||||
- Filter by layer index
|
||||
@@ -135,15 +135,12 @@ https://{username}.cartodb.com/api/v1/map/{layergroupid}/all/{z}/{x}/{y}.png
|
||||
A list of comma separated layer indexes can be used to just render a subset of layers. For example `0,3,4` will filter and blend layers with indexes 0, 3, and 4.
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/{layergroupid}/0,3,4/{z}/{x}/{y}.png
|
||||
https://{username}.carto.com/api/v1/map/{layergroupid}/0,3,4/{z}/{x}/{y}.png
|
||||
```
|
||||
|
||||
Some notes about filtering:
|
||||
|
||||
- Invalid index values or out of bounds indexes will end in `Invalid layer filtering` errors.
|
||||
- Once a Mapnik layer is selected, all Mapnik layers will get blended. As this may change in the future **it is
|
||||
recommended** to always select all Mapnik layers if you want to select at least one so you will get a consistent
|
||||
behavior in the future.
|
||||
- Ordering is not considered. So right now filtering layers 0,3,4 is the very same thing as filtering 3,4,0. As this
|
||||
may change in the future **it is recommended** to always select the layers in ascending order so you will get a
|
||||
consistent behavior in the future.
|
||||
@@ -172,7 +169,7 @@ callback | JSON callback name.
|
||||
#### Call
|
||||
|
||||
```bash
|
||||
curl "https://{username}.cartodb.com/api/v1/map?callback=callback&config=%7B%22version%22%3A%221.0.1%22%2C%22layers%22%3A%5B%7B%22type%22%3A%22cartodb%22%2C%22options%22%3A%7B%22sql%22%3A%22select+%2A+from+european_countries_e%22%2C%22cartocss%22%3A%22%23european_countries_e%7B+polygon-fill%3A+%23FF6600%3B+%7D%22%2C%22cartocss_version%22%3A%222.3.0%22%2C%22interactivity%22%3A%5B%22cartodb_id%22%5D%7D%7D%5D%7D"
|
||||
curl "https://{username}.carto.com/api/v1/map?callback=callback&config=%7B%22version%22%3A%221.0.1%22%2C%22layers%22%3A%5B%7B%22type%22%3A%22cartodb%22%2C%22options%22%3A%7B%22sql%22%3A%22select+%2A+from+european_countries_e%22%2C%22cartocss%22%3A%22%23european_countries_e%7B+polygon-fill%3A+%23FF6600%3B+%7D%22%2C%22cartocss_version%22%3A%222.3.0%22%2C%22interactivity%22%3A%5B%22cartodb_id%22%5D%7D%7D%5D%7D"
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
@@ -4,7 +4,7 @@ The following concepts are the same for every endpoint in the API except when it
|
||||
|
||||
## Auth
|
||||
|
||||
By default, users do not have access to private tables in CartoDB. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a Named Map).
|
||||
By default, users do not have access to private tables in CARTO. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a Named Map).
|
||||
|
||||
To execute an authorized request, `api_key=YOURAPIKEY` should be added to the request URL. The param can be also passed as POST param. Using HTTPS is mandatory when you are performing requests that include your `api_key`.
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Named Maps
|
||||
|
||||
Named Maps are essentially the same as Anonymous Maps except the MapConfig is stored on the server, and the map is given a unique name. You can create Named Maps from private data, and users without an API Key can view your Named Map (while keeping your data private).
|
||||
Named Maps are essentially the same as Anonymous Maps except the MapConfig is stored on the server, and the map is given a unique name. You can create Named Maps from private data, and users without an API Key can view your Named Map (while keeping your data private).
|
||||
|
||||
The Named Map workflow consists of uploading a MapConfig file to CartoDB servers, to select data from your CartoDB user database by using SQL, and specifying the CartoCSS for your map.
|
||||
The Named Map workflow consists of uploading a MapConfig file to CARTO servers, to select data from your CARTO user database by using SQL, and specifying the CartoCSS for your map.
|
||||
|
||||
The response back from the API provides the template_id of your Named Map as the `name` (the identifier of your Named Map), which is the name that you specified in the MapConfig. You can which you can then use to create your Named Map details, or [fetch XYZ tiles](#fetching-xyz-tiles-for-named-maps) directly for Named Maps.
|
||||
The response back from the API provides the template_id of your Named Map as the `name` (the identifier of your Named Map), which is the name that you specified in the MapConfig. You can which you can then use to create your Named Map details, or [fetch XYZ tiles](#fetching-xyz-tiles-for-named-maps) directly for Named Maps.
|
||||
|
||||
**Tip:** You can also use a Named Map that you created (which is defined by its `name`), to create a map using CartoDB.js. This is achieved by adding the [`namedmap` type](http://docs.cartodb.com/cartodb-platform/cartodb-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
|
||||
**Tip:** You can also use a Named Map that you created (which is defined by its `name`), to create a map using CARTO.js. This is achieved by adding the [`namedmap` type](http://docs.carto.com/carto-engine/carto-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
|
||||
|
||||
The main differences, compared to Anonymous Maps, is that Named Maps include:
|
||||
|
||||
@@ -14,11 +14,11 @@ The main differences, compared to Anonymous Maps, is that Named Maps include:
|
||||
This allows you to control who is able to see the map based on an auth token, and create a secure Named Map with password-protection.
|
||||
|
||||
- **template map**
|
||||
The template map is static and may contain placeholders, enabling you to modify your maps appearance by using variables. Templates maps are persistent with no preset expiration. They can only be created, or deleted, by a CartoDB user with a valid API KEY (See [auth argument](#arguments)).
|
||||
The template map is static and may contain placeholders, enabling you to modify your maps appearance by using variables. Templates maps are persistent with no preset expiration. They can only be created, or deleted, by a CARTO user with a valid API KEY (See [auth argument](#arguments)).
|
||||
|
||||
Uploading a MapConfig creates a Named Map. MapConfigs are uploaded to the server by sending the server a "template".json file, which contain the [MapConfig specifications](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/).
|
||||
Uploading a MapConfig creates a Named Map. MapConfigs are uploaded to the server by sending the server a "template".json file, which contain the [MapConfig specifications](http://docs.carto.com/carto-engine/maps-api/mapconfig/).
|
||||
|
||||
**Note:** There is a limit of 4,096 Named Maps allowed per account. If you need to create more Named Maps, it is recommended to use a single Named Map and change the variables using [placeholders](#placeholder-format), instead of uploading multiple [Named Map MapConfigs](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/#named-map-layer-options).
|
||||
**Note:** There is a limit of 4,096 Named Maps allowed per account. If you need to create more Named Maps, it is recommended to use a single Named Map and change the variables using [placeholders](#placeholder-format), instead of uploading multiple [Named Map MapConfigs](http://docs.carto.com/carto-engine/maps-api/mapconfig/#named-map-layer-options).
|
||||
|
||||
## Create
|
||||
|
||||
@@ -33,7 +33,7 @@ POST /api/v1/map/named
|
||||
Params | Description
|
||||
--- | ---
|
||||
api_key | is required
|
||||
MapConfig | a [Named Map MapConfig](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/#named-map-layer-options) is required to create a Named Map
|
||||
MapConfig | a [Named Map MapConfig](http://docs.carto.com/carto-engine/maps-api/mapconfig/#named-map-layer-options) is required to create a Named Map
|
||||
|
||||
#### template.json
|
||||
|
||||
@@ -73,6 +73,10 @@ The `name` argument defines how to name this "template_name".json. Note that the
|
||||
}
|
||||
]
|
||||
},
|
||||
"preview_layers": {
|
||||
"0": true,
|
||||
"layer1": false
|
||||
},
|
||||
"view": {
|
||||
"zoom": 4,
|
||||
"center": {
|
||||
@@ -95,22 +99,22 @@ Params | Description
|
||||
--- | ---
|
||||
name | There can only be _one_ template with the same name for any user. Valid names start with a letter or a number, and only contain letters, numbers, dashes (-), or underscores (_). _This is specific to the name of your Named Map that is specified in the `name` property of the template file_.
|
||||
|
||||
auth |
|
||||
auth |
|
||||
--- | ---
|
||||
|_ method | `"token"` or `"open"` (`"open"` is the default if no method is specified. Use `"token"` to password-protect your map)
|
||||
|_ valid_tokens | when `"method"` is set to `"token"`, the values listed here allow you to instantiate the Named Map. See this [example](http://docs.cartodb.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map.
|
||||
|_ valid_tokens | when `"method"` is set to `"token"`, the values listed here allow you to instantiate the Named Map. See this [example](http://docs.carto.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map.
|
||||
placeholders | Placeholders are variables that can be placed in your template.json file's SQL or CartoCSS.
|
||||
layergroup | the layergroup configurations, as specified in the template. See [MapConfig File Format](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/) for more information.
|
||||
layergroup | the layergroup configurations, as specified in the template. See [MapConfig File Format](http://docs.carto.com/carto-engine/maps-api/mapconfig/) for more information.
|
||||
view (optional) | extra keys to specify the view area for the map. It can be used to have a static preview of a Named Map without having to instantiate it. It is possible to specify it with `center` + `zoom` or with a bounding box `bbox`. Center+zoom takes precedence over bounding box.
|
||||
--- | ---
|
||||
|_ zoom | The zoom level to use
|
||||
|
||||
|_ center |
|
||||
|_ center |
|
||||
--- | ---
|
||||
|_ |_ lng | The longitude to use for the center
|
||||
|_ |_ lat | The latitude to use for the center
|
||||
|
||||
|_ bounds |
|
||||
|_ bounds |
|
||||
--- | ---
|
||||
|_ |_ west | LowerCorner longitude for the bounding box, in decimal degrees (aka most western)
|
||||
|_ |_ south | LowerCorner latitude for the bounding box, in decimal degrees (aka most southern)
|
||||
@@ -120,7 +124,7 @@ view (optional) | extra keys to specify the view area for the map. It can be use
|
||||
|
||||
### Placeholder Format
|
||||
|
||||
Placeholders are variables that can be placed in your template.json file. Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergoup configurations](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/#layergroup-configurations).
|
||||
Placeholders are variables that can be placed in your template.json file. Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergoup configurations](http://docs.carto.com/carto-engine/maps-api/mapconfig/#layergroup-configurations).
|
||||
|
||||
Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced inside the Named Maps API.
|
||||
|
||||
@@ -155,12 +159,12 @@ This is the call for creating the Named Map. It is sending the template.json fil
|
||||
curl -X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d @template.json \
|
||||
'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}'
|
||||
'https://{username}.carto.com/api/v1/map/named?api_key={api_key}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
The response back from the API provides the name of your MapConfig as a template, enabling you to edit the Named Map details by inserting your variables into the template where placeholders are defined, and create custom queries using SQL.
|
||||
The response back from the API provides the name of your MapConfig as a template, enabling you to edit the Named Map details by inserting your variables into the template where placeholders are defined, and create custom queries using SQL.
|
||||
|
||||
```javascript
|
||||
{
|
||||
@@ -170,7 +174,7 @@ The response back from the API provides the name of your MapConfig as a template
|
||||
|
||||
## Instantiate
|
||||
|
||||
Instantiating a Named Map allows you to fetch the map tiles. You can use the Maps API to instantiate, or use the CartoDB.js `createLayer()` function. The result is an Anonymous Map.
|
||||
Instantiating a Named Map allows you to fetch the map tiles. You can use the Maps API to instantiate, or use the CARTO.js `createLayer()` function. The result is an Anonymous Map.
|
||||
|
||||
#### Definition
|
||||
|
||||
@@ -209,7 +213,7 @@ Valid auth token will be needed, if required by the template.
|
||||
curl -X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d @params.json \
|
||||
'https://{username}.cartodb.com/api/v1/map/named/{template_name}?auth_token={auth_token}'
|
||||
'https://{username}.carto.com/api/v1/map/named/{template_name}?auth_token={auth_token}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
@@ -229,7 +233,7 @@ curl -X POST \
|
||||
}
|
||||
```
|
||||
|
||||
You can then use the `layergroupid` for fetching tiles and grids as you would normally (see [Anonymous Maps](http://docs.cartodb.com/cartodb-platform/maps-api/anonymous-maps/)).
|
||||
You can then use the `layergroupid` for fetching tiles and grids as you would normally (see [Anonymous Maps](http://docs.carto.com/carto-engine/maps-api/anonymous-maps/)).
|
||||
|
||||
## Update
|
||||
|
||||
@@ -261,7 +265,7 @@ Updating a Named Map removes all the Named Map instances, so they need to be ini
|
||||
curl -X PUT \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d @template.json \
|
||||
'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}'
|
||||
'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
@@ -303,7 +307,7 @@ api_key | is required
|
||||
#### Call
|
||||
|
||||
```bash
|
||||
curl -X DELETE 'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}'
|
||||
curl -X DELETE 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
@@ -337,7 +341,7 @@ api_key | is required
|
||||
#### Call
|
||||
|
||||
```bash
|
||||
curl -X GET 'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}'
|
||||
curl -X GET 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
@@ -377,7 +381,7 @@ api_key | is required
|
||||
#### Call
|
||||
|
||||
```bash
|
||||
curl -X GET 'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}'
|
||||
curl -X GET 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
@@ -418,7 +422,7 @@ callback | JSON callback name
|
||||
#### Call
|
||||
|
||||
```bash
|
||||
curl 'https://{username}.cartodb.com/api/v1/map/named/{template_name}/jsonp?auth_token={auth_token}&callback=callback&config=template_params_json'
|
||||
curl 'https://{username}.carto.com/api/v1/map/named/{template_name}/jsonp?auth_token={auth_token}&callback=callback&config=template_params_json'
|
||||
```
|
||||
|
||||
#### Response
|
||||
@@ -450,9 +454,9 @@ callback({
|
||||
})
|
||||
```
|
||||
|
||||
## CartoDB.js for Named Maps
|
||||
## CARTO.js for Named Maps
|
||||
|
||||
You can use a Named Map that you created (which is defined by its `name`), to create a map using CartoDB.js. This is achieved by adding the [`namedmap` type](http://docs.cartodb.com/cartodb-platform/cartodb-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
|
||||
You can use a Named Map that you created (which is defined by its `name`), to create a map using CARTO.js. This is achieved by adding the [`namedmap` type](http://docs.carto.com/carto-engine/carto-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
|
||||
|
||||
```javascript
|
||||
{
|
||||
@@ -482,17 +486,17 @@ You can use a Named Map that you created (which is defined by its `name`), to cr
|
||||
|
||||
**Note:** Instantiating a Named Map over a `createLayer` does not require an API Key and by default, does not include auth tokens. _If_ you defined auth tokens for the Named Map configuration, then you will have to include them.
|
||||
|
||||
[CartoDB.js](http://docs.cartodb.com/cartodb-platform/cartodb-js/) has methods for accessing your Named Maps.
|
||||
[CARTO.js](http://docs.carto.com/carto-engine/carto-js/) has methods for accessing your Named Maps.
|
||||
|
||||
1. [layer.setParams()](http://docs.cartodb.com/cartodb-platform/cartodb-js/api-methods/#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript
|
||||
1. [layer.setParams()](http://docs.carto.com/carto-engine/carto-js/api-methods/#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript
|
||||
|
||||
**Note:** The CartoDB.js `layer.setParams()` function is not supported when using Named Maps for Torque. Alternatively, you can create a [Torque layer in a Named Map](http://bl.ocks.org/iriberri/de37be6406f9cc7cfe5a)
|
||||
**Note:** The CARTO.js `layer.setParams()` function is not supported when using Named Maps for Torque. Alternatively, you can create a [Torque layer in a Named Map](http://bl.ocks.org/iriberri/de37be6406f9cc7cfe5a)
|
||||
|
||||
2. [layer.setAuthToken()](http://docs.cartodb.com/cartodb-platform/cartodb-js/api-methods/#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer
|
||||
2. [layer.setAuthToken()](http://docs.carto.com/carto-engine/carto-js/api-methods/#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer
|
||||
|
||||
### Torque Layer in a Named Map
|
||||
|
||||
If you are creating a Torque layer in a Named Map without using the Torque.js library, you can apply the Torque layer by applying the following code with CartoDBjs:
|
||||
If you are creating a Torque layer in a Named Map without using the Torque.js library, you can apply the Torque layer by applying the following code with CARTO.js:
|
||||
|
||||
```javascript
|
||||
// add cartodb layer with one sublayer
|
||||
@@ -516,12 +520,12 @@ If you are creating a Torque layer in a Named Map without using the Torque.js li
|
||||
})
|
||||
.addTo(map)
|
||||
.done(function(layer) {
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### Examples of Named Maps created with CartoDB.js
|
||||
#### Examples of Named Maps created with CARTO.js
|
||||
|
||||
- [Named Map selectors with interaction](http://bl.ocks.org/ohasselblad/515a8af1f99d5e690484)
|
||||
|
||||
@@ -535,7 +539,7 @@ Optionally, authenticated users can fetch projected tiles (XYZ tiles or Mapnik R
|
||||
|
||||
### Fetch XYZ Tiles Directly with a URL
|
||||
|
||||
Authenticated users, with an auth token, can use XYZ-based URLs to fetch tiles directly, and instantiate the Named Map as part of the request to your application. You do not have to do any other steps to initialize your map.
|
||||
Authenticated users, with an auth token, can use XYZ-based URLs to fetch tiles directly, and instantiate the Named Map as part of the request to your application. You do not have to do any other steps to initialize your map.
|
||||
|
||||
To call a template_id in a URL:
|
||||
|
||||
@@ -543,21 +547,21 @@ To call a template_id in a URL:
|
||||
|
||||
For example, a complete URL might appear as:
|
||||
|
||||
"https://{username}.cartodb.com/api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.png"
|
||||
"https://{username}.carto.com/api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.png"
|
||||
|
||||
The placeholders indicate the following:
|
||||
|
||||
- [`template_id`](http://docs.cartodb.com/cartodb-platform/maps-api/named-maps/#response) is the response of your Named Map.
|
||||
- [`template_id`](http://docs.carto.com/carto-engine/maps-api/named-maps/#response) is the response of your Named Map.
|
||||
- layers can be a number (referring to the # layer of your map), all layers of your map, or a list of layers.
|
||||
- To show just the basemap layer, enter the number value `0` in the layer placeholder "https://{username}.cartodb.com/api/v1/map/named/{template_id}/0/{z}/{x}/{y}.png"
|
||||
- To show the first layer, enter the number value `1` in the layer placeholder "https://{username}.cartodb.com/api/v1/map/named/{template_id}/1/{z}/{x}/{y}.png"
|
||||
- To show all layers, enter the value `all` for the layer placeholder "https://{username}.cartodb.com/api/v1/map/named/{template_id}/all/{z}/{x}/{y}.png"
|
||||
- To show a [list of layers](http://docs.cartodb.com/cartodb-platform/maps-api/anonymous-maps/#blending-and-layer-selection), enter the comma separated layer value as 0,1,2 in the layer placeholder. For example, to show the basemap and the first layer, "https://{username}.cartodb.com/api/v1/map/named/{template_id}/0,1/{z}/{x}/{y}.png"
|
||||
- To show just the basemap layer, enter the number value `0` in the layer placeholder "https://{username}.carto.com/api/v1/map/named/{template_id}/0/{z}/{x}/{y}.png"
|
||||
- To show the first layer, enter the number value `1` in the layer placeholder "https://{username}.carto.com/api/v1/map/named/{template_id}/1/{z}/{x}/{y}.png"
|
||||
- To show all layers, enter the value `all` for the layer placeholder "https://{username}.carto.com/api/v1/map/named/{template_id}/all/{z}/{x}/{y}.png"
|
||||
- To show a [list of layers](http://docs.carto.com/carto-engine/maps-api/anonymous-maps/#blending-and-layer-selection), enter the comma separated layer value as 0,1,2 in the layer placeholder. For example, to show the basemap and the first layer, "https://{username}.carto.com/api/v1/map/named/{template_id}/0,1/{z}/{x}/{y}.png"
|
||||
|
||||
|
||||
### Get Mapnik Retina Tiles
|
||||
|
||||
Mapnik Retina tiles are not directly supported for Named Maps, so you cannot use the Named Map template_id. To fetch Mapnik Retina tiles, get the [layergroupid](http://docs.cartodb.com/cartodb-platform/maps-api/named-maps/#response-1) to initialize the map.
|
||||
Mapnik Retina tiles are not directly supported for Named Maps, so you cannot use the Named Map template_id. To fetch Mapnik Retina tiles, get the [layergroupid](http://docs.carto.com/carto-engine/maps-api/named-maps/#response-1) to initialize the map.
|
||||
|
||||
Instantiate the map by using your `layergroupid` in the token placeholder:
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ $.ajax({
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
url: 'https://{username}.cartodb.com/api/v1/map',
|
||||
url: 'https://{username}.carto.com/api/v1/map',
|
||||
data: JSON.stringify(mapconfig),
|
||||
success: function(data) {
|
||||
var templateUrl = 'https://{username}.cartodb.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png'
|
||||
var templateUrl = 'https://{username}.carto.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png'
|
||||
console.log(templateUrl);
|
||||
}
|
||||
})
|
||||
@@ -33,7 +33,7 @@ $.ajax({
|
||||
|
||||
## Named Maps
|
||||
|
||||
Let's create a Named Map using some private tables in a CartoDB account.
|
||||
Let's create a Named Map using some private tables in a CARTO account.
|
||||
The following map config sets up a map of European countries that have a white fill color:
|
||||
|
||||
```javascript
|
||||
@@ -56,12 +56,12 @@ The following map config sets up a map of European countries that have a white f
|
||||
}
|
||||
```
|
||||
|
||||
The MapConfig needs to be sent to CartoDB's Map API using an authenticated call. Here we will use a command line tool called `curl`. For more info about this tool, see [this blog post](http://quickleft.com/blog/command-line-tutorials-curl), or type `man curl` in bash. Using `curl`, and storing the config from above in a file `MapConfig.json`, the call would look like:
|
||||
The MapConfig needs to be sent to CARTO's Map API using an authenticated call. Here we will use a command line tool called `curl`. For more info about this tool, see [this blog post](http://quickleft.com/blog/command-line-tutorials-curl), or type `man curl` in bash. Using `curl`, and storing the config from above in a file `MapConfig.json`, the call would look like:
|
||||
|
||||
#### Call
|
||||
|
||||
```bash
|
||||
curl 'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}' -H 'Content-Type: application/json' -d @mapconfig.json
|
||||
curl 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}' -H 'Content-Type: application/json' -d @mapconfig.json
|
||||
```
|
||||
|
||||
To get the `URL` to fetch the tiles you need to instantiate the map, where `template_id` is the template name from the previous response.
|
||||
@@ -69,7 +69,7 @@ To get the `URL` to fetch the tiles you need to instantiate the map, where `temp
|
||||
#### Call
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://{username}.cartodb.com/api/v1/map/named/{template_id}' -H 'Content-Type: application/json'
|
||||
curl -X POST 'https://{username}.carto.com/api/v1/map/named/{template_id}' -H 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
The response will return JSON with properties for the `layergroupid`, the timestamp (`last_updated`) of the last data modification and some key/value pairs with `metadata` for the `layers`.
|
||||
@@ -96,5 +96,5 @@ Note: all `layers` in `metadata` will always have a `type` string and a `meta` d
|
||||
You can use the `layergroupid` to instantiate a URL template for accessing tiles on the client. Here we use the `layergroupid` from the example response above in this URL template:
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
|
||||
https://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
|
||||
```
|
||||
|
||||
@@ -78,7 +78,7 @@ format | the format for the image, supported types: `png`, `jpg`
|
||||
--- | ---
|
||||
|_ jpg | will have a default quality of 85.
|
||||
|
||||
A Named Maps static image will get its constraints from the [`view` argument of the Create Named Map function](http://docs.cartodb.com/cartodb-platform/maps-api/named-maps/#arguments). If `view` is not defined, it will estimate the extent based on the involved tables, otherwise it fallbacks to `"zoom": 1`, `"lng": 0` and `"lat": 0`.
|
||||
A Named Maps static image will get its constraints from the [`view` argument of the Create Named Map function](http://docs.carto.com/carto-engine/maps-api/named-maps/#arguments). If `view` is not defined, it will estimate the extent based on the involved tables, otherwise it fallbacks to `"zoom": 1`, `"lng": 0` and `"lat": 0`.
|
||||
|
||||
#### Layers
|
||||
|
||||
@@ -122,9 +122,9 @@ By manipulating the `"urlTemplate"` custom basemaps can be used in generating st
|
||||
},
|
||||
```
|
||||
|
||||
**CartoDB**
|
||||
**CARTO**
|
||||
|
||||
As described in the [MapConfig File Format](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility.
|
||||
As described in the [MapConfig File Format](http://docs.carto.com/carto-engine/maps-api/mapconfig/), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility.
|
||||
|
||||
```javascript
|
||||
{
|
||||
@@ -142,18 +142,18 @@ Additionally, static images from Torque maps and other map layers can be used to
|
||||
|
||||
### Caching
|
||||
|
||||
It is important to note that generated images are cached from the live data referenced with the `layergroupid token` on the specified CartoDB account. This means that if the data changes, the cached image will also change. When linking dynamically, it is important to take into consideration the state of the data and longevity of the static image to avoid broken images or changes in how the image is displayed. To obtain a static snapshot of the map as it is today and preserve the image long-term regardless of changes in data, the image must be saved and stored locally.
|
||||
It is important to note that generated images are cached from the live data referenced with the `layergroupid token` on the specified CARTO account. This means that if the data changes, the cached image will also change. When linking dynamically, it is important to take into consideration the state of the data and longevity of the static image to avoid broken images or changes in how the image is displayed. To obtain a static snapshot of the map as it is today and preserve the image long-term regardless of changes in data, the image must be saved and stored locally.
|
||||
|
||||
### Limits
|
||||
|
||||
* While images can encompass an entirety of a map, the default limit for pixel range is 8192 x 8192.
|
||||
* Image resolution by default is set to 72 DPI
|
||||
* JPEG quality by default is 85%
|
||||
* Timeout limits for generating static maps are the same across the CartoDB Editor and Platform. It is important to ensure timely processing of queries.
|
||||
* Timeout limits for generating static maps are the same across the CARTO Editor and CARTO Engine. It is important to ensure timely processing of queries.
|
||||
|
||||
## Examples
|
||||
|
||||
After instantiating a map from a CartoDB account:
|
||||
After instantiating a map from a CARTO account:
|
||||
|
||||
#### Call
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var _ = require('underscore');
|
||||
var step = require('step');
|
||||
var CamshaftFilter = require('../models/filter/camshaft');
|
||||
var AnalysisFilter = require('../models/filter/analysis');
|
||||
|
||||
function FilterStatsApi(pgQueryRunner) {
|
||||
this.pgQueryRunner = pgQueryRunner;
|
||||
@@ -40,8 +40,8 @@ FilterStatsApi.prototype.getFilterStats = function (username, unfiltered_query,
|
||||
},
|
||||
function getFilteredRows() {
|
||||
if ( filters && !_.isEmpty(filters)) {
|
||||
var camshaftFilter = new CamshaftFilter(filters);
|
||||
var query = camshaftFilter.sql(unfiltered_query);
|
||||
var analysisFilter = new AnalysisFilter(filters);
|
||||
var query = analysisFilter.sql(unfiltered_query);
|
||||
getEstimatedRows(self.pgQueryRunner, username, query, this);
|
||||
} else {
|
||||
this(null, null);
|
||||
|
||||
@@ -9,7 +9,10 @@ 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 statusQuery = [
|
||||
'SELECT node_id, status, updated_at, last_error_message as error_message',
|
||||
'FROM cdb_analysis_catalog where node_id = \'' + nodeId + '\''
|
||||
].join(' ');
|
||||
|
||||
var pg = new PSQL(dbParamsFromReqParams(params));
|
||||
pg.query(statusQuery, function(err, result) {
|
||||
@@ -21,10 +24,16 @@ AnalysisStatusBackend.prototype.getNodeStatus = function (params, callback) {
|
||||
|
||||
var rows = result.rows || [];
|
||||
|
||||
return callback(null, rows[0] || {
|
||||
var statusResponse = rows[0] || {
|
||||
node_id: nodeId,
|
||||
status: 'unknown'
|
||||
});
|
||||
};
|
||||
|
||||
if (statusResponse.status !== 'failed') {
|
||||
delete statusResponse.error_message;
|
||||
}
|
||||
|
||||
return callback(null, statusResponse);
|
||||
}, true); // use read-only transaction
|
||||
};
|
||||
|
||||
|
||||
@@ -1,19 +1,50 @@
|
||||
var camshaft = require('camshaft');
|
||||
var fs = require('fs');
|
||||
|
||||
function AnalysisBackend(options) {
|
||||
var batchConfig = options.batch || {};
|
||||
function AnalysisBackend (options) {
|
||||
options = options || {};
|
||||
this.setBatchConfig(options.batch);
|
||||
this.setLoggerConfig(options.logger);
|
||||
}
|
||||
|
||||
module.exports = AnalysisBackend;
|
||||
|
||||
AnalysisBackend.prototype.setBatchConfig = function (options) {
|
||||
var batchConfig = options || {};
|
||||
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.setLoggerConfig = function (options) {
|
||||
var loggerConfig = options || {};
|
||||
|
||||
loggerConfig.filename = loggerConfig.filename;
|
||||
|
||||
this.loggerConfig = loggerConfig;
|
||||
|
||||
if (this.loggerConfig.filename) {
|
||||
this.stream = fs.createWriteStream(this.loggerConfig.filename, { flags: 'a', encoding: 'utf8' });
|
||||
|
||||
process.on('SIGHUP', function () {
|
||||
if (this.stream) {
|
||||
this.stream.destroy();
|
||||
}
|
||||
|
||||
this.stream = fs.createWriteStream(this.loggerConfig.filename, { flags: 'a', encoding: 'utf8' });
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
analysisConfiguration.logger = {
|
||||
stream: this.stream ? this.stream : process.stdout
|
||||
};
|
||||
|
||||
camshaft.create(analysisConfiguration, analysisDefinition, callback);
|
||||
};
|
||||
|
||||
@@ -2,11 +2,8 @@ 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');
|
||||
@@ -26,110 +23,36 @@ function DataviewBackend(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) {
|
||||
function runDataviewQuery(err, mapConfig) {
|
||||
assert.ifError(err);
|
||||
|
||||
mapConfig = _mapConfig;
|
||||
|
||||
var _dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
|
||||
if (!_dataviewDefinition) {
|
||||
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 = {
|
||||
user: user,
|
||||
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.getNodes().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 = layerQuery(node, dataviewName, ownFilter);
|
||||
|
||||
var query = (ownFilter) ? dataviewDefinition.sql.own_filter_on : dataviewDefinition.sql.own_filter_off;
|
||||
var sourceId = dataviewDefinition.source.id; // node.id
|
||||
var layer = _.find(
|
||||
mapConfig.obj().layers,
|
||||
function(l){ return l.options.source && (l.options.source.id === sourceId); }
|
||||
);
|
||||
var layer = _.find(mapConfig.obj().layers, function(l) {
|
||||
return l.options.source && (l.options.source.id === sourceId);
|
||||
});
|
||||
var queryRewriteData = layer && layer.options.query_rewrite_data;
|
||||
if ( queryRewriteData ) {
|
||||
if ( node.type === 'source' ) {
|
||||
var filters = node.getFilters();
|
||||
var filters_disabler = Object.keys(filters).reduce(
|
||||
function(disabler, filter_id){ disabler[filter_id] = false; return disabler; },
|
||||
{}
|
||||
);
|
||||
var unfiltered_query = node.getQuery(filters_disabler);
|
||||
queryRewriteData = _.extend(
|
||||
{},
|
||||
queryRewriteData, { filters: filters, unfiltered_query: unfiltered_query }
|
||||
);
|
||||
}
|
||||
if (queryRewriteData && dataviewDefinition.node.type === 'source') {
|
||||
queryRewriteData = _.extend({}, queryRewriteData, {
|
||||
filters: dataviewDefinition.node.filters,
|
||||
unfiltered_query: dataviewDefinition.sql.own_filter_on
|
||||
});
|
||||
}
|
||||
|
||||
if (params.bbox) {
|
||||
@@ -166,98 +89,32 @@ DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, param
|
||||
dataview.getResult(pg, overrideParams, this);
|
||||
},
|
||||
function returnCallback(err, result) {
|
||||
return callback(err, result, timer.getTimes());
|
||||
return callback(err, result);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
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) {
|
||||
function runDataviewSearchQuery(err, mapConfig) {
|
||||
assert.ifError(err);
|
||||
|
||||
mapConfig = _mapConfig;
|
||||
|
||||
var _dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
|
||||
if (!_dataviewDefinition) {
|
||||
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 = {
|
||||
user: user,
|
||||
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.getNodes().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 = layerQuery(node, dataviewName, ownFilter);
|
||||
var query = (ownFilter) ? dataviewDefinition.sql.own_filter_on : dataviewDefinition.sql.own_filter_off;
|
||||
|
||||
if (params.bbox) {
|
||||
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
|
||||
@@ -270,23 +127,11 @@ DataviewBackend.prototype.search = function (mapConfigProvider, user, params, ca
|
||||
dataview.search(pg, userQuery, this);
|
||||
},
|
||||
function returnCallback(err, result) {
|
||||
return callback(err, result, timer.getTimes());
|
||||
return callback(err, result);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
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];
|
||||
@@ -311,31 +156,3 @@ function dbParamsFromReqParams(params) {
|
||||
}
|
||||
return dbParams;
|
||||
}
|
||||
|
||||
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(node, dataviewName, ownFilter) {
|
||||
var applyFilters = {};
|
||||
if (!ownFilter) {
|
||||
applyFilters[dataviewName] = false;
|
||||
}
|
||||
|
||||
if (node.type === 'source') {
|
||||
return node.getQuery(applyFilters);
|
||||
}
|
||||
var _columns = ['ST_Transform(the_geom, 3857) the_geom_webmercator'].concat(skipColumns(node.getColumns()));
|
||||
return layerQueryTemplate({ _query: node.getQuery(applyFilters), _columns: _columns.join(', ') });
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ dot.templateSettings.strip = false;
|
||||
function createTemplate(method) {
|
||||
return dot.template([
|
||||
'SELECT',
|
||||
'min({{=it._column}}) min_val,',
|
||||
'max({{=it._column}}) max_val,',
|
||||
'avg({{=it._column}}) avg_val,',
|
||||
method,
|
||||
'FROM ({{=it._sql}}) _table_sql WHERE {{=it._column}} IS NOT NULL'
|
||||
].join('\n'));
|
||||
@@ -32,14 +35,9 @@ methodTemplates.category = dot.template([
|
||||
' ORDER BY 2 DESC',
|
||||
'),',
|
||||
'agg_categories AS (',
|
||||
' SELECT \'__other\' category',
|
||||
' SELECT category',
|
||||
' FROM categories',
|
||||
' WHERE rank >= {{=it._buckets}}',
|
||||
' GROUP BY 1',
|
||||
' UNION ALL',
|
||||
' SELECT CAST(category AS text)',
|
||||
' FROM categories',
|
||||
' WHERE rank < {{=it._buckets}}',
|
||||
' WHERE rank <= {{=it._buckets}}',
|
||||
')',
|
||||
'SELECT array_agg(category) AS category FROM agg_categories'
|
||||
].join('\n'));
|
||||
@@ -79,11 +77,14 @@ PostgresDatasource.prototype.getRamp = function (column, buckets, method, callba
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
resultSet = resultSet || {};
|
||||
var result = resultSet.rows || [];
|
||||
|
||||
var result = getResult(resultSet);
|
||||
var strategy = method2strategy[methodName];
|
||||
var ramp = result[0][methodName] || [];
|
||||
var ramp = result[methodName] || [];
|
||||
var stats = {
|
||||
min_val: result.min_val,
|
||||
max_val: result.max_val,
|
||||
avg_val: result.avg_val
|
||||
};
|
||||
// Skip null values from ramp
|
||||
// Generated turbo-carto won't be correct, but better to keep it working than failing
|
||||
// TODO fix cartodb-postgres extension quantification functions
|
||||
@@ -94,8 +95,16 @@ PostgresDatasource.prototype.getRamp = function (column, buckets, method, callba
|
||||
});
|
||||
}
|
||||
|
||||
return callback(null, { ramp: ramp, strategy: strategy });
|
||||
return callback(null, { ramp: ramp, strategy: strategy, stats: stats });
|
||||
}, true); // use read-only transaction
|
||||
};
|
||||
|
||||
function getResult(resultSet) {
|
||||
resultSet = resultSet || {};
|
||||
var result = resultSet.rows || [];
|
||||
result = result[0] || {};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = PostgresDatasource;
|
||||
|
||||
169
lib/cartodb/controllers/analyses.js
Normal file
@@ -0,0 +1,169 @@
|
||||
var step = require('step');
|
||||
var assert = require('assert');
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
var PSQL = require('cartodb-psql');
|
||||
|
||||
var util = require('util');
|
||||
var BaseController = require('./base');
|
||||
|
||||
var cors = require('../middleware/cors');
|
||||
var userMiddleware = require('../middleware/user');
|
||||
|
||||
function AnalysesController(authApi, pgConnection) {
|
||||
BaseController.call(this, authApi, pgConnection);
|
||||
}
|
||||
|
||||
util.inherits(AnalysesController, BaseController);
|
||||
|
||||
module.exports = AnalysesController;
|
||||
|
||||
AnalysesController.prototype.register = function(app) {
|
||||
app.get(app.base_url_mapconfig + '/analyses/catalog', cors(), userMiddleware, this.catalog.bind(this));
|
||||
};
|
||||
|
||||
AnalysesController.prototype.sendResponse = function(req, res, resource) {
|
||||
res.set('Cache-Control', 'public,max-age=10,must-revalidate');
|
||||
this.send(req, res, resource, 200);
|
||||
};
|
||||
|
||||
AnalysesController.prototype.catalog = function(req, res) {
|
||||
var self = this;
|
||||
var username = req.context.user;
|
||||
|
||||
step(
|
||||
function reqParams() {
|
||||
self.req2params(req, this);
|
||||
},
|
||||
function catalogQuery(err) {
|
||||
assert.ifError(err);
|
||||
var pg = new PSQL(dbParamsFromReqParams(req.params));
|
||||
getMetadata(username, pg, this);
|
||||
},
|
||||
function prepareResponse(err, results) {
|
||||
assert.ifError(err);
|
||||
|
||||
var analysisIdToTable = results.tables.reduce(function(analysisIdToTable, table) {
|
||||
var analysisId = table.relname.split('_')[2];
|
||||
if (analysisId && analysisId.length === 40) {
|
||||
analysisIdToTable[analysisId] = table;
|
||||
}
|
||||
return analysisIdToTable;
|
||||
}, {});
|
||||
|
||||
var catalogWithTables = results.catalog.map(function(analysis) {
|
||||
if (analysisIdToTable.hasOwnProperty(analysis.node_id)) {
|
||||
analysis.table = analysisIdToTable[analysis.node_id];
|
||||
}
|
||||
return analysis;
|
||||
});
|
||||
|
||||
return catalogWithTables.sort(function(analysisA, analysisB) {
|
||||
if (!!analysisA.table && !!analysisB.table) {
|
||||
return analysisB.table.size - analysisA.table.size;
|
||||
}
|
||||
|
||||
if (!!analysisA.table) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!!analysisB.table) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
});
|
||||
},
|
||||
function sendResponse(err, catalogWithTables) {
|
||||
if (err) {
|
||||
if (err.message.match(/permission\sdenied/)) {
|
||||
err = new Error('Unauthorized');
|
||||
err.http_status = 401;
|
||||
}
|
||||
self.sendError(req, res, err);
|
||||
} else {
|
||||
self.sendResponse(req, res, { catalog: catalogWithTables });
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
var catalogQueryTpl = dot.template(
|
||||
'SELECT analysis_def->>\'type\' as type, * FROM cartodb.cdb_analysis_catalog WHERE username = \'{{=it._username}}\''
|
||||
);
|
||||
|
||||
var tablesQueryTpl = dot.template([
|
||||
"WITH analysis_tables AS (",
|
||||
" SELECT",
|
||||
" n.nspname AS nspname,",
|
||||
" c.relname AS relname,",
|
||||
" pg_total_relation_size(",
|
||||
" format('%s.%s', pg_catalog.quote_ident(n.nspname), pg_catalog.quote_ident(c.relname))",
|
||||
" ) AS size,",
|
||||
" format('%s.%s', pg_catalog.quote_ident(nspname), pg_catalog.quote_ident(relname)) AS fully_qualified_name",
|
||||
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n",
|
||||
" WHERE c.relnamespace = n.oid",
|
||||
" AND pg_catalog.quote_ident(c.relname) ~ '^analysis_[a-z0-9]{10}_[a-z0-9]{40}$'",
|
||||
" AND n.nspname IN ('{{=it._username}}', 'public')",
|
||||
")",
|
||||
"SELECT *, pg_size_pretty(size) as size_pretty",
|
||||
"FROM analysis_tables",
|
||||
"ORDER BY size DESC"
|
||||
].join('\n'));
|
||||
|
||||
|
||||
function getMetadata(username, pg, callback) {
|
||||
var results = {
|
||||
catalog: [],
|
||||
tables: []
|
||||
};
|
||||
step(
|
||||
function getCatalog() {
|
||||
pg.query(catalogQueryTpl({_username: username}), this, true); // use read-only transaction
|
||||
},
|
||||
function handleCatalog(err, resultSet) {
|
||||
assert.ifError(err);
|
||||
resultSet = resultSet || {};
|
||||
results.catalog = resultSet.rows || [];
|
||||
this();
|
||||
},
|
||||
function getTables(err) {
|
||||
assert.ifError(err);
|
||||
pg.query(tablesQueryTpl({_username: username}), this, true); // use read-only transaction
|
||||
},
|
||||
function handleTables(err, resultSet) {
|
||||
assert.ifError(err);
|
||||
resultSet = resultSet || {};
|
||||
results.tables = resultSet.rows || [];
|
||||
this();
|
||||
},
|
||||
function finish(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
return callback(null, results);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
module.exports = {
|
||||
Analyses: require('./analyses'),
|
||||
Layergroup: require('./layergroup'),
|
||||
Map: require('./map'),
|
||||
NamedMaps: require('./named_maps'),
|
||||
|
||||
@@ -171,6 +171,7 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
|
||||
var analysesResults = context.analysesResults || [];
|
||||
addDataviewsAndWidgetsUrls(req.context.user, layergroup, mapConfig.obj());
|
||||
addAnalysesMetadata(req.context.user, layergroup, analysesResults, true);
|
||||
addContextMetadata(layergroup, mapConfig.obj(), context);
|
||||
res.set('X-Layergroup-Id', layergroup.layergroupid);
|
||||
self.send(req, res, layergroup, 200);
|
||||
}
|
||||
@@ -178,6 +179,17 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
|
||||
);
|
||||
};
|
||||
|
||||
function addContextMetadata(layergroup, mapConfig, context) {
|
||||
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
|
||||
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
|
||||
if (context.turboCarto && Array.isArray(context.turboCarto.layers)) {
|
||||
layer.meta.cartocss_meta = context.turboCarto.layers[layerIndex];
|
||||
}
|
||||
return layer;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn) {
|
||||
var self = this;
|
||||
|
||||
@@ -229,8 +241,10 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
|
||||
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
|
||||
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
|
||||
|
||||
addDataviewsAndWidgetsUrls(cdbuser, layergroup, mapConfig.obj());
|
||||
var _mapConfig = mapConfig.obj();
|
||||
addDataviewsAndWidgetsUrls(cdbuser, layergroup, _mapConfig);
|
||||
addAnalysesMetadata(cdbuser, layergroup, mapConfigProvider.analysesResults);
|
||||
addContextMetadata(layergroup, _mapConfig, mapConfigProvider.context);
|
||||
|
||||
res.set('X-Layergroup-Id', layergroup.layergroupid);
|
||||
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, mapConfigProvider.getTemplateName()));
|
||||
@@ -330,7 +344,7 @@ function getLastUpdatedTime(analysesResults, lastUpdateTime) {
|
||||
return lastUpdateTime;
|
||||
}
|
||||
return analysesResults.reduce(function(lastUpdateTime, analysis) {
|
||||
return analysis.getSortedNodes().reduce(function(lastNodeUpdatedAtTime, node) {
|
||||
return analysis.getNodes().reduce(function(lastNodeUpdatedAtTime, node) {
|
||||
var nodeUpdatedAtDate = node.getUpdatedAt();
|
||||
var nodeUpdatedTimeAt = (nodeUpdatedAtDate && nodeUpdatedAtDate.getTime()) || 0;
|
||||
return nodeUpdatedTimeAt > lastNodeUpdatedAtTime ? nodeUpdatedTimeAt : lastNodeUpdatedAtTime;
|
||||
@@ -344,18 +358,22 @@ function addAnalysesMetadata(username, layergroup, analysesResults, includeQuery
|
||||
layergroup.metadata.analyses = [];
|
||||
|
||||
analysesResults.forEach(function(analysis) {
|
||||
var nodes = analysis.getSortedNodes();
|
||||
var nodes = analysis.getNodes();
|
||||
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] = {
|
||||
var nodeRepr = {
|
||||
status: node.getStatus(),
|
||||
url: getUrls(username, nodeResource)
|
||||
};
|
||||
if (includeQuery) {
|
||||
nodesIdMap[node.params.id].query = node.getQuery();
|
||||
nodeRepr.query = node.getQuery();
|
||||
}
|
||||
if (node.getStatus() === 'failed') {
|
||||
nodeRepr.error_message = node.getErrorMessage();
|
||||
}
|
||||
nodesIdMap[node.params.id] = nodeRepr;
|
||||
}
|
||||
|
||||
return nodesIdMap;
|
||||
|
||||
@@ -137,9 +137,15 @@ NamedMapsController.prototype.staticMap = function(req, res) {
|
||||
this
|
||||
);
|
||||
},
|
||||
function prepareImageOptions(err, _namedMapProvider) {
|
||||
function prepareLayerVisibility(err, _namedMapProvider) {
|
||||
assert.ifError(err);
|
||||
|
||||
namedMapProvider = _namedMapProvider;
|
||||
|
||||
self.prepareLayerFilterFromPreviewLayers(cdbUser, req, namedMapProvider, this);
|
||||
},
|
||||
function prepareImageOptions(err) {
|
||||
assert.ifError(err);
|
||||
self.getStaticImageOptions(cdbUser, req.params, namedMapProvider, this);
|
||||
},
|
||||
function getImage(err, imageOpts) {
|
||||
@@ -184,6 +190,45 @@ NamedMapsController.prototype.staticMap = function(req, res) {
|
||||
);
|
||||
};
|
||||
|
||||
NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (user, req, namedMapProvider, callback) {
|
||||
var self = this;
|
||||
namedMapProvider.getTemplate(function (err, template) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!template || !template.view || !template.view.preview_layers) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
var previewLayers = template.view.preview_layers;
|
||||
var layerVisibilityFilter = [];
|
||||
|
||||
template.layergroup.layers.forEach(function (layer, index) {
|
||||
if (previewLayers[''+index] !== false && previewLayers[layer.id] !== false) {
|
||||
layerVisibilityFilter.push(''+index);
|
||||
}
|
||||
});
|
||||
|
||||
if (!layerVisibilityFilter.length) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
// overwrites 'all' default filter
|
||||
req.params.layer = layerVisibilityFilter.join(',');
|
||||
|
||||
// recreates the provider
|
||||
self.namedMapProviderCache.get(
|
||||
user,
|
||||
req.params.template_id,
|
||||
req.query.config,
|
||||
req.query.auth_token,
|
||||
req.params,
|
||||
callback
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
var DEFAULT_ZOOM_CENTER = {
|
||||
zoom: 1,
|
||||
center: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var filters = {
|
||||
category: require('./camshaft/category'),
|
||||
range: require('./camshaft/range')
|
||||
category: require('./analysis/category'),
|
||||
range: require('./analysis/range')
|
||||
};
|
||||
|
||||
function createFilter(filterDefinition) {
|
||||
@@ -11,11 +11,11 @@ function createFilter(filterDefinition) {
|
||||
return new filters[filterType](filterDefinition.column, filterDefinition.params);
|
||||
}
|
||||
|
||||
function CamshaftFilters(filters) {
|
||||
function AnalysisFilters(filters) {
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
CamshaftFilters.prototype.sql = function(rawSql) {
|
||||
AnalysisFilters.prototype.sql = function(rawSql) {
|
||||
var filters = this.filters || {};
|
||||
var applyFilters = {};
|
||||
|
||||
@@ -32,4 +32,4 @@ CamshaftFilters.prototype.sql = function(rawSql) {
|
||||
}, rawSql);
|
||||
};
|
||||
|
||||
module.exports = CamshaftFilters;
|
||||
module.exports = AnalysisFilters;
|
||||
@@ -6,7 +6,7 @@ dot.templateSettings.strip = false;
|
||||
|
||||
var filterQueryTpl = dot.template([
|
||||
'SELECT *',
|
||||
'FROM ({{=it._sql}}) _camshaft_category_filter',
|
||||
'FROM ({{=it._sql}}) _analysis_category_filter',
|
||||
'WHERE {{=it._filters}}'
|
||||
].join('\n'));
|
||||
var escapeStringTpl = dot.template('$escape_{{=it._i}}${{=it._value}}$escape_{{=it._i}}$');
|
||||
@@ -6,7 +6,7 @@ dot.templateSettings.strip = false;
|
||||
var betweenFilterTpl = dot.template('{{=it._column}} BETWEEN {{=it._min}} AND {{=it._max}}');
|
||||
var minFilterTpl = dot.template('{{=it._column}} >= {{=it._min}}');
|
||||
var maxFilterTpl = dot.template('{{=it._column}} <= {{=it._max}}');
|
||||
var filterQueryTpl = dot.template('SELECT * FROM ({{=it._sql}}) _camshaft_range_filter WHERE {{=it._filter}}');
|
||||
var filterQueryTpl = dot.template('SELECT * FROM ({{=it._sql}}) _analysis_range_filter WHERE {{=it._filter}}');
|
||||
|
||||
function Range(column, filterParams) {
|
||||
this.column = column;
|
||||
@@ -65,6 +65,7 @@ AnalysisMapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfi
|
||||
error.type = 'analysis';
|
||||
error.analysis = {
|
||||
id: analysisDefinition.id,
|
||||
node_id: err.node_id,
|
||||
type: analysisDefinition.type
|
||||
};
|
||||
return done(error);
|
||||
@@ -74,7 +75,7 @@ AnalysisMapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfi
|
||||
});
|
||||
}
|
||||
|
||||
var analysesQueue = queue(requestMapConfig.analyses.length);
|
||||
var analysesQueue = queue(1);
|
||||
requestMapConfig.analyses.forEach(function(analysis) {
|
||||
analysesQueue.defer(createAnalysis, analysis);
|
||||
});
|
||||
@@ -123,14 +124,37 @@ AnalysisMapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfi
|
||||
return layer;
|
||||
});
|
||||
|
||||
|
||||
debug('mapconfig output', JSON.stringify(requestMapConfig, null, 4));
|
||||
|
||||
var missingDataviewsNodesErrors = getMissingDataviewsSourceIds(dataviews, sourceId2Node);
|
||||
if (missingNodesErrors.length > 0 || missingDataviewsNodesErrors.length > 0) {
|
||||
return callback(missingNodesErrors.concat(missingDataviewsNodesErrors));
|
||||
}
|
||||
|
||||
// Augment dataviews with sql from analyses
|
||||
Object.keys(dataviews).forEach(function(dataviewName) {
|
||||
var dataview = requestMapConfig.dataviews[dataviewName];
|
||||
var dataviewSourceId = dataview.source.id;
|
||||
var dataviewNode = sourceId2Node[dataviewSourceId];
|
||||
dataview.node = {
|
||||
type: dataviewNode.type,
|
||||
filters: dataviewNode.getFilters()
|
||||
};
|
||||
dataview.sql = {
|
||||
own_filter_on: dataviewQuery(dataviewNode, dataviewName, true),
|
||||
own_filter_off: dataviewQuery(dataviewNode, dataviewName, false),
|
||||
no_filters: dataviewNode.getQuery(Object.keys(dataviewNode.getFilters())
|
||||
.reduce(function(applyFilters, filterId) {
|
||||
applyFilters[filterId] = false;
|
||||
return applyFilters;
|
||||
}, {})
|
||||
)
|
||||
};
|
||||
});
|
||||
if (Object.keys(dataviews).length > 0) {
|
||||
requestMapConfig.dataviews = dataviews;
|
||||
}
|
||||
|
||||
debug('mapconfig output', JSON.stringify(requestMapConfig, null, 4));
|
||||
|
||||
context.analysesResults = analysesResults;
|
||||
|
||||
return callback(null, requestMapConfig);
|
||||
@@ -147,7 +171,7 @@ function skipColumns(columnNames) {
|
||||
.filter(function(columnName) { return !SKIP_COLUMNS[columnName]; });
|
||||
}
|
||||
|
||||
var layerQueryTemplate = dot.template([
|
||||
var wrappedQueryTpl = dot.template([
|
||||
'SELECT {{=it._columns}}',
|
||||
'FROM ({{=it._query}}) _cdb_analysis_query'
|
||||
].join('\n'));
|
||||
@@ -157,7 +181,20 @@ function layerQuery(node) {
|
||||
return node.getQuery();
|
||||
}
|
||||
var _columns = ['ST_Transform(the_geom, 3857) the_geom_webmercator'].concat(skipColumns(node.getColumns()));
|
||||
return layerQueryTemplate({ _query: node.getQuery(), _columns: _columns.join(', ') });
|
||||
return wrappedQueryTpl({ _query: node.getQuery(), _columns: _columns.join(', ') });
|
||||
}
|
||||
|
||||
function dataviewQuery(node, dataviewName, ownFilter) {
|
||||
var applyFilters = {};
|
||||
if (!ownFilter) {
|
||||
applyFilters[dataviewName] = false;
|
||||
}
|
||||
|
||||
if (node.type === 'source') {
|
||||
return node.getQuery(applyFilters);
|
||||
}
|
||||
var _columns = ['ST_Transform(the_geom, 3857) the_geom_webmercator'].concat(skipColumns(node.getColumns()));
|
||||
return wrappedQueryTpl({ _query: node.getQuery(applyFilters), _columns: _columns.join(', ') });
|
||||
}
|
||||
|
||||
function appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId) {
|
||||
|
||||
@@ -32,12 +32,27 @@ TurboCartoAdapter.prototype.getMapConfig = function (user, requestMapConfig, par
|
||||
parseCartoQueue.defer(self._parseCartoCss.bind(self), user, params, layer, index, layerId);
|
||||
});
|
||||
|
||||
parseCartoQueue.awaitAll(function (err, layers) {
|
||||
parseCartoQueue.awaitAll(function (err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
requestMapConfig.layers = layers;
|
||||
var errors = results.reduce(function(errors, result) {
|
||||
if (result.error) {
|
||||
errors.push(result.error);
|
||||
}
|
||||
return errors;
|
||||
}, []);
|
||||
if (errors.length > 0) {
|
||||
return callback(errors);
|
||||
}
|
||||
|
||||
requestMapConfig.layers = results.map(function(result) { return result.layer; });
|
||||
context.turboCarto = {
|
||||
layers: results.map(function(result) {
|
||||
return result.meta;
|
||||
})
|
||||
};
|
||||
|
||||
return callback(null, requestMapConfig);
|
||||
});
|
||||
@@ -70,14 +85,14 @@ var tokensQueryTpl = dot.template([
|
||||
|
||||
TurboCartoAdapter.prototype._parseCartoCss = function (username, params, layer, layerIndex, layerId, callback) {
|
||||
if (!shouldParseLayerCartocss(layer)) {
|
||||
return callback(null, layer);
|
||||
return callback(null, { layer: layer });
|
||||
}
|
||||
|
||||
var pg = new PSQL(dbParamsFromReqParams(params));
|
||||
function processCallback(err, cartocss) {
|
||||
function processCallback(err, cartocss, meta) {
|
||||
// Only return turbo-carto errors
|
||||
if (err && err.name === 'TurboCartoError') {
|
||||
var error = new Error('turbo-carto: ' + err.message);
|
||||
var error = new Error(err.message);
|
||||
error.http_status = 400;
|
||||
error.type = 'layer';
|
||||
error.subtype = 'turbo-carto';
|
||||
@@ -88,14 +103,14 @@ TurboCartoAdapter.prototype._parseCartoCss = function (username, params, layer,
|
||||
context: err.context
|
||||
};
|
||||
|
||||
return callback(error);
|
||||
return callback(null, { error: error });
|
||||
}
|
||||
|
||||
// Try to continue in the rest of the cases
|
||||
if (cartocss) {
|
||||
layer.options.cartocss = cartocss;
|
||||
}
|
||||
return callback(null, layer);
|
||||
return callback(null, { layer: layer, meta: meta });
|
||||
}
|
||||
|
||||
var layerSql = layer.options.sql;
|
||||
@@ -105,9 +120,7 @@ TurboCartoAdapter.prototype._parseCartoCss = function (username, params, layer,
|
||||
var tokensQuery = tokensQueryTpl({_sql: layerRawSql});
|
||||
return pg.query(tokensQuery, function(err, resultSet) {
|
||||
if (err) {
|
||||
var error = new Error('turbo-carto: ' + err.message);
|
||||
error.type = 'turbo-carto';
|
||||
return callback(error);
|
||||
return processCallback(err);
|
||||
}
|
||||
|
||||
resultSet = resultSet || {};
|
||||
|
||||
@@ -120,6 +120,7 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
|
||||
self.mapConfig = (mapConfig === null) ? null : new MapConfig(mapConfig, context.datasource);
|
||||
self.analysesResults = context.analysesResults || [];
|
||||
self.rendererParams = rendererParams;
|
||||
self.context = context;
|
||||
self.context.limits = renderLimits || {};
|
||||
return callback(self.err, self.mapConfig, self.rendererParams, self.context);
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@ 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();
|
||||
@@ -222,6 +223,8 @@ module.exports = function(serverOptions) {
|
||||
|
||||
new controller.NamedMapsAdmin(authApi, pgConnection, templateMaps).register(app);
|
||||
|
||||
new controller.Analyses(authApi, pgConnection).register(app);
|
||||
|
||||
new controller.ServerInfo(versions).register(app);
|
||||
|
||||
/*******************************************************************************************************************
|
||||
|
||||
@@ -36,6 +36,9 @@ var analysisConfig = _.defaults(global.environment.analysis || {}, {
|
||||
inlineExecution: false,
|
||||
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
|
||||
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
|
||||
},
|
||||
logger: {
|
||||
filename: undefined
|
||||
}
|
||||
});
|
||||
|
||||
@@ -95,6 +98,9 @@ module.exports = {
|
||||
inlineExecution: analysisConfig.batch.inlineExecution,
|
||||
endpoint: analysisConfig.batch.endpoint,
|
||||
hostHeaderTemplate: analysisConfig.batch.hostHeaderTemplate
|
||||
},
|
||||
logger: {
|
||||
filename: analysisConfig.logger.filename
|
||||
}
|
||||
},
|
||||
// Do not send unwatch on release. See http://github.com/CartoDB/Windshaft-cartodb/issues/161
|
||||
|
||||
@@ -2,7 +2,7 @@ var _ = require('underscore');
|
||||
var TableNameParser = require('./table_name_parser');
|
||||
|
||||
var BBoxFilter = require('../models/filter/bbox');
|
||||
var CamshaftFilter = require('../models/filter/camshaft');
|
||||
var AnalysisFilter = require('../models/filter/analysis');
|
||||
|
||||
// Minimim number of filtered rows to use overviews
|
||||
var FILTER_MIN_ROWS = 65536;
|
||||
@@ -11,8 +11,8 @@ var FILTER_MAX_FRACTION = 0.2;
|
||||
|
||||
function apply_filters_to_query(query, filters, bbox_filter) {
|
||||
if ( filters && !_.isEmpty(filters)) {
|
||||
var camshaftFilter = new CamshaftFilter(filters);
|
||||
query = camshaftFilter.sql(query);
|
||||
var analysisFilter = new AnalysisFilter(filters);
|
||||
query = analysisFilter.sql(query);
|
||||
}
|
||||
if ( bbox_filter ) {
|
||||
var bboxFilter = new BBoxFilter(bbox_filter.options, bbox_filter.params);
|
||||
|
||||
929
npm-shrinkwrap.json
generated
15
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "2.55.0",
|
||||
"version": "2.78.0",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
@@ -20,26 +20,27 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"body-parser": "~1.14.0",
|
||||
"camshaft": "0.26.0",
|
||||
"camshaft": "0.44.1",
|
||||
"cartodb-psql": "~0.6.1",
|
||||
"cartodb-query-tables": "~0.1.0",
|
||||
"cartodb-redis": "~0.13.0",
|
||||
"cartodb-redis": "0.13.1",
|
||||
"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",
|
||||
"log4js": "cartodb/log4js-node#cdb",
|
||||
"lru-cache": "2.6.5",
|
||||
"lzma": "~1.3.7",
|
||||
"lzma": "~2.3.2",
|
||||
"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.12.1",
|
||||
"turbo-carto": "0.17.1",
|
||||
"underscore": "~1.6.0",
|
||||
"windshaft": "2.4.0"
|
||||
"windshaft": "2.5.0",
|
||||
"yargs": "~5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"istanbul": "~0.4.3",
|
||||
|
||||
@@ -202,7 +202,7 @@ describe('analysis-layers', function() {
|
||||
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']);
|
||||
assert.deepEqual(nodesIds, ['HEAD', '2570e105-7b37-40d2-bdf4-1af889598745']);
|
||||
nodesIds.forEach(function(nodeId) {
|
||||
var node = nodes[nodeId];
|
||||
assert.ok(node.hasOwnProperty('url'), 'Missing "url" attribute in node');
|
||||
|
||||
@@ -233,7 +233,7 @@ describe('analysis-layers error cases', function() {
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"id": "HEAD",
|
||||
"id": "HEAD2",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from populated_places_simple_reduced"
|
||||
@@ -264,5 +264,114 @@ describe('analysis-layers error cases', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return missing param error of outer node indicating the node_id and context', function(done) {
|
||||
var mapConfig = createMapConfig([{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"source": {
|
||||
"id": "HEAD"
|
||||
},
|
||||
"cartocss": '#polygons { polygon-fill: red; }',
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}], {}, [{
|
||||
"id": "HEAD",
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"id": "HEAD2",
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"id": "HEAD3",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from populated_places_simple_reduced"
|
||||
}
|
||||
},
|
||||
"radius": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
// radius: 'missing'
|
||||
}]);
|
||||
|
||||
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 required param "radius"'
|
||||
);
|
||||
|
||||
assert.equal(layergroupResult.errors_with_context[0].type, 'analysis');
|
||||
assert.equal(layergroupResult.errors_with_context[0].message, 'Missing required param "radius"');
|
||||
assert.equal(layergroupResult.errors_with_context[0].analysis.id, 'HEAD');
|
||||
assert.equal(layergroupResult.errors_with_context[0].analysis.type, 'buffer');
|
||||
assert.equal(layergroupResult.errors_with_context[0].analysis.node_id, 'HEAD');
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return invalid param type error of inner node indicating the node_id and context', function(done) {
|
||||
var mapConfig = createMapConfig([{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"source": {
|
||||
"id": "HEAD"
|
||||
},
|
||||
"cartocss": '#polygons { polygon-fill: red; }',
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}], {}, [{
|
||||
"id": "HEAD",
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"id": "HEAD2",
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
"source": {
|
||||
"id": "HEAD3",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": "select * from populated_places_simple_reduced"
|
||||
}
|
||||
},
|
||||
"radius": 'invalid_radius'
|
||||
}
|
||||
},
|
||||
"radius": 10
|
||||
}
|
||||
}]);
|
||||
|
||||
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],
|
||||
'Invalid type for param "radius", expects "number" type, got `"invalid_radius"`'
|
||||
);
|
||||
|
||||
assert.equal(layergroupResult.errors_with_context[0].type, 'analysis');
|
||||
assert.equal(
|
||||
layergroupResult.errors_with_context[0].message,
|
||||
'Invalid type for param "radius", expects "number" type, got `"invalid_radius"`'
|
||||
);
|
||||
assert.equal(layergroupResult.errors_with_context[0].analysis.id, 'HEAD');
|
||||
assert.equal(layergroupResult.errors_with_context[0].analysis.type, 'buffer');
|
||||
assert.equal(layergroupResult.errors_with_context[0].analysis.node_id, 'HEAD2');
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -141,7 +141,7 @@ describe('named-maps analysis', function() {
|
||||
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']);
|
||||
assert.deepEqual(nodesIds, ['HEAD', '2570e105-7b37-40d2-bdf4-1af889598745']);
|
||||
nodesIds.forEach(function(nodeId) {
|
||||
var node = nodes[nodeId];
|
||||
assert.ok(node.hasOwnProperty('url'), 'Missing "url" attribute in node');
|
||||
|
||||
@@ -46,9 +46,9 @@ describe('analysis-layers regressions', function() {
|
||||
"analyses": [
|
||||
{
|
||||
"id": "a4",
|
||||
"type": "intersection",
|
||||
"type": "point-in-polygon",
|
||||
"params": {
|
||||
"source": {
|
||||
"polygons_source": {
|
||||
"id": "a3",
|
||||
"type": "buffer",
|
||||
"params": {
|
||||
@@ -76,7 +76,7 @@ describe('analysis-layers regressions', function() {
|
||||
"radius": 200000
|
||||
}
|
||||
},
|
||||
"target": {
|
||||
"points_source": {
|
||||
"id": "customer_home_locations",
|
||||
"type": "source",
|
||||
"params": {
|
||||
@@ -100,10 +100,13 @@ describe('analysis-layers regressions', function() {
|
||||
assert.equal(analyses.length, 1);
|
||||
|
||||
var expectedIds = ['customer_home_locations', 'b0', 'b1', 'a2', 'a3', 'a4'];
|
||||
assert.equal(Object.keys(analyses[0].nodes).length, expectedIds.length, Object.keys(analyses[0].nodes));
|
||||
expectedIds.forEach(function(expectedId) {
|
||||
analyses[0].nodes.hasOwnProperty(expectedId);
|
||||
assert.ok(
|
||||
analyses[0].nodes.hasOwnProperty(expectedId),
|
||||
'Missing "' + expectedId + '" from node list.'
|
||||
);
|
||||
});
|
||||
assert.equal(Object.keys(analyses[0].nodes).length, expectedIds.length, Object.keys(analyses[0].nodes));
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
|
||||
210
test/acceptance/named_layers_visibility.js
Normal file
@@ -0,0 +1,210 @@
|
||||
var step = require('step');
|
||||
var test_helper = require('../support/test_helper');
|
||||
|
||||
var assert = require('../support/assert');
|
||||
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
var RedisPool = require('redis-mpool');
|
||||
var TemplateMaps = require('../../lib/cartodb/backends/template_maps.js');
|
||||
var mapnik = require('windshaft').mapnik;
|
||||
|
||||
var IMAGE_TOLERANCE = 20;
|
||||
|
||||
describe('layers visibility for previews', function() {
|
||||
// configure redis pool instance to use in tests
|
||||
var redisPool = new RedisPool(global.environment.redis);
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
max_user_templates: global.environment.maxUserTemplates
|
||||
});
|
||||
|
||||
var username = 'localhost';
|
||||
|
||||
function createLayer (color, layerId) {
|
||||
var mod;
|
||||
|
||||
if (color === 'red') {
|
||||
mod = 0;
|
||||
} else if (color === 'orange') {
|
||||
mod = 1;
|
||||
} else if (color === 'blue') {
|
||||
mod = 2;
|
||||
} else {
|
||||
mod = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'mapnik',
|
||||
id: layerId,
|
||||
options: {
|
||||
sql: 'select * from populated_places_simple_reduced where cartodb_id % 3 = ' + mod,
|
||||
cartocss: '#layer { marker-fill: ' + color + '; }',
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createTemplate(scenario) {
|
||||
return {
|
||||
version: '0.0.1',
|
||||
name: scenario.name,
|
||||
auth: {
|
||||
method: 'open'
|
||||
},
|
||||
view: {
|
||||
bounds: {
|
||||
west: 0,
|
||||
south: 0,
|
||||
east: 45,
|
||||
north: 45
|
||||
},
|
||||
zoom: 4,
|
||||
center: {
|
||||
lng: 40,
|
||||
lat: 20
|
||||
},
|
||||
preview_layers: scenario.layerPerview
|
||||
},
|
||||
layergroup: {
|
||||
layers: scenario.layers
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(function (done) {
|
||||
test_helper.deleteRedisKeys({
|
||||
'user:localhost:mapviews:global': 5
|
||||
}, done);
|
||||
});
|
||||
|
||||
function previewFixture(version) {
|
||||
return './test/fixtures/previews/populated_places_simple_reduced-' + version + '.png';
|
||||
}
|
||||
|
||||
var threeLayerPointDistintColor = [
|
||||
createLayer('red'),
|
||||
createLayer('orange'),
|
||||
createLayer('blue', 'layer2')
|
||||
];
|
||||
|
||||
var scenarios = [{
|
||||
name: 'preview_layers_red',
|
||||
layerPerview: {
|
||||
'0': true,
|
||||
'1': false,
|
||||
'layer2': false
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_orange',
|
||||
layerPerview: {
|
||||
'0': false,
|
||||
'1': true,
|
||||
'layer2': false
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_blue',
|
||||
layerPerview: {
|
||||
'0': false,
|
||||
'1': false,
|
||||
'layer2': true
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_red_orange',
|
||||
layerPerview: {
|
||||
'0': true,
|
||||
'1': true,
|
||||
'layer2': false
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_red_blue',
|
||||
layerPerview: {
|
||||
'0': true,
|
||||
'1': false,
|
||||
'layer2': true
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_orange_blue',
|
||||
layerPerview: {
|
||||
'0': false,
|
||||
'1': true,
|
||||
'layer2': true
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_red_orange_blue',
|
||||
layerPerview: {
|
||||
'0': true,
|
||||
'1': true,
|
||||
'layer2': true
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_all',
|
||||
layerPerview: {},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_undefined',
|
||||
layerPerview: undefined,
|
||||
layers: threeLayerPointDistintColor
|
||||
}];
|
||||
|
||||
scenarios.forEach(function (scenario) {
|
||||
it('should filter layers for template: ' + scenario.name, function (done) {
|
||||
step(
|
||||
function addTemplate () {
|
||||
var next = this;
|
||||
var template = createTemplate(scenario);
|
||||
|
||||
templateMaps.addTemplate(username, template, next);
|
||||
},
|
||||
function requestPreview (err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/static/named/' + scenario.name + '/640/480.png',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
encoding: 'binary'
|
||||
}, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'content-type': 'image/png'
|
||||
}
|
||||
}, function (res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function checkPreview (err, res) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
var img = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||
var previewFixturePath = previewFixture(scenario.name);
|
||||
|
||||
assert.imageIsSimilarToFile(img, previewFixturePath, IMAGE_TOLERANCE, next);
|
||||
},
|
||||
function deleteTemplate(err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
|
||||
templateMaps.delTemplate(username, scenario.name, next);
|
||||
},
|
||||
function finish (err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -50,14 +50,14 @@ describe('turbo-carto error cases', function() {
|
||||
});
|
||||
|
||||
it('should return invalid number of ramp error', function(done) {
|
||||
this.testClient = new TestClient(makeMapconfig('ramp([pop_max], (8,24,96), (8,24,96,128))'));
|
||||
this.testClient = new TestClient(makeMapconfig('ramp([pop_max], 8, 96, 3, (8,24,96,128))'));
|
||||
this.testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroup) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(layergroup.hasOwnProperty('errors'));
|
||||
assert.equal(layergroup.errors.length, 1);
|
||||
assert.ok(layergroup.errors[0].match(/^turbo-carto/));
|
||||
assert.ok(layergroup.errors[0].match(/invalid\sramp\slength/i));
|
||||
assert.ok(layergroup.errors[0].match(/^Failed\sto\sprocess/), layergroup.errors[0]);
|
||||
assert.ok(layergroup.errors[0].match(/invalid\sramp\slength/i), layergroup.errors[0]);
|
||||
|
||||
done();
|
||||
});
|
||||
@@ -70,7 +70,7 @@ describe('turbo-carto error cases', function() {
|
||||
|
||||
assert.ok(layergroup.hasOwnProperty('errors'));
|
||||
assert.equal(layergroup.errors.length, 1);
|
||||
assert.ok(layergroup.errors[0].match(/^turbo-carto/));
|
||||
assert.ok(layergroup.errors[0].match(/^Failed\sto\sprocess/));
|
||||
assert.ok(layergroup.errors[0].match(/unable\sto\scompute\sramp/i));
|
||||
assert.ok(layergroup.errors[0].match(/wadus_column/));
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('turbo-carto error cases', function() {
|
||||
|
||||
assert.ok(layergroup.hasOwnProperty('errors'));
|
||||
assert.equal(layergroup.errors.length, 1);
|
||||
assert.ok(layergroup.errors[0].match(/^turbo-carto/));
|
||||
assert.ok(layergroup.errors[0].match(/^Failed\sto\sprocess/));
|
||||
assert.ok(layergroup.errors[0].match(/unable\sto\scompute\sramp/i));
|
||||
assert.ok(layergroup.errors[0].match(/invalid\smethod\s\"wadusmethod\"/i));
|
||||
|
||||
@@ -100,7 +100,7 @@ describe('turbo-carto error cases', function() {
|
||||
|
||||
assert.ok(layergroup.hasOwnProperty('errors'));
|
||||
assert.equal(layergroup.errors.length, 1);
|
||||
assert.ok(!layergroup.errors[0].match(/^turbo-carto/));
|
||||
assert.ok(!layergroup.errors[0].match(/^Failed\sto\sprocess/));
|
||||
assert.ok(layergroup.errors[0].match(/invalid\scode/i));
|
||||
|
||||
done();
|
||||
@@ -116,7 +116,7 @@ describe('turbo-carto error cases', function() {
|
||||
assert.equal(layergroup.errors_with_context.length, 1);
|
||||
assert.equal(layergroup.errors_with_context[0].type, 'layer');
|
||||
assert.equal(layergroup.errors_with_context[0].subtype, 'turbo-carto');
|
||||
assert.ok(layergroup.errors_with_context[0].message.match(/^turbo-carto/));
|
||||
assert.ok(layergroup.errors_with_context[0].message.match(/^Failed\sto\sprocess/));
|
||||
assert.ok(layergroup.errors_with_context[0].message.match(/unable\sto\scompute\sramp/i));
|
||||
assert.ok(layergroup.errors_with_context[0].message.match(/wadus_column/));
|
||||
|
||||
@@ -138,4 +138,53 @@ describe('turbo-carto error cases', function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return multiple errors', function(done) {
|
||||
|
||||
var multipleErrorsMapConfig = {
|
||||
"version": "1.4.0",
|
||||
"layers": [
|
||||
{
|
||||
"type": 'mapnik',
|
||||
"options": {
|
||||
"cartocss_version": '2.3.0',
|
||||
"sql": 'SELECT * FROM populated_places_simple_reduced',
|
||||
"cartocss": createCartocss(null, 'ramp([wadus_column], (red, green, blue))')
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": 'mapnik',
|
||||
"options": {
|
||||
"cartocss_version": '2.3.0',
|
||||
"sql": 'SELECT * FROM populated_places_simple_reduced',
|
||||
"cartocss": createCartocss('ramp([invalid_column], (red, green, blue))')
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.testClient = new TestClient(multipleErrorsMapConfig);
|
||||
this.testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroup) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(layergroup.hasOwnProperty('errors'));
|
||||
assert.equal(layergroup.errors_with_context.length, 2);
|
||||
|
||||
assert.equal(layergroup.errors_with_context[0].type, 'layer');
|
||||
assert.equal(layergroup.errors_with_context[0].subtype, 'turbo-carto');
|
||||
assert.ok(layergroup.errors_with_context[0].message.match(/^Failed\sto\sprocess/));
|
||||
assert.ok(layergroup.errors_with_context[0].message.match(/unable\sto\scompute\sramp/i));
|
||||
assert.ok(layergroup.errors_with_context[0].message.match(/wadus_column/));
|
||||
assert.equal(layergroup.errors_with_context[0].layer.id, 'layer0');
|
||||
|
||||
assert.equal(layergroup.errors_with_context[1].type, 'layer');
|
||||
assert.equal(layergroup.errors_with_context[1].subtype, 'turbo-carto');
|
||||
assert.ok(layergroup.errors_with_context[1].message.match(/^Failed\sto\sprocess/));
|
||||
assert.ok(layergroup.errors_with_context[1].message.match(/unable\sto\scompute\sramp/i));
|
||||
assert.ok(layergroup.errors_with_context[1].message.match(/invalid_column/));
|
||||
assert.equal(layergroup.errors_with_context[1].layer.id, 'layer1');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -287,4 +287,97 @@ describe('turbo-carto regressions', function() {
|
||||
});
|
||||
});
|
||||
|
||||
var scenarios = [
|
||||
{
|
||||
desc: 'numeric datasource',
|
||||
cartocss: [
|
||||
"#points {",
|
||||
" marker-fill: ramp([scalerank], colorbrewer(Reds), category);",
|
||||
"}"
|
||||
].join('\n'),
|
||||
expected: [
|
||||
'#points {',
|
||||
' marker-fill: #fee5d9;',
|
||||
' [ scalerank = 6 ] {',
|
||||
' marker-fill: #fcae91',
|
||||
' }',
|
||||
' [ scalerank = 8 ] {',
|
||||
' marker-fill: #fb6a4a',
|
||||
' }',
|
||||
' [ scalerank = 4 ] {',
|
||||
' marker-fill: #de2d26',
|
||||
' }',
|
||||
' [ scalerank = 10 ] {',
|
||||
' marker-fill: #a50f15',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n')
|
||||
},
|
||||
{
|
||||
desc: 'string datasource',
|
||||
cartocss: [
|
||||
"#points {",
|
||||
" marker-fill: ramp([adm0name], colorbrewer(Reds), category);",
|
||||
"}"
|
||||
].join('\n'),
|
||||
expected: [
|
||||
'#points {',
|
||||
' marker-fill: #fee5d9;',
|
||||
' [ adm0name = "Russia" ] {',
|
||||
' marker-fill: #fcae91',
|
||||
' }',
|
||||
' [ adm0name = "China" ] {',
|
||||
' marker-fill: #fb6a4a',
|
||||
' }',
|
||||
' [ adm0name = "Brazil" ] {',
|
||||
' marker-fill: #de2d26',
|
||||
' }',
|
||||
' [ adm0name = "Canada" ] {',
|
||||
' marker-fill: #a50f15',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n')
|
||||
},
|
||||
{
|
||||
desc: 'numeric manual',
|
||||
cartocss: [
|
||||
"#points {",
|
||||
" marker-fill: ramp([scalerank], colorbrewer(Reds), (-1, 6, 8, 4, 10), category);",
|
||||
"}"
|
||||
].join('\n'),
|
||||
expected: [
|
||||
'#points {',
|
||||
' marker-fill: #fee5d9;',
|
||||
' [ scalerank = 6 ] {',
|
||||
' marker-fill: #fcae91',
|
||||
' }',
|
||||
' [ scalerank = 8 ] {',
|
||||
' marker-fill: #fb6a4a',
|
||||
' }',
|
||||
' [ scalerank = 4 ] {',
|
||||
' marker-fill: #de2d26',
|
||||
' }',
|
||||
' [ scalerank = 10 ] {',
|
||||
' marker-fill: #a50f15',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n')
|
||||
}
|
||||
];
|
||||
|
||||
scenarios.forEach(function(scenario) {
|
||||
it('category ramps should use original type: ' + scenario.desc, function(done) {
|
||||
var mapConfig = makeMapconfig('SELECT * FROM populated_places_simple_reduced', scenario.cartocss);
|
||||
this.testClient = new TestClient(mapConfig);
|
||||
this.testClient.getLayergroup(function(err, layergroup) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(layergroup.hasOwnProperty('layergroupid'));
|
||||
assert.deepEqual(layergroup.metadata.layers[0].meta.cartocss, scenario.expected);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_all.png
vendored
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_blue.png
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_orange.png
vendored
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_orange_blue.png
vendored
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_red.png
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_red_blue.png
vendored
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_red_orange.png
vendored
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_red_orange_blue.png
vendored
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_undefined.png
vendored
Normal file
|
After Width: | Height: | Size: 59 KiB |
@@ -26,5 +26,7 @@ CREATE TABLE IF NOT EXISTS
|
||||
-- last job modifying the node
|
||||
last_modified_by uuid,
|
||||
-- store error message for failures
|
||||
last_error_message text
|
||||
last_error_message text,
|
||||
-- cached tables involved in the analysis
|
||||
cache_tables regclass[] NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
@@ -527,7 +527,7 @@ describe('Overviews query rewriter', function() {
|
||||
it('generates query with filters', function(){
|
||||
var sql = "SELECT ST_Transform(the_geom, 3857) the_geom_webmercator, cartodb_id, name\
|
||||
FROM (SELECT *\
|
||||
FROM (select * from table1) _camshaft_category_filter\
|
||||
FROM (select * from table1) _analysis_category_filter\
|
||||
WHERE name IN ($escape_0$X$escape_0$)) _cdb_analysis_query";
|
||||
var data = {
|
||||
overviews: {
|
||||
@@ -555,7 +555,7 @@ describe('Overviews query rewriter', function() {
|
||||
SELECT * FROM table1_ov3, _vovw_scale WHERE _vovw_z = 3\
|
||||
UNION ALL\
|
||||
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 3\
|
||||
) AS _vovw_table1) _camshaft_category_filter\
|
||||
) AS _vovw_table1) _analysis_category_filter\
|
||||
WHERE name IN ($escape_0$X$escape_0$)\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
@@ -564,7 +564,7 @@ describe('Overviews query rewriter', function() {
|
||||
it('generates query with filters for specific zoom level', function(){
|
||||
var sql = "SELECT ST_Transform(the_geom, 3857) the_geom_webmercator, cartodb_id, name\
|
||||
FROM (SELECT *\
|
||||
FROM (select * from table1) _camshaft_category_filter\
|
||||
FROM (select * from table1) _analysis_category_filter\
|
||||
WHERE name IN ($escape_0$X$escape_0$)) _cdb_analysis_query";
|
||||
var data = {
|
||||
overviews: {
|
||||
@@ -581,7 +581,7 @@ describe('Overviews query rewriter', function() {
|
||||
};
|
||||
var overviews_sql = overviewsQueryRewriter.query(sql, data, { zoom_level: 2 });
|
||||
var expected_sql = "\
|
||||
SELECT * FROM (SELECT * FROM table1_ov2) _camshaft_category_filter\
|
||||
SELECT * FROM (SELECT * FROM table1_ov2) _analysis_category_filter\
|
||||
WHERE name IN ($escape_0$X$escape_0$)\
|
||||
";
|
||||
assertSameSql(overviews_sql, expected_sql);
|
||||
@@ -590,7 +590,7 @@ describe('Overviews query rewriter', function() {
|
||||
it('does not generates query with aggressive filtering', function(){
|
||||
var sql = "SELECT ST_Transform(the_geom, 3857) the_geom_webmercator, cartodb_id, name\
|
||||
FROM (SELECT *\
|
||||
FROM (select * from table1) _camshaft_category_filter\
|
||||
FROM (select * from table1) _analysis_category_filter\
|
||||
WHERE name IN ($escape_0$X$escape_0$)) _cdb_analysis_query";
|
||||
var data = {
|
||||
overviews: {
|
||||
|
||||