Compare commits

..

141 Commits
7.0.0 ... 7.1.0

Author SHA1 Message Date
Daniel García Aubert
90740146ff Release 7.1.0 2019-05-06 10:40:02 +02:00
Daniel G. Aubert
778eb54890 Merge pull request #1088 from CartoDB/cartonik
Upgrade windshaft to version 5.2.0
2019-04-29 13:24:01 +02:00
Daniel García Aubert
5196d2fed0 Update NEWS 2019-04-29 13:03:15 +02:00
Daniel García Aubert
5e969aa41a Upgrade windshaft to version 5.2.0 2019-04-29 12:46:06 +02:00
Daniel García Aubert
1ac03eeb91 Update windshaft devel branch 2019-04-25 17:55:57 +02:00
Daniel García Aubert
261e6e3307 Merge branch 'master' into cartonik 2019-04-25 15:41:28 +02:00
Raúl Marín
e38f8b6133 Merge pull request #1095 from Algunenano/blessed_fix_deps
Update package-lock.json to point to CARTOs forks
2019-04-25 15:38:48 +02:00
Raul Marin
bc4204243c Update package-lock.json to point to CARTOs forks 2019-04-25 14:50:11 +02:00
Daniel García Aubert
f93ea0e95c Merge branch 'master' into cartonik 2019-04-25 14:14:16 +02:00
Raúl Marín
b90e15207f Merge pull request #1094 from CartoDB/mapnik13
Update node-mapnik to 3.6.2-carto.13
2019-04-15 13:43:27 +02:00
Raúl Marín
19da16229c Merge pull request #1092 from CartoDB/fix-typo-patch-1
fix typo
2019-04-15 13:27:37 +02:00
Raul Marin
73c29660d6 Update @carto/mapnik to 3.6.2-carto.13 2019-04-15 13:23:06 +02:00
Raul Marin
6549a3a023 Agg tests: Sort before compare to avoid failures due to paralellism 2019-04-10 19:32:32 +02:00
Mario de Frutos Dieguez
4d30fb57d8 Remove unused travisci variable 2019-04-10 16:43:24 +02:00
Mario de Frutos Dieguez
022e6a2f89 Tests using PG11 and Postgis 2.5 (#1093) 2019-04-10 13:06:39 +02:00
Daniel García Aubert
da613df71c Update windshaft devel branch 2019-04-02 17:01:04 +02:00
Daniel García Aubert
66882b3c25 Merge branch 'master' into cartonik 2019-04-02 16:45:27 +02:00
Daniel G. Aubert
9285764e31 Merge pull request #1091 from CartoDB/upgrade-windshaft-next
Upgrade windshaft to version 5.1.0
2019-04-02 12:52:34 +02:00
Daniel García Aubert
835f55e563 Update NEWS 2019-04-02 12:42:13 +02:00
Daniel García Aubert
ef1bd7502f Use released version of Windhshaft 2019-04-02 12:34:31 +02:00
Daniel García Aubert
320d3c196d Update windshaft devel branch 2019-04-01 11:34:06 +02:00
Iñigo Medina (aka MacGyver)
9d88a4659f fix typo
The output in the Developer Center is not being properly rendered just because of a typo in the headline format.
2019-03-29 19:03:25 +01:00
Daniel García Aubert
dae33bab5e Update fixtures 2019-03-29 18:56:18 +01:00
Daniel García Aubert
c0a305babf Update windshaft devel branch 2019-03-29 18:20:48 +01:00
Daniel G. Aubert
4ffbcea819 Merge pull request #1090 from CartoDB/graceful-shutdown
Use semver for cheking dependencies version
2019-03-29 15:40:12 +01:00
Daniel García Aubert
6808ab496a Use semver for cheking dependencies version 2019-03-29 15:30:29 +01:00
Daniel G. Aubert
40d43331e4 Merge pull request #1089 from CartoDB/graceful-shutdown
Implement graceful shutdown
2019-03-29 15:23:27 +01:00
Daniel García Aubert
674c276b57 Upgrade windshaft to version 5 2019-03-29 13:36:16 +01:00
Daniel García Aubert
5fdc501836 Upgrade camshaft to version 0.64.0 2019-03-29 13:26:38 +01:00
Daniel García Aubert
c3b1b469b2 Update NEWS 2019-03-29 13:21:35 +01:00
Daniel García Aubert
c30a81acbe Update NEWS 2019-03-29 13:18:40 +01:00
Daniel García Aubert
035acb343e Fix uncaught exception: TypeError: Cannot read property 'id' of undefined 2019-03-27 11:45:47 +01:00
Daniel García Aubert
53b19d576c Update dynamic Node.js version check 2019-03-26 19:14:39 +01:00
Daniel García Aubert
7efb8abd97 Use code while exiting 2019-03-26 19:06:10 +01:00
Daniel García Aubert
b314acf5b5 PLase jshint 2019-03-26 18:57:49 +01:00
Daniel García Aubert
b222dd15c0 Handle ENOMEM signal emitted by grainstore 2019-03-26 18:54:42 +01:00
Daniel García Aubert
01212920ae Draft 2019-03-26 18:47:57 +01:00
Daniel García Aubert
4b2d736f0e Update windshaft devel branch 2019-03-26 17:33:45 +01:00
Daniel García Aubert
57d5b17a84 Update windshaft devel branch 2019-03-26 09:54:19 +01:00
Daniel García Aubert
791d0f5787 Update windshaft devel branch 2019-03-25 19:27:52 +01:00
Daniel García Aubert
fa9d604fa3 Fix broken test 2019-03-22 19:09:58 +01:00
Daniel García Aubert
bdb55e4b21 Update windshaft devel branch 2019-03-22 18:17:06 +01:00
Daniel García Aubert
e2d707b158 Update windshaft devel branch 2019-03-22 17:36:01 +01:00
Daniel García Aubert
7963681585 Update windshaft devel branch 2019-03-21 17:18:48 +01:00
Daniel García Aubert
b0158d9388 Update cartonik 2019-03-21 12:08:34 +01:00
Daniel G. Aubert
0e86c4aae8 Merge pull request #1087 from CartoDB/upgrade-windshaft-4.13.5
Upgrade windshaft to version 4.13.5
2019-03-20 10:25:08 +01:00
Daniel García Aubert
2f838f0b6a Update NEWS 2019-03-19 18:39:56 +01:00
Daniel García Aubert
362d48ab81 Upgrade windshaft to version 4.13.5 2019-03-19 18:34:11 +01:00
Daniel G. Aubert
8d7b3beb09 Merge pull request #1086 from CartoDB/cartofy
Upgrade windshaft to version 4.13.4
2019-03-19 15:55:56 +01:00
Daniel García Aubert
6d360a8b03 Update NEWs 2019-03-19 15:45:20 +01:00
Daniel García Aubert
bf53a905a2 Upgrade windshaft to version 4.13.4 2019-03-19 13:41:07 +01:00
Daniel García Aubert
290cdd2692 Update windshaft 2019-03-18 12:46:27 +01:00
Daniel García Aubert
00570a21f5 Use devel branch of windshaft 2019-03-17 18:01:41 +01:00
Raúl Marín
9a98422076 Merge pull request #1082 from CartoDB/1073-aggregation-false
Fix boolean aggregation layer option
2019-03-14 17:14:26 +01:00
Raul Marin
ab9cfee7b1 Update NEWS 2019-03-14 17:02:23 +01:00
Raul Marin
ae59c4f996 Merge remote-tracking branch 'blessed/master' into 1073-aggregation-false 2019-03-14 17:01:07 +01:00
Raúl Marín
514fc908b9 Merge pull request #1080 from Algunenano/histogram_irq_improvement
Numeric histogram performance improvement
2019-03-14 15:23:44 +01:00
Raul Marin
59107496b4 Update NEWS 2019-03-14 15:17:42 +01:00
Raul Marin
8db090ae9c Numeric histogram: Test when start and end are provided but not bins 2019-03-14 15:16:23 +01:00
Raul Marin
730076469e Numeric histogram: Simplify bin calculation 2019-03-14 15:16:23 +01:00
Raul Marin
6241b23d4f Histogram: Speed up IRQ calculation 2019-03-14 15:16:23 +01:00
Daniel G. Aubert
e65872d5df Merge pull request #1085 from CartoDB/upgrade-windshaft-4.13.3
Upgrade windshaft to version 4.13.3
2019-03-14 12:00:01 +01:00
Daniel García Aubert
eaa38b7676 Upgrade windshaft to version 4.13.3 2019-03-13 18:52:17 +01:00
Daniel G. Aubert
f76fe9efc1 Merge pull request #1084 from CartoDB/fix-cluster-test-for-pg11
Place points out of boundaries to avoid conficlts with PG11
2019-03-12 15:27:54 +01:00
Daniel García Aubert
6b2ad8826b Move points to avoid x/y axes 2019-03-12 15:18:31 +01:00
Daniel García Aubert
8324d4c4c2 Place points out of boundaries to avoid conficlts with PG11 2019-03-12 10:35:16 +01:00
Daniel G. Aubert
2821d797a9 Merge pull request #1083 from CartoDB/cluster-refactor
Cluster refactor
2019-03-11 19:10:40 +01:00
Daniel García Aubert
367ca399c8 Improve readability 2019-03-11 18:53:47 +01:00
Daniel García Aubert
d86b01ba33 Add debug statement 2019-03-11 18:09:41 +01:00
Daniel García Aubert
0aa3b288a0 Improve validation 2019-03-11 18:04:47 +01:00
Daniel García Aubert
589996b79c Reduce complexity 2019-03-11 17:53:34 +01:00
Daniel García Aubert
49104a6add Reduce complexity by extracting function to validate expressions 2019-03-11 17:25:29 +01:00
Daniel García Aubert
8051dc5110 Reduce complexity by extracting validation condition to its function 2019-03-11 17:14:07 +01:00
Daniel García Aubert
fecedfdc68 Reduce complexity by extracting validation to a function 2019-03-11 17:06:04 +01:00
Daniel García Aubert
f0c82f21d2 Reduce complexity by extracting a complex condition to a function 2019-03-11 17:01:13 +01:00
cillas
9f71fcf255 Merge pull request #879 from CartoDB/developer-center
New docs folder for developer center
2019-03-07 18:35:20 +01:00
Javier Goizueta
c91b28ee92 Lint fixes 2019-03-06 18:32:17 +01:00
Javier Goizueta
113b3728b1 Fix support of boolean aggregation layer option 2019-03-06 18:14:15 +01:00
Javier Goizueta
cf193a71b2 Add tests for enabling default aggregation 2019-03-06 15:28:38 +01:00
csubira
2d37d4bf9d Fix typo in named maps guide 2019-03-06 15:26:14 +01:00
csubira
a768575fea Add multilayer api file as internal doc 2019-03-06 14:55:41 +01:00
csubira
51ff565c5f Update links to deprecated docs site 2019-03-06 14:54:48 +01:00
Javier Goizueta
c5f5f43ccb Fix test for disabling aggregations
See #1073
2019-03-05 22:17:04 +01:00
csubira
df7b5db47b Remove extra line 2019-03-04 12:26:04 +01:00
csubira
98121e0e64 Add liquid raw to fix block code 2019-03-04 12:20:51 +01:00
csubira
e74f734546 Revert addition due to conflict when deploying devcenter 2019-03-01 18:24:11 +01:00
Daniel G. Aubert
d836b75dc4 Merge pull request #1077 from CartoDB/mvt-list-aggregated-features
Experimental support for listing features in a grid
2019-03-01 18:06:38 +01:00
Daniel García Aubert
47321aebc4 Updates NEWS and next release version 2019-03-01 17:53:15 +01:00
Daniel García Aubert
2a0287b358 Add todos 2019-03-01 17:49:26 +01:00
Daniel García Aubert
f5fb60aa56 Filter output 2019-03-01 17:34:32 +01:00
Daniel García Aubert
a412e37a32 Improve test descriptions 2019-03-01 17:21:55 +01:00
Daniel García Aubert
dcf147cdfb linter 2019-03-01 17:05:35 +01:00
Daniel García Aubert
f9e5d9d0a9 Validate aggregation expresions 2019-03-01 17:02:06 +01:00
Daniel García Aubert
57a229655c Implement aggregation functions 2019-03-01 15:45:38 +01:00
csubira
a92a2b7291 Remove older doc files 2019-03-01 15:25:16 +01:00
csubira
d9fe5bf388 Update with latest changes 2019-03-01 15:19:27 +01:00
Daniel García Aubert
6dadb1bf6f Validate aggregation input 2019-03-01 15:17:22 +01:00
csubira
b7cf5ca174 Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb into developer-center 2019-03-01 15:06:41 +01:00
Daniel García Aubert
77d5d8ebd4 Be able to aggregate by a field 2019-03-01 11:21:18 +01:00
Daniel García Aubert
92e62069d4 Improve error handling 2019-02-28 12:13:15 +01:00
Daniel García Aubert
32938eeab7 Validated only aggregated layers can be requested by the new endpoint 2019-02-27 18:54:21 +01:00
Daniel García Aubert
6531770e48 Remove subtitution tokens 2019-02-27 12:47:20 +01:00
Daniel García Aubert
561be2e5e8 Add tests 2019-02-27 12:43:26 +01:00
Daniel García Aubert
b87298bad9 Fix resolution definition and use zoom as path param 2019-02-27 12:42:54 +01:00
Daniel García Aubert
c3df075d91 Draft 2019-02-26 19:19:44 +01:00
Daniel García Aubert
f82e403180 Implement query to get features of a cluster for an aggregated map from @jgoizueta's cluster query 2019-02-25 19:40:18 +01:00
Daniel García Aubert
afd81a7814 Stubs next version 2019-02-22 13:09:00 +01:00
Iñigo Medina (aka MacGyver)
b7aed2a85d Merge pull request #1064 from CartoDB/dc-updating-rate-limits
rate limits new values
2019-01-11 14:23:58 +01:00
Simon Martín
a47c5b5568 rate limits new values 2019-01-11 13:53:11 +01:00
Daniel G. Aubert
bdd1481024 Merge pull request #1023 from CartoDB/975-document-named-and-static-maps
Document named and static maps
2018-08-28 11:02:55 +02:00
Daniel García Aubert
810ad46446 Simplify schemas 2018-08-28 09:38:59 +02:00
Daniel García Aubert
94cd3d008c Add placeholder default value 2018-08-28 09:37:16 +02:00
Daniel García Aubert
2ec0b4674c Add 429 too many request error 2018-08-27 17:49:38 +02:00
Daniel García Aubert
5b30c390fd Add 500 server internal error 2018-08-27 17:25:52 +02:00
Daniel García Aubert
9de9c57dc2 Remove meaningless sentence 2018-08-27 11:55:52 +02:00
Daniel García Aubert
02dc61b7c7 Add Bad Request 2018-08-24 18:04:16 +02:00
Daniel García Aubert
7822f59fd4 Typo 2018-08-24 17:40:04 +02:00
Daniel García Aubert
158f2159b7 Add layer query params 2018-08-24 17:37:46 +02:00
Daniel García Aubert
44c4b29ea2 Adding curl example 2018-08-24 15:11:08 +02:00
Daniel García Aubert
7c13561a4f Add jsonp named map instantiation endpoint 2018-08-24 13:28:53 +02:00
Daniel García Aubert
380dab1461 Add title to components 2018-08-24 12:46:00 +02:00
Daniel García Aubert
91002933e3 Add more endpoints 2018-08-24 11:42:46 +02:00
Daniel García Aubert
cd75581ccb Add list template endpoint 2018-08-24 10:42:08 +02:00
Daniel García Aubert
49d5f560a7 Extract components 2018-08-23 19:07:13 +02:00
Daniel García Aubert
409170f661 Create TemplatePlaceholders 2018-08-23 18:24:17 +02:00
Daniel García Aubert
e3f6d4e9fd Fix swagger errors 2018-08-23 17:19:45 +02:00
Daniel García Aubert
346189cf4c Draft 2018-08-23 16:50:47 +02:00
cillas
2764eb9669 Delete old reference markdown 2018-08-16 15:00:49 +02:00
cillas
338ff63153 Add swagger.yaml 2018-08-16 14:22:18 +02:00
Iñigo Medina (aka MacGyver)
6e1f66ad94 Create 05-quota-limiting.md 2018-06-26 15:08:36 +02:00
Iñigo Medina (aka MacGyver)
3934e231fe Rename 05-metrics.md to 06-metrics.md 2018-06-26 14:52:44 +02:00
Iñigo Medina
c8273cec2c Update 02-contribute.md 2018-04-17 14:43:50 +02:00
Iñigo Medina
b117e7e2bb Rename 05-timeout-limiting.md to 04-timeout-limiting.md 2018-04-13 00:01:51 +02:00
Iñigo Medina
64fd2304a9 Create 05-timeout-limiting.md 2018-04-13 00:01:32 +02:00
Iñigo Medina
6b521e9985 Rename 04-metrics.md to 05-metrics.md 2018-04-13 00:00:50 +02:00
Iñigo Medina
2e19bf5b17 Create 03-rate-limiting.md 2018-04-13 00:00:30 +02:00
Iñigo Medina
69f41a0200 Update and rename 01-metrics.md to 04-metrics.md 2018-04-12 23:58:47 +02:00
Iñigo Medina
3cbf741209 Create 02-contribute.md 2018-04-12 23:41:54 +02:00
Iñigo Medina
9d2473bbe3 Create 01-support-options.md 2018-04-12 23:40:39 +02:00
csubira
9259d04771 Remove routes md 2018-02-28 11:22:23 +01:00
csubira
b1fb838b19 Add new structure to docs folder 2018-02-21 19:22:02 +01:00
56 changed files with 4119 additions and 648 deletions

View File

@@ -1,8 +1,12 @@
jobs:
include:
- sudo: required
services:
- docker
language: generic
before_install: docker pull carto/nodejs-xenial-pg101:latest
script: npm run docker-test -- 10.15.1 # Node.js version
language: generic
sudo: required
env:
matrix:
- NODE_VERSION=10.15.1
DOCKER_IMAGE=carto/nodejs-xenial-pg101:latest
- NODE_VERSION=10.15.1
DOCKER_IMAGE=carto/nodejs-xenial-pg1121:latest
services:
- docker
before_install: docker pull ${DOCKER_IMAGE}
script: npm run docker-test -- ${DOCKER_IMAGE} ${NODE_VERSION}

21
NEWS.md
View File

@@ -1,5 +1,26 @@
# Changelog
## 7.1.0
Released 2019-05-06
Announcements:
- Fix uncaught exception: TypeError: Cannot read property 'id' of undefined
- Implements graceful shutdown for:
- system signals `SIGINT` and `SIGTERM`
- events `uncaughtException`, `unhandledRejection` and, `ENOMEM`
- Experimental support for listing features in a grid when the map uses the dynamic agregation.
- Numeric histogram performance improvement (#1080)
- Fix boolean aggregation layer option not working when numbers of rows are above the threshold (#1082)
- Update deps:
- camshat@0.64.0
- windshaft@5.2.0:
- Use [`@carto/cartonik`](https://github.com/CartoDB/cartonik/releases/tag/v0.5.0) instead of `@mapbox/tilelive` to fetch raster/vertor tiles.
- Upgrade `grainstore` to version `2.0.0`
- Upgrade `torque.js` to version `3.1.0`
- Upgrade `canvas` to version `2.4.1`
- Update @carto/mapnik to [`3.6.2-carto.13`](https://github.com/CartoDB/node-mapnik/blob/v3.6.2-carto.13/CHANGELOG.carto.md#362-carto13).
## 7.0.0
Released 2019-02-22

51
app.js
View File

@@ -14,8 +14,9 @@ var logError = console.error.bind(console);
// jshint undef:true
var nodejsVersion = process.versions.node;
if (!semver.satisfies(nodejsVersion, '>=6.9.0')) {
logError(`Node version ${nodejsVersion} is not supported, please use Node.js 6.9 or higher.`);
const { engines } = require('./package.json');
if (!semver.satisfies(nodejsVersion, engines.node)) {
logError(`Node version ${nodejsVersion} is not supported, please use Node.js ${engines.node}.`);
process.exit(1);
}
@@ -177,10 +178,6 @@ process.on('SIGHUP', function() {
});
});
process.on('uncaughtException', function(err) {
global.logger.error('Uncaught exception: ' + err.stack);
});
if (global.gc) {
var gcInterval = Number.isFinite(global.environment.gc_interval) ?
global.environment.gc_interval :
@@ -231,3 +228,45 @@ function getGCTypeValue (type) {
return value;
}
addHandlers(listener, global.logger, 45000);
function addHandlers(listener, logger, killTimeout) {
process.on('uncaughtException', exitProcess(listener, logger, killTimeout));
process.on('unhandledRejection', exitProcess(listener, logger, killTimeout));
process.on('ENOMEM', exitProcess(listener, logger, killTimeout));
process.on('SIGINT', exitProcess(listener, logger, killTimeout));
process.on('SIGTERM', exitProcess(listener, logger, killTimeout));
}
function exitProcess (listener, logger, killTimeout) {
return function exitProcessFn (signal) {
scheduleForcedExit(killTimeout, logger);
let code = 0;
if (!['SIGINT', 'SIGTERM'].includes(signal)) {
const err = signal instanceof Error ? signal : new Error(signal);
signal = undefined;
code = 1;
logger.fatal(err);
} else {
logger.info(`Process has received signal: ${signal}`);
}
logger.info(`Process is going to exit with code: ${code}`);
listener.close(() => global.log4js.shutdown(() => process.exit(code)));
};
}
function scheduleForcedExit (killTimeout, logger) {
// Schedule exit if there is still ongoing work to deal with
const killTimer = setTimeout(() => {
logger.info('Process didn\'t close on time. Force exit');
process.exit(1);
}, killTimeout);
// Don't keep the process open just for this
killTimer.unref();
}

View File

@@ -1,4 +1,4 @@
#!/bin/bash
docker run -e "NODEJS_VERSION=${1}" -v `pwd`:/srv carto/nodejs-xenial-pg101:latest bash run_tests_docker.sh && \
docker run -e "NODEJS_VERSION=${2}" -v `pwd`:/srv ${1} bash run_tests_docker.sh && \
docker ps --filter status=dead --filter status=exited -aq | xargs docker rm -v

View File

@@ -0,0 +1,85 @@
FROM ubuntu:xenial
# Use UTF8 to avoid encoding problems with pgsql
ENV LANG C.UTF-8
ENV NPROCS 1
ENV JOBS 1
ENV CXX g++-4.9
ENV PGUSER postgres
# Add external repos
RUN set -ex \
&& apt-get update \
&& apt-get install -y \
curl \
software-properties-common \
locales \
&& add-apt-repository -y ppa:ubuntu-toolchain-r/test \
&& add-apt-repository -y ppa:cartodb/postgresql-11 \
&& add-apt-repository -y ppa:cartodb/redis-next \
&& curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash \
&& . ~/.nvm/nvm.sh \
&& locale-gen en_US.UTF-8 \
&& update-locale LANG=en_US.UTF-8
RUN set -ex \
&& apt-get update \
&& apt-get install -y \
g++-4.9 \
gcc-4.9 \
git \
libcairo2-dev \
libgdal-dev=2.3.2+dfsg-2build2~carto1 \
libgdal20=2.3.2+dfsg-2build2~carto1 \
libgeos-dev=3.7.1~carto1 \
libgif-dev \
libjpeg8-dev \
libjson-c-dev \
libpango1.0-dev \
libpixman-1-dev \
libproj-dev \
libprotobuf-c-dev \
libxml2-dev \
gdal-bin=2.3.2+dfsg-2build2~carto1 \
make \
nodejs \
protobuf-c-compiler \
pkg-config \
wget \
zip \
libopenscenegraph100v5 \
libsfcgal1 \
liblwgeom-2.5.0=2.5.1.4+carto-1 \
postgresql-11 \
postgresql-11-plproxy \
postgis=2.5.1.4+carto-1 \
postgresql-11-postgis-2.5=2.5.1.4+carto-1 \
postgresql-11-postgis-2.5-scripts=2.5.1.4+carto-1 \
postgresql-client-11 \
postgresql-client-common \
postgresql-common \
postgresql-contrib \
postgresql-plpython-11 \
postgresql-server-dev-11 \
redis=5:4.0.9-1carto1~xenial1 \
&& apt-get purge -y wget protobuf-c-compiler \
&& apt-get autoremove -y
# Configure PostgreSQL
RUN set -ex \
&& echo "listen_addresses='*'" >> /etc/postgresql/11/main/postgresql.conf \
&& echo "local all all trust" > /etc/postgresql/11/main/pg_hba.conf \
&& echo "host all all 0.0.0.0/0 trust" >> /etc/postgresql/11/main/pg_hba.conf \
&& echo "host all all ::1/128 trust" >> /etc/postgresql/11/main/pg_hba.conf \
&& /etc/init.d/postgresql start \
&& createdb template_postgis \
&& createuser publicuser \
&& psql -c "CREATE EXTENSION postgis" template_postgis \
&& /etc/init.d/postgresql stop
WORKDIR /srv
EXPOSE 5858
COPY ./scripts/nodejs-install.sh /src/nodejs-install.sh
RUN chmod 777 /src/nodejs-install.sh
CMD /src/nodejs-install.sh

View File

@@ -1,20 +0,0 @@
# Maps API
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 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.
## Documentation
* [Quickstart](quickstart.md)
* [General Concepts](general_concepts.md)
* [Anonymous Maps](anonymous_maps.md)
* [Named Maps](named_maps.md)
* [Static Maps API](static_maps_api.md)
* [MapConfig File Format]([local file in the docs repo](https://github.com/CartoDB/docs/blob/master/_app/_mapsapi/06-mapconfig.md))

View File

@@ -1,114 +0,0 @@
This document list all routes available in Windshaft-cartodb Maps API server.
## Routes list
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:z/:x/:y@:scale_factor?x.:format {:user(f),:token(f),:z(f),:x(f),:y(f),:scale_factor(t),:format(f)} (1)`
<br/>Notes: Mapnik retina tiles [0]
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:z/:x/:y.:format {:user(f),:token(f),:z(f),:x(f),:y(f),:format(f)} (1)`
<br/>Notes: Mapnik tiles [0]
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/:z/:x/:y.(:format) {:user(f),:token(f),:layer(f),:z(f),:x(f),:y(f),:format(f)} (1)`
<br/>Notes: Per :layer rendering based on :format [0]
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/attributes/:fid {:user(f),:token(f),:layer(f),:fid(f)} (1)`
<br/>Notes: Endpoint for info windows data, alternative for sql api when tables are private [0]
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/center/:token/:z/:lat/:lng/:width/:height.:format {:user(f),:token(f),:z(f),:lat(f),:lng(f),:width(f),:height(f),:format(f)} (1)`
<br/>Notes: Static Maps API [0]
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format {:user(f),:token(f),:west(f),:south(f),:east(f),:north(f),:width(f),:height(f),:format(f)} (1)`
<br/>Notes: Static Maps API [0]
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/widget/:widgetName {:user(f),:token(f),:layer(f),:widgetName(f)} (1)`
<br/>Notes: By :widgetName per :layer widget [0]
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/widget/:widgetName/search {:user(f),:token(f),:layer(f),:widgetName(f)} (1)`
<br/>Notes: By :widgetName per :layer widget search [0]
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
<br/>Notes: Map instantiation [0]
1. `POST (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
<br/>Notes: Map instantiation [0]
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/jsonp {:user(f),:template_id(f)} (1)`
<br/>Notes: Named maps JSONP instantiation [1]
1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
<br/>Notes: Instantiate named map [1]
1. `OPTIONS (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
<br/>Notes: CORS [0]
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/:layer/:z/:x/:y.(:format) {:user(f),:template_id(f),:layer(f),:z(f),:x(f),:y(f),:0(f),:format(f)} (1)`
<br/>Notes: Per :layer fixed URL named map tiles [1]
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/named/:template_id/:width/:height.:format {:user(f),:template_id(f),:width(f),:height(f),:format(f)} (1)`
<br/>Notes: Static map for named maps [1]
1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)`
<br/>Notes: Create named map (w/ API KEY) [1]
1. `PUT (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
<br/>Notes: Update a named map (w/ API KEY) [1]
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
<br/>Notes: Named map retrieval (w/ API KEY) [1]
1. `DELETE (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
<br/>Notes: Delete named map (w/ API KEY) [1]
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)`
<br/>Notes: List named maps (w/ API KEY) [1]
1. `OPTIONS (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
<br/>Notes: CORS [1]
1. `GET /health {} (1)`
<br/>Notes: Health check
1. `GET / {} (1)`
<br/>Notes: Welcome message
1. `GET /version {} (1)`
<br/>Notes: Return relevant module versions: mapnik, grainstore, etc
## Optional deprecated routes
- [0] `/tiles/layergroup` is deprecated and `/api/v1/map` should be used but we keep it for now.
- [1] `/tiles/template` is deprecated and `/api/v1/map/named` should be used but we keep it for now.
## How to generate the list of routes
Something like the following patch should do the trick
```javascript
diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js
index 5f62850..bca377d 100644
--- a/lib/cartodb/server.js
+++ b/lib/cartodb/server.js
@@ -215,6 +215,20 @@ module.exports = function(serverOptions) {
* END Routing
******************************************************************************************************************/
+ var format = require('util').format;
+ var routesNotes = app._router.stack
+ .filter(function(handler) { return !!handler.route; })
+ .map(function(handler) {
+ return format("\n1. `%s %s {%s} (1)`\n<br/>Notes: [DEPRECATED]? ",
+ Object.keys(handler.route.methods)[0].toUpperCase(),
+ handler.route.path,
+ handler.keys.map(function(k) {
+ return format(':%s(%s)', k.name, k.optional ? 't' : 'f');
+ }).join(',')
+ );
+ });
+ console.log(routesNotes.join('\n'));
+
return app;
};
```

View File

@@ -0,0 +1 @@
## Example 1

View File

@@ -1,27 +0,0 @@
# General Concepts
The following concepts are the same for every endpoint in the API except when it's noted explicitly.
## Auth
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`.
## Errors
Errors are reported using standard HTTP codes and extended information encoded in JSON with this format:
```javascript
{
"errors": [
"access forbidden to table TABLE"
]
}
```
If you use JSONP, the 200 HTTP code is always returned so the JavaScript client can receive errors from the JSON object.
## CORS Support
All the endpoints, which might be accessed using a web browser, add CORS headers and allow OPTIONS method.

View File

@@ -1,6 +1,6 @@
# Quickstart
## Quickstart
## Anonymous Maps
### Anonymous Maps
Here is an example of how to create an Anonymous Map with JavaScript:
@@ -31,7 +31,7 @@ $.ajax({
})
```
## Named Maps
### Named Maps
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:
@@ -58,7 +58,7 @@ The following map config sets up a map of European countries that have a white f
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
##### Call
```bash
curl 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}' -H 'Content-Type: application/json' -d @mapconfig.json
@@ -66,7 +66,7 @@ curl 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}' -H 'Conte
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.
#### Call
##### Call
```bash
curl -X POST 'https://{username}.carto.com/api/v1/map/named/{template_id}' -H 'Content-Type: application/json'
@@ -76,7 +76,7 @@ The response will return JSON with properties for the `layergroupid`, the timest
Note: all `layers` in `metadata` will always have a `type` string and a `meta` dictionary with the key/value pairs.
#### Response
##### Response
```javascript
{

View File

@@ -0,0 +1,46 @@
## General Concepts
The following concepts are the same for every endpoint in the API except when it's noted explicitly.
### Auth
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, an API Key is also required to use some of the API endpoints (e.g. to create 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`.
### Errors
Errors are reported using standard HTTP codes and extended information encoded in JSON with this format:
```javascript
{
"errors": [
"access forbidden to table TABLE"
]
}
```
If you use JSONP, the 200 HTTP code is always returned so the JavaScript client can receive errors from the JSON object.
### CORS Support
All the endpoints, which might be accessed using a web browser, add CORS headers and allow OPTIONS method.
### Map Tile Rendering
Map tiles create the graphical representation of your map in a web browser. The performance rendering of map tiles is dependent on the type of geospatial data model (raster or vector) that you are using.
- **Raster**: Generates map tiles based on a grid of pixels to represent your data. Each cell is a fixed size and contains values for particular map features. On the server-side, each request queries a dataset to retrieve data for each map tile. The grid size of map tiles can often lead to graphic quality issues.
- **Vector**: Generates map tiles based on pre-defined coordinates to represent your data, similar to how basemap image tiles are rendered. On the client-side, map tiles represent real-world geometries of a map. Depending on the coordinates, vertices are used to connect the data and display points, lines, or polygons for the map tiles.
**Note:** By default, CARTO uses vector graphics for map rendering. Please [contact us](mailto:support@carto.com) if you need raster rendering enabled as part of your requirements.
### Mapbox Vector Tiles (MVT)
[Mapbox Vector Tiles (MVT)](https://www.mapbox.com/vector-tiles/specification/) are map tiles that store geographic vector data on the client-side. Browser performance is fast since you can pan and zoom without having to query the server.
CARTO uses a Web Graphics Library (WebGL) to process MVT files. This is useful since WebGL's are compatible with most web browsers, include support for multiple client-side mapping engines, and do not require additional information from the server; which makes it more efficient for rendering map tiles.
**Tip:** You can process MVT files with the [`ST_AsMVT` PostGIS function](https://postgis.net/docs/manual-dev/ST_AsMVT.html) with the [Maps API Windshaft renderer](https://github.com/CartoDB/Windshaft/blob/1000x/lib/windshaft/renderers/pg_mvt/renderer.js).

View File

@@ -1,18 +1,18 @@
# Anonymous Maps
## Anonymous Maps
Anonymous Maps allows you to instantiate a map given SQL and CartoCSS. It also allows you to add interaction capabilities using [UTF Grid.](https://github.com/mapbox/utfgrid-spec).
Alternatively, you can get the data for the map (geometry and attributes for each layer) using vector tiles (in which case CartoCSS is not required).
## Instantiate
### Instantiate
#### Definition
##### Definition
```html
POST /api/v1/map
```
#### Params
##### Params
```javascript
{
@@ -29,9 +29,9 @@ POST /api/v1/map
}
```
See [MapConfig File Formats](http://docs.carto.com/carto-engine/maps-api/mapconfig/) for details.
See [MapConfig File Formats]({{site.mapsapi_docs}}/guides/MapConfig-file-format/) for details.
#### Response
##### Response
The response includes:
@@ -49,15 +49,15 @@ Now, for convenience, the layergroup includes the final URLs in two formats:
1. Leaflet's urlTemplate alike: useful when working with raster tiles or with libraries with an API similar to Leaflet's one.
1. [TileJSON spec](https://github.com/mapbox/tilejson-spec): useful when working with Mapbox GL or any other library that supports TileJSON.
### Example
#### Example
#### Call
##### Call
```bash
curl 'https://{username}.carto.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json
```
#### Response
##### Response
```javascript
{
@@ -97,7 +97,7 @@ curl 'https://{username}.carto.com/api/v1/map' -H 'Content-Type: application/jso
}
```
## Map Tile Rendering
### Map Tile Rendering
Map tiles are used to create the graphic representation of your map in a web browser. Tiles can be requested either as pre-rendered *raster* tiles (images) or as *vector* map data to be rendered by the client (browser).
@@ -105,11 +105,11 @@ Map tiles are used to create the graphic representation of your map in a web bro
- **Vector**: Tiles can also be requested as MVT (Mapbox Vector Tiles). In this case, only the geospatial vector data, without any styling, is returned. These tiles should be processed in the client-side to render the map. In this case layers do not need to define CartoCSS, as any rendering and styling will be performed on the client side. The vector data of a tile represents real-world geometries by defining the vertices of points, lines or polygons in a tile-specific coordinate system.
## Retrieve resources from the layergroup
### Retrieve resources from the layergroup
When you have a layergroup, there are several resources for retrieving layergoup details such as, accessing Mapnik tiles, getting individual layers, accessing defined Attributes, and blending and layer selection.
### Raster tiles
#### Raster tiles
These raster tiles are PNG images that represent only the Mapnik layers of a map. See [individual layers](#individual-layers) for details about how to retrieve other layers.
@@ -117,7 +117,7 @@ These raster tiles are PNG images that represent only the Mapnik layers of a map
https://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
```
### Mapbox Vector Tiles (MVT)
#### Mapbox Vector Tiles (MVT)
[Mapbox Vector Tiles (MVT)](https://www.mapbox.com/vector-tiles/specification/) are map tiles that transfer geographic vector data to the client-side. Browser performance is fast since you can pan and zoom without having to query the server.
@@ -125,7 +125,7 @@ CARTO uses Web Graphics Library (WebGL) to process MVT files on the browser. Thi
The following examples describe how to fetch MVT tiles with a cURL request.
#### MVT and Windshaft
##### MVT and Windshaft
CARTO uses Windshaft as the map tiler library to render multilayer maps with the Maps API. You can use Windshaft to request MVT using the same layer type that is used for requesting raster tiles (Mapnik layer). Simply change the file format `.mvt` in the URL.
@@ -149,7 +149,7 @@ The following example instantiates an anonymous map with layer options:
**Note**: If no layer type is specified, Mapnik tiles are used by default. To access MVT tiles, specify `https://{username}.cartodb.com/api/v1/map/HASH/{z}/{x}/{y}.mvt` as the `maps_api_template` variable.
**Tip:** If you are using [Named Maps](https://carto.com/docs/carto-engine/maps-api/named-maps/) to instantiate a layer, indicate the MVT file format and layer in the response:
**Tip:** If you are using [Named Maps]({{site.mapasapi_docs}}/guides/named-maps/) to instantiate a layer, indicate the MVT file format and layer in the response:
```bash
https://{username}.cartodb.com/api/v1/map/named/:templateId/:layer/{z}/{x}/{y}.mvt
@@ -161,7 +161,7 @@ For all layers in a Named Map, you must indicate Mapnik as the layer filter:
https://{username}.cartodb.com/api/v1/map/named/:templateId/mapnik/{z}/{x}/{y}.mvt
```
#### Layergroup Filter for MVT Tiles
##### Layergroup Filter for MVT Tiles
To filter layers using Windshaft, use the following request where layers are numbered:
@@ -181,7 +181,7 @@ To filter a specific layer:
https://{username}.cartodb.com/api/v1/map/HASH/2/{z}/{x}/{y}.mvt
```
#### Example 1: MVT Tiles with Windshaft, CARTO.js, and MapboxGL
##### Example 1: MVT Tiles with Windshaft, CARTO.js, and MapboxGL
1) Import the required libraries:
@@ -223,7 +223,7 @@ cartocss: "...",
5) Request Tiles (from CARTO) and Set to Map Object (Mapbox):
**Note:** By default, [CARTO core functions](https://carto.com/docs/carto-engine/carto-js/core-api/) retrieve URLs for fully rendered tiles. You must replace the default format (.png) with the MVT format (.mvt).
**Note:** By default, [CARTO core functions]({{site.cartojs_docs}}/v3/guides/core-API-functionality/) retrieve URLs for fully rendered tiles. You must replace the default format (.png) with the MVT format (.mvt).
```bash
@@ -237,7 +237,7 @@ map.setStyle(simpleStyle(tiles));
});
```
#### Example 2: MVT Libraries with Windshaft and MapboxGL
##### Example 2: MVT Libraries with Windshaft and MapboxGL
When you are not including CARTO.js to implement MVT tiles, you must use the `map.setStyle` parameter to specify vector map rendering.
@@ -291,7 +291,7 @@ map.setStyle({
- [MapboxGL Style Specifications](https://www.mapbox.com/mapbox-gl-js/style-spec/)
- [Example of MapboxGL Implementation](https://www.mapbox.com/mapbox-gl-js/examples/)
### Individual layers
#### Individual layers
The MapConfig specification holds the layers definition in a 0-based index. Layers can be requested individually, in different formats, depending on the layer type.
@@ -309,7 +309,7 @@ If the MapConfig had a Torque layer at index 1 it could be possible to request i
https://{username}.carto.com/api/v1/map/{layergroupid}/1/{z}/{x}/{y}.torque.json
```
### Attributes defined in `attributes` section
#### Attributes defined in `attributes` section
```bash
https://{username}.carto.com/api/v1/map/{layergroupid}/{layer}/attributes/{feature_id}
@@ -321,7 +321,7 @@ Which returns JSON with the attributes defined, such as:
{ "c": 1, "d": 2 }
```
### Blending and layer selection
#### Blending and layer selection
```bash
https://{username}.carto.com/api/v1/map/{layergroupid}/{layer_filter}/{z}/{x}/{y}.png
@@ -352,17 +352,17 @@ Some notes about filtering:
- Invalid index values or out of bounds indexes will end in `Invalid layer filtering` errors.
- 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 that you will always get consistent behavior.
## Create JSONP
### Create JSONP
The JSONP endpoint is provided in order to allow web browsers access which don't support CORS.
#### Definition
##### Definition
```bash
GET /api/v1/map?callback=method
```
#### Params
##### Params
Param | Description
--- | ---
@@ -370,15 +370,15 @@ config | Encoded JSON with the params for creating Named Maps (the variables def
lmza | This attribute contains the same as config but LZMA compressed. It cannot be used at the same time as `config`.
callback | JSON callback name.
### Example
#### Example
#### Call
##### Call
```bash
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
##### Response
```javascript
callback({
@@ -391,6 +391,6 @@ callback({
})
```
## Remove
### Remove
Anonymous Maps cannot be removed by an API call. They will expire after about five minutes, or sometimes longer. If an Anonymous Map expires and tiles are requested from it, an error will be raised. This could happen if a user leaves a map open and after time, returns to the map and attempts to interact with it in a way that requires new tiles (e.g. zoom). The client will need to go through the steps of creating the map again to fix the problem.

View File

@@ -1,4 +1,4 @@
# Named Maps
## 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).
@@ -6,7 +6,7 @@ The Named Map workflow consists of uploading a MapConfig file to CARTO servers,
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 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.
**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]({{site.cartojs_docs}}/v3/guides/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:
@@ -16,26 +16,26 @@ The main differences, compared to Anonymous Maps, is that Named Maps include:
- **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 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.carto.com/carto-engine/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]({{site.mapsapi_docs}}/guides/MapConfig-file-format/).
**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).
**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]({{site.mapsapi_docs}}/guides/MapConfig-file-format/#named-map-layer-options).
## Create
### Create
#### Definition
##### Definition
```html
POST /api/v1/map/named
```
#### Params
##### Params
Params | Description
--- | ---
api_key | is required
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
MapConfig | a [Named Map MapConfig]({{site.mapsapi_docs}}/guides/MapConfig-file-format/#named-map-layer-options) is required to create a Named Map
#### template.json
##### template.json
The `name` argument defines how to name this "template_name".json. Note that there are some requirements for how to name a Named Map template. See the [`name`](#arguments) argument description for details.
@@ -93,7 +93,7 @@ The `name` argument defines how to name this "template_name".json. Note that the
}
```
#### Arguments
##### Arguments
Params | Description
--- | ---
@@ -104,7 +104,7 @@ auth |
&#124;_ method | `"token"` or `"open"` (`"open"` is the default if no method is specified. Use `"token"` to password-protect your map)
&#124;_ 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.carto.com/carto-engine/maps-api/mapconfig/) for more information.
layergroup | the layergroup configurations, as specified in the template. See [MapConfig File Format]({{site.mapsapi_docs}}/guides/MapConfig-file-format/) 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. Also it is possible to choose which layers are visible or not with `preview_layers` indicating its visibility by layer index or id (visible by default).
--- | ---
&#124;_ zoom | The zoom level to use
@@ -122,13 +122,13 @@ view (optional) | extra keys to specify the view area for the map. It can be use
&#124;_ &#124;_ north | UpperCorner latitude for the bounding box, in decimal degrees (aka most northern)
### Placeholder Format
#### 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 [Layergroup configurations](http://docs.carto.com/carto-engine/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 [Layergroup configurations]({{site.mapsapi_docs}}/guides/MapConfig-file-format/#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.
#### Example
##### Example
```javascript
<%= my_color %>
@@ -136,7 +136,7 @@ Valid placeholder names start with a letter and can only contain letters, number
The set of supported placeholders for a template need to be explicitly defined with a specific type, and default value, for each placeholder.
### Placeholder Types
#### Placeholder Types
The placeholder type will determine the kind of escaping for the associated value. Supported types are:
@@ -151,7 +151,7 @@ Placeholder default values will be used whenever new values are not provided as
When using templates, be very careful about your selections as they can give broad access to your data if they are defined loosely.
#### Call
##### Call
This is the call for creating the Named Map. It is sending the template.json file to the service, and the server responds with the template id.
@@ -162,7 +162,7 @@ curl -X POST \
'https://{username}.carto.com/api/v1/map/named?api_key={api_key}'
```
#### Response
##### 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.
@@ -172,17 +172,17 @@ The response back from the API provides the name of your MapConfig as a template
}
```
## Instantiate
### Instantiate
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
##### Definition
```html
POST /api/v1/map/named/{template_name}
```
#### Param
##### Param
Param | Description
--- | ---
@@ -200,14 +200,14 @@ The fields you pass as `params.json` depend on the variables allowed by the Name
**Note:** It is required that you include a `params.json` file to instantiate a Named Map that contains variables, even if you have no fields to pass and the JSON is empty. (This is specific to when a Named Map allows variables (if placeholders were defined in the template.json by the user).
#### Example
##### Example
You can initialize a template map by passing all of the required parameters in a POST to `/api/v1/map/named/{template_name}`.
Valid auth token will be needed, if required by the template.
#### Call
##### Call
```bash
curl -X POST \
@@ -216,7 +216,7 @@ curl -X POST \
'https://{username}.carto.com/api/v1/map/named/{template_name}?auth_token={auth_token}'
```
#### Response
##### Response
```javascript
{
@@ -225,7 +225,7 @@ curl -X POST \
}
```
#### Error
##### Error
```javascript
{
@@ -233,33 +233,33 @@ curl -X POST \
}
```
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/)).
You can then use the `layergroupid` for fetching tiles and grids as you would normally (see [Anonymous Maps]({{site.mapsapi_docs}}/guides/anonymous-maps/)).
## Update
### Update
#### Definition
##### Definition
```bash
PUT /api/v1/map/named/{template_name}
```
#### Params
##### Params
Param | Description
--- | ---
api_key | is required
#### Response
##### Response
Same as updating a map.
### Other Information
#### Other Information
Updating a Named Map removes all the Named Map instances, so they need to be initialized again.
### Example
#### Example
#### Call
##### Call
```bash
curl -X PUT \
@@ -268,7 +268,7 @@ curl -X PUT \
'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}'
```
#### Response
##### Response
```javascript
{
@@ -286,31 +286,31 @@ If a template with the same name does NOT exist, a 400 HTTP response is generate
}
```
## Delete
### Delete
Deletes the specified template map from the server, and disables any previously initialized versions of the map.
#### Definition
##### Definition
```bash
DELETE /api/v1/map/named/{template_name}
```
#### Params
##### Params
Param | Description
--- | ---
api_key | is required
### Example
#### Example
#### Call
##### Call
```bash
curl -X DELETE 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}'
```
#### Response
##### Response
```javascript
{
@@ -320,31 +320,31 @@ curl -X DELETE 'https://{username}.carto.com/api/v1/map/named/{template_name}?ap
On success, a 204 (No Content) response will be issued. Otherwise a 4xx response with an error will be returned.
## Listing Available Templates
### Listing Available Templates
This allows you to get a list of all available templates.
#### Definition
##### Definition
```bash
GET /api/v1/map/named/
```
#### Params
##### Params
Param | Description
--- | ---
api_key | is required
### Example
#### Example
#### Call
##### Call
```bash
curl -X GET 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}'
```
#### Response
##### Response
```javascript
{
@@ -352,7 +352,7 @@ curl -X GET 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}'
}
```
#### Error
##### Error
```javascript
{
@@ -360,31 +360,31 @@ curl -X GET 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}'
}
```
## Get Template Definition
### Get Template Definition
This gets the definition of a requested template.
#### Definition
##### Definition
```bash
GET /api/v1/map/named/{template_name}
```
#### Params
##### Params
Param | Description
--- | ---
api_key | is required
### Example
#### Example
#### Call
##### Call
```bash
curl -X GET 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}'
```
#### Response
##### Response
```javascript
{
@@ -392,7 +392,7 @@ curl -X GET 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_k
}
```
#### Error
##### Error
```javascript
{
@@ -400,17 +400,17 @@ curl -X GET 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_k
}
```
## JSONP for Named Maps
### JSONP for Named Maps
If using a [JSONP](https://en.wikipedia.org/wiki/JSONP) (for old browsers) request, there is a special endpoint used to initialize and create a Named Map.
#### Definition
##### Definition
```bash
GET /api/v1/map/named/{template_name}/jsonp
```
#### Params
##### Params
Params | Description
--- | ---
@@ -419,13 +419,13 @@ params | Encoded JSON with the params (variables) needed for the Named Map
lmza | You can use an LZMA compressed file instead of a params JSON file
callback | JSON callback name
#### Call
##### Call
```bash
curl 'https://{username}.carto.com/api/v1/map/named/{template_name}/jsonp?auth_token={auth_token}&callback=callback&config=template_params_json'
```
#### Response
##### Response
```javascript
callback({
@@ -454,9 +454,9 @@ callback({
})
```
## CARTO.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 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.
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]({{site.cartojs_docs}}/v3/guides/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
```javascript
{
@@ -486,15 +486,15 @@ 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.
[CARTO.js](http://docs.carto.com/carto-engine/carto-js/) has methods for accessing your Named Maps.
[CARTO.js]({{site.cartojs_docs}}/v3/) has methods for accessing your Named Maps.
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
1. [layer.setParams()]({{site.cartojs_docs}}/v3/reference/#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript
**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.carto.com/carto-engine/carto-js/api-methods/#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer
2. [layer.setAuthToken()](h{{site.cartojs_docs}}/v3/reference/#layersetauthtokenauth_token) allows you to set the auth tokens to create the layer
### Torque Layer in a Named Map
#### 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 CARTO.js:
@@ -525,7 +525,7 @@ If you are creating a Torque layer in a Named Map without using the Torque.js li
}
```
#### Examples of Named Maps created with CARTO.js
##### Examples of Named Maps created with CARTO.js
- [Named Map selectors with interaction](http://bl.ocks.org/andy-esch/515a8af1f99d5e690484)
@@ -533,11 +533,11 @@ If you are creating a Torque layer in a Named Map without using the Torque.js li
- [Toggling sublayers in a Named Map](http://bl.ocks.org/andy-esch/c1a0f4913610eec53cd3)
## Fetching XYZ Tiles for Named Maps
### Fetching XYZ Tiles for Named Maps
Optionally, authenticated users can fetch projected tiles (XYZ tiles or Mapnik Retina tiles) for your Named Map.
### Fetch XYZ Tiles Directly with a URL
#### 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.
@@ -551,17 +551,17 @@ For example, a complete URL might appear as:
The placeholders indicate the following:
- [`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.
- [`template_id`]({{site.mapsapi_docs}}/guides/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}.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"
- To show a [list of layers]({{site.mapsapi_docs}}/guides/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
#### 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.carto.com/carto-engine/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]({{site.mapsapi_docs}}/guides/named-maps/#response-1) to initialize the map.
Instantiate the map by using your `layergroupid` in the token placeholder:

View File

@@ -1,24 +1,24 @@
# Static Maps API
## Static Maps API
The Static Maps API can be initiated using both Named and Anonymous Maps using the 'layergroupid' token. The API can be used to create static images of parts of maps and thumbnails for use in web design, graphic design, print, field work, and many other applications that require standard image formats.
The Static Maps API can be initiated using both Named and Anonymous Maps using the `layergroupid` token. The API can be used to create static images of parts of maps and thumbnails for use in web design, graphic design, print, field work, and many other applications that require standard image formats.
## Maps API endpoints
### Maps API endpoints
Begin by instantiating either a Named or Anonymous Map using the `layergroupid token` as demonstrated in the Maps API documentation above. The `layergroupid` token calls to the map and allows for parameters in the definition to generate static images.
Begin by instantiating either a Named or Anonymous Map using the `layergroupid` token as demonstrated in the Maps API documentation above. The `layergroupid` token calls to the map and allows for parameters in the definition to generate static images.
### Zoom + center
#### Zoom + center
#### Definition
##### Definition
```bash
GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}{{?}extra_options}
{% raw %}GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}{{?}extra_options}{% endraw %}
```
#### Params
##### Params
Param | Description
--- | ---
token | the layergroupid token from the map instantiation
token | the `layergroupid` token from the map instantiation
z | the zoom level of the map
lat | the latitude for the center of the map
@@ -26,19 +26,19 @@ format | the format for the image, supported types: `png`, `jpg`
--- | ---
&#124;_ jpg | will have a default quality of 85.
### Bounding Box
#### Bounding Box
#### Definition
##### Definition
```bash
GET /api/v1/map/static/bbox/{token}/{bbox}/{width}/{height}.{format}`
```
#### Params
##### Params
Param | Description
--- | ---
token | the layergroupid token from the map instantiation
token | the `layergroupid` token from the map instantiation
bbox | the bounding box in WGS 84 (EPSG:4326), comma separated values for:
--- | ---
@@ -57,19 +57,19 @@ Note: you can see this endpoint as
```bash
GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format}`
```
#### Extra options
* Layer: List of layers to be shown in the image (by default `all`), for example `?layer=0,1`.
### Named Map
#### Definition
#### Named Map
##### Definition
```bash
GET /api/v1/map/static/named/{name}/{width}/{height}.{format}
```
#### Params
##### Params
Param | Description
--- | ---
@@ -81,9 +81,9 @@ format | the format for the image, supported types: `png`, `jpg`
--- | ---
&#124;_ 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.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`.
A Named Maps static image will get its constraints from the [`view` argument of the Create Named Map function]({{site.mapasapi_docs}}/guides/named-maps/). 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
##### Layers
The Static Maps API allows for multiple layers of incorporation into the `MapConfig` to allow for maximum versatility in creating a static map. The examples below were used to generate the static image example in the next section, and appear in the specific order designated.
@@ -127,7 +127,7 @@ By manipulating the `"urlTemplate"` custom basemaps can be used in generating st
**CARTO**
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.
As described in the [MapConfig File Format]({{site.mapsapi_docs}}/guides/MapConfig-file-format/), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility.
```javascript
{
@@ -143,11 +143,11 @@ As described in the [MapConfig File Format](http://docs.carto.com/carto-engine/m
Additionally, static images from Torque maps and other map layers can be used together to generate highly customizable and versatile static maps.
### Caching
#### Caching
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.
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
#### Limits
* While images can encompass an entirety of a map, the limit for pixel range is 8192 x 8192.
* Image resolution is set to 72 DPI
@@ -159,21 +159,21 @@ It is important to note that generated images are cached from the live data refe
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/attributions">CARTO</a>
{% endhighlight %}
## Examples
### Examples
After instantiating a map from a CARTO account:
#### Call
##### Call
```bash
GET /api/v1/map/static/center/{layergroupid}/{z}/{x}/{y}/{width}/{height}.png
```
#### Response
##### Response
<p class="wrap-border"><img src="https://raw.githubusercontent.com/namessanti/Pictures/master/static_api.png" alt="static-api"/></p>
### MapConfig
#### MapConfig
For this map, the multiple layers, order, and stylings are defined by the MapConfig.

View File

@@ -1,4 +1,4 @@
# Tile Aggregation
## Tile Aggregation
To be able to represent a large amount of data (say, hundred of thousands to millions of points) in a tile. This can be useful both for raster tiles (where the aggregation reduces the number of features to be rendered) and vector tiles (the tile contais less features).
@@ -6,7 +6,7 @@ Aggregation is available only for point geometries. During aggregation the point
- The position of the aggregated point is controlled by the `placement` parameter.
- The aggregated rows always contain at least a column, named `_cdb_feature_count`, which contains the number of the original points that the aggregated point represents.
### Special default aggregation
#### Special default aggregation
When no placement or columns are specified a special default aggregation is performed.
@@ -16,13 +16,13 @@ Regarding the randomness of the sample: currently we use the row with the minimu
The rationale behind having this special aggregation with all the original columns is to provide a mostly transparent way to handle large datasets without having to provide special map configurations for those cases (i.e. preserving the logic used to produce the maps with smaller datasets). [Overviews have been used so far with this intent](https://carto.com/docs/tips-and-tricks/back-end-data-performance/), but they are inflexible.
### User defined aggregations
#### User defined aggregations
When either a explicit placement or columns are requested we no longer use the special, query; we use one determined by the placement (which will default to "centroid"), and it will have as columns only the aggregated columns specified, in addition to `_cdb_feature_count`, which is always present.
We might decide in the future to allow sampling column values for any of the different placement modes.
### Behaviour for raster and vector tiles
#### Behaviour for raster and vector tiles
The vector tiles from a vector-only map will be aggregated by default.
However, Raster tiles (or vector tiles from a map which defines CartoCSS styles) will be aggregated only upon request.
@@ -79,7 +79,7 @@ layer will be aggregated when tiles are requested, both for vector (mvt) and ras
}
```
## Aggregation parameters
### Aggregation parameters
The aggregation parameters for a layer are defined inside an `aggregation` option of the layer:
@@ -96,24 +96,24 @@ The aggregation parameters for a layer are defined inside an `aggregation` optio
}
```
### `placement`
#### `placement`
Determines the kind of aggregated geometry generated:
#### `point-sample`
##### `point-sample`
This is the default placement. It will place the aggregated point at a random sample of the grouped points,
like the default aggregation does. No other attribute is sampled, though, the point will contain the aggregated attributes determined by the `columns` parameter.
#### `point-grid`
##### `point-grid`
Generates points at the center of the aggregation grid cells (squares).
#### `centroid`
##### `centroid`
Generates points with the averaged coordinated of the grouped points (i.e. the points inside each grid cell).
### `columns`
#### `columns`
The aggregated attributes defined by `columns` are computed by a applying an _aggregate function_ to all the points in each group.
Valid aggregate functions are `sum`, `avg` (average), `min` (minimum), `max` (maximum) and `mode` (the most frequent value in the group).
@@ -134,13 +134,9 @@ of the original dataset applying three different aggregate functions.
> Note that you can use the original column names as names of the result, but all the result column names must be unique. In particular, the names `cartodb_id`, `the_geom`, `the_geom_webmercator` and `_cdb_feature_count` cannot be used for aggregated columns, as they correspond to columns always present in the result.
#### Limitations:
* The iso text format does not admit `starting` or `count` parameters
* Cyclic units (day of the week, etc.) don't admit `count` or `starting` either.
#### `resolution`
### `resolution`
Defines the cell-size of the spatial aggregation grid. This is equivalent to the [CartoCSS `-torque-resolution`](https://carto.com/docs/carto-engine/cartocss/properties-for-torque/#-torque-resolution-float) property of Torque maps.
Defines the cell-size of the spatial aggregation grid. This is equivalent to the [CartoCSS `-torque-resolution`]({{site.styling_cartocss}}/#-torque-resolution-float) property of Torque maps.
The aggregation cells are `resolution`×`resolution` pixels in size, where pixels here are defined to be 1/256 of the (linear) size of a tile.
The default value is 1, so that aggregation coincides with raster pixels. A value of 2 would make each cell to be 4 (2×2) pixels, and a value of
@@ -149,11 +145,11 @@ The default value is 1, so that aggregation coincides with raster pixels. A valu
> Note that is independent of the number of pixels for raster tile or the coordinate resolution (mvt_extent) of vector tiles.
### `threshold`
#### `threshold`
This is the minimum number of (estimated) rows in the dataset (query results) for aggregation to be applied. If the number of rows estimate is less than the threshold aggregation will be disabled for the layer; the instantiation response will reflect that and tiles will be generated without aggregation.
### Example
#### Example
```json
{
@@ -265,4 +261,4 @@ Note that the filtered columns have to be defined with the `columns` parameter,
}
]
}
```
```

View File

@@ -0,0 +1,270 @@
{% comment %}
The original resource for this was:
https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.4.0.md. However this is internal documenation only. This file (07-mapconfig.md) contains select content from the Windshaft internal doc. *I instructed @rochoa to add new Doc issues if/when they make a change to this content - so that the public docs can also be updated.
{% endcomment %}
## MapConfig File Format
CARTO uses Windshaft as the map tiler library to render multilayer maps with the [Maps API]({{ site.mapsapi_docs }}/). The MapConfig file is where these Windshaft layers are stored and applied. You can configure tiles and use the MapConfig document to request different resources for your map.
This section describes the MapConfig specifications, and required formats, when using the Maps API.
### Layergroup Configurations
The following MapConfig Layergroup configurations are applied using the [RFC 4627](http://www.ietf.org/rfc/rfc4627.txt) JSON format.
Layergroup Configuration | Description | Optional or Required?
--- | ---
`version` | Spec version to use for validation.<br /><br />**Note:** The default value is `"1.0.0"`. | Optional
`extent` | The default map extent for the map projection.<br /><br />**Note:** Currently, only webmercator is supported. | Optional
`srid` | The spatial reference identifier for the map. The default is `3857`. | Optional
`maxzoom` | The maximum zoom level for your map. A request beyond the defined maxzoom returns a 404 error.<br /><br />**Note:** The default value is undefined (infinite). | Optional
`minzoom` | The minimum zoom level for your map. A request beyond the defined minzoom returns a 404 error.<br /><br />**Note:** The default value is `0`. | Optional
`layers` | Defines the layer type, and the layers, in rendering order.<br /><br />**Note:** The following layers options are available: |
--- | ---
<i class="Icon Icon--s5 Icon--cGrey Icon--mAlign Icon--indent"></i> type | A string value that defines the layer type. You can define up to four values:<br /><br />`mapnik`, rasterized tiles<br /><br />`cartodb`, an alias for mapnik (for backward compatibility)<br /><br />`torque`, render vector tiles in torque format<br /><br />`http`, load tiles over HTTP<br /><br />`plain`, color or background image url<br /><br />`named`, use a Named Map as a layer | Required
<i class="Icon Icon--s5 Icon--cGrey Icon--mAlign Icon--indent"></i> options | An object value that sets different options for each layer type.<br /><br />**Note:** Options that are not defined in different layers will be discarded. | Required
#### Example of MapConfig
{% highlight json %}
{
"version": "1.7.0",
"extent": [-20037508.5, -20037508.5, 20037508.5, 20037508.5],
"srid": 3857,
"maxzoom": 18,
"minzoom": 3,
"layers": [
{
"type": "mapnik",
"options": {
"sql": "select * from table",
"cartocss": "#table { marker-placement: point; }",
"cartocss_version": "2.3.0"
}
}
]
}
{% endhighlight %}
---
### Mapnik Layer Options
If you are using Mapnik as a layer resource, the following configurations are required in your MapConfig file.
Mapnik Layer Option | Description | Optional or Required?
--- | ---
`sql` | A string value, the SQL request to the user database that will fetch the rendered data.<br /><br />**Tip:** The SQL request should include the following Mapnik layer configurations: `geom_column`, `interactivity`, and `attributes`, as described in this section.<br /><br />**Note:** The SQL request may contain substitutions tokens, such as `!bbox!`, `!pixel_width!`, and `!pixel_height!`. It is suggested to define the layergroup `minzoom` and `extent` variables to prevent errors. | Required
`cartocss` | A string value, specifying the CartoCSS style to render the tiles. If this is not present, only vector tiles can be requested for this layer. For a map to be valid either all the layers or none of them must have CartoCSS style.<br /><br />**Note:** The CartoCSS specification is dependent on the layer type. For details, see [mapnik-reference.json](https://github.com/mapnik/mapnik-reference). | Optional
`cartocss_version` | A string value, specifying the CartoCSS style version of the CartoCSS attribute.<br /><br />**Note:** The CartoCSS version is specific to the layer type. | Optional
`geom_column` | The name of the column containing the geometry. The default is `the_geom_webmercator`.<br /><br />*You must specify this value as part of the Mapnik layer `SQL`configuration. | *Optional
`geom_type` | Defines the type of column as either `geometry` (the default) or `raster`.<br /><br />**Note:** `geom_type` is not compatible with the Mapnik layer `interactivity` option. | Optional
`raster_band` | Defines the raster band (this option is only applicable when the `geom_type=raster`. The default value is `0`.<br /><br />**Note:** If the default, or no value is specified, raster bands are interpreted as either: grayscale (for single bands), RGB (for 3 bands), or RGBA (for 4 bands). | Optional
`srid` | The spatial reference identifier for the geometry column. The default is `3857`. | Optional
`affected_tables` | A string of values containing the tables that the Mapnik layer `SQL` configuration is using. This value is used if there is a problem guessing what the affected tables are from the SQL configuration (i.e. when using PL/SQL functions). | Optional
`interactivity` | A string of values that contains the fields rendered inside grid.json. All the parameters should be exposed as a result of executing the Mapnik layer `SQL` query.<br /><br />**Note:** `interactivity` is not compatible with the Mapnik layer `geom_type` option. For example, you cannot create a layergroup instance with a raster layer by defining the `geom_type=raster`.<br /><br />*You must specify this value as part of the Mapnik layer `SQL` configuration. | *Optional
`attributes`<a name="attributes"></a> | The id and column values returned by the Mapnik attributes service. (This option is disabled if no configuration is defined).<br /><br />*You must specify this value as part of the Mapnik layer `SQL`configuration.| *Optional
--- | ---
<i class="Icon Icon--s5 Icon--cGrey Icon--mAlign Icon--indent"></i> id | The key value used to fetch columns. | Required
<i class="Icon Icon--s5 Icon--cGrey Icon--mAlign Icon--indent"></i> columns | A string of values (columns) returned by the Mapnik attribute service. | Required
#### Example of Mapnik MapConfig
{% highlight json %}
{
"type": "mapnik",
"options": {
"sql": "select * from table",
"cartocss": "#layer { marker-placement: point; }",
"cartocss_version": "2.3.0",
"geom_column": "the_geom_webmercator",
"geom_type": "geometry",
"interactivity": [ "column1", "column2", "..."],
"attributes": {
"id": "cartodb_id",
"columns": ["column1", "column2"]
}
}
}
{% endhighlight %}
### Torque Layer Options
If you are using Torque as a layer resource, the following configurations are required in your MapConfig file. For more details about Torque layers in general, see the [Torque API]({{ site.torque_docs}}/reference/) documentation.
Torque Layer Option | Description | Optional or Required?
--- | ---
`sql` | A string value, the SQL request to the user database that will fetch the rendered data.<br /><br />**Tip:** The SQL request should include the following Torque layer configurations: `geom_column`, `interactivity`, and `attributes`, as described in this section. | Required
`cartocss` | A string value, specifying the CartoCSS style to render the tiles.<br /><br />**Note:** The CartoCSS specification is dependent on the layer type. For details, see [Torque cartocss-reference.js](https://github.com/CartoDB/torque/blob/master/lib/torque/cartocss_reference.js).| Required
`cartocss_version` | A string value, specifying the CartoCSS style version of the CartoCSS attribute.<br /><br />**Note:** The CartoCSS version is specific to the layer type. | Required
`step` | The number of [animation steps]({{site.styling_cartocss}}/-#torque-frame-count-number) to render when requesting a torque.png tile. The default value is `0`. | Optional
`geom_column` | The name of the column containing the geometry. The default is `the_geom_webmercator`.<br /><br />*You must specify this value as part of the Torque layer `SQL`configuration. | *Optional
`srid` | The spatial reference identifier for the geometry column. The default is `3857`. | Optional
`affected_tables` | A string of values containing the tables that the Mapnik layer `SQL` configuration is using. This value is used if there is a problem guessing what the affected tables are from the SQL configuration (i.e. when using PL/SQL functions). | Optional
`attributes` | The id and column values returned by the Torque attributes service. (This option is disabled if no configuration is defined).<br /><br />*You must specify this value as part of the Torque layer `SQL`configuration.| *Optional
--- | ---
<i class="Icon Icon--s5 Icon--cGrey Icon--mAlign Icon--indent"></i> id | The key value used to fetch columns. | Required
<i class="Icon Icon--s5 Icon--cGrey Icon--mAlign Icon--indent"></i> columns | A string of values (columns) returned by the Torque attribute service. | Required
#### Example of Torque MapConfig
{% highlight json %}
{
"type": "torque",
"options": {
"sql": "select * from table",
"cartocss": "#layer { ... }",
"cartocss_version": "1.0.0",
"geom_column": "the_geom_webmercator"
}
}
{% endhighlight %}
### HTTP Layer Options
If you are using an HTTP destination as the resource for a map layer, the following configurations are required in your MapConfig file.
HTTP Layer Option | Description | Optional or Required?
--- | ---
`urlTemplate` | A string value, end URL, from where the tile data is retrieved. _URLs must be included in the configuration whitelist to be valid._ <br /><br />**Note:** The {String} value includes:<br /><br />`{z}` as the zoom level<br /><br />`{x} and {y}` as the tile coordinates<br /><br />Optionally, the subdomain `{s}` may be included as part of the `urlTemplate` configuration. Otherwise, you can define the `subdomains` separately, as shown below. | Required
`subdomains` | A string of values used to retrieve tiles from different subdomains. The default value is [`a`, `b`, `c`] when `{s}` is defined in the `urlTemplate` configuration. Otherwise, the default value is `[ ]`.<br /><br />**Note:** The subdomains value will consistently replace the `{s}` value defined in the `urlTemplate`.| Optional
`tms` | A boolean value that specifies whether the tile is using Tile Map Service format. The default value is `false`.<br /><br />**Note:** If the value is `true`, the TMS inverses the Y axis numbering for tiles. | Optional
#### Example of HTTP MapConfig
{% highlight json %}
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.example.com/{z}/{x}/{y}.png",
"subdomains": ["a", "b", "c"],
"tms": false
}
}
{% endhighlight %}
### Plain Layer Options
If you are using plain layer options as your map resource, the following configurations are required in your MapConfig file.
_**Note:** At least one of the plain layer options (either `color` or `imageUrl`) must be defined. If both options are defined, only `color` is used as part of the plain layer configuration._
Plain Layer Option | Description | Optional or Required?
--- | ---
`color` | A string value of numbers that defines the valid colors to include. The default value is `null`. Valid colors include:<br /><br />- A string value that includes CSS colors (i.e. `blue`) or a hex color string (i.e. `#0000ff`)<br /><br />- An integer array of r,g,b values (i.e. `[255,0,0]`)<br /><br />- An integer array of r,g,b,a values (i.e. `[255,0,0,128]`)<br /><br />* If **only** the `color` value is used for a plain layer, this value is Required.<br /><br />* If **both** `color` and `imageUrl` are defined, only the `color` value is used for the plain layer configuration.| *Both
`imageUrl` | A string value, end URL, from where the image is retrieved. The default value is `null`.<br /><br />* If **only** the `imageUrl` value is used for a plain layer, this value is Required.<br /><br />* If `color` is defined, this `imageUrl` value is ignored. | *Both
#### Example of Plain MapConfig
{% highlight json %}
{
"type": "plain",
"options": {
"color": "blue",
"imageUrl": "http://example.com/background.png"
}
}
{% endhighlight %}
### Named Map Layer Options
You can use a [Named Map]({{site.mapsapi_docs}}/guides/named-maps/) as a map layer. Note the following limitations before referencing the MapConfig options for a Named Map layer.
_**Limitations:**_
- A Named Map will not allow you to have `named` type layers inside of your template layergroup's layers definition
- A `named` layer does not allow Named Maps from other accounts. You can only use Named Maps from the _same_ user account
If you are using `named` layer options as your map resource, the following configurations are required in your MapConfig file.
Named Layer Option | Description | Optional or Required?
--- | ---
`name` | A string value, the name for the Named Map to use. | Required
`config` | An object, the replacement values for the Named Map's template placeholders. | Optional
`auth_tokens` | Strings array, the authorized tokens in case the Named Map has auth method set to `token`. | Optional
#### Example of Named MapConfig
{% highlight json %}
{
"type": "named",
"options": {
"name": "world_borders",
"config": {
"color": "#000"
},
"auth_tokens": ["token1", "token2"]
}
}
{% endhighlight %}
### Aggregation Options
The data used to render tiles, or contained in the tiles (for the case of vector tiles), can be spatially [aggregated]({{site.mapsapi_docs}}/guides/named-maps/) under some circumstances.
An `aggregation` attribute can be used in the layer `options` to control the aggregation. A value of `false` will disable aggregation for the layer. Otherwise, an object can be passed with the following aggregation parameters:
Parameter|Description|Default value
`placement`|Determines the kind of aggregated geometry generated ("point-sample", "point-grind" or "centroid").|"centroid"
`columns`|Defines aggregated columns; each one by an "aggregate_function" ("sum", "avg", "min, "max", "mode", "count") and "aggregated_column" name.|
`resolution`|Defines the cell-size of the spatial aggregation grid.|1 (for 256x256 cells per tile)
`threshold`|Minimum rows in the dataset to apply aggregation.
#### Example of Aggregation MapConfig
{% highlight json %}
{
"version": "1.7.0",
"extent": [-20037508.5, -20037508.5, 20037508.5, 20037508.5],
"srid": 3857,
"maxzoom": 18,
"minzoom": 3,
"layers": [
{
"type": "mapnik",
"options": {
"sql": "select * from table",
"cartocss": "#table { marker-width: [total]; marker-fill: ramp(value, (red, green, blue), jenks); }",
"cartocss_version": "2.3.0",
"aggregation": {
"placement": "centroid",s
"columns": {
"value": {
"aggregate_function": "avg",
"aggregated_column": "value"
},
"total": {
"aggregate_function": "sum",
"aggregated_column": "value"
}
},
"resolution": 2, // Aggregation cell is 2x2 pixels
"threshold": 500000
}
}
}
]
}
{% endhighlight %}
### MapConfig Requirements
All of these are MapConfig requirements for [Anonymous Maps]({{site.mapsapi_docs}}/guides/anonymous-maps/#retrieve-resources-from-the-layergroup).
- Identified by `{z}/{x}/{y}` path
- If applicable, additionally identified by `LAYER_NUMBER`
- Can be of different formats:
- png
- grid.json
- torque.json
- Static images/previews
- With a center or a bounding box
- Attributes
-Identified by LAYER_NUMBER and FEATURE_ID
**Tip:** The MapConfig file may be extended for specific uses. For example, [Windshaft-CartoDB](https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/MultiLayer-API.md) defines the addition of a `stat_tag` element in the config. This extension is also covered as part of the [Named Map Layer Options](#named-map-layer-options).

View File

@@ -1,14 +1,16 @@
# 1. Purpose
## MapConfig Aggregation Extension
### 1. Purpose
This specification describes an extension for
[MapConfig 1.7.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.7.0.md) version.
# 2. Changes over specification
### 2. Changes over specification
This extension introduces a new layer options for aggregated data tile generation.
## 2.1 Aggregation options
#### 2.1 Aggregation options
The layer options attribute is extended with a new optional `aggregation` attribute.
The value of this attribute can be `false` to explicitly disable aggregation for the layer.
@@ -55,8 +57,8 @@ The value of this attribute can be `false` to explicitly disable aggregation for
}
```
# History
### History
## 1.0.0
#### 1.0.0
- Initial version

View File

@@ -1,16 +1,18 @@
# 1. Purpose
## MapConfig Analyses Extension
### 1. Purpose
This specification describes an extension for
[MapConfig 1.4.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.4.0.md) version.
# 2. Changes over specification
### 2. Changes over specification
This extension targets layers with `sql` option, including layer types: `cartodb`, `mapnik`, and `torque`.
It extends MapConfig with a new attribute: `analyses`.
## 2.1 Analyses attribute
#### 2.1 Analyses attribute
The new analyses attribute must be an array of analyses as per [camshaft](https://github.com/CartoDB/camshaft). Each
analysis must adhere to the [camshaft-reference](https://github.com/CartoDB/camshaft/blob/0.8.0/reference/versions/0.7.0/reference.json) specification.
@@ -37,7 +39,7 @@ Basic analyses example:
]
```
# 2.2. Integration with layers
### 2.2. Integration with layers
As pointed before an analysis node id can be referenced from layers to consume its output query.
@@ -57,7 +59,7 @@ The layer consuming the output must reference it with the following option:
}
```
## 2.3. Complete example
#### 2.3. Complete example
```
{
@@ -86,8 +88,8 @@ The layer consuming the output must reference it with the following option:
}
```
# History
### History
## 1.0.0
#### 1.0.0
- Initial version

View File

@@ -1,18 +1,20 @@
# 1. Purpose
## MapConfig Dataviews Extension
### 1. Purpose
This specification describes an extension for
[MapConfig 1.4.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.4.0.md) version.
# 2. Changes over specification
### 2. Changes over specification
This extension depends on Analyses extension. It extends MapConfig with a new attribute: `dataviews`.
It makes possible to get tabular data from analysis nodes: aggregated lists, aggregations, and histograms.
## 2.1. Dataview types
#### 2.1. Dataview types
### Aggregation
##### Aggregation
An aggregation is a list with aggregated results by a column and a given aggregation function.
@@ -56,7 +58,7 @@ Expected output
}
```
### Histograms
##### Histograms
Histograms represent the data distribution for a column.
@@ -88,7 +90,7 @@ Expected output
}
```
### Formula
##### Formula
Formulas given a final value representing the whole dataset.
@@ -124,7 +126,7 @@ Result
```
## 2.2 Dataviews attribute
#### 2.2 Dataviews attribute
The new dataviews attribute must be a dictionary of dataviews.
@@ -145,7 +147,7 @@ The layer consuming the output must reference it with the following option:
}
```
## 2.3. Complete example
#### 2.3. Complete example
```
{
@@ -185,16 +187,16 @@ The layer consuming the output must reference it with the following option:
}
```
## 3. Filters
#### 3. Filters
Camshaft's analyses expose a filtering capability and `aggregation` and `histogram` dataviews get them for free with
this extension. Filters are available with the very dataview id, so if you have a "basic_histogram" histogram dataview
you can filter with a range filter with "basic_histogram" name.
## 3.1 Filter types
#### 3.1 Filter types
### Category
##### Category
Allows to remove results that are not contained within a set of elements.
Initially this filter can be applied to a `numeric` or `text` columns.
@@ -208,7 +210,7 @@ Params
}
```
### Range filter
##### Range filter
Allows to remove results that dont satisfy numeric min and max values.
Filter is applied to a numeric column.
@@ -222,13 +224,13 @@ Params
}
```
## 3.2. How to apply filters
#### 3.2. How to apply filters
Filters must be applied at map instantiation time.
With :mapconfig as a valid MapConfig and with :filters (a valid JSON) as:
### Anonymous map
##### Anonymous map
`GET /api/v1/map?config=:mapconfig&filters=:filters`
@@ -241,7 +243,7 @@ If in the future we need to support a bigger filters param and it doesnt fit
`POST /api/v1/map`
with `BODY={“config”: :mapconfig, “filters”: :filters}`
### Named map
##### Named map
Assume :params (a valid JSON) as named maps params, like in: `{“color”: “red”}`
@@ -257,7 +259,7 @@ If, again, in the future we need to support a bigger filters param that doesn
with `BODY={“config”: :params, “filters”: :filters}`
## 3.3 Bounding box special filter
#### 3.3 Bounding box special filter
A bounding box filter allows to remove results that dont satisfy a geospatial range.
@@ -270,8 +272,8 @@ param must be in the form `west,south,east,north`.
So applying a bbox filter to a dataview looks like:
GET /api/v1/map/:layergroupid/dataview/:dataview_name?bbox=-90,-45,90,45
# History
### History
## 1.0.0-alpha
#### 1.0.0-alpha
- WIP document

View File

@@ -1,14 +1,16 @@
# 1. Purpose
## MapConfig Named Maps Extension
### 1. Purpose
This specification describes an extension for
[MapConfig 1.3.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.3.0.md) version.
# 2. Changes over specification
### 2. Changes over specification
This extension introduces a new layer type so it's possible to use a Named Map by its name as a layer.
## 2.1 Named layers definition
#### 2.1 Named layers definition
```javascript
{
@@ -42,15 +44,15 @@ This extension introduces a new layer type so it's possible to use a Named Map b
}
```
## 2.2 Limitations
#### 2.2 Limitations
1. A Named Map will not allow to have `named` type layers inside their templates layergroup's layers definition.
2. A `named` layer does not allow Named Maps form other accounts, it's only possible to use Named Maps from the very
same user account.
# History
### History
## 1.0.0
#### 1.0.0
- Initial version

View File

@@ -1,4 +1,4 @@
The Windshaft-CartoDB MultiLayer API extends the [Windshaft MultiLayer API](https://github.com/CartoDB/Windshaft/blob/master/doc/Multilayer-API.md) in a few ways.
The Windshaft-CartoDB MultiLayer API extends the [Windshaft MultiLayer API](https://github.com/CartoDB/Windshaft/blob/master/doc/internal/multilayer-API.md) in a few ways.
## Last modification timestamp embedded in the token
@@ -25,4 +25,4 @@ Windshaft-CartoDB adds the following attributes in the response object
## Stats tag
Windshaft-CartoDB adds support for a ``stat_tag`` element in the multilayer configuration to help [stats](https://github.com/CartoDB/Windshaft-cartodb/wiki/Redis-stats-format) gathering.
Windshaft-CartoDB adds support for a ``stat_tag`` element in the multilayer configuration to help [stats](https://github.com/CartoDB/Windshaft-cartodb/wiki/Redis-stats-format) gathering.

1472
docs/reference/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
## Support Options
Feeling stuck? There are many ways to find help.
* Ask a question on [GIS StackExchange](https://gis.stackexchange.com/questions/tagged/carto) using the `CARTO` tag.
* [Report an issue](https://github.com/CartoDB/cartodb/issues) in Github.
* Engine Plan customers have additional access to enterprise-level support through CARTO's support representatives.
If you just want to describe an issue or share an idea, just <a class="typeform-share" href="https://cartohq.typeform.com/to/mH6RRl" data-mode="popup" target="_blank"> send your feedback</a>
### Issues on Github
If you think you may have found a bug, or if you have a feature request that you would like to share with the Maps API team, please [open an issue](https://github.com/CartoDB/Windshaft-cartodb/issues/new).
### Community support on GIS Stack Exchange
GIS Stack Exchange is the most popular community in the geospatial industry. This is a collaboratively-edited question and answer site for geospatial programmers and technicians. It is a fantastic resource for asking technical questions about developing and maintaining your application.
When posting a new question, please consider the following:
* Read the GIS Stack Exchange [help](https://gis.stackexchange.com/help) and [how to ask](https://gis.stackexchange.com/help/how-to-ask) pages for guidelines and tips about posting questions.
* Be very clear about your question in the subject. A clear explanation helps those trying to answer your question, as well as those who may be looking for information in the future.
* Be informative in your post. Details, code snippets, logs, screenshots, etc. help others to understand your problem.
* Use code that demonstrates the problem. It is very hard to debug errors without sample code to reproduce the problem.
### Engine Plan Customers
Engine Plan customers have additional support options beyond general community support. As per your account Terms of Service, you have access to enterprise-level support through CARTO's support representatives available at [enterprise-support@carto.com](mailto:enterprise-support@carto.com)
In order to speed up the resolution of your issue, provide as much information as possible (even if it is a link from community support). This allows our engineers to investigate your problem as soon as possible.
If you are not yet CARTO customer, browse our [plans & pricing](https://carto.com/pricing/) and find the right plan for you.

View File

@@ -0,0 +1,36 @@
## Contribute
CARTO platform is an open-source ecosystem. You can read about the [fundamentals]({{site.fundamental_docs}}/components/) of CARTO architecture and its components.
We are more than happy to receive your contributions to the code and the documentation as well.
## Filling a ticket
If you want to open a new issue in our repository, please follow these instructions:
1. Descriptive title.
2. Write a good description, it always helps.
3. Specify the steps to reproduce the problem.
4. Try to add an example showing the problem.
## Contributing code
Best part of open source, collaborate in Maps API code!. We like hearing from you, so if you have any bug fixed, or a new feature ready to be merged, those are the steps you should follow:
1. Fork the repository.
2. Create a new branch in your forked repository.
3. Commit your changes. Add new tests if it is necessary.
4. Open a pull request.
5. Any of the maintainers will take a look.
6. If everything works, it will merged and released \o/.
If you want more detailed information, this [GitHub guide](https://guides.github.com/activities/contributing-to-open-source/) is a must.
## Completing documentation
Maps API documentation is located in ```docs/```. That folder is the content that appears in the [Developer Center](https://carto.com/developers/maps-api/). Just follow the instructions described in [contributing code](#contributing-code) and after accepting your pull request, we will make it appear online :).
**Tip:** A convenient, easy way of proposing changes in documentation is by using the GitHub editor directly on the web. You can easily create a branch with your changes and make a PR from there.
## Submitting contributions
You will need to sign a Contributor License Agreement (CLA) before making a submission. [Learn more here](https://carto.com/contributions).

View File

@@ -0,0 +1,121 @@
## Rate limiting
Rate limits ensure that CARTO platform is not flooded with so many requests it does not have the time and resources to service them all.
Of course, there is nothing we can do to prevent people from actually sending as many requests to our platform as they want, but requests over a user's rate limit will be acknowledged with an error so that the sender understands they need to lower the rate at which requests are sent before they are serviced again.
Currently, Maps API is affected by rate limiting.
### Per user and endpoint
Rate limit is on a per-user basis (or more accurately described, per user access) and by endpoint. For example, suppose you have 2 different apps (with 2 different maps) and both call to the same endpoint that allows 100 requests per second. Both apps/maps "share" 100 requests per second regardless the map calling to this endpoint.
### How it works
We are using the [generic cell rate algorithm](https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm), a [leaky bucket](https://en.wikipedia.org/wiki/Leaky_bucket) algorithm type.
The main keys to keep in mind about this algorithm and our implementation are:
- We allow a request every a certain time period
```
If an endpoint has a limit of 5 requests per second, you will have a request available every 200ms and when you spend all the available requests, you will need to wait 200ms to have another available request, instead of 1 second
```
- Most of the endpoints are limited per second
```
If an endpoint has a limit of 5 requests per second, after a second without requests, you will have at least 5 available requests
```
- Most of the endpoints allow an initial burst equal to the number of requests per second
```
If an endpoint has a limit of 5 requests per second, initially you will have 5 available requests
```
### Caches
In computing, a cache is a high-speed data storage layer which stores data, typically a set of data, so that future requests for that data are served up faster than by accessing the original location.
CARTO caching allows you to efficiently reuse previously retrieved or computed data, as the data in a cache is stored by CARTO in fast access hardware in combination with specific software to manage this.
Resources accessed by caches don't count against the limits. That is, any request that is handled by any cache layer is out of limits. You can always know which resources are served through cache looking at the `X-Cache` HTTP Header.
### HTTP Headers and Response Codes
When an application exceeds the rate limit for a given API endpoint, the API will return an HTTP `429 Too Many Requests` error.
Use the HTTP headers in order to understand where the application is at for a given rate limit, on the method that was just utilized. Note that the HTTP headers are contextual. That is, they indicate the rate limit for the user context. If you have multiple apps (maps) accessing to their resources with the same user, HTTP headers are related to that user.
- **Carto-Rate-Limit-Limit**: total allowed requests
- **Carto-Rate-Limit-Remaining**: remaining requests
- **Retry-After**: seconds until next available request (returns `-1` if the current request is allowed)
- **Carto-Rate-Limit-Reset**: seconds until the limit will reset to its maximum capacity
### Tips
We only have 1 tip:
- If you receive a rate limit error, you must wait the seconds indicated by the `Retry-After` HTTP header (most of the time will be 1 second)
### Rate Limits Chart
Below, you can find the values of the rate limit by user account type and endpoint. Note that endpoints not listed in the chart are disabled by default.
#### Enterprise plans
|Endpoint |Request |Time period |Burst |
| :--- | ---: | ---: | ---: |
| GET /api/v1/map <br> POST /api/v1/map |10 |1 |10 |
| GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format} <br> GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format} |3 |1 |3 |
| GET /api/v1/map/static/named/{template_id}/{width}/{height}.{format} |3 |1 |3 |
| GET /api/v1/map/{token}/{layer}/widget/{dataviewName} <br> GET /api/v1/map/{token}/dataview/{dataviewName} |25 |1 |25 |
| GET /{token}/{layer}/widget/{dataviewName}/search <br> GET /{token}/dataview/{dataviewName}/search |3 |1 |3 |
| GET /api/v1/map/{token}/analysis/node/{nodeId} |3 |1 |3 |
| GET /api/v1/map/{token}/{z}/{x}/{y}@{scale_factor}?x.{format} <br> GET /api/v1/map/{token}/{z}/{x}/{y}.{format} <br> GET /api/v1/map/{token}/{layer}/{z}/{x}/{y}.{format} |120<br> 1500 |1<br> 60 |120<br> 750 |
| GET /api/v1/map/{token}/{layer}/attributes/{fid} |10 |1 |10 |
| GET /api/v1/map/named |3 |1 |3 |
| POST /api/v1/map/named |3 |1 |3 |
| GET /api/v1/map/named/{template_id} |10 |1 |10 |
| POST /api/v1/map/named/{template_id} <br> GET /api/v1/map/named/{template_id}/jsonp |10 |1 |10 |
| PUT /api/v1/map/named/{template_id} |10 |1 |10 |
| DELETE /api/v1/map/named/{template_id} |3 |1 |3 |
| GET /api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.{format} |25 |1 |25 |
#### Professional plans
|Endpoint |Request |Time period |Burst |
| :--- | ---: | ---: | ---: |
| GET /api/v1/map <br> POST /api/v1/map |5 |1 |5 |
| GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format} <br> GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format} |1 |1 |1 |
| GET /api/v1/map/static/named/{template_id}/{width}/{height}.{format} |1 |1 |1 |
| GET /api/v1/map/{token}/{layer}/widget/{dataviewName} <br> GET /api/v1/map/{token}/dataview/{dataviewName} |15 |1 |15 |
| GET /{token}/{layer}/widget/{dataviewName}/search <br> GET /{token}/dataview/{dataviewName}/search |1 |1 |1 |
| GET /api/v1/map/{token}/analysis/node/{nodeId} |1 |1 |1 |
| GET /api/v1/map/{token}/{z}/{x}/{y}@{scale_factor}?x.{format} <br> GET /api/v1/map/{token}/{z}/{x}/{y}.{format} <br> GET /api/v1/map/{token}/{layer}/{z}/{x}/{y}.{format} |40<br> 600 |1<br> 60 |40<br> 300 |
| GET /api/v1/map/{token}/{layer}/attributes/{fid} |5 |1 |5 |
| GET /api/v1/map/named |1 |1 |1 |
| POST /api/v1/map/named |1 |1 |1 |
| GET /api/v1/map/named/{template_id} |5 |1 |5 |
| POST /api/v1/map/named/{template_id} <br> GET /api/v1/map/named/{template_id}/jsonp |5 |1 |5 |
| PUT /api/v1/map/named/{template_id} |5 |1 |5 |
| DELETE /api/v1/map/named/{template_id} |1 |1 |1 |
| GET /api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.{format} |10 |1 |10 |
#### Free plans
|Endpoint |Request |Time period |Burst |
| :--- | ---: | ---: | ---: |
| GET /api/v1/map <br> POST /api/v1/map |2 |1 |2 |
| GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format} <br> GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format} |1 |1 |1 |
| GET /api/v1/map/static/named/{template_id}/{width}/{height}.{format} |1 |1 |1 |
| GET /api/v1/map/{token}/{layer}/widget/{dataviewName} <br> GET /api/v1/map/{token}/dataview/{dataviewName} |10 |1 |10 |
| GET /{token}/{layer}/widget/{dataviewName}/search <br> GET /{token}/dataview/{dataviewName}/search |1 |1 |1 |
| GET /api/v1/map/{token}/analysis/node/{nodeId} |1 |1 |1 |
| GET /api/v1/map/{token}/{z}/{x}/{y}@{scale_factor}?x.{format} <br> GET /api/v1/map/{token}/{z}/{x}/{y}.{format} <br> GET /api/v1/map/{token}/{layer}/{z}/{x}/{y}.{format} |20<br> 600 |1<br> 60 |20<br> 300 |
| GET /api/v1/map/{token}/{layer}/attributes/{fid} |2 |1 |2 |
| GET /api/v1/map/named |1 |1 |1 |
| POST /api/v1/map/named |1 |1 |1 |
| GET /api/v1/map/named/{template_id} |2 |1 |2 |
| POST /api/v1/map/named/{template_id} <br> GET /api/v1/map/named/{template_id}/jsonp |2 |1 |2 |
| PUT /api/v1/map/named/{template_id} |2 |1 |2 |
| DELETE /api/v1/map/named/{template_id} |1 |1 |1 |
| GET /api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.{format} |10 |1 |10 |

View File

@@ -0,0 +1,32 @@
## Timeout limit
Our APIs work following a request <-> response model. While CARTO is busy getting that action done or retrieving that information, part of our infrastructure is devoted to that process and is therefore unavailable for any other user. Typically this is not a problem, as most requests get serviced quickly enough. However, certain requests can take a long time to process, either by design (e.g., updating a huge table) or by mistake. To prevent this long-running queries from effectively blocking the usage of our platform resources, CARTO will discard requests that cannot be fulfilled in less than a certain amount of time.
Maps API is affected by this kind of limiting.
### Per User
Timeout limit is on a per-user basis (or more accurately described, per user access).
### How it works
Every query has a statement timeout. When a request reaches that value, the response returns an error.
### Response Codes
When query exceeds the timeout limit, the API will return an HTTP `429 Too Many Requests` error.
### Tips
You are able to avoid common issues that trigger timeout limits following these actions:
- Always use database indexes
- Try to use batch API to insert/update/delete data
### Timeout Limits Chart
Below, you can find the values of the timeout limit by user account type.
|Enterprise plans |Professional plans |Free plans |
| --- | --- | --- |
| 25 seconds | 15 seconds | 5 seconds |

View File

@@ -0,0 +1,16 @@
## Quota limiting
CARTO platform imposes limits on how much data you can store at CARTO, for every user account and organization. You can learn more about this topic by reading the [fundamentals about limits]({{site.fundamental_docs}}/limits/of the CARTO platform.
Maps API is affected by this kind of limiting.
### Quota Limits Chart
Below, you can find the values of the different quota limits by user account type.
|Limit |Enterprise plans |Professional plans |Free plans |
| :--- | ---: | ---: | ---: |
| Maximum Static Map image size |4000 X 4000 pixels |4000 X 4000 pixels |4000 X 4000 pixels |
| Maximum number of Named Maps |4096 |4096 |4096 |
| Maximum number of layers |10 |8 |8 |
| Maximum number of layers |16 |8 |8 |

View File

@@ -1,9 +1,10 @@
Windshaft-cartodb metrics
=========================
See [Windshaft metrics documentation](https://github.com/CartoDB/Windshaft/blob/master/doc/metrics.md) to understand the full picture.
## Metrics
See [metrics guide](https://github.com/CartoDB/Windshaft/blob/master/doc/metrics.md) to understand the full picture.
The next list includes the API endpoints, each endpoint may have several inner timers, some of them are displayed within this list as subitems. Find the description for them in the Inner timers section.
## Timers
### Timers
- **windshaft-cartodb.flush_cache**: time to flush the tile and sql cache
- **windshaft-cartodb.get_template**: time to retrieve an specific template
- **windshaft-cartodb.delete_template**: time to delete an specific template
@@ -39,4 +40,3 @@ Again, each inner timer may have several inner timers.
- **setDBConn**: time to retrieve from redis and set db host and db name from a user
- **setDBParams**: time to prepare all db params to be able to connect/query a database, see *setDBAuth* and *setDBConn*
- **tablePrivacy_getUserDBName**: time to retrieve from redis the database for a user

View File

@@ -21,6 +21,8 @@ const OverviewsMetadataBackend = require('../backends/overviews-metadata');
const FilterStatsApi = require('../backends/filter-stats');
const TablesExtentBackend = require('../backends/tables-extent');
const ClusterBackend = require('../backends/cluster');
const LayergroupAffectedTablesCache = require('../cache/layergroup_affected_tables');
const SurrogateKeysCache = require('../cache/surrogate_keys_cache');
const VarnishHttpCacheBackend = require('../cache/backend/varnish_http');
@@ -110,6 +112,7 @@ module.exports = class ApiRouter {
const analysisBackend = new AnalysisBackend(metadataBackend, serverOptions.analysis);
const dataviewBackend = new DataviewBackend(analysisBackend);
const statsBackend = new StatsBackend();
const clusterBackend = new ClusterBackend();
const userLimitsBackend = new UserLimitsBackend(metadataBackend, {
limits: {
@@ -179,7 +182,8 @@ module.exports = class ApiRouter {
statsBackend,
layergroupMetadata,
namedMapProviderCache,
tablesExtentBackend
tablesExtentBackend,
clusterBackend
};
this.mapRouter = new MapRouter({ collaborators });

View File

@@ -0,0 +1,95 @@
'use strict';
const layergroupToken = require('../middlewares/layergroup-token');
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
const credentials = require('../middlewares/credentials');
const dbConnSetup = require('../middlewares/db-conn-setup');
const authorize = require('../middlewares/authorize');
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const createMapStoreMapConfigProvider = require('../middlewares/map-store-map-config-provider');
const cacheControlHeader = require('../middlewares/cache-control-header');
const cacheChannelHeader = require('../middlewares/cache-channel-header');
const surrogateKeyHeader = require('../middlewares/surrogate-key-header');
const lastModifiedHeader = require('../middlewares/last-modified-header');
module.exports = class AggregatedFeaturesLayergroupController {
constructor (
clusterBackend,
pgConnection,
mapStore,
userLimitsBackend,
layergroupAffectedTablesCache,
authBackend,
surrogateKeysCache
) {
this.clusterBackend = clusterBackend;
this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.userLimitsBackend = userLimitsBackend;
this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
this.authBackend = authBackend;
this.surrogateKeysCache = surrogateKeysCache;
}
register (mapRouter) {
mapRouter.get('/:token/:layer/:z/cluster/:clusterId', this.middlewares());
}
middlewares () {
return [
layergroupToken(),
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
// TODO: create its rate limit
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.ATTRIBUTES),
cleanUpQueryParams([ 'aggregation' ]),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache
),
getClusteredFeatures(this.clusterBackend),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader()
];
}
};
function getClusteredFeatures (clusterBackend) {
return function getFeatureAttributesMiddleware (req, res, next) {
req.profiler.start('windshaft.maplayer_cluster_features');
const { mapConfigProvider } = res.locals;
const { user, token } = res.locals;
const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals;
const { layer, z: zoom, clusterId } = req.params;
const { aggregation } = req.query;
const params = {
user, token,
dbuser, dbname, dbpassword, dbhost, dbport,
layer, zoom, clusterId,
aggregation
};
clusterBackend.getClusterFeatures(mapConfigProvider, params, (err, features, stats = {}) => {
req.profiler.add(stats);
if (err) {
err.label = 'GET CLUSTERED FEATURES';
return next(err);
}
res.statusCode = 200;
const { rows, fields } = features;
res.body = { rows, fields };
next();
});
};
}

View File

@@ -10,6 +10,7 @@ const TileLayergroupController = require('./tile-layergroup-controller');
const AnonymousMapController = require('./anonymous-map-controller');
const PreviewTemplateController = require('./preview-template-controller');
const AnalysesCatalogController = require('./analyses-catalog-controller');
const ClusteredFeaturesLayergroupController = require('./clustered-features-layergroup-controller');
module.exports = class MapRouter {
constructor ({ collaborators }) {
@@ -32,7 +33,8 @@ module.exports = class MapRouter {
statsBackend,
layergroupMetadata,
namedMapProviderCache,
tablesExtentBackend
tablesExtentBackend,
clusterBackend
} = collaborators;
this.analysisLayergroupController = new AnalysisLayergroupController(
@@ -112,6 +114,16 @@ module.exports = class MapRouter {
authBackend,
userLimitsBackend
);
this.clusteredFeaturesLayergroupController = new ClusteredFeaturesLayergroupController(
clusterBackend,
pgConnection,
mapStore,
userLimitsBackend,
layergroupAffectedTablesCache,
authBackend,
surrogateKeysCache
);
}
register (apiRouter, mapPaths) {
@@ -125,6 +137,7 @@ module.exports = class MapRouter {
this.anonymousMapController.register(mapRouter);
this.previewTemplateController.register(mapRouter);
this.analysesController.register(mapRouter);
this.clusteredFeaturesLayergroupController.register(mapRouter);
mapPaths.forEach(path => apiRouter.use(path, mapRouter));
}

View File

@@ -0,0 +1,241 @@
'use strict';
const PSQL = require('cartodb-psql');
const dbParamsFromReqParams = require('../utils/database-params');
const debug = require('debug')('backend:cluster');
const AggregationMapConfig = require('../models/aggregation/aggregation-mapconfig');
module.exports = class ClusterBackend {
getClusterFeatures (mapConfigProvider, params, callback) {
mapConfigProvider.getMapConfig((err, _mapConfig) => {
if (err) {
return callback(err);
}
let pg;
try {
pg = new PSQL(dbParamsFromReqParams(params));
} catch (error) {
debug(error);
return callback(error);
}
const { user, layer: layerIndex } = params;
const mapConfig = new AggregationMapConfig(user, _mapConfig.obj(), pg);
const layer = mapConfig.getLayer(layerIndex);
let { aggregation } = params;
try {
validateAggregationLayer(mapConfig, layerIndex);
aggregation = parseAggregation(aggregation);
validateAggregation(aggregation);
} catch (error) {
error.http_status = 400;
error.type = 'layer';
error.subtype = 'aggregation';
error.layer = {
index: layerIndex,
type: layer.type
};
debug(error);
return callback(error);
}
params.aggregation = aggregation;
getFeatures(pg, layer, params, callback);
});
}
};
function getFeatures (pg, layer, params, callback) {
const query = layer.options.sql_raw;
const resolution = layer.options.aggregation.resolution || 1;
getColumnsName(pg, query, (err, columns) => {
if (err) {
return callback(err);
}
const { zoom, clusterId, aggregation } = params;
getClusterFeatures(pg, zoom, clusterId, columns, query, resolution, aggregation, callback);
});
}
const SKIP_COLUMNS = {
'the_geom': true,
'the_geom_webmercator': true
};
function getColumnsName (pg, query, callback) {
const sql = schemaQuery({
query: query
});
debug('> getColumnsName:', sql);
pg.query(sql, function (err, resultSet) {
if (err) {
return callback(err);
}
const fields = resultSet.fields || [];
const columnNames = fields.map(field => field.name)
.filter(columnName => !SKIP_COLUMNS[columnName]);
return callback(null, columnNames);
}, true);
}
function getClusterFeatures (pg, zoom, clusterId, columns, query, resolution, aggregation, callback) {
let sql = clusterFeaturesQuery({
zoom: zoom,
id: clusterId,
query: query,
res: 256/resolution,
columns: columns
});
if (aggregation !== undefined) {
let { columns = [], expressions = [] } = aggregation;
if (expressions) {
expressions = Object.entries(expressions)
.map(([columnName, exp]) => `${exp.aggregate_function}(${exp.aggregated_column}) AS ${columnName}`);
}
sql = aggregationQuery({
columns,
expressions,
query: sql
});
}
debug('> getClusterFeatures:', sql);
pg.query(sql, (err, data) => {
if (err) {
return callback(err);
}
return callback(null, data);
} , true); // use read-only transaction
}
const schemaQuery = ctx => `SELECT * FROM (${ctx.query}) __cdb_schema LIMIT 0`;
const clusterFeaturesQuery = ctx => `
WITH
_cdb_params AS (
SELECT
${gridResolution(ctx)} AS res
),
_cell AS (
SELECT
ST_MakeEnvelope(
Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res)*_cdb_params.res,
Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)*_cdb_params.res,
Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res + 1)*_cdb_params.res,
Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res + 1)*_cdb_params.res,
3857
) AS bbox
FROM (${ctx.query}) _cdb_query, _cdb_params
WHERE _cdb_query.cartodb_id = ${ctx.id}
)
SELECT
${ctx.columns.join(', ')}
FROM (
SELECT _cdb_query.*
FROM _cell, (${ctx.query}) _cdb_query
WHERE ST_Intersects(_cdb_query.the_geom_webmercator, _cell.bbox)
) __cdb_non_geoms_query
`;
const gridResolution = ctx => {
const minimumResolution = 2*Math.PI*6378137/Math.pow(2,38);
const pixelSize = `CDB_XYZ_Resolution(${ctx.zoom})`;
return `GREATEST(${256/ctx.res}*${pixelSize}, ${minimumResolution})::double precision`;
};
const aggregationQuery = ctx => `
SELECT
count(1) as _cdb_feature_count
${ctx.columns.length ? `,${ctx.columns.join(', ')}` : ''}
${ctx.expressions.length ? `,${ctx.expressions.join(', ')}` : ''}
FROM (${ctx.query}) __cdb_aggregation
${ctx.columns.length ? `GROUP BY ${ctx.columns.join(', ')}` : ''}
`;
function validateAggregationLayer (mapConfig, layerIndex) {
if (!hasAggregationLayer(mapConfig, layerIndex)) {
throw new Error(`Map ${mapConfig.id()} has no aggregation defined for layer ${layerIndex}`);
}
}
// TODO: update when https://github.com/CartoDB/Windshaft-cartodb/pull/1082 is merged
function hasAggregationLayer (mapConfig, layerIndex) {
const layer = mapConfig.getLayer(layerIndex);
if (typeof layer.options.aggregation === 'boolean') {
return layer.options.aggregation;
}
return mapConfig.isAggregationLayer(layerIndex);
}
function parseAggregation (aggregation) {
if (aggregation !== undefined) {
try {
aggregation = JSON.parse(aggregation);
} catch (err) {
throw new Error(`Invalid aggregation input, should be a a valid JSON`);
}
}
return aggregation;
}
function validateAggregation (aggregation) {
if (aggregation !== undefined) {
const { columns, expressions } = aggregation;
if (!hasColumns(columns)) {
throw new Error(`Invalid aggregation input, columns should be and array of column names`);
}
validateExpressions(expressions);
}
}
function hasColumns (columns) {
return Array.isArray(columns) && columns.length;
}
function validateExpressions (expressions) {
if (expressions !== undefined) {
if (!isValidExpression(expressions)) {
throw new Error(`Invalid aggregation input, expressions should be and object with valid functions`);
}
for (const { aggregate_function, aggregated_column } of Object.values(expressions)) {
if (typeof aggregated_column !== 'string') {
throw new Error(`Invalid aggregation input, aggregated column should be an string`);
}
if (typeof aggregate_function !== 'string') {
throw new Error(`Invalid aggregation input, aggregate function should be an string`);
}
}
}
}
function isValidExpression (expressions) {
const invalidTypes = ['string', 'number', 'boolean'];
return expressions !== null && !Array.isArray(expressions) && !invalidTypes.includes(typeof expressions);
}

View File

@@ -49,6 +49,10 @@ module.exports = class AggregationMapConfig extends MapConfig {
];
}
static get HAS_AGGREGATION_DISABLED () {
return null;
}
static supportsGeometryType(geometryType) {
return AggregationMapConfig.SUPPORTED_GEOMETRY_TYPES.includes(geometryType);
}
@@ -103,19 +107,19 @@ module.exports = class AggregationMapConfig extends MapConfig {
});
}
isAggregationMapConfig () {
return this.isVectorOnlyMapConfig() || this.hasAnyLayerAggregation();
}
isAggregationLayer (index) {
return this.isVectorOnlyMapConfig() || this.hasLayerAggregation(index);
let hasAggregation = this.hasLayerAggregation(index);
// for vector-only MapConfig are aggregated unless explicitly disabled
return hasAggregation || (
this.isVectorOnlyMapConfig() && hasAggregation !== AggregationMapConfig.HAS_AGGREGATION_DISABLED
);
}
hasAnyLayerAggregation () {
isAggregationMapConfig () {
const layers = this.getLayers();
for (let index = 0; index < layers.length; index++) {
if (this.hasLayerAggregation(index)) {
if (this.isAggregationLayer(index)) {
return true;
}
}
@@ -123,22 +127,30 @@ module.exports = class AggregationMapConfig extends MapConfig {
return false;
}
/* Three possible return values:
* * true: explicit aggregation ({}, true)
* * false: no aggregation (undefined)
* * AggregationMapConfig.HAS_AGGREGATION_DISABLED: explicitly disabled aggregation (false)
*/
hasLayerAggregation (index) {
const layer = this.getLayer(index);
const { aggregation } = layer.options;
return aggregation !== undefined && (typeof aggregation === 'object' || typeof aggregation === 'boolean');
if (aggregation !== undefined && (typeof aggregation === 'object' || typeof aggregation === 'boolean')) {
if (aggregation === false) {
return AggregationMapConfig.HAS_AGGREGATION_DISABLED;
}
return true;
}
return false;
}
getAggregation (index) {
if (this.isVectorOnlyMapConfig() && !this.hasLayerAggregation(index)) {
return {};
}
const { aggregation } = this.getLayer(index).options;
if (typeof aggregation === 'boolean') {
return {};
if (this.isAggregationLayer(index)) {
if (typeof aggregation === 'boolean' || !aggregation) {
return {};
}
}
return aggregation;
@@ -221,10 +233,7 @@ module.exports = class AggregationMapConfig extends MapConfig {
_isDefaultLayerAggregation (index) {
const aggregation = this.getAggregation(index);
return (this.isVectorOnlyMapConfig() && !this.hasLayerAggregation(index)) ||
aggregation === true ||
this._isDefaultAggregation(aggregation);
return aggregation && this._isDefaultAggregation(aggregation);
}
_isDefaultAggregation (aggregation) {

View File

@@ -448,3 +448,31 @@ const aggregationQueryTemplates = {
module.exports.SUPPORTED_PLACEMENTS = Object.keys(aggregationQueryTemplates);
module.exports.GEOMETRY_COLUMN = 'the_geom_webmercator';
const clusterFeaturesQuery = ctx => `
WITH
_cdb_params AS (
SELECT
${gridResolution(ctx)} AS res
),
_cell AS (
SELECT
ST_MakeEnvelope(
Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res)*_cdb_params.res,
Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)*_cdb_params.res,
Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res + 1)*_cdb_params.res,
Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res + 1)*_cdb_params.res,
3857
) AS bbox
FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params
WHERE _cdb_query.cartodb_id = ${ctx.id}
)
SELECT _cdb_query.* FROM _cell, (${ctx.sourceQuery}) _cdb_query
WHERE ST_Intersects(_cdb_query.the_geom_webmercator, _cell.bbox)
`;
module.exports.featuresQuery = (id, options) => clusterFeaturesQuery({
id,
sourceQuery: options.query,
res: 256/options.resolution
});

View File

@@ -4,50 +4,37 @@ const BaseHistogram = require('./base-histogram');
const debug = require('debug')('windshaft:dataview:numeric-histogram');
const utils = require('../../../utils/query-utils');
/** Query to get min and max values from the query */
/** Query to get min, max, count and (if necessary) bin number of the query */
const irqQueryTpl = ctx => `
__cdb_filtered_source AS (
SELECT *
FROM (${ctx.query}) __cdb_filtered_source_query
WHERE ${utils.handleFloatColumn(ctx)} IS NOT NULL
),
__cdb_basics AS (
__cdb_basics AS (
SELECT
*,
CASE
WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0 THEN 1
ELSE GREATEST(
LEAST(
${ctx.minBins},
__cdb_total_rows::int),
LEAST(
${ctx.maxBins},
((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3)))::int)
)
END AS __cdb_bins_number
FROM
(
SELECT
max(${ctx.column}) AS __cdb_max_val,
min(${ctx.column}) AS __cdb_min_val,
count(1) AS __cdb_total_rows
FROM __cdb_filtered_source
)
`;
/* Query to calculate the number of bins (needs irqQueryTpl before it*/
const binsQueryTpl = ctx => `
__cdb_iqrange AS (
SELECT max(quartile_max) - min(quartile_max) AS __cdb_iqr
FROM (
SELECT quartile, max(_cdb_iqr_column) AS quartile_max from (
SELECT ${ctx.column} AS _cdb_iqr_column, ntile(4) over (order by ${ctx.column}
) AS quartile
FROM __cdb_filtered_source) _cdb_quartiles
WHERE quartile = 1 or quartile = 3
GROUP BY 1
) __cdb_iqr
),
__cdb_bins AS (
SELECT
CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0
THEN 1
ELSE GREATEST(
LEAST(${ctx.minBins}, CAST(__cdb_total_rows AS INT)),
LEAST(
CAST(((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3))) AS INT),
${ctx.maxBins}
)
)
END AS __cdb_bins_number
FROM __cdb_basics, __cdb_iqrange, __cdb_filtered_source
LIMIT 1
)
count(1) AS __cdb_total_rows,
${ctx.irq ? ctx.irq : `0`} AS __cdb_iqr
FROM
(
SELECT *
FROM (${ctx.query}) __cdb_filtered_source_query
WHERE ${utils.handleFloatColumn(ctx)} IS NOT NULL
) __cdb_filtered_source
) __cdb_basics_2
)
`;
const BIN_MIN_NUMBER = 6;
@@ -117,10 +104,12 @@ module.exports = class NumericHistogram extends BaseHistogram {
}
if (ctx.bins <= 0) {
ctx.bins = `__cdb_bins.__cdb_bins_number`;
extra_groupby += `, __cdb_bins.__cdb_bins_number`;
extra_tables += `, __cdb_bins`;
extra_queries = `WITH ${irqQueryTpl(ctx)}, ${binsQueryTpl(ctx)}`;
ctx.bins = `__cdb_basics.__cdb_bins_number`;
ctx.irq = `percentile_disc(0.75) within group (order by ${ctx.column})
- percentile_disc(0.25) within group (order by ${ctx.column})`;
extra_groupby += `, __cdb_basics.__cdb_bins_number`;
extra_tables = `, __cdb_basics`;
extra_queries = `WITH ${irqQueryTpl(ctx)}`;
}
return `

View File

@@ -74,8 +74,8 @@ module.exports = class NamedMapMapConfigProvider extends BaseMapConfigProvider {
JSON.parse(this.config) :
this.config;
} catch (e) {
const error = new Error('malformed config parameter, should be a valid JSON');
this.err = error;
const err = new Error('malformed config parameter, should be a valid JSON');
this.err = err;
return callback(err);
}

View File

@@ -1,6 +1,7 @@
'use strict';
const _ = require('underscore');
const semver = require('semver');
const express = require('express');
const windshaft = require('windshaft');
const { mapnik } = windshaft;
@@ -82,7 +83,7 @@ function getAndValidateVersions(options) {
dependenciesToValidate.forEach(function(depName) {
var declaredDependencyVersion = declaredDependencies[depName];
var installedDependencyVersion = installedDependenciesVersions[depName];
if (declaredDependencyVersion !== installedDependencyVersion) {
if (!semver.satisfies(installedDependencyVersion,declaredDependencyVersion)) {
warn(
'Dependency="%s" installed version="%s" does not match declared version="%s". Check your installation.',
depName, installedDependencyVersion, declaredDependencyVersion

337
package-lock.json generated
View File

@@ -1,52 +1,53 @@
{
"name": "windshaft-cartodb",
"version": "7.0.0",
"version": "7.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@carto/cartonik": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@carto/cartonik/-/cartonik-0.5.0.tgz",
"integrity": "sha512-vdtNbdip+ZBhoRmEIu3gfkHVsRq657yz+rcbSrRZbX9bNwGMzYTlIj6EchI76bDhCAZszm+qfbptH3iOZzaW0w==",
"requires": {
"@carto/mapnik": "^3.6.2-carto.13",
"generic-pool": "^3.6.1",
"mime": "^2.4.0"
},
"dependencies": {
"mime": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.2.tgz",
"integrity": "sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg=="
}
}
},
"@carto/fqdn-sync": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@carto/fqdn-sync/-/fqdn-sync-0.2.2.tgz",
"integrity": "sha512-hvD8HlzgWkFsCswIHs+wtrIK8HEGC2L8jny3JY00lQ/xwurLxWR+xMYpgjI821INmjEPRFtajFQkniB1HxNBkQ=="
},
"@carto/mapnik": {
"version": "3.6.2-carto.11",
"resolved": "https://registry.npmjs.org/@carto/mapnik/-/mapnik-3.6.2-carto.11.tgz",
"integrity": "sha1-8vC8TQBRCAFpJnxccpuQxryTRmE=",
"version": "3.6.2-carto.13",
"resolved": "https://registry.npmjs.org/@carto/mapnik/-/mapnik-3.6.2-carto.13.tgz",
"integrity": "sha512-F1hxZEBpTLvf04ddzf1t1sjNtHBajFPHL6exfzj47lipTDYj1HB1/50ojlg6a+aBE1n5CcCqocHuB7r1cZMxDA==",
"requires": {
"mapnik-vector-tile": "github:cartodb/mapnik-vector-tile#e7ca5471f9e5de81243e6035e70444321fc0a82f",
"nan": "2.10.0",
"node-pre-gyp": "0.10.0"
"nan": "2.13.2",
"node-pre-gyp": "0.12.0"
},
"dependencies": {
"nan": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
}
}
},
"@carto/tilelive-bridge": {
"version": "github:cartodb/tilelive-bridge#e35ae36a6e2d555a6b312440f7e1904c5ad03664",
"from": "github:cartodb/tilelive-bridge#2.5.1-cdb11",
"requires": {
"@carto/mapnik": "3.6.2-carto.11",
"@mapbox/sphericalmercator": "~1.0.1",
"mapnik-pool": "~0.1.3"
},
"dependencies": {
"@mapbox/sphericalmercator": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.0.5.tgz",
"integrity": "sha1-cCN7l3QJXtHP286nqP0fyCsmkfI="
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
"integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw=="
}
}
},
"abaculus": {
"version": "github:cartodb/abaculus#3283c523d8bd4bdec78d90166d9c23ff09865ca8",
"from": "github:cartodb/abaculus#2.0.3-cdb13",
"version": "github:cartodb/abaculus#7288aa301e843197225e94b0b123ed5d1b804601",
"from": "github:cartodb/abaculus#2.0.3-cdb14",
"requires": {
"@carto/mapnik": "3.6.2-carto.11",
"@carto/mapnik": "3.6.2-carto.13",
"d3-queue": "^2.0.2",
"sphericalmercator": "1.0.5"
}
@@ -291,9 +292,9 @@
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
},
"camshaft": {
"version": "0.63.4",
"resolved": "https://registry.npmjs.org/camshaft/-/camshaft-0.63.4.tgz",
"integrity": "sha512-v6CpIyL2lCjCCUhksTrUSsmCUcDfjn9BD0ULs9lkz0TUwHfEMy3w2Q14NMsBXCCu0OjuiivnbPNm5XGjQe5Awg==",
"version": "0.64.0",
"resolved": "https://registry.npmjs.org/camshaft/-/camshaft-0.64.0.tgz",
"integrity": "sha512-5FPNdrkNlgfqq+1X2KWfc1MlZ/si8sqPmjogUZSLY/RnJIp+XlS80HDIsF3FYKSvmps9GO8k2Qi73fRll0EyGg==",
"requires": {
"async": "^1.5.2",
"bunyan": "1.8.1",
@@ -305,7 +306,7 @@
"dependencies": {
"request": {
"version": "2.85.0",
"resolved": "http://registry.npmjs.org/request/-/request-2.85.0.tgz",
"resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz",
"integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==",
"requires": {
"aws-sign2": "~0.7.0",
@@ -335,10 +336,57 @@
}
},
"canvas": {
"version": "github:cartodb/node-canvas#45bd3c7cc5e6f99ba75b0d417b6ac6384b43083d",
"from": "github:cartodb/node-canvas#1.6.2-cdb3",
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.4.1.tgz",
"integrity": "sha512-SaFomFqDuuuSTScTHQ7nXc5ea71Ieb8ctvwXjR7vzLsBMfp3euTv2xsTY70zIoC5r4sSQZYXv6tiHiORJ4y1vg==",
"requires": {
"nan": "^2.4.0"
"nan": "^2.12.1",
"node-pre-gyp": "^0.11.0"
},
"dependencies": {
"glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"nan": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
"integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw=="
},
"node-pre-gyp": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz",
"integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==",
"requires": {
"detect-libc": "^1.0.2",
"mkdirp": "^0.5.1",
"needle": "^2.2.1",
"nopt": "^4.0.1",
"npm-packlist": "^1.1.6",
"npmlog": "^4.0.2",
"rc": "^1.2.7",
"rimraf": "^2.6.1",
"semver": "^5.3.0",
"tar": "^4"
}
},
"rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"requires": {
"glob": "^7.1.3"
}
}
}
},
"carto": {
@@ -1941,9 +1989,9 @@
}
},
"generic-pool": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.2.2.tgz",
"integrity": "sha1-eon0kdV1tC+fBpoOjixtuqPCQb4="
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.7.1.tgz",
"integrity": "sha512-ug6DAZoNgWm6q5KhPFA+hzXfBLFQu5sTXxPpv44DmE0A2g+CiHoq9LTVdkXpZMkYVMoGw83F6W+WT0h0MFMK/w=="
},
"get-caller-file": {
"version": "1.0.3",
@@ -1988,9 +2036,9 @@
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
},
"grainstore": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/grainstore/-/grainstore-1.10.0.tgz",
"integrity": "sha512-mHBznPEqsM7g11ycVOrWBO7/1VAE4i/G8rFHTw7O5Ru79cbJdl0hJrnuG2ILBY207VNjSPxBC80uN1Pm1tx0Bg==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/grainstore/-/grainstore-2.0.0.tgz",
"integrity": "sha512-eDCr8kkpvKaenLqomRnYPQT8ARJZh+QzqtCJ+iHCLyj2pAfGSEnnld6XtCnQdbtCgJsFPrUaaWn12DqCmJiDnQ==",
"requires": {
"carto": "0.16.3",
"debug": "~3.1.0",
@@ -2018,12 +2066,17 @@
},
"dependencies": {
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
}
}
},
"generic-pool": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.2.2.tgz",
"integrity": "sha1-eon0kdV1tC+fBpoOjixtuqPCQb4="
},
"mapnik-reference": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/mapnik-reference/-/mapnik-reference-8.5.6.tgz",
@@ -2033,9 +2086,9 @@
},
"dependencies": {
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
}
}
},
@@ -2173,7 +2226,7 @@
},
"hoek": {
"version": "4.2.1",
"resolved": "http://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
"integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA=="
},
"hosted-git-info": {
@@ -2557,15 +2610,6 @@
"resolved": "https://registry.npmjs.org/lzma/-/lzma-2.3.2.tgz",
"integrity": "sha1-N4OySFi5wOdHoN88vx+1/KqSxEE="
},
"mapnik-pool": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/mapnik-pool/-/mapnik-pool-0.1.3.tgz",
"integrity": "sha1-m9rRyzTYehm8eOy0y/uqIDsvPys=",
"requires": {
"generic-pool": "~2.2.1",
"xtend": "~4.0.0"
}
},
"mapnik-reference": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/mapnik-reference/-/mapnik-reference-6.0.5.tgz",
@@ -2774,27 +2818,32 @@
},
"ncp": {
"version": "2.0.0",
"resolved": "http://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
"integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=",
"optional": true
},
"needle": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz",
"integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.3.1.tgz",
"integrity": "sha512-CaLXV3W8Vnbps8ZANqDGz7j4x7Yj1LW4TWF/TQuDfj7Cfx4nAPTvw98qgTevtto1oHDrh3pQkaODbqupXlsWTg==",
"requires": {
"debug": "^2.1.2",
"debug": "^4.1.0",
"iconv-lite": "^0.4.4",
"sax": "^1.2.4"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "2.0.0"
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
@@ -2821,17 +2870,17 @@
}
},
"node-pre-gyp": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz",
"integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==",
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz",
"integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==",
"requires": {
"detect-libc": "^1.0.2",
"mkdirp": "^0.5.1",
"needle": "^2.2.0",
"needle": "^2.2.1",
"nopt": "^4.0.1",
"npm-packlist": "^1.1.6",
"npmlog": "^4.0.2",
"rc": "^1.1.7",
"rc": "^1.2.7",
"rimraf": "^2.6.1",
"semver": "^5.3.0",
"tar": "^4"
@@ -2886,9 +2935,9 @@
},
"dependencies": {
"resolve": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
"integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz",
"integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==",
"requires": {
"path-parse": "^1.0.6"
}
@@ -2901,9 +2950,9 @@
"integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g=="
},
"npm-packlist": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.3.0.tgz",
"integrity": "sha512-qPBc6CnxEzpOcc4bjoIBJbYdy0D/LFFPUdxvfwor4/w3vxeE0h6TiOVurCEPpQ6trjN77u/ShyfeJGsbAfB3dA==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz",
"integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==",
"requires": {
"ignore-walk": "^3.0.1",
"npm-bundled": "^1.0.1"
@@ -2943,11 +2992,6 @@
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-keys": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz",
"integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -3288,16 +3332,6 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
},
"progress-stream": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-0.5.0.tgz",
"integrity": "sha1-zEdZFnpv9PBYdheThPC2rg0ddYc=",
"requires": {
"single-line-log": "~0.3.1",
"speedometer": "~0.1.2",
"through2": "~0.2.3"
}
},
"propagate": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz",
@@ -3480,7 +3514,7 @@
},
"rimraf": {
"version": "2.4.5",
"resolved": "http://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
"integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
"optional": true,
"requires": {
@@ -3613,11 +3647,6 @@
"resolved": "https://registry.npmjs.org/simple-statistics/-/simple-statistics-0.9.2.tgz",
"integrity": "sha1-PjXLEDCPx2ljqk7nJS5qbqENKOQ="
},
"single-line-log": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.3.1.tgz",
"integrity": "sha1-p61lB/IYzl3+FsS/LWWSRkGeegY="
},
"sntp": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz",
@@ -3655,14 +3684,9 @@
}
},
"spdx-license-ids": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz",
"integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g=="
},
"speedometer": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz",
"integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0="
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz",
"integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA=="
},
"sphericalmercator": {
"version": "1.0.5",
@@ -3880,84 +3904,10 @@
"resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"through2": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz",
"integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=",
"requires": {
"readable-stream": "~1.1.9",
"xtend": "~2.1.1"
},
"dependencies": {
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"xtend": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz",
"integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=",
"requires": {
"object-keys": "~0.4.0"
}
}
}
},
"tilelive": {
"version": "5.12.3",
"resolved": "https://registry.npmjs.org/tilelive/-/tilelive-5.12.3.tgz",
"integrity": "sha1-nIx3DhGUqo01PZodAxR0BDB83r0=",
"requires": {
"minimist": "~0.2.0",
"progress-stream": "~0.5.x",
"queue-async": "~1.0.7",
"sphericalmercator": "~1.0.1"
},
"dependencies": {
"minimist": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz",
"integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784="
},
"queue-async": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/queue-async/-/queue-async-1.0.7.tgz",
"integrity": "sha1-Iq4KHaxKkvW81GNPmTxoKiqBCUU="
}
}
},
"tilelive-mapnik": {
"version": "github:cartodb/tilelive-mapnik#a89d4147b3288804b679a018271893dda18373c2",
"from": "github:cartodb/tilelive-mapnik#0.6.18-cdb18",
"requires": {
"@carto/mapnik": "3.6.2-carto.11",
"generic-pool": "3.5.0",
"mime": "2.3.1"
},
"dependencies": {
"generic-pool": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.5.0.tgz",
"integrity": "sha512-dEkxmX+egB2o4NR80c/q+xzLLzLX+k68/K8xv81XprD+Sk7ZtP14VugeCz+fUwv5FzpWq40pPtAkzPRqT8ka9w=="
},
"mime": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz",
"integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg=="
}
}
},
"torque.js": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/torque.js/-/torque.js-2.17.1.tgz",
"integrity": "sha512-jZuw5P3muNoILsmiN2kaclzczYW6h9HBSQo5riEB1ub/oNzmj7FqvFJOJKG3dbsxuAqOMn0+AwcQUC3Fb0YTRw==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/torque.js/-/torque.js-3.1.0.tgz",
"integrity": "sha512-xX0BVo/gAy5A2Qx17dbgzjB2cnzuSDwzohdT1hGaKbG0jveL3sKgKE5VyzlN1/l4hP3p6f/m20HvZo3waIawCQ==",
"requires": {
"carto": "github:cartodb/carto#85881d99dd7fcf2c4e16478b04db67108d27a50c",
"d3": "3.5.17",
@@ -4156,27 +4106,24 @@
"integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU="
},
"windshaft": {
"version": "4.13.1",
"resolved": "https://registry.npmjs.org/windshaft/-/windshaft-4.13.1.tgz",
"integrity": "sha512-sjk8D6gOZTp2jb7iuF38FyhPIcAAOUsOQtS+SmTLLIk1AipEK+xQU11IVl9cOvhiaCYjs7dRLscM54qoMqKVxw==",
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/windshaft/-/windshaft-5.2.0.tgz",
"integrity": "sha512-1kgT2syKc0B+XiHr0/njDJHJ3BvPrZzLQv+rkbyXZZa06kMXOx0qxDc+D1e2BkuirxI7+zgAFlp3PWpyQVeqHA==",
"requires": {
"@carto/mapnik": "3.6.2-carto.11",
"@carto/tilelive-bridge": "github:cartodb/tilelive-bridge#e35ae36a6e2d555a6b312440f7e1904c5ad03664",
"abaculus": "github:cartodb/abaculus#3283c523d8bd4bdec78d90166d9c23ff09865ca8",
"canvas": "github:cartodb/node-canvas#45bd3c7cc5e6f99ba75b0d417b6ac6384b43083d",
"@carto/cartonik": "^0.5.0",
"@carto/mapnik": "3.6.2-carto.13",
"abaculus": "github:cartodb/abaculus#7288aa301e843197225e94b0b123ed5d1b804601",
"canvas": "^2.4.1",
"carto": "github:cartodb/carto#85881d99dd7fcf2c4e16478b04db67108d27a50c",
"cartodb-psql": "0.13.1",
"debug": "3.1.0",
"dot": "1.1.2",
"grainstore": "1.10.0",
"grainstore": "^2.0.0",
"queue-async": "1.1.0",
"redis-mpool": "0.7.0",
"request": "2.87.0",
"semver": "5.5.0",
"sphericalmercator": "1.0.5",
"tilelive": "5.12.3",
"tilelive-mapnik": "github:cartodb/tilelive-mapnik#a89d4147b3288804b679a018271893dda18373c2",
"torque.js": "2.17.1",
"torque.js": "^3.1.0",
"underscore": "1.6.0"
}
},

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "windshaft-cartodb",
"version": "7.0.0",
"version": "7.1.0",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
@@ -27,7 +27,7 @@
"@carto/fqdn-sync": "0.2.2",
"basic-auth": "2.0.0",
"body-parser": "1.18.3",
"camshaft": "0.63.4",
"camshaft": "^0.64.0",
"cartodb-psql": "0.13.1",
"cartodb-query-tables": "0.4.0",
"cartodb-redis": "2.1.0",
@@ -49,7 +49,7 @@
"step-profiler": "0.3.0",
"turbo-carto": "0.21.0",
"underscore": "1.6.0",
"windshaft": "4.13.1",
"windshaft": "5.2.0",
"yargs": "11.1.0"
},
"devDependencies": {

View File

@@ -91,6 +91,8 @@ describe('aggregation', function () {
from generate_series(-3, 3) x
`;
const POINTS_OVER_THRESHOLD = 'SELECT * FROM test_table_200k';
const POLYGONS_SQL_1 = `
select
x + 4 as cartodb_id,
@@ -665,7 +667,9 @@ describe('aggregation', function () {
{
type: 'cartodb',
options: {
sql: POINTS_SQL_2,
// Note that we need the table to have more than AggregationMapConfig.THRESHOLD rows
// otherwise it won't get aggregated in any case
sql: POINTS_OVER_THRESHOLD,
aggregation: false
}
}
@@ -689,6 +693,66 @@ describe('aggregation', function () {
});
});
it('use default aggregation by setting `aggregation: true`', function (done) {
const mapConfig = createVectorMapConfig([
{
type: 'cartodb',
options: {
sql: POINTS_OVER_THRESHOLD,
cartocss: '#layer { marker-width: 7; }',
cartocss_version: '2.3.0',
aggregation: true
}
}
]);
this.testClient = new TestClient(mapConfig);
this.testClient.getLayergroup((err, body) => {
if (err) {
return done(err);
}
assert.equal(typeof body.metadata, 'object');
assert.ok(Array.isArray(body.metadata.layers));
assert.equal(body.metadata.layers[0].meta.aggregation.mvt, true);
assert.equal(body.metadata.layers[0].meta.aggregation.png, true);
done();
});
});
it('but do not aggregate below threshold by setting `aggregation: true`', function (done) {
const mapConfig = createVectorMapConfig([
{
type: 'cartodb',
options: {
sql: POINTS_SQL_2,
cartocss: '#layer { marker-width: 7; }',
cartocss_version: '2.3.0',
aggregation: true
}
}
]);
this.testClient = new TestClient(mapConfig);
this.testClient.getLayergroup((err, body) => {
if (err) {
return done(err);
}
assert.equal(typeof body.metadata, 'object');
assert.ok(Array.isArray(body.metadata.layers));
assert.equal(body.metadata.layers[0].meta.aggregation.mvt, false);
assert.equal(body.metadata.layers[0].meta.aggregation.png, false);
done();
});
});
it('when the aggregation param is not valid should respond with error', function (done) {
const mapConfig = createVectorMapConfig([
{

842
test/acceptance/cluster.js Normal file
View File

@@ -0,0 +1,842 @@
'use strict';
require('../support/test_helper');
const assert = require('../support/assert');
const TestClient = require('../support/test-client');
const POINTS_SQL_1 = `
select
x + 4 as cartodb_id,
st_setsrid(st_makepoint(x*10 + 18, x*10 + 5), 4326) as the_geom,
st_transform(st_setsrid(st_makepoint(x*10 + 18, x*10 + 5), 4326), 3857) as the_geom_webmercator,
x as value,
CASE
WHEN x % 2 = 0 THEN 'even'
ELSE 'odd'
END AS type
from generate_series(-3, 3) x
`;
const defaultLayers = [{
type: 'cartodb',
options: {
sql: POINTS_SQL_1,
aggregation: {
threshold: 1
}
}
}];
function createVectorMapConfig (layers = defaultLayers) {
return {
version: '1.8.0',
layers: layers
};
}
describe('cluster', function () {
describe('w/o aggregation', function () {
it('should return error while fetching disaggregated features', function (done) {
const mapConfig = createVectorMapConfig([{
type: 'cartodb',
options: {
sql: POINTS_SQL_1,
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}]);
const testClient = new TestClient(mapConfig);
const zoom = 0;
const cartodb_id = 1;
const layerId = 0;
const params = {
response: {
status: 400
}
};
testClient.getClusterFeatures(zoom, cartodb_id, layerId, params, (err, body) => {
if (err) {
return done(err);
}
assert.deepStrictEqual(body, {
errors:[ 'Map f697fb370c6479559ae2f66d684e8227 has no aggregation defined for layer 0' ],
errors_with_context:[
{
layer: {
index: '0',
type: 'cartodb'
},
message: 'Map f697fb370c6479559ae2f66d684e8227 has no aggregation defined for layer 0',
subtype: 'aggregation',
type: 'layer'
}
]
});
testClient.drain(done);
});
});
it('with aggregation disabled should return error while fetching disaggregated features', function (done) {
const mapConfig = createVectorMapConfig([{
type: 'cartodb',
options: {
sql: POINTS_SQL_1,
aggregation: false
}
}]);
const testClient = new TestClient(mapConfig);
const zoom = 0;
const cartodb_id = 1;
const layerId = 0;
const params = {
response: {
status: 400
}
};
testClient.getClusterFeatures(zoom, cartodb_id, layerId, params, (err, body) => {
if (err) {
return done(err);
}
assert.deepStrictEqual(body, {
errors:[ 'Map 7521bcd1029c401289dd651ce91d5d9d has no aggregation defined for layer 0' ],
errors_with_context:[
{
layer: {
index: '0',
type: 'cartodb'
},
message: 'Map 7521bcd1029c401289dd651ce91d5d9d has no aggregation defined for layer 0',
subtype: 'aggregation',
type: 'layer'
}
]
});
testClient.drain(done);
});
});
});
describe('fetch features within a cluster grid', function () {
const suite = [
{
zoom: 0,
cartodb_id: 1,
resolution: 0.5,
expected: [ { cartodb_id: 1, value: -3, type: 'odd' } ]
},
{
zoom: 0,
cartodb_id: 2,
resolution: 0.5,
expected: [ { cartodb_id: 2, value: -2, type: 'even' } ]
},
{
zoom: 0,
cartodb_id: 3,
resolution: 0.5,
expected: [ { cartodb_id: 3, value: -1, type: 'odd' } ]
},
{
zoom: 0,
cartodb_id: 4,
resolution: 0.5,
expected: [ { cartodb_id: 4, value: 0, type: 'even' } ]
},
{
zoom: 0,
cartodb_id: 5,
resolution: 0.5,
expected: [ { cartodb_id: 5, value: 1, type: 'odd' } ]
},
{
zoom: 0,
cartodb_id: 6,
resolution: 0.5,
expected: [ { cartodb_id: 6, value: 2, type: 'even' } ]
},
{
zoom: 0,
cartodb_id: 7,
resolution: 0.5,
expected: [ { cartodb_id: 7, value: 3, type: 'odd' } ]
},
{
zoom: 0,
cartodb_id: 1,
resolution: 1,
expected: [ { cartodb_id: 1, value: -3, type: 'odd' } ]
},
{
zoom: 0,
cartodb_id: 2,
resolution: 1,
expected: [ { cartodb_id: 2, value: -2, type: 'even' } ]
},
{
zoom: 0,
cartodb_id: 3,
resolution: 1,
expected: [ { cartodb_id: 3, value: -1, type: 'odd' } ]
},
{
zoom: 0,
cartodb_id: 4,
resolution: 1,
expected: [ { cartodb_id: 4, value: 0, type: 'even' } ]
},
{
zoom: 0,
cartodb_id: 5,
resolution: 1,
expected: [ { cartodb_id: 5, value: 1, type: 'odd' } ]
},
{
zoom: 0,
cartodb_id: 6,
resolution: 1,
expected: [ { cartodb_id: 6, value: 2, type: 'even' } ]
},
{
zoom: 0,
cartodb_id: 7,
resolution: 1,
expected: [ { cartodb_id: 7, value: 3, type: 'odd' } ]
},
{
zoom: 0,
cartodb_id: 1,
resolution: 50,
expected: [
{ cartodb_id: 1, value: -3, type: 'odd' },
{ cartodb_id: 2, value: -2, type: 'even' }
]
},
{
zoom: 0,
cartodb_id: 5,
resolution: 50,
expected: [
{ cartodb_id: 4, value: 0, type: 'even' },
{ cartodb_id: 5, value: 1, type: 'odd' },
{ cartodb_id: 6, value: 2, type: 'even' },
{ cartodb_id: 7, value: 3, type: 'odd' }
]
},
{
zoom: 1,
cartodb_id: 1,
resolution: 1,
expected: [ { cartodb_id: 1, value: -3, type: 'odd' } ]
},
{
zoom: 1,
cartodb_id: 2,
resolution: 1,
expected: [ { cartodb_id: 2, value: -2, type: 'even' } ]
},
{
zoom: 1,
cartodb_id: 3,
resolution: 1,
expected: [ { cartodb_id: 3, value: -1, type: 'odd' } ]
},
{
zoom: 1,
cartodb_id: 4,
resolution: 1,
expected: [ { cartodb_id: 4, value: 0, type: 'even' } ]
},
{
zoom: 1,
cartodb_id: 5,
resolution: 1,
expected: [ { cartodb_id: 5, value: 1, type: 'odd' } ]
},
{
zoom: 1,
cartodb_id: 6,
resolution: 1,
expected: [ { cartodb_id: 6, value: 2, type: 'even' } ]
},
{
zoom: 1,
cartodb_id: 7,
resolution: 1,
expected: [ { cartodb_id: 7, value: 3, type: 'odd' } ]
},
{
zoom: 1,
cartodb_id: 1,
resolution: 50,
expected: [
{ cartodb_id: 1, value: -3, type: 'odd' },
{ cartodb_id: 2, value: -2, type: 'even'}
]
},
{
zoom: 1,
cartodb_id: 5,
resolution: 50,
expected: [
{ cartodb_id: 4, value: 0, type: 'even' },
{ cartodb_id: 5, value: 1, type: 'odd' }
]
}
];
suite.forEach(({ zoom, cartodb_id, resolution, expected }) => {
const description = `should get features for z: ${zoom} cartodb_id: ${cartodb_id}, res: ${resolution}`;
it(description, function (done) {
const mapConfig = createVectorMapConfig([{
type: 'cartodb',
options: {
sql: POINTS_SQL_1,
aggregation: {
threshold: 1,
resolution: resolution
}
}
}]);
const testClient = new TestClient(mapConfig);
const layerId = 0;
const params = {};
testClient.getClusterFeatures(zoom, cartodb_id, layerId, params, (err, body) => {
if (err) {
return done(err);
}
assert.deepStrictEqual(body.rows, expected);
testClient.drain(done);
});
});
});
});
describe('valid aggregation input', function () {
const suite = [
{
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: { columns: ['type'] },
expected: [ { _cdb_feature_count: 1, type: 'odd' } ]
},
{
zoom: 0,
cartodb_id: 2,
resolution: 1,
aggregation: { columns: ['type'] },
expected: [ { _cdb_feature_count: 1, type: 'even' } ]
},
{
zoom: 0,
cartodb_id: 3,
resolution: 1,
aggregation: { columns: ['type'] },
expected: [ { _cdb_feature_count: 1, type: 'odd' } ]
},
{
zoom: 0,
cartodb_id: 4,
resolution: 1,
aggregation: { columns: ['type'] },
expected: [ { _cdb_feature_count: 1, type: 'even' } ]
},
{
zoom: 0,
cartodb_id: 5,
resolution: 1,
aggregation: { columns: ['type'] },
expected: [ { _cdb_feature_count: 1, type: 'odd' } ]
},
{
zoom: 0,
cartodb_id: 6,
resolution: 1,
aggregation: { columns: ['type'] },
expected: [ { _cdb_feature_count: 1, type: 'even' } ]
},
{
zoom: 0,
cartodb_id: 7,
resolution: 1,
aggregation: { columns: ['type'] },
expected: [ { _cdb_feature_count: 1, type: 'odd' } ]
},
{
zoom: 0,
cartodb_id: 1,
resolution: 50,
aggregation: { columns: ['type'] },
expected: [
{ _cdb_feature_count: 1, type: 'even' },
{ _cdb_feature_count: 1, type: 'odd' }
]
},
{
zoom: 0,
cartodb_id: 5,
resolution: 50,
aggregation: { columns: ['type'] },
expected: [
{ _cdb_feature_count: 2, type: 'even' },
{ _cdb_feature_count: 2, type: 'odd' }
]
},
{
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 'max',
aggregated_column: 'value',
}
}
},
expected: [ { _cdb_feature_count: 1, type: 'odd', max_value: -3 } ]
},
{
zoom: 0,
cartodb_id: 2,
resolution: 1,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 'max',
aggregated_column: 'value',
}
}
},
expected: [ { _cdb_feature_count: 1, type: 'even', max_value: -2 } ]
},
{
zoom: 0,
cartodb_id: 3,
resolution: 1,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 'max',
aggregated_column: 'value',
}
}
},
expected: [ { _cdb_feature_count: 1, type: 'odd', max_value: -1 } ]
},
{
zoom: 0,
cartodb_id: 4,
resolution: 1,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 'max',
aggregated_column: 'value',
}
}
},
expected: [ { _cdb_feature_count: 1, type: 'even', max_value: 0 } ]
},
{
zoom: 0,
cartodb_id: 5,
resolution: 1,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 'max',
aggregated_column: 'value',
}
}
},
expected: [ { _cdb_feature_count: 1, type: 'odd', max_value: 1 } ]
},
{
zoom: 0,
cartodb_id: 6,
resolution: 1,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 'max',
aggregated_column: 'value',
}
}
},
expected: [ { _cdb_feature_count: 1, type: 'even', max_value: 2 } ]
},
{
zoom: 0,
cartodb_id: 7,
resolution: 1,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 'max',
aggregated_column: 'value',
}
}
},
expected: [ { _cdb_feature_count: 1, type: 'odd', max_value: 3 } ]
},
{
zoom: 0,
cartodb_id: 1,
resolution: 50,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 'max',
aggregated_column: 'value',
}
}
},
expected: [
{ _cdb_feature_count: 1, type: 'even', max_value: -2 },
{ _cdb_feature_count: 1, type: 'odd', max_value: -3 }
]
},
{
zoom: 0,
cartodb_id: 5,
resolution: 50,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 'max',
aggregated_column: 'value',
}
}
},
expected: [
{ _cdb_feature_count: 2, type: 'even', max_value: 2 },
{ _cdb_feature_count: 2, type: 'odd', max_value: 3 }
]
}
];
suite.forEach(({ zoom, cartodb_id, resolution, aggregation, expected }) => {
it(`should aggregate by type; z: ${zoom}, cartodb_id: ${cartodb_id}, res: ${resolution}`, function (done) {
const mapConfig = createVectorMapConfig([{
type: 'cartodb',
options: {
sql: POINTS_SQL_1,
aggregation: {
threshold: 1,
resolution
}
}
}]);
const testClient = new TestClient(mapConfig);
const layerId = 0;
const params = { aggregation };
testClient.getClusterFeatures(zoom, cartodb_id, layerId, params, (err, body) => {
if (err) {
return done(err);
}
const sort_f = ((a, b) => {
return (a._cdb_feature_count < b._cdb_feature_count) ||
(a._cdb_feature_count === b._cdb_feature_count &&
(a.type < b.type ||
a.type === b.type && a.max_value < b.max_value));
});
assert.deepStrictEqual(body.rows.sort(sort_f), expected.sort(sort_f));
testClient.drain(done);
});
});
});
});
describe('invalid aggregation input', function () {
const expectedColumnsError = {
errors:[ 'Invalid aggregation input, columns should be and array of column names' ],
errors_with_context:[
{
layer: {
index: '0',
type: 'cartodb'
},
message: 'Invalid aggregation input, columns should be and array of column names',
subtype: 'aggregation',
type: 'layer'
}
]
};
const expectedExpressionsError = {
errors:[ 'Invalid aggregation input, expressions should be and object with valid functions' ],
errors_with_context:[
{
layer: {
index: '0',
type: 'cartodb'
},
message: 'Invalid aggregation input, expressions should be and object with valid functions',
subtype: 'aggregation',
type: 'layer'
}
]
};
const invalidFunctionExpressionsError = {
errors:[ 'function wadus(integer) does not exist' ],
errors_with_context:[
{
message: 'function wadus(integer) does not exist',
type: 'unknown'
}
]
};
const invalidColumnExpressionsError = {
errors:[ 'column \"wadus\" does not exist' ],
errors_with_context:[
{
message: 'column \"wadus\" does not exist',
type: 'unknown'
}
]
};
const expectedAggregatedColumnError = {
errors:[ 'Invalid aggregation input, aggregated column should be an string' ],
errors_with_context:[
{
layer: {
index: '0',
type: 'cartodb'
},
message: 'Invalid aggregation input, aggregated column should be an string',
subtype: 'aggregation',
type: 'layer'
}
]
};
const expectedAggregateFunctionError = {
errors:[ 'Invalid aggregation input, aggregate function should be an string' ],
errors_with_context:[
{
layer: {
index: '0',
type: 'cartodb'
},
message: 'Invalid aggregation input, aggregate function should be an string',
subtype: 'aggregation',
type: 'layer'
}
]
};
const suite = [
{
description: 'empty aggregation object should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: {},
expected: expectedColumnsError
},
{
description: 'empty aggregation array should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: [],
expected: expectedColumnsError
},
{
description: 'aggregation as string should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: 'wadus',
expected: expectedColumnsError
},
{
description: 'empty columns array should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: { columns: [] },
expected: expectedColumnsError
},
{
description: 'empty columns object should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: { columns: {} },
expected: expectedColumnsError
},
{
description: 'columns as string should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: { columns: 'wadus' },
expected: expectedColumnsError
},
{
description: 'columns as null should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: { columns: null },
expected: expectedColumnsError
},
{
description: 'empty expressions array should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: { columns: [ 'type' ], expressions: [] },
expected: expectedExpressionsError
},
{
description: 'empty expressions number should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: { columns: [ 'type' ], expressions: 1 },
expected: expectedExpressionsError
},
{
description: 'expressions as string should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: { columns: [ 'type' ], expressions: 'wadus' },
expected: expectedExpressionsError
},
{
description: 'expressions as null should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: { columns: [ 'type' ], expressions: null },
expected: expectedExpressionsError
},
{
description: 'invalid aggregation function should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 'wadus',
aggregated_column: 'value'
}
}
},
expected: invalidFunctionExpressionsError
},
{
description: 'invalid aggregation column should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 'max',
aggregated_column: 'wadus'
}
}
},
status: 404,
expected: invalidColumnExpressionsError
},
{
description: 'aggregated column as non string should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 'max',
aggregated_column: 1
}
}
},
expected: expectedAggregatedColumnError
},
{
description: 'aggregate function as non string should respond with error',
zoom: 0,
cartodb_id: 1,
resolution: 1,
aggregation: {
columns: [ 'type' ],
expressions: {
max_value: {
aggregate_function: 1,
aggregated_column: 'value'
}
}
},
expected: expectedAggregateFunctionError
}
];
suite.forEach(({ description, zoom, cartodb_id, resolution, aggregation, expected, status = 400 }) => {
it(description, function (done) {
const mapConfig = createVectorMapConfig([{
type: 'cartodb',
options: {
sql: POINTS_SQL_1,
aggregation: {
threshold: 1,
resolution
}
}
}]);
const testClient = new TestClient(mapConfig);
const layerId = 0;
const params = {
response: { status },
aggregation
};
testClient.getClusterFeatures(zoom, cartodb_id, layerId, params, (err, body) => {
if (err) {
return done(err);
}
assert.deepStrictEqual(body, expected);
testClient.drain(done);
});
});
});
});
});

View File

@@ -90,6 +90,26 @@ describe('histogram-dataview', function() {
});
});
it('should work with min >= start and max <= end, autodetect bins', function(done) {
var params = {
start: 50,
end: 500
};
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getDataview('pop_max_histogram', params, function(err, dataview) {
assert.ok(!err, err);
assert.ok(6 === dataview.bins_count, 'Unexpected bin count: ' + dataview.bins_count);
assert.ok(6 === dataview.bins.length, 'Unexpected number of bins: ' + dataview.bins.length);
dataview.bins.forEach(function(bin) {
assert.ok(bin.min >= params.start, 'bin min < start: ' + JSON.stringify(bin));
assert.ok(bin.max <= params.end, 'bin max > end: ' + JSON.stringify(bin));
});
done();
});
});
it('should get bin_width right when max > min in filter', function(done) {
var params = {
bins: 10,

View File

@@ -106,7 +106,15 @@ describe('named maps static view', function() {
}
getStaticMap(function(err, img) {
assert.ok(!err);
assert.imageIsSimilarToFile(img, previewFixture('estimated'), PNG_IMAGE_TOLERANCE, done);
assert.imageIsSimilarToFile(img, previewFixture('estimated'), PNG_IMAGE_TOLERANCE, err => {
if (err) {
assert.imageIsSimilarToFile(img, previewFixture('estimated-proj5'), PNG_IMAGE_TOLERANCE, done);
}
else
{
done();
}
});
});
});
});

View File

@@ -129,7 +129,13 @@ describe('user render timeout limit', function () {
const mapconfig = createMapConfig();
this.testClient = new TestClient(mapconfig, 1234);
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
this.testClient.getLayergroup(mapconfig, (err, layergroup) => {
if (err) {
return done(err);
}
this.layergroup = layergroup;
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
});
});
afterEach(function (done) {
@@ -144,7 +150,12 @@ describe('user render timeout limit', function () {
});
it('layergroup creation works but tile request fails due to render timeout', function (done) {
this.testClient.getTile(0, 0, 0, { cacheBuster: true }, (err, res, tile) => {
const params = {
layergroupid: this.layergroup.layergroupid,
cacheBuster: true
};
this.testClient.getTile(0, 0, 0, params, (err, res, tile) => {
assert.ifError(err);
assert.imageIsSimilarToFile(tile, timeoutErrorTilePath, 0.05, (err) => {
@@ -164,7 +175,13 @@ describe('user render timeout limit', function () {
const mapconfig = createMapConfig();
this.testClient = new TestClient(mapconfig, 1234);
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
this.testClient.getLayergroup(mapconfig, (err, layergroup) => {
if (err) {
return done(err);
}
this.layergroup = layergroup;
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
});
});
afterEach(function (done) {
@@ -180,6 +197,7 @@ describe('user render timeout limit', function () {
it('layergroup creation works and render tile fails', function (done) {
var params = {
layergroupid: this.layergroup.layergroupid,
response: {
status: 429,
headers: {
@@ -215,7 +233,13 @@ describe('user render timeout limit', function () {
serverOptions.renderer.mvt.usePostGIS = usePostGIS;
const mapconfig = createMapConfig();
this.testClient = new TestClient(mapconfig, 1234);
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
this.testClient.getLayergroup(mapconfig, (err, layergroup) => {
if (err) {
return done(err);
}
this.layergroup = layergroup;
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
});
});
afterEach(function (done) {
@@ -230,6 +254,7 @@ describe('user render timeout limit', function () {
it('layergroup creation works but vector tile request fails due to render timeout', function (done) {
const params = {
layergroupid: this.layergroup.layergroupid,
format: 'mvt',
response: {
status: 429,
@@ -258,7 +283,13 @@ describe('user render timeout limit', function () {
beforeEach(function (done) {
const mapconfig = createMapConfig();
this.testClient = new TestClient(mapconfig, 1234);
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
this.testClient.getLayergroup(mapconfig, (err, layergroup) => {
if (err) {
return done(err);
}
this.layergroup = layergroup;
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
});
});
afterEach(function (done) {
@@ -272,6 +303,7 @@ describe('user render timeout limit', function () {
it('layergroup creation works but "grid.json" tile request fails due to render timeout', function (done) {
const params = {
layergroupid: this.layergroup.layergroupid,
layers: 'mapnik',
format: 'grid.json',
response: {
@@ -310,7 +342,13 @@ describe('user render timeout limit', function () {
const mapconfig = createMapConfig();
this.testClient = new TestClient(mapconfig, 1234);
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
this.testClient.getLayergroup(mapconfig, (err, layergroup) => {
if (err) {
return done(err);
}
this.layergroup = layergroup;
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
});
});
afterEach(function (done) {
@@ -326,6 +364,7 @@ describe('user render timeout limit', function () {
it('layergroup creation works but static image fails due to render timeout', function (done) {
const params = {
layergroupid: this.layergroup.layergroupid,
zoom: 0,
lat: 0,
lng: 0,
@@ -354,7 +393,13 @@ describe('user render timeout limit', function () {
const mapconfig = createMapConfig();
this.testClient = new TestClient(mapconfig, 1234);
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
this.testClient.getLayergroup(mapconfig, (err, layergroup) => {
if (err) {
return done(err);
}
this.layergroup = layergroup;
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
});
});
afterEach(function (done) {
@@ -370,6 +415,7 @@ describe('user render timeout limit', function () {
it('layergroup creation works and render static center tile fails', function (done) {
const params = {
layergroupid: this.layergroup.layergroupid,
zoom: 0,
lat: 0,
lng: 0,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -13,7 +13,6 @@
PREPARE_REDIS=yes
PREPARE_PGSQL=yes
DOWNLOAD_SQL_FILES=yes
PG_PARALLEL=$(pg_config --version | (awk '{$2*=1000; if ($2 >= 9600) print 1; else print 0;}' 2> /dev/null || echo 0))
while [ -n "$1" ]; do
OPTION=$(echo "$1" | tr -d '[:space:]')

View File

@@ -819,5 +819,46 @@ CREATE INDEX test_table_100_the_geom_webmercator_idx ON test_table_100 USING gis
GRANT ALL ON TABLE test_table_100 TO :TESTUSER;
GRANT SELECT ON TABLE test_table_100 TO :PUBLICUSER;
-- Table with 200K rows
CREATE TABLE test_table_200k (
cartodb_id integer NOT NULL,
value int,
the_geom geometry,
the_geom_webmercator geometry,
CONSTRAINT enforce_dims_the_geom CHECK ((st_ndims(the_geom) = 2)),
CONSTRAINT enforce_dims_the_geom_webmercator CHECK ((st_ndims(the_geom_webmercator) = 2)),
CONSTRAINT enforce_geotype_the_geom CHECK (((geometrytype(the_geom) = 'POINT'::text) OR (the_geom IS NULL))),
CONSTRAINT enforce_geotype_the_geom_webmercator CHECK (((geometrytype(the_geom_webmercator) = 'POINT'::text) OR (the_geom_webmercator IS NULL))),
CONSTRAINT enforce_srid_the_geom CHECK ((st_srid(the_geom) = 4326)),
CONSTRAINT enforce_srid_the_geom_webmercator CHECK ((st_srid(the_geom_webmercator) = 3857))
);
CREATE SEQUENCE test_table_200k_cartodb_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE test_table_200k_cartodb_id_seq OWNED BY test_table_200k.cartodb_id;
SELECT pg_catalog.setval('test_table_200k_cartodb_id_seq', 60, true);
ALTER TABLE test_table_200k ALTER COLUMN cartodb_id SET DEFAULT nextval('test_table_200k_cartodb_id_seq'::regclass);
INSERT INTO test_table_200k(the_geom, the_geom_webmercator, value)
SELECT
ST_SetSRID(ST_MakePoint(n*1E-4 + 9E-3, n*1E-4 + 9E-3), 4326) AS the_geom,
ST_Transform(ST_SetSRID(ST_MakePoint(n*1E-4 + 9E-3, n*1E-4 + 9E-3), 4326), 3857) AS the_geom_webmercator,
n AS value
FROM generate_series(1, 200000) n;
ALTER TABLE ONLY test_table_200k ADD CONSTRAINT test_table_200k_pkey PRIMARY KEY (cartodb_id);
CREATE INDEX test_table_200k_the_geom_idx ON test_table_200k USING gist (the_geom);
CREATE INDEX test_table_200k_the_geom_webmercator_idx ON test_table_200k USING gist (the_geom_webmercator);
GRANT ALL ON TABLE test_table_200k TO :TESTUSER;
GRANT SELECT ON TABLE test_table_200k TO :PUBLICUSER;
ANALYZE;

View File

@@ -620,6 +620,111 @@ TestClient.prototype.getFeatureAttributes = function(featureId, layerId, params,
);
};
TestClient.prototype.getClusterFeatures = function (zoom, clusterId, layerId, params, callback) {
var self = this;
if (!callback) {
callback = params;
params = {};
}
var extraParams = {};
if (this.apiKey) {
extraParams.api_key = this.apiKey;
}
// if (params && params.filters) {
// extraParams.filters = JSON.stringify(params.filters);
// }
var url = '/api/v1/map';
if (Object.keys(extraParams).length > 0) {
url += '?' + qs.stringify(extraParams);
}
var expectedResponse = params.response || {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
step(
function createLayergroup() {
var next = this;
assert.response(self.server,
{
url: url,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(self.mapConfig)
},
{
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
},
function(res, err) {
if (err) {
return next(err);
}
var parsedBody = JSON.parse(res.body);
if (parsedBody.layergroupid) {
self.keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
self.keysToDelete['user:localhost:mapviews:global'] = 5;
}
return next(null, parsedBody.layergroupid);
}
);
},
function getCLusterFeatures (err, layergroupId) {
assert.ifError(err);
var next = this;
let queryParams = '';
if (params.aggregation) {
queryParams = qs.stringify({ aggregation: JSON.stringify(params.aggregation) });
}
url = `/api/v1/map/${layergroupId}/${layerId}/${zoom}/cluster/${clusterId}?${queryParams}`;
assert.response(self.server,
{
url: url,
method: 'GET',
headers: {
host: 'localhost'
}
},
expectedResponse,
function(res, err) {
if (err) {
return next(err);
}
next(null, JSON.parse(res.body));
}
);
},
function finish(err, attributes) {
if (err) {
return callback(err);
}
return callback(null, attributes);
}
);
};
TestClient.prototype.getTile = function(z, x, y, params, callback) {
var self = this;