Compare commits

...

321 Commits

Author SHA1 Message Date
Raul Ochoa
c5afc0dc94 Release 2.41.1 2016-05-11 18:51:05 +02:00
Raul Ochoa
a7e00c5856 Merge pull request #447 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.8.0
2016-05-11 18:50:14 +02:00
Raul Ochoa
2482accb42 Upgrades camshaft to 0.8.0 2016-05-11 18:39:07 +02:00
Raul Ochoa
3e4f71d873 Nicer error message when missing sql from layer options
Fixes #446
2016-05-11 18:24:47 +02:00
Raul Ochoa
bbadd46766 Stubs next version 2016-05-11 16:47:33 +02:00
Raul Ochoa
b1f618a98e Release 2.41.0 2016-05-11 16:46:57 +02:00
Raul Ochoa
cdf3fe3a25 Bumps version 2016-05-11 16:45:36 +02:00
Raul Ochoa
1440841ac8 Merge pull request #445 from CartoDB/upgrade-camshaft
Upgrade camshaft to 0.7.0
2016-05-11 16:44:07 +02:00
Raul Ochoa
fc57fd2638 Upgrade camshaft to 0.7.0 2016-05-11 16:25:42 +02:00
Raul Ochoa
776cb8d47a Stubs next version 2016-05-10 17:26:27 +02:00
Raul Ochoa
c36f52415e Release 2.40.0 2016-05-10 17:23:50 +02:00
Raul Ochoa
2ee2c5bb55 Update news and bump version 2016-05-10 17:22:51 +02:00
Raul Ochoa
dccb557cd7 Merge pull request #444 from CartoDB/optimize-source-type-query-usage
Use original query from source nodes
2016-05-10 17:19:04 +02:00
Raul Ochoa
4570d17ce1 Use original query from source nodes
Doing a st_transform doesn't make sense as we already should have
the_geom_webmercator column available
2016-05-10 17:09:36 +02:00
Raul Ochoa
b3b3abcdb8 Merge pull request #443 from CartoDB/override-static-previews-params
Allow override zoom+center or bbox for static named maps previews
2016-05-09 22:53:35 +02:00
Raul Ochoa
6639664b3f Allow override zoom+center or bbox for static named maps previews 2016-05-09 21:13:13 +02:00
Raul Ochoa
b99db7cb69 Merge pull request #441 from CartoDB/sql-wrap
Analysis layers can have a sql_wrap option to wrap node queries
2016-05-09 14:09:16 +02:00
Raul Ochoa
3e94e3288f Use sql as replacement variable 2016-05-06 17:09:41 +02:00
Raul Ochoa
1115f9fba2 Merge pull request #442 from CartoDB/remove-repeated-config
Added config parameter for ST_RemoveRepeatedPoints
2016-05-06 17:01:23 +02:00
Daniel García Aubert
24dde1e4d0 Added config parameter to windshaft in order to use postgres' function ST_RemoveRepeatedPoints (set false by the default)
Conflicts:
	lib/cartodb/server_options.js
2016-05-06 16:50:25 +02:00
Raul Ochoa
7d4caf6974 Analysis layers can have a sql_wrap option to wrap node queries 2016-05-06 16:37:52 +02:00
Raul Ochoa
e4ba68850c Stubs next version 2016-05-05 18:36:50 +02:00
csobier
e2154f6561 Merge pull request #435 from CartoDB/docs-add-named-map-torque-link
link to named map with a torque layer block page
2016-05-05 12:35:41 -04:00
Raul Ochoa
778860c81f Release 2.39.0 2016-05-05 18:09:44 +02:00
Raul Ochoa
ee3c56efba Update news 2016-05-05 18:09:15 +02:00
Raul Ochoa
5dc328724a Merge pull request #440 from CartoDB/node-status-cache-control-header
Use a more aggressive cache control header for node status endpoint
2016-05-05 18:07:16 +02:00
Raul Ochoa
c77ea49594 Use a more aggressive cache control header for node status endpoint 2016-05-05 17:52:37 +02:00
Raul Ochoa
73aa159b98 Upgrade istanbul 2016-05-05 16:06:52 +02:00
Raul Ochoa
b7d7cffb67 Reformat package.json with npm cli tool 2016-05-05 16:05:46 +02:00
Raul Ochoa
eba8db292c Merge pull request #439 from CartoDB/avoid-profiler-dots
Upgrades step-profiler to 0.3.0 to avoid dots in json keys
2016-05-05 15:41:30 +02:00
Raul Ochoa
d5391ef15b Merge remote-tracking branch 'origin/master' into avoid-profiler-dots
Conflicts:
	NEWS.md
2016-05-05 15:30:12 +02:00
Francisco Dans
685cbb1eec updates news 2016-05-05 14:34:19 +02:00
Francisco Dans
5842a239fd shrinkwrap 2016-05-05 14:32:57 +02:00
Francisco Dans
285363aa40 bumps current version and turbocarto's 2016-05-05 14:32:11 +02:00
Raul Ochoa
92e130d8de Upgrades step-profiler to 0.3.0 to avoid dots in json keys
Closes #438
2016-05-05 13:45:25 +02:00
Francisco Dans
f674f90eba Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb 2016-05-05 12:28:01 +02:00
Francisco Dans
0542b65cbb shrinkwrap changes for 2.38.0 2016-05-05 12:26:12 +02:00
Francisco Dans
60fa94781e adds upgrade to NEWS 2016-05-05 12:25:12 +02:00
Raul Ochoa
38d57533c2 Discourage console usage, global.logger should be used when required 2016-05-05 12:18:22 +02:00
Raul Ochoa
e3d6da06a7 Remove console usage 2016-05-05 12:17:51 +02:00
Raul Ochoa
3af05bb734 Remove console usage 2016-05-05 12:17:33 +02:00
Francisco Dans
ffd58cc7ca bumps minor 2016-05-05 12:07:26 +02:00
Francisco Dans
e103c52d27 upgrades turbo carto 2016-05-05 12:04:14 +02:00
csobier
46a4defab7 added torque layer in named maps as a new section under the cartodb.js heading 2016-05-04 12:53:23 -04:00
Daniel García Aubert
00a71af95d Stubs next version 2016-05-03 17:58:47 +02:00
Daniel García Aubert
2fb8036740 Release 2.37.0 2016-05-03 17:53:08 +02:00
Daniel García Aubert
6902a8c9b3 Upgraded camashaft version to 0.6.0 2016-05-03 17:46:38 +02:00
csobier
fd8be0352f link to named map with a torque layer block page 2016-05-03 11:38:57 -04:00
Raul Ochoa
c19a345f6c Stubs next version 2016-04-29 12:54:47 +02:00
Raul Ochoa
bdc3ecff5a Release 2.36.1 2016-04-29 12:53:58 +02:00
Raul Ochoa
6c4ec29e18 Add test to validate new behaviour in camshaft 0.5.1 2016-04-29 12:51:48 +02:00
Raul Ochoa
412712e62e Upgrades camshaft to 0.5.1 2016-04-29 12:51:39 +02:00
Raul Ochoa
0edcb30f75 Stubs next version 2016-04-28 18:46:49 +02:00
Raul Ochoa
c0f49b3acb Release 2.36.0 2016-04-28 18:45:51 +02:00
Raul Ochoa
b926244085 Upgrades windshaft to 1.19.0 2016-04-28 17:30:57 +02:00
Raul Ochoa
d21136b475 Stubs next version 2016-04-27 18:44:47 +02:00
Raul Ochoa
8788aaaf25 Release 2.35.0 2016-04-27 18:43:50 +02:00
Raul Ochoa
120e800089 Bump version and update news 2016-04-27 18:42:56 +02:00
Raul Ochoa
92dc4b148c Merge pull request #432 from CartoDB/dataviews-columns
Append dataviews related columns to layers
2016-04-27 18:40:33 +02:00
Raul Ochoa
755dfe6822 Append dataviews related columns to layers 2016-04-27 18:30:05 +02:00
Daniel García Aubert
b787ee1033 Stubs next version 2016-04-27 15:12:13 +02:00
Daniel García Aubert
9ee3612da0 Release 2.34.1 2016-04-27 15:09:13 +02:00
Daniel García Aubert
fe0b0a9c50 Upgraded windshaft to 1.17.3 2016-04-27 15:05:45 +02:00
Raul Ochoa
dea19f20dd Stubs next version 2016-04-27 10:24:16 +02:00
Raul Ochoa
f9f70cc6e7 Release 2.34.0 2016-04-27 10:23:24 +02:00
Raul Ochoa
23ef1157cb Merge pull request #431 from CartoDB/dataviews-filters
Dataviews filters
2016-04-26 19:21:43 +02:00
Raul Ochoa
8ca4d537ce Update news 2016-04-26 19:18:04 +02:00
Raul Ochoa
98d5731555 Add test to validate latest windshaft uses dataviews filters
Windshaft is transforming dataview filters into widget filters
2016-04-26 19:13:30 +02:00
Raul Ochoa
5da8929a5d Upgrade windshaft 2016-04-26 19:13:10 +02:00
Raul Ochoa
e198bafac1 Merge pull request #429 from CartoDB/upgrade-turbo-carto
Starts using turbo-carto dependency
2016-04-26 16:56:11 +02:00
Raul Ochoa
dd731399dc Starts using turbo-carto dependency 2016-04-26 16:28:05 +02:00
csobier
66fd899ffb Merge pull request #426 from CartoDB/docs-801-misplaced-text
removed dup text from static api section
2016-04-24 14:16:40 -04:00
csobier
bca8a33417 removed dup text from static api section 2016-04-24 13:59:38 -04:00
Raul Ochoa
a11c8d882e Each error-case will have different expectations 2016-04-21 17:27:20 +02:00
Raul Ochoa
c5bed48d61 Handle missing analyses nodes for layers pointing to them
Fixes #422
2016-04-21 17:24:52 +02:00
Raul Ochoa
98b7d12796 Merge pull request #424 from CartoDB/analysis-named-metadata
Analysis named maps metadata
2016-04-21 17:10:36 +02:00
Raul Ochoa
93dd8a2213 Add analyses metadata for named maps excluding queries 2016-04-21 17:03:41 +02:00
Raul Ochoa
4e4a223f24 Better naming for analysis mapconfig adapter 2016-04-21 16:25:59 +02:00
Raul Ochoa
4a73f3874d Better naming 2016-04-21 16:18:17 +02:00
Raul Ochoa
bc845b2e8d Validate dataviews format before instantiating 2016-04-21 16:16:00 +02:00
Raul Ochoa
83eceb349c Merge pull request #423 from CartoDB/multierror-support
Adds support to return multiple errors in BaseController.sendError
2016-04-21 16:15:28 +02:00
Raul Ochoa
bb518f0744 Update news 2016-04-21 16:09:57 +02:00
Raul Ochoa
08ad961123 Adds support to return multiple errors in BaseController.sendError 2016-04-21 16:05:48 +02:00
Raul Ochoa
146d494cae Adds dataview example in named map 2016-04-21 15:35:45 +02:00
Javier Goizueta
bb40ecf7c2 Stub next version 2016-04-20 18:30:42 +02:00
Javier Goizueta
7de9f64f2a Release 2.33.1 2016-04-20 18:27:06 +02:00
Javier Goizueta
0bb6178d49 Merge pull request #421 from CartoDB/420-overviews-schema
Support unneeded schema names in overviews queries
2016-04-20 18:16:16 +02:00
Javier Goizueta
084b3e94a6 Remove unneeded variable 2016-04-20 18:01:34 +02:00
Javier Goizueta
a0445b5cdd 💄 Fix indentation
2 spaces were used instead of 4 in some places
2016-04-20 17:47:43 +02:00
Javier Goizueta
1d4ddd373b Remove unneeded callback from synchronous tests 2016-04-20 17:27:52 +02:00
Raul Ochoa
92795963ae Stubs next version 2016-04-20 17:26:30 +02:00
Raul Ochoa
e634ad6f7d Release 2.33.0 2016-04-20 17:24:17 +02:00
Javier Goizueta
ecbae52abe Refactor: use reduce for collecting overviews metadata 2016-04-20 17:24:16 +02:00
Raul Ochoa
e1c5af8602 Bump version and update news 2016-04-20 17:15:17 +02:00
Raul Ochoa
6704a94f36 Merge pull request #418 from CartoDB/analysis-layers
Analysis layers
2016-04-20 17:13:14 +02:00
Raul Ochoa
a35403cd91 Add option to modify host header template for camshaft batch client 2016-04-20 16:36:29 +02:00
Raul Ochoa
41ead9664b Upgrade camshaft to 0.5.0 2016-04-20 16:34:48 +02:00
Raul Ochoa
e04a9a2579 Append dataviews filters after checking if mapconfig must be adapted 2016-04-20 15:40:14 +02:00
Raul Ochoa
d70af7c9c1 Fix tests with typo in s/radio/radius/ 2016-04-20 15:33:17 +02:00
Raul Ochoa
d910f5ef3e Update camshaft to use 0.4.0 fixed version 2016-04-20 15:33:04 +02:00
Javier Goizueta
57cba3d511 Fix comment 2016-04-20 14:30:13 +02:00
Javier Goizueta
7902b276ad Support unneeded schema names in overviews queries
Fixes #420
Keep table schema of overviews base tables and use it
to support queries that use the schema name when not
strictly needed.
2016-04-19 22:50:05 +02:00
Raul Ochoa
f932862ce4 Improve configuration for batch queries 2016-04-18 15:13:00 +02:00
Raul Ochoa
ab55b083b4 Style 2016-04-18 14:48:14 +02:00
Raul Ochoa
263b3e3682 Rename file 2016-04-18 14:47:35 +02:00
Raul Ochoa
5baad96924 remove commented out code 2016-04-18 14:43:29 +02:00
Raul Ochoa
68b19c65fe newline at end of file 2016-04-18 14:40:34 +02:00
Raul Ochoa
3a81302699 Merge branch 'master' into analysis-layers
Conflicts:
	package.json
2016-04-18 14:36:43 +02:00
Raul Ochoa
6b942ecf0b Merge pull request #417 from CartoDB/update-cartodb-psql
Upgrades cartodb-psql to 0.6.1 version
2016-04-18 14:33:59 +02:00
Raul Ochoa
1ed0c73525 Upgrades cartodb-psql to 0.6.1 version. 2016-04-18 14:30:20 +02:00
Raul Ochoa
7175b2b05e Merge branch 'master' into analysis-layers 2016-04-18 14:09:57 +02:00
Raul Ochoa
61137610c7 Merge pull request #416 from CartoDB/update-windshaft
Upgrades windshaft to 1.17.1
2016-04-18 13:53:57 +02:00
Raul Ochoa
b53031972e Upgrades windshaft to 1.17.1 2016-04-18 13:18:54 +02:00
Raul Ochoa
da602eeda0 Use inline execution in camshaft instead of a database service stub 2016-04-14 17:25:08 +02:00
Raul Ochoa
a26025b259 Add analysis backend so it's possible to inject configuration 2016-04-14 17:09:07 +02:00
Raul Ochoa
25a61c8479 Remove console log 2016-04-14 17:08:23 +02:00
Raul Ochoa
e73b64bfed Merge branch 'master' into analysis-layers 2016-04-14 13:40:51 +02:00
Raul Ochoa
b5b8085444 Reformat for better indentation 2016-04-14 13:40:02 +02:00
Raul Ochoa
388f08a277 Remove unneeded comma 2016-04-14 13:39:19 +02:00
Raul Ochoa
aa36236ed2 Rename analysis to analysis status backend, making room for analysis backend 2016-04-14 13:25:56 +02:00
Raul Ochoa
a9ca453b17 Remove some JSON.stringify 2016-04-14 13:20:22 +02:00
Raul Ochoa
9ab4eb5801 Change error expectation 2016-04-14 12:56:20 +02:00
Raul Ochoa
26149e7755 cdb_analysis_catalog is already retrieved from camshaft 2016-04-14 11:53:48 +02:00
Raul Ochoa
2d4fd62acf Do not create triggers for tests 2016-04-14 11:44:17 +02:00
Raul Ochoa
e037c8c1b2 Do not use layer index as analysis node is are unique 2016-04-14 11:08:39 +02:00
Raul Ochoa
09687b3811 Proper endpoint to check node status from analysis 2016-04-14 10:59:51 +02:00
Raul Ochoa
28400f4544 Better naming 2016-04-13 19:15:06 +02:00
Raul Ochoa
7a87b8ebef Use node id instead of param id for endpoint
It will be easier to retrieve node status with that id
2016-04-13 18:04:49 +02:00
Raul Ochoa
9ff661480b Do not append root as it is already included in sorted nodes 2016-04-13 18:02:28 +02:00
Raul Ochoa
ca564bfaad Add fake analysis node status endpoint 2016-04-11 18:49:56 +02:00
Raul Ochoa
dd36877a20 Add per node status 2016-04-11 18:49:43 +02:00
Raul Ochoa
1d860fd202 Remove top level url for analysis 2016-04-11 18:49:22 +02:00
Raul Ochoa
e9111ec3bb Update routes document 2016-04-08 11:13:40 +02:00
Raul Ochoa
0981ccd0c4 Add metadata information about analyses 2016-04-07 17:58:12 +02:00
Raul Ochoa
077c4ab907 Adds analysis MapConfig adapter to named maps 2016-04-07 16:18:48 +02:00
Raul Ochoa
efacafaa0d Merge remote-tracking branch 'origin/master' into analysis-layers 2016-04-07 15:04:25 +02:00
Raul Ochoa
1c250bf243 Remove dependency 2016-04-07 14:30:49 +02:00
Daniel García Aubert
c974b91c6b Stubs next version 2016-04-06 19:32:16 +02:00
Daniel García Aubert
6aebe26cc9 Release 2.32.0 2016-04-06 19:29:07 +02:00
Daniel García Aubert
f93794717e Upgrades windshaft to 1.17.0 2016-04-06 19:26:55 +02:00
Raul Ochoa
0ebf482936 Merge pull request #410 from CartoDB/named-dynamic-styling
Overrided cartocss in the instantiation of named maps
2016-04-06 18:09:11 +02:00
Daniel García Aubert
b5b8083acd Overrided cartocss in the instantiation of named maps 2016-04-06 17:43:25 +02:00
Raul Ochoa
ab6bae6a7f Merge branch 'master' into analysis-layers 2016-04-04 16:24:31 +02:00
Javier Goizueta
219658761f Stub next version 2016-04-04 14:34:35 +02:00
Javier Goizueta
e3a68a6b4d Release 2.31.2
This fixes a couple of overviews-related bugs
2016-04-04 14:31:57 +02:00
Javier Goizueta
01218c6ea1 Merge pull request #409 from CartoDB/405-wrapped-overviews-queries
Support overviews for named layer wrapped queries
2016-04-04 14:18:57 +02:00
Javier Goizueta
83d27a8e29 Merge pull request #407 from CartoDB/400-named-layer-overviews
Fix overviews integration for named layers
2016-04-04 14:18:34 +02:00
Javier Goizueta
fa2e884605 Support overviews for named layer wrapped queries
Fix #405
2016-04-01 15:40:44 +02:00
Javier Goizueta
f4554f41d2 Add tests for named maps overviews 2016-03-31 18:36:50 +02:00
Javier Goizueta
b97a67b844 Fix overviews integration for named layers
Fixes #400
2016-03-31 18:33:41 +02:00
csobier
3b13fad5e7 Merge pull request #403 from CartoDB/docs-592-consistent-placeholder-examples
added tip about placeholder format and errors
2016-03-28 07:12:59 -04:00
Raul Ochoa
4d39d30f6e Merge branch 'master' into analysis-layers
Conflicts:
	npm-shrinkwrap.json
2016-03-23 16:09:28 +01:00
Raul Ochoa
9a8964bd39 Stubs next version 2016-03-23 15:14:52 +01:00
Raul Ochoa
aae251b178 Release 2.31.1 2016-03-23 15:09:38 +01:00
Raul Ochoa
2db7a3d110 Merge pull request #404 from CartoDB/upgrade-windshaft
Upgrade windshaft to 1.16.1
2016-03-23 15:08:23 +01:00
Raul Ochoa
5fe96a618d Upgrade windshaft to 1.16.1 2016-03-23 15:01:30 +01:00
Raul Ochoa
499178319d Add search endpoint/backend for dataviews 2016-03-23 12:14:17 +01:00
Raul Ochoa
7b7e9ffe59 Upgrade cartodb-psql module to get some bufixes 2016-03-23 12:13:26 +01:00
csobier
43d1e5c613 changed documentation variable to username throughout, and other clean-up 2016-03-22 11:23:04 -04:00
csobier
8547cb836f consistent placeholder examples with brackets 2016-03-22 09:19:50 -04:00
Raul Ochoa
2bd3e46a4d Build dataviews with factory to generalise them 2016-03-22 13:10:42 +01:00
Raul Ochoa
e44b5eaccd Fix test 2016-03-22 13:10:37 +01:00
Raul Ochoa
26512f6485 Remove unused function 2016-03-22 12:22:48 +01:00
Raul Ochoa
90b92f0180 Adds support for category filters 2016-03-22 12:22:04 +01:00
Raul Ochoa
ebe25761d2 Extract variable 2016-03-22 10:52:02 +01:00
Raul Ochoa
f928147559 Fix bbox template 2016-03-21 18:16:54 +01:00
Raul Ochoa
d5c5c7bdbb Do not remove analysis, camshaft takes care of source root nodes now 2016-03-21 18:02:19 +01:00
Raul Ochoa
ff147ca3bf Add dataviews to layergroup metadata 2016-03-18 18:09:17 +01:00
Raul Ochoa
f745e915d3 Own filter test for dataviews 2016-03-18 17:49:20 +01:00
Raul Ochoa
1e239658d8 Just remove analysis if there are analysis 2016-03-18 17:34:40 +01:00
Raul Ochoa
52f35d74b9 Allow a higher jshint maxcomplexity 2016-03-18 17:31:28 +01:00
Raul Ochoa
57e6e49749 Another workaround to not delete analyses if there are dataviews 2016-03-18 17:28:36 +01:00
Raul Ochoa
b3bbb9d97a Initial checkin for dataviews
It only supports histograms.
2016-03-18 17:22:02 +01:00
Raul Ochoa
697749b204 Add timer helper 2016-03-18 17:21:43 +01:00
Raul Ochoa
a769545e39 Rely on camshaft.reference for now 2016-03-18 17:16:51 +01:00
Raul Ochoa
df40302fd0 Add camshaft-reference 2016-03-18 13:04:00 +01:00
csobier
85e5a89298 added tip about placeholder format and errors 2016-03-17 17:42:57 -04:00
Raul Ochoa
5bd30b6b5f Analysis layers adapter skips analysis if there is only source nodes 2016-03-17 12:50:42 +01:00
Raul Ochoa
ed84ed8475 Test client detect png request based on regex 2016-03-17 12:45:05 +01:00
Raul Ochoa
a444b80c96 Fix typo 2016-03-17 12:44:51 +01:00
Daniel García Aubert
fc94f3ad94 Fixed stub next version 2016-03-16 18:07:28 +01:00
Daniel García Aubert
d43b766448 Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb 2016-03-16 17:40:49 +01:00
Daniel García Aubert
6ceca348cb Stubs next version 2016-03-16 17:40:30 +01:00
Daniel García Aubert
bff6f056d0 Stubs next version 2016-03-16 17:33:26 +01:00
Daniel García Aubert
366a66b331 Release 2.31.0 2016-03-16 17:30:58 +01:00
Raul Ochoa
43c56af0fc Merge remote-tracking branch 'origin/master' into analysis-layers
Conflicts:
	npm-shrinkwrap.json
2016-03-16 16:13:44 +01:00
Daniel García Aubert
c0370c5703 Stubs next version 2016-03-15 17:45:50 +01:00
Daniel García Aubert
8d5551343b Release 2.30.0 2016-03-15 17:40:26 +01:00
Daniel García Aubert
4089e8537a Upgraded windshaft to version 1.15.0 2016-03-15 17:35:13 +01:00
csobier
cf06c6e974 Merge pull request #401 from CartoDB/docs-685-named-map-tiles-and-cleanup
moved fetch tiles section up one heading level
2016-03-15 09:16:40 -04:00
csobier
2ebab4c89e moved fetch tiles section up one heading level 2016-03-15 09:08:28 -04:00
csobier
8023fbae24 Merge pull request #379 from CartoDB/docs-685-named-map-tiles-and-cleanup
Docs 685 named map tiles and cleanup
2016-03-15 08:50:20 -04:00
csobier
35cb652c6c edited token placeholder for consistency 2016-03-14 13:01:10 -04:00
csobier
c6085779a4 use the layergroupid, not SRID, as the mapnik token 2016-03-14 12:25:59 -04:00
csobier
86232eb239 clarified mapnik description and removed unnecessary content, as instructed by Carla 2016-03-14 11:51:07 -04:00
Raul Ochoa
6db48a24b8 Adds test for analysis with no api key 2016-03-14 16:42:51 +01:00
csobier
5cc3e914fa added missing bracket, removed note about API key 2016-03-14 11:34:52 -04:00
Raul Ochoa
1da937d639 Add commented code to generate image output for validation 2016-03-14 16:19:55 +01:00
Raul Ochoa
4924bcc298 Validate image from analysis 2016-03-14 16:16:27 +01:00
Raul Ochoa
a05f3d6ee9 Add cdb_analysis_catalog table and first test using source:id 2016-03-14 16:06:25 +01:00
Daniel García Aubert
d52d3d909f Fixed double-check error in turbo-cartocss preprocesing. 2016-03-14 15:25:56 +01:00
Raul Ochoa
eec44dd62d Merge branch 'master' into analysis-layers
Conflicts:
	npm-shrinkwrap.json
	test/support/test-client.js
2016-03-14 15:13:19 +01:00
Daniel García Aubert
e7da9a151b Stubs next version 2016-03-14 14:47:02 +01:00
Daniel García Aubert
f75ba9d2c3 Release 2.29.0 2016-03-14 14:42:40 +01:00
Daniel García Aubert
be61d41f5e Stubs next version 2016-03-14 12:12:56 +01:00
Daniel García Aubert
f619a97f1a Release 2.28.0 2016-03-14 12:06:33 +01:00
Raul Ochoa
3f41f19ab9 Rename adapter 2016-03-14 11:50:52 +01:00
Daniel
e47449e357 Merge pull request #399 from CartoDB/move-turbo-cartocss
Move turbo cartocss
2016-03-14 11:35:06 +01:00
Daniel García Aubert
178345ab12 Fixed typo 2016-03-14 11:18:32 +01:00
Daniel García Aubert
a8340fef68 Bump image tolerance in turbo-cartocss test 2016-03-11 18:33:52 +01:00
Daniel García Aubert
052b58ab90 Moved turbo-cartocss integration from named maps admin to named map provider 2016-03-11 18:28:14 +01:00
Daniel García Aubert
cc5443152b Now turbo-cartocss is also parsed in template modification. 2016-03-11 11:06:51 +01:00
Daniel García Aubert
d937d8970d Fixed broken test in turbo-cartocss for named maps 2016-03-10 21:25:01 +01:00
Daniel García Aubert
dab4b6d56b Implemented integration of turbo-cartocss for named maps 2016-03-10 20:45:00 +01:00
Raul Ochoa
1535820d4f Regenerate npm-shrinkwrap.json 2016-03-10 11:32:43 +01:00
Raul Ochoa
b2378939c5 Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb into analysis-layers 2016-03-10 11:14:10 +01:00
Daniel García Aubert
46b212b2cd Merge branch 'move-turbo-cartocss' of github.com:CartoDB/Windshaft-cartodb into move-turbo-cartocss 2016-03-09 20:14:06 +01:00
Daniel García Aubert
f47842c96d Integrated turbo-cartocss adapter for named maps 2016-03-09 20:12:51 +01:00
Raul Ochoa
050f90a07b Regenerate npm-shrinkwrap.json 2016-03-09 18:11:34 +01:00
Raul Ochoa
c1642dfa73 Merge branch 'master' into move-turbo-cartocss
Conflicts:
	lib/cartodb/controllers/map.js
2016-03-09 18:08:46 +01:00
Raul Ochoa
bbfcc640d1 Style 2016-03-09 18:08:06 +01:00
Raul Ochoa
e9b8c512c9 Re-indent 2016-03-09 18:07:11 +01:00
Raul Ochoa
15b54a2918 Re-indent 2016-03-09 18:05:17 +01:00
Alejandro Martínez
cf8ce42049 Release 2.27.0 2016-03-09 17:57:18 +01:00
Raul Ochoa
7fa8d1e0c9 Analyses are now an array and layers consume from their nodes
Layers now can define a `source: {id: 'a0'}` option to point to an
analysis node that will be used as the query for that layer.
2016-03-09 17:39:20 +01:00
Daniel García Aubert
eefa9f4222 Merge branch 'master' into move-turbo-cartocss 2016-03-09 12:03:29 +01:00
Daniel García Aubert
a0073da4b3 Added regression test for turbo-cartocss' integration 2016-03-09 11:48:07 +01:00
Daniel García Aubert
c6fbb08c8f Regenerated npm-shrinkwrap 2016-03-09 10:36:57 +01:00
Daniel García Aubert
affa254b9d Moved and adapted acceptance test for turbo-cartocss integration 2016-03-08 20:06:43 +01:00
Raul Ochoa
d5fbcfcecb Merge branch 'master' into analysis-layers
Conflicts:
	npm-shrinkwrap.json
2016-03-08 16:06:01 +01:00
Raul Ochoa
ecd33e5561 TestClient with method to retrieve tiles 2016-03-08 15:56:08 +01:00
Raul Ochoa
9d77aea6be Regenerate npm-shrinkwrap.json 2016-03-08 15:23:56 +01:00
Raul Ochoa
20609bc37e Merge pull request #397 from CartoDB/remove-deprecated-tools
Remove deprecated tools directory
2016-03-08 15:14:06 +01:00
Raul Ochoa
b56d110f50 Remove deprecated tools directory 2016-03-08 15:07:37 +01:00
Daniel García Aubert
3e0c19a669 Fixed typo 2016-03-08 14:41:10 +01:00
Daniel García Aubert
ab6004f21e Integrated turbo-cartocss for anonymous maps 2016-03-08 14:34:57 +01:00
Alejandro Martínez
34863765ed Freeze cartodb-query-tables version 2016-03-08 10:05:49 +00:00
csobier
32223baaef removed internal code, displayed somexyz examples 2016-03-07 10:53:51 -05:00
Raul Ochoa
3cb007d147 Merge pull request #388 from CartoDB/new_querytables_library
Use new querytables library
2016-03-07 16:42:25 +01:00
csobier
2f038f006b applied consistent terminology and applied rest of Carla's edits 2016-03-07 09:08:38 -05:00
Raul Ochoa
634a4c2a01 Debug option for internal nodes: it allows to display and customize cartocss 2016-03-04 16:20:23 +01:00
Raul Ochoa
f504807812 Regenerate npm-shrinkwrap.json 2016-03-04 12:12:12 +01:00
Raul Ochoa
c7bdabfc65 Merge branch 'new_querytables_library' into analysis-layers 2016-03-04 12:08:57 +01:00
Raul Ochoa
16a7c4fa3d Merge branch 'master' into new_querytables_library
Conflicts:
	test/support/prepare_db.sh
	test/support/sql/CDB_QueryStatements.sql
2016-03-04 00:39:14 +01:00
Raul Ochoa
3979cda8c2 Change comment about regex 2016-03-04 00:11:06 +01:00
Raul Ochoa
250d52f72c Merge pull request #395 from CartoDB/no-plpythonu-pg-dependency
No plpythonu pg dependency
2016-03-03 23:58:18 +01:00
Raul Ochoa
26e5b4f404 Do not install postgresql-plpython-9.3 in travis, so no more sudo:true 2016-03-03 20:51:53 +01:00
Raul Ochoa
f19c1a34ec Implement CDB_QueryStatements as SQL language function
Do not relies on cartodb-postgresql extension which relies on
plpythonu language. That avoid installing it in travis-ci.
2016-03-03 20:48:37 +01:00
Raul Ochoa
94c7bc41be Merge branch 'master' into new_querytables_library 2016-03-03 19:30:51 +01:00
Raul Ochoa
df0597f12a Rename suite 2016-03-03 19:29:42 +01:00
Raul Ochoa
52cb224225 Add integration test with QueryTables module
This tests should be better placed at cartodb-query-tables repo but
it's easier to do it here. Lazy dev.
2016-03-03 19:27:44 +01:00
Raul Ochoa
baf87e90d7 Just callback as result is handled internally 2016-03-03 19:25:32 +01:00
Javier Goizueta
6c3fde70e8 Stub next version 2016-03-03 19:05:06 +01:00
Raul Ochoa
d9f6df9815 Remove nested step call 2016-03-03 19:01:58 +01:00
Javier Goizueta
b2539f52b8 Release 2.26.3 2016-03-03 19:01:40 +01:00
Raul Ochoa
7c154dd405 Add notes about why we keep feeding the layergroupAffectedTables cache 2016-03-03 19:01:21 +01:00
Javier Goizueta
47dfdf964e Merge pull request #390 from CartoDB/overviews-optimization
Optimize overviews queries
2016-03-03 18:59:24 +01:00
Raul Ochoa
10a602a4f3 Merge remote-tracking branch 'origin/master' into analysis-layers 2016-03-03 17:52:05 +01:00
Raul Ochoa
e3a5c52ebf Merge branch 'master' into analysis-layers 2016-03-03 17:51:46 +01:00
Raul Ochoa
66aea5e10f Merge pull request #393 from CartoDB/travis-pg-93
Back to pg 9.3 and postgresql-plpython-9.3 using sudo=true build
2016-03-03 17:39:28 +01:00
Raul Ochoa
e0d18e3c20 Back to pg 9.3 and postgresql-plpython-9.3 using sudo=true build 2016-03-03 17:20:22 +01:00
Raul Ochoa
4b79d06ae3 Back to pg 9.3 and postgresql-plpython-9.3 using sudo=true build 2016-03-03 16:20:20 +01:00
Raul Ochoa
f9c0e29db0 Dataviews separated from analysis
They are just another consumer of the analysis as layers are.
2016-03-03 12:07:05 +01:00
Raul Ochoa
e53d823b5a Fix total population column name for widget 2016-03-03 12:04:03 +01:00
Raul Ochoa
31dede5d06 Notes to make clear the total-population analysis 2016-03-03 12:01:49 +01:00
Raul Ochoa
69142964c6 fix trade-area params 2016-03-03 11:54:50 +01:00
Raul Ochoa
2eac808e18 Change analysis name so it's easier to understand 2016-03-03 11:45:37 +01:00
Javier Goizueta
4e40a61795 Change form of overviews queries so they can be optimized
The PostgreSQL planner wasn't applying the spatial filtering of
tile bounds to the queries efficiently.
2016-03-02 19:25:08 +01:00
Raul Ochoa
011b60eeab Change ids 2016-03-02 13:27:53 +01:00
Raul Ochoa
16654c016a Style 2016-03-02 13:27:45 +01:00
Raul Ochoa
9b9e6b13b7 Fix query table 2016-03-02 13:27:28 +01:00
Raul Ochoa
3b11525cfb Add analysis use cases that we need to support 2016-03-02 12:43:14 +01:00
Raul Ochoa
6823fd8b03 Better datasets for analysis 2016-03-02 12:43:00 +01:00
Raul Ochoa
ce032fcc96 Improve styling in analysis layers 2016-03-02 12:42:42 +01:00
Raul Ochoa
a44477dddc TestClient with method to retrieve tiles 2016-03-02 12:40:53 +01:00
Javier Goizueta
2a789b5a5b Merge pull request #386 from CartoDB/384-overviews-error-message
Change error messages when getting overviews metadata fails
2016-02-26 19:10:09 +01:00
Raul Ochoa
3709d1f1d5 Merge branch 'master' into analysis-layers 2016-02-25 11:46:41 +01:00
Raul Ochoa
1789993467 Stubs next version 2016-02-25 11:43:36 +01:00
Raul Ochoa
e958f925d3 Merge branch 'master' into analysis-layers 2016-02-24 17:28:03 +01:00
Raul Ochoa
d923b343fc Merge branch 'master' into analysis-layers 2016-02-24 10:56:19 +01:00
Raul Ochoa
b9d2e297b6 Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb into analysis-layers 2016-02-24 10:35:28 +01:00
Alejandro Martínez
0036056c07 Reopen PR 2016-02-23 19:31:38 +01:00
Alejandro Martínez
dcf156ba21 Merge remote-tracking branch 'origin/master' into new_querytables_library 2016-02-23 19:20:10 +01:00
Javier Goizueta
f0a1e7a0e0 Simplify error passing 2016-02-23 18:15:14 +01:00
Javier Goizueta
21f3c8a387 Change error messages when getting overviews metadata fails
Remove the detail that the error occurred trying to get overviews
metadata from the error message. This should be less confusing
to the user.
2016-02-23 11:45:26 +01:00
Alejandro Martínez
e491c0b825 Rename node-cartodb-query-tables to cartodb-query-tables 2016-02-22 19:11:54 +01:00
csobier
14f58e12bb moved response info from template.json section to the related Response section, and left the name description as part of the template.json description 2016-02-22 11:41:06 -05:00
Alejandro Martínez
37fcfe69c7 Merge remote-tracking branch 'origin/master' into new_querytables_library 2016-02-22 15:35:36 +01:00
Alejandro Martínez
850f1cb7f4 Remove stray spaces 2016-02-22 15:28:14 +01:00
Alejandro Martínez
e67f7b0d0e Drop old QueryTablesApi 2016-02-22 15:26:06 +01:00
Alejandro Martínez
ba8e3d419e Fix package.json 2016-02-22 15:09:09 +01:00
csobier
adefa8b819 applied Carla's edits to date 2016-02-22 09:01:04 -05:00
Alejandro Martínez
2d6ee93448 Delete query_tables_api.js, wrap shrinkwrap 2016-02-22 13:40:20 +00:00
Alejandro Martínez
cf06ff86c2 Use node-cartodb-query-tables library 2016-02-22 11:40:25 +01:00
csobier
cfa2714dbb added fetch tile in for Maps API 2016-02-19 11:45:06 -05:00
Raul Ochoa
30f8234bd0 Use analysis configuration as per new camshaft api 2016-02-19 17:13:28 +01:00
Raul Ochoa
ae8e3f2ef8 Merge branch 'master' into analysis-layers 2016-02-19 15:45:56 +01:00
csobier
4d6b4b1755 added xyz tile info-first draft 2016-02-18 16:58:28 -05:00
csobier
0994571895 clean-up portion of Named Maps doc-WIP 2016-02-18 13:15:28 -05:00
Raul Ochoa
6d91172630 Remove console.log 2016-02-18 13:43:39 +01:00
Alejandro Martínez
e1732076fc Merge branch 'new_querytables' of github.com:CartoDB/Windshaft-cartodb into new_querytables 2016-02-17 15:37:06 +01:00
Alejandro Martínez
587f66c23d Sort cache channels and keys alphabetically 2016-02-17 15:36:26 +01:00
Raul Ochoa
3d0c0f34ad Use a set to compare surrogate keys, avoiding key order errors 2016-02-17 12:18:57 +01:00
Raul Ochoa
8d4ebc171b Use a set to compare surrogate keys, avoiding key order errors 2016-02-17 12:15:43 +01:00
Raul Ochoa
4d3c21f1bc Merge remote-tracking branch 'origin/new_querytables' into analysis-layers 2016-02-17 11:48:09 +01:00
Raul Ochoa
6ece30fa2c Ignore CDB_ sql files downloaded for tests 2016-02-17 11:47:27 +01:00
Raul Ochoa
dfcb3b6dc1 Merge branch 'new_querytables' into analysis-layers 2016-02-17 11:31:55 +01:00
Raul Ochoa
87b4a37f3f Move camshaft dep to avoid future conflicts while updating windshaft 2016-02-17 11:29:40 +01:00
Raul Ochoa
4db2c715a0 Merge branch 'master' into analysis-layers
Conflicts:
	npm-shrinkwrap.json
	package.json
2016-02-17 11:29:11 +01:00
Alejandro Martínez
19596245b8 Fix long line 2016-02-15 16:21:13 +01:00
Alejandro Martínez
0e83420e24 Fix long line 2016-02-15 16:15:43 +01:00
Alejandro Martínez
119846b56b Fix specs 2016-02-15 16:04:13 +01:00
Alejandro Martínez
33ba629c6d Add publicuser creation to Travis config 2016-02-15 13:00:21 +01:00
Alejandro Martínez
9e7b288f44 Merge remote-tracking branch 'origin/master' into new_querytables 2016-02-15 11:47:29 +00:00
Raul Ochoa
2f51ad9c3f Regenerate npm-shrinkwrap.json to include camshaft dep 2016-02-12 18:49:01 +01:00
Raul Ochoa
ed1f753690 Fix style 2016-02-12 18:45:46 +01:00
Raul Ochoa
bcf3ce71ef Adds experimental adapter to use queries based on camshaft analysis 2016-02-12 18:38:06 +01:00
Alejandro Martínez
a656285001 Run tests against master cartodb-postgresql 2016-02-12 17:25:57 +01:00
Raul Ochoa
354c982ea0 Fix jsdoc 2016-02-12 16:12:02 +01:00
Alejandro Martínez
b7ff554209 Use new _Updated_At function and new names 2016-02-11 11:45:09 +01:00
Alejandro Martínez
95ab99be4d Use new CDB_QueryTablesUpdatedAt function 2016-02-09 19:06:34 +01:00
110 changed files with 7682 additions and 3136 deletions

View File

@@ -82,13 +82,14 @@
// "wsh" : false, // Windows Scripting Host
// "yui" : false, // Yahoo User Interface
// Custom Globals
"globals" : { // additional predefined global variables
"describe": true,
"before": true,
"after": true,
"beforeEach": true,
"afterEach": true,
"it": true
}
// Custom predefined global variables
"predef": [
"-console", // disallows console, use debug
"beforeEach",
"afterEach",
"before",
"after",
"describe",
"it"
]
}

View File

@@ -1,18 +1,18 @@
sudo: false
addons:
postgresql: "9.4"
postgresql: "9.3"
apt:
packages:
- postgresql-plpython-9.4
- pkg-config
- libcairo2-dev
- libjpeg8-dev
- libgif-dev
- pkg-config
- libcairo2-dev
- libjpeg8-dev
- libgif-dev
before_install:
- npm install -g npm@2
- createdb template_postgis
- createuser publicuser
- psql -c "CREATE EXTENSION postgis" template_postgis
env:

212
NEWS.md
View File

@@ -1,5 +1,217 @@
# Changelog
## 2.41.1
Released 2016-05-11
Announcements:
- Upgrades camshaft to [0.8.0](https://github.com/CartoDB/camshaft/releases/tag/0.8.0)
Bug fixes:
- Nicer error message when missing sql from layer options #446
## 2.41.0
Released 2016-05-11
Announcements:
- Upgrades camshaft to [0.7.0](https://github.com/CartoDB/camshaft/releases/tag/0.7.0)
## 2.40.0
Released 2016-05-10
Enhancements:
- Use original query from source nodes #444
New features:
- Allow override zoom+center or bbox for static named maps previews #443
- Analysis layers can have a sql_wrap option to wrap node queries #441
## 2.39.0
Released 2016-05-05
Announcements:
- Upgrades step-profiler to 0.3.0 to avoid dots in json keys #438
- Use a more aggressive cache control header for node status endpoint
## 2.38.1
Released 2016-05-05
Announcements:
- Fixes problem in turbo-carto dependency
- Removes console usages
## 2.38.0
Released 2016-05-05
Announcements:
- Upgrades turbo-carto to [0.7.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.7.0)
## 2.37.0
Released 2016-05-03
Announcements:
- Upgrades camshaft to [0.6.0](https://github.com/CartoDB/camshaft/releases/tag/0.6.0)
## 2.36.1
Released 2016-04-29
Announcements:
- Upgrades camshaft to [0.5.1](https://github.com/CartoDB/camshaft/releases/tag/0.5.1)
## 2.36.0
Released 2016-04-28
Announcements:
- Upgrades windshaft to [1.19.0](https://github.com/CartoDB/Windshaft/releases/tag/1.19.0)
## 2.35.0
Released 2016-04-27
Announcements:
- Upgrades windshaft to [1.18.0](https://github.com/CartoDB/Windshaft/releases/tag/1.18.0)
- Appends columns to layers from associated dataviews
## 2.34.1
Released 2016-04-27
Announcements:
- Upgrades windshaft to [1.17.3](https://github.com/CartoDB/Windshaft/releases/tag/1.17.3)
## 2.34.0
Released 2016-04-27
Enhancements:
- Adds support to return multiple errors in BaseController.sendError #423
- Starts using turbo-carto dependency
Announcements:
- Upgrades windshaft to [1.17.2](https://github.com/CartoDB/Windshaft/releases/tag/1.17.2)
## 2.33.1
Released 2016-04-20
Bug fixes:
- Support unneeded schema names in overviews queries #421
## 2.33.0
Released 2016-04-20
New features:
- Adds experimental support for analysis and dataviews
Announcements:
- Upgrades cartodb-psql to 0.6.1 version.
- Upgrades windshaft to [1.17.1](https://github.com/CartoDB/Windshaft/releases/tag/1.17.1)
## 2.32.0
Released 2016-04-06
New features:
- Added support for dynamic styling for widgets in named maps
Announcements:
- Upgrades windshaft to [1.17.0](https://github.com/CartoDB/Windshaft/releases/tag/1.17.0)
## 2.31.2
Released 2016-04-04
Bug fixes:
- Overviews integration for named layers #400
- Support wrapped queries in named layers #405
## 2.31.1
Released 2016-03-23
Announcements:
- Upgrades windshaft to [1.16.1](https://github.com/CartoDB/Windshaft/releases/tag/1.16.1)
## 2.31.0
Released 2016-03-16
Announcements:
- Upgrades windshaft to [1.16.0](https://github.com/CartoDB/Windshaft/releases/tag/1.16.0)
## 2.30.0
Released 2016-03-15
Announcements:
- Upgrades windshaft to [1.15.0](https://github.com/CartoDB/Windshaft/releases/tag/1.15.0)
## 2.29.0
Released 2016-03-14
Announcements:
- Upgrades windshaft to [1.14.0](https://github.com/CartoDB/Windshaft/releases/tag/1.14.0)
## 2.28.0
Released 2016-03-14
New features:
- Added [turbo-cartocss](https://github.com/CartoDB/turbo-cartocss) to preprocess CartoCSS.
## 2.27.0
Released 2016-03-09
New features:
- Add [Surrogate-Key](https://github.com/CartoDB/cartodb/wiki/CartoDB-Surrogate-Keys) headers to responses
Enhancements:
- Use new `node-cartodb-query-tables` library to obtain affected tables in queries
Announcements:
- Remove deprecated tools directory
## 2.26.3
Released 2016-03-03
Improvements:
- Optimize overviews queries for efficient spatial filtering in PostgreSQL
## 2.26.2
Released 2016-02-25

17
app.js
View File

@@ -14,6 +14,11 @@ if ( process.argv[2] ) {
ENVIRONMENT = 'development';
}
// jshint undef:false
var log = console.log.bind(console);
var logError = console.error.bind(console);
// jshint undef:true
var availableEnvironments = {
production: true,
staging: true,
@@ -22,8 +27,8 @@ var availableEnvironments = {
// sanity check
if (!availableEnvironments[ENVIRONMENT]){
console.error('node app.js [environment]');
console.error('environments: %s', Object.keys(availableEnvironments).join(', '));
logError('node app.js [environment]');
logError('environments: %s', Object.keys(availableEnvironments).join(', '));
process.exit(1);
}
@@ -58,10 +63,10 @@ if ( global.environment.log_filename ) {
// See cwd inlog4js.configure call below
logdir = path.resolve(__dirname, logdir);
if ( ! fs.existsSync(logdir) ) {
console.error("Log filename directory does not exist: " + logdir);
logError("Log filename directory does not exist: " + logdir);
process.exit(1);
}
console.log("Logs will be written to " + global.environment.log_filename);
log("Logs will be written to " + global.environment.log_filename);
log4js_config.appenders.push(
{ type: "file", filename: global.environment.log_filename }
);
@@ -94,7 +99,7 @@ var listener = server.listen(serverOptions.bind.port, serverOptions.bind.host, b
var version = require("./package").version;
listener.on('listening', function() {
console.log(
log(
"Windshaft tileserver %s started on %s:%s PID=%d (%s)",
version, serverOptions.bind.host, serverOptions.bind.port, process.pid, ENVIRONMENT
);
@@ -111,7 +116,7 @@ process.on('SIGHUP', function() {
global.log4js.clearAndShutdownAppenders(function() {
global.log4js.configure(log4js_config);
global.logger = global.log4js.getLogger();
console.log('Log files reloaded');
log('Log files reloaded');
});
});

View File

@@ -163,6 +163,10 @@ var config = {
// Returning the portion of a geometry falling within a rectangle
// It will only work if snapToGrid is enabled
clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5
// geometries will be simplified using ST_RemoveRepeatedPoints
// which cost is no more expensive than snapping and results are
// much closer to the original geometry
removeRepeatedPoints: false // this requires postgis >=2.2
}
},
@@ -191,6 +195,22 @@ var config = {
}
}
}
// anything analyses related
,analysis: {
// batch configuration
batch: {
// Inline execution avoid the use of SQL API as batch endpoint
// When set to true it will run all analysis queries in series, with a direct connection to the DB
// This might be useful for:
// - testing
// - running an standalone server without any dependency on external services
inlineExecution: false,
// where the SQL API is running, it will use a custom Host header to specify the username.
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
// the template to use for adding the host header in the batch api requests
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
}
}
,millstone: {
// Needs to be writable by server user
cache_basedir: '/tmp/cdb-tiler-dev/millstone-dev'

View File

@@ -157,6 +157,10 @@ var config = {
// Returning the portion of a geometry falling within a rectangle
// It will only work if snapToGrid is enabled
clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5
// geometries will be simplified using ST_RemoveRepeatedPoints
// which cost is no more expensive than snapping and results are
// much closer to the original geometry
removeRepeatedPoints: false // this requires postgis >=2.2
}
},
@@ -185,6 +189,22 @@ var config = {
}
}
}
// anything analyses related
,analysis: {
// batch configuration
batch: {
// Inline execution avoid the use of SQL API as batch endpoint
// When set to true it will run all analysis queries in series, with a direct connection to the DB
// This might be useful for:
// - testing
// - running an standalone server without any dependency on external services
inlineExecution: false,
// where the SQL API is running, it will use a custom Host header to specify the username.
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
// the template to use for adding the host header in the batch api requests
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
}
}
,millstone: {
// Needs to be writable by server user
cache_basedir: '/home/ubuntu/tile_assets/'

View File

@@ -157,6 +157,10 @@ var config = {
// Returning the portion of a geometry falling within a rectangle
// It will only work if snapToGrid is enabled
clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5
// geometries will be simplified using ST_RemoveRepeatedPoints
// which cost is no more expensive than snapping and results are
// much closer to the original geometry
removeRepeatedPoints: false // this requires postgis >=2.2
}
},
@@ -185,6 +189,22 @@ var config = {
}
}
}
// anything analyses related
,analysis: {
// batch configuration
batch: {
// Inline execution avoid the use of SQL API as batch endpoint
// When set to true it will run all analysis queries in series, with a direct connection to the DB
// This might be useful for:
// - testing
// - running an standalone server without any dependency on external services
inlineExecution: false,
// where the SQL API is running, it will use a custom Host header to specify the username.
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
// the template to use for adding the host header in the batch api requests
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
}
}
,millstone: {
// Needs to be writable by server user
cache_basedir: '/home/ubuntu/tile_assets/'

View File

@@ -156,7 +156,11 @@ var config = {
// SQL queries will be wrapped with ST_ClipByBox2D
// Returning the portion of a geometry falling within a rectangle
// It will only work if snapToGrid is enabled
clipByBox2d: false // this requires postgis >=2.2 and geos >=3.5
clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5
// geometries will be simplified using ST_RemoveRepeatedPoints
// which cost is no more expensive than snapping and results are
// much closer to the original geometry
removeRepeatedPoints: false // this requires postgis >=2.2
}
},
http: {
@@ -186,6 +190,22 @@ var config = {
}
}
}
// anything analyses related
,analysis: {
// batch configuration
batch: {
// Inline execution avoid the use of SQL API as batch endpoint
// When set to true it will run all analysis queries in series, with a direct connection to the DB
// This might be useful for:
// - testing
// - running an standalone server without any dependency on external services
inlineExecution: true,
// where the SQL API is running, it will use a custom Host header to specify the username.
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
// the template to use for adding the host header in the batch api requests
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
}
}
,millstone: {
// Needs to be writable by server user
cache_basedir: '/tmp/cdb-tiler-test/millstone'

View File

@@ -4,10 +4,10 @@ The CartoDB Maps API allows you to generate maps based on data hosted in your Ca
You can create two types of maps with the Maps API:
- **Anonymous maps**
- **Anonymous Maps**
You can create maps using your CartoDB public data. Any client can change the read-only SQL and CartoCSS parameters that generate the map tiles. These maps can be created from a JavaScript application alone and no authenticated calls are needed. See [this CartoDB.js example](/cartodb-platform/cartodb-js/getting-started/).
- **Named maps**
- **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

View File

@@ -6,7 +6,7 @@ This specification describes an extension for
# 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.
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
@@ -21,18 +21,18 @@ This extension introduces a new layer type so it's possible to use a named map b
options: {
// REQUIRED
// string, the name for the named map to use
// string, the name for the Named Map to use
name: "world_borders",
// OPTIONAL
// object, the replacement values for the named map's template placeholders
// object, the replacement values for the Named Map's template placeholders
// See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md#instantiate-1 for more details
config: {
"color": "#000"
},
// OPTIONAL
// string array, the authorized tokens in case the named map has auth method set to `token`
// string array, the authorized tokens in case the Named Map has auth method set to `token`
// See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md#named-maps-1 for more details
auth_tokens: [
"token1",

View File

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

View File

@@ -1,6 +1,6 @@
# 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)
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)
## Instantiate
@@ -36,7 +36,7 @@ The response includes:
Attributes | Description
--- | ---
layergroupid | The ID for that map, used to compose the URL for the tiles. The final URL is: `https://{account}.cartodb.com/api/v1/map/:layergroupid/{z}/{x}/{y}.png`
layergroupid | The ID for that map, used to compose the URL for the tiles. The final URL is: `https://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png`
updated_at | The ISO date of the last time the data involved in the query was updated.
metadata | Includes information about the layers.
cdn_url | URLs to fetch the data using the best CDN for your zone.
@@ -46,7 +46,7 @@ cdn_url | URLs to fetch the data using the best CDN for your zone.
#### Call
```bash
curl 'https://documentation.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json
curl 'https://{username}.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json
```
#### Response
@@ -72,12 +72,14 @@ curl 'https://documentation.cartodb.com/api/v1/map' -H 'Content-Type: applicatio
### Retrieve resources from the layergroup
#### Mapnik tiles can be accessed using
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.
These tiles will get just the mapnik layers. To get individual layers see next section.
#### Mapnik tiles
These tiles will get just the Mapnik layers. To get individual layers, see the following section.
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
```
#### Individual layers
@@ -87,21 +89,21 @@ The MapConfig specification holds the layers definition in a 0-based index. Laye
Individual layers can be accessed using that 0-based index. For UTF grid tiles:
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/{z}/{x}/{y}.grid.json
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer}/{z}/{x}/{y}.grid.json
```
In this case, `:layer` as 0 returns the UTF grid tiles/attributes for layer 0, the only layer in the example MapConfig.
In this case, `layer` as 0 returns the UTF grid tiles/attributes for layer 0, the only layer in the example MapConfig.
If the MapConfig had a Torque layer at index 1 it could be possible to request it with:
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/1/{z}/{x}/{y}.torque.json
https://{username}.cartodb.com/api/v1/map/{layergroupid}/1/{z}/{x}/{y}.torque.json
```
#### Attributes defined in `attributes` section
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/attributes/:feature_id
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer}/attributes/{feature_id}
```
Which returns JSON with the attributes defined, like:
@@ -113,19 +115,19 @@ Which returns JSON with the attributes defined, like:
#### Blending and layer selection
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer_filter/{z}/{x}/{y}.png
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer_filter}/{z}/{x}/{y}.png
```
Note: currently format is limited to `png`.
`:layer_filter` can be used to select some layers to be rendered together. `:layer_filter` supports two formats:
`layer_filter` can be used to select some layers to be rendered together. `layer_filter` supports two formats:
- `all` alias
Using `all` as `:layer_filter` will blend all layers in the layergroup
Using `all` as `layer_filter` will blend all layers in the layergroup
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/all/{z}/{x}/{y}.png
https://{username}.cartodb.com/api/v1/map/{layergroupid}/all/{z}/{x}/{y}.png
```
- Filter by layer index
@@ -133,14 +135,14 @@ https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/
A list of comma separated layer indexes can be used to just render a subset of layers. For example `0,3,4` will filter and blend layers with indexes 0, 3, and 4.
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/0,3,4/{z}/{x}/{y}.png
https://{username}.cartodb.com/api/v1/map/{layergroupid}/0,3,4/{z}/{x}/{y}.png
```
Some notes about filtering:
- Invalid index values or out of bounds indexes will end in `Invalid layer filtering` errors.
- Once a mapnik layer is selected, all mapnik layers will get blended. As this may change in the future **it is
recommended** to always select all mapnik layers if you want to select at least one so you will get a consistent
- Once a Mapnik layer is selected, all Mapnik layers will get blended. As this may change in the future **it is
recommended** to always select all Mapnik layers if you want to select at least one so you will get a consistent
behavior in the future.
- Ordering is not considered. So right now filtering layers 0,3,4 is the very same thing as filtering 3,4,0. As this
may change in the future **it is recommended** to always select the layers in ascending order so you will get a
@@ -161,7 +163,7 @@ GET /api/v1/map?callback=method
Param | Description
--- | ---
config | Encoded JSON with the params for creating named maps (the variables defined in the template).
config | Encoded JSON with the params for creating Named Maps (the variables defined in the template).
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.
@@ -170,7 +172,7 @@ callback | JSON callback name.
#### Call
```bash
curl "https://documentation.cartodb.com/api/v1/map?callback=callback&config=%7B%22version%22%3A%221.0.1%22%2C%22layers%22%3A%5B%7B%22type%22%3A%22cartodb%22%2C%22options%22%3A%7B%22sql%22%3A%22select+%2A+from+european_countries_e%22%2C%22cartocss%22%3A%22%23european_countries_e%7B+polygon-fill%3A+%23FF6600%3B+%7D%22%2C%22cartocss_version%22%3A%222.3.0%22%2C%22interactivity%22%3A%5B%22cartodb_id%22%5D%7D%7D%5D%7D"
curl "https://{username}.cartodb.com/api/v1/map?callback=callback&config=%7B%22version%22%3A%221.0.1%22%2C%22layers%22%3A%5B%7B%22type%22%3A%22cartodb%22%2C%22options%22%3A%7B%22sql%22%3A%22select+%2A+from+european_countries_e%22%2C%22cartocss%22%3A%22%23european_countries_e%7B+polygon-fill%3A+%23FF6600%3B+%7D%22%2C%22cartocss_version%22%3A%222.3.0%22%2C%22interactivity%22%3A%5B%22cartodb_id%22%5D%7D%7D%5D%7D"
```
#### Response
@@ -189,4 +191,4 @@ callback({
## Remove
Anonymous maps cannot be removed by an API call. They will expire after about five minutes but 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.
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

@@ -4,7 +4,7 @@ The following concepts are the same for every endpoint in the API except when it
## Auth
By default, users do not have access to private tables in CartoDB. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a named map).
By default, users do not have access to private tables in CartoDB. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a Named Map).
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`.

View File

@@ -1,18 +1,24 @@
# 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). The Named map workflow consists of making a call to your database, referencing a table, inserting your variables into the template where placeholders are defined, and creating custom queries.
Named Maps are essentially the same as Anonymous Maps except the MapConfig is stored on the server, and the map is given a unique name. You can create Named Maps from private data, and users without an API Key can view your Named Map (while keeping your data private).
The main two differences compared to anonymous maps are:
The Named Map workflow consists of uploading a MapConfig file to CartoDB servers, to select data from your CartoDB user database by using SQL, and specifying the CartoCSS for your map.
- **auth layer**
This allows you to control who is able to see the map based on a token auth
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.
- **templates**
Since the MapConfig is static it can contain some variables so the client can modify the map's appearance using those variables.
**Tip:** You can also use a Named Map that you created (which is defined by its `name`), to create a map using CartoDB.js. This is achieved by adding the [`namedmap` type](http://docs.cartodb.com/cartodb-platform/cartodb-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
Template maps are persistent with no preset expiration. They can only be created or deleted by a CartoDB user with a valid API_KEY (see auth section).
The main differences, compared to Anonymous Maps, is that Named Maps include:
**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 templates.
- **auth token**
This allows you to control who is able to see the map based on an auth token, and create a secure Named Map with password-protection.
- **template map**
The template map is static and may contain placeholders, enabling you to modify your maps appearance by using variables. Templates maps are persistent with no preset expiration. They can only be created, or deleted, by a CartoDB user with a valid API KEY (See [auth argument](#arguments)).
Uploading a MapConfig creates a Named Map. MapConfigs are uploaded to the server by sending the server a "template".json file, which contain the [MapConfig specifications](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/).
**Note:** There is a limit of 4,096 Named Maps allowed per account. If you need to create more Named Maps, it is recommended to use a single Named Map and change the variables using [placeholders](#placeholder-format), instead of uploading multiple [Named Map MapConfigs](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/#named-map-layer-options).
## Create
@@ -27,9 +33,12 @@ POST /api/v1/map/named
Params | Description
--- | ---
api_key | is required
MapConfig | a [Named Map MapConfig](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/#named-map-layer-options) is required to create a Named Map
#### 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.
```javascript
{
"version": "0.0.1",
@@ -84,16 +93,15 @@ api_key | is required
Params | Description
--- | ---
name | There can be at most _one_ template with the same name for any user. Valid names start with a letter or a number, and only contain letters, numbers, dashes (-) or underscores (_).
name | There can only be _one_ template with the same name for any user. Valid names start with a letter or a number, and only contain letters, numbers, dashes (-), or underscores (_). _This is specific to the name of your Named Map that is specified in the `name` property of the template file_.
auth |
--- | ---
&#124;_ method | `"token"` or `"open"` (the default if no `"method"` is given).
&#124;_ valid_tokens | when `"method"` is set to `"token"`, the values listed here allow you to instantiate the named map.
placeholders | Variables not listed here are not substituted. Variables not provided at instantiation time trigger an error. A default is required for optional variables. Type specification is used for quoting, to avoid injections see template format section below.
layergroup | the layer list definition. This is the MapConfig explained in anonymous maps. See [MapConfig File Format](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/) for more info.
view (optional) | extra keys to specify the compelling 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.
&#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.cartodb.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map.
placeholders | Placeholders are variables that can be placed in your template.json file's SQL or CartoCSS.
layergroup | the layergroup configurations, as specified in the template. See [MapConfig File Format](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/) for more information.
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.
--- | ---
&#124;_ zoom | The zoom level to use
@@ -109,11 +117,12 @@ view (optional) | extra keys to specify the compelling area for the map. It can
&#124;_ &#124;_ east | UpperCorner longitude for the bounding box, in decimal degrees (aka most eastern)
&#124;_ &#124;_ north | UpperCorner latitude for the bounding box, in decimal degrees (aka most northern)
### Template Format
A templated `layergroup` allows the use of placeholders in the "cartocss" and "sql" elements of the "option" object in any "layer" of a `layergroup` configuration
### Placeholder Format
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.
Placeholders are variables that can be placed in your template.json file. Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergoup configurations](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/#layergroup-configurations).
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
@@ -121,7 +130,7 @@ Valid placeholder names start with a letter and can only contain letters, number
<%= my_color %>
```
The set of supported placeholders for a template will need to be explicitly defined with a specific type and default value for each.
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
@@ -134,60 +143,64 @@ sql_ident | internal double-quotes will be sql-escaped
number | can only contain numerical representation
css_color | can only contain color names or hex-values
Placeholder default values will be used whenever new values are not provided as options at the time of creation on the client. They can also be used to test the template by creating a default version with new options provided.
Placeholder default values will be used whenever new values are not provided as options, at the time of creation on the client. They can also be used to test the template by creating a default version with new options provided.
When using templates, be very careful about your selections as they can give broad access to your data if they are defined losely.
When using templates, be very careful about your selections as they can give broad access to your data if they are defined loosely.
#### Call
```html
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.
```bash
curl -X POST \
-H 'Content-Type: application/json' \
-d @template.json \
'https://documentation.cartodb.com/api/v1/map/named?api_key=APIKEY'
'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}'
```
#### Response
The response back from the API provides the name of your MapConfig as a template, enabling you to edit the Named Map details by inserting your variables into the template where placeholders are defined, and create custom queries using SQL.
```javascript
{
"template_id":"name",
"template_id":"name"
}
```
## Instantiate
Instantiating a map allows you to get the information needed to fetch tiles. That temporal map is an anonymous map.
Instantiating a Named Map allows you to fetch the map tiles. You can use the Maps API to instantiate, or use the CartoDB.js `createLayer()` function. The result is an Anonymous Map.
#### Definition
```html
POST /api/v1/map/named/:template_name
POST /api/v1/map/named/{template_name}
```
#### Param
Param | Description
--- | ---
auth_token | optional, but required when `"method"` is set to `"token"`
auth_token | `"token"` or `"open"` (`"open"` is the default if not specified. Use `"token"` to password-protect your map)
```javascript
// params.json
// params.json, this is required if the Named Map allows variables (if placeholders were defined in the template.json by the user)
{
"color": "#ff0000",
"cartodb_id": 3
}
```
The fields you pass as `params.json` depend on the variables allowed by the named map. If there are variables missing it will raise an error (HTTP 400)
The fields you pass as `params.json` depend on the variables allowed by the Named Map. If there are variables missing, it will raise an error (HTTP 400).
- **auth_token** *optional* if the named map needs auth
**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`.
You can initialize a template map by passing all of the required parameters in a POST to `/api/v1/map/named/{template_name}`.
Valid credentials will be needed if required by the template.
Valid auth token will be needed, if required by the template.
#### Call
@@ -196,7 +209,7 @@ Valid credentials will be needed if required by the template.
curl -X POST \
-H 'Content-Type: application/json' \
-d @params.json \
'https://documentation.cartodb.com/api/v1/map/named/@template_name?auth_token=AUTH_TOKEN'
'https://{username}.cartodb.com/api/v1/map/named/{template_name}?auth_token={auth_token}'
```
#### Response
@@ -216,68 +229,14 @@ curl -X POST \
}
```
You can then use the `layergroupid` for fetching tiles and grids as you would normally (see anonymous map section). However you'll need to show the `auth_token`, if required by the template.
## Using JSONP
There is also a special endpoint to be able to initialize a map using JSONP (for old browsers).
#### Definition
```bash
GET /api/v1/map/named/:template_name/jsonp
```
#### Params
Params | Description
--- | ---
auth_token | optional, but required when `"method"` is set to `"token"`
config | Encoded JSON with the params for creating named maps (the variables defined in the template)
lmza | This attribute contains the same as config but LZMA compressed. It cannot be used at the same time than `config`.
callback | JSON callback name
#### Call
```bash
curl 'https://documentation.cartodb.com/api/v1/map/named/:template_name/jsonp?auth_token=AUTH_TOKEN&callback=callback&config=template_params_json'
```
#### Response
```javascript
callback({
"layergroupid":"c01a54877c62831bb51720263f91fb33:0",
"last_updated":"1970-01-01T00:00:00.000Z"
"cdn_url": {
"http": "http://cdb.com",
"https": "https://cdb.com"
}
})
```
This takes the `callback` function (required), `auth_token` if the template needs auth, and `config` which is the variable for the template (in cases where it has variables).
```javascript
url += "config=" + encodeURIComponent(
JSON.stringify({ color: 'red' });
```
The response is in this format:
```javascript
callback({
layergroupid: "dev@744bd0ed9b047f953fae673d56a47b4d:1390844463021.1401",
last_updated: "2014-01-27T17:41:03.021Z"
})
```
You can then use the `layergroupid` for fetching tiles and grids as you would normally (see [Anonymous Maps](http://docs.cartodb.com/cartodb-platform/maps-api/anonymous-maps/)).
## Update
#### Definition
```bash
PUT /api/v1/map/named/:template_name
PUT /api/v1/map/named/{template_name}
```
#### Params
@@ -290,9 +249,9 @@ api_key | is required
Same as updating a map.
### Other Info
### Other Information
Updating a named map removes all the named map instances so they need to be initialized again.
Updating a Named Map removes all the Named Map instances, so they need to be initialized again.
### Example
@@ -302,7 +261,7 @@ Updating a named map removes all the named map instances so they need to be init
curl -X PUT \
-H 'Content-Type: application/json' \
-d @template.json \
'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY'
'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}'
```
#### Response
@@ -325,12 +284,12 @@ If a template with the same name does NOT exist, a 400 HTTP response is generate
## Delete
Delete the specified template map from the server and it disables any previously initialized versions of the map.
Deletes the specified template map from the server, and disables any previously initialized versions of the map.
#### Definition
```bash
DELETE /api/v1/map/named/:template_name
DELETE /api/v1/map/named/{template_name}
```
#### Params
@@ -344,7 +303,7 @@ api_key | is required
#### Call
```bash
curl -X DELETE 'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY'
curl -X DELETE 'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}'
```
#### Response
@@ -378,7 +337,7 @@ api_key | is required
#### Call
```bash
curl -X GET 'https://documentation.cartodb.com/api/v1/map/named?api_key=APIKEY'
curl -X GET 'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}'
```
#### Response
@@ -397,14 +356,14 @@ curl -X GET 'https://documentation.cartodb.com/api/v1/map/named?api_key=APIKEY'
}
```
## Getting a Specific Template
## Get Template Definition
This gets the definition of a template.
This gets the definition of a requested template.
#### Definition
```bash
GET /api/v1/map/named/:template_name
GET /api/v1/map/named/{template_name}
```
#### Params
@@ -418,14 +377,14 @@ api_key | is required
#### Call
```bash
curl -X GET 'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY'
curl -X GET 'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}'
```
#### Response
```javascript
{
"template": {...} // see template.json above
"template": {...} // see [template.json](#templatejson)
}
```
@@ -437,39 +396,169 @@ curl -X GET 'https://documentation.cartodb.com/api/v1/map/named/:template_name?a
}
```
## Use CartoDB.js to Create Named Maps
Named maps can be used with CartoDB.js by specifying a named map in a layer source as follows. Named maps are treated almost the same as other layer source types in most other ways.
## JSONP for Named Maps
```js
var layerSource = {
user_name: '{your_user_name}',
type: 'namedmap',
named_map: {
name: '{template_name}',
layers: [{
layer_name: "layer1",
interactivity: "column1, column2, ..."
}]
}
}
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.
cartodb.createLayer('map_dom_id',layerSource)
.addTo(map_object);
#### Definition
```bash
GET /api/v1/map/named/{template_name}/jsonp
```
[CartoDB.js](http://docs.cartodb.com/cartodb-platform/cartodb-js/) has methods for accessing your named maps.
#### Params
Params | Description
--- | ---
auth_token | `"token"` or `"open"` (`"open"` is the default if no method is specified. Use `"token"` to password-protect your map)
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
```bash
curl 'https://{username}.cartodb.com/api/v1/map/named/{template_name}/jsonp?auth_token={auth_token}&callback=callback&config=template_params_json'
```
#### Response
```javascript
callback({
"layergroupid":"c01a54877c62831bb51720263f91fb33:0",
"last_updated":"1970-01-01T00:00:00.000Z"
"cdn_url": {
"http": "http://cdb.com",
"https": "https://cdb.com"
}
})
```
This takes the `callback` function (required), `auth_token` if the template needs auth, and `config` which is the variable for the template (in cases where it has variables).
```javascript
url += "config=" + encodeURIComponent(
JSON.stringify({ color: 'red' });
```
The response is:
```javascript
callback({
layergroupid: "dev@744bd0ed9b047f953fae673d56a47b4d:1390844463021.1401",
last_updated: "2014-01-27T17:41:03.021Z"
})
```
## CartoDB.js for Named Maps
You can use a Named Map that you created (which is defined by its `name`), to create a map using CartoDB.js. This is achieved by adding the [`namedmap` type](http://docs.cartodb.com/cartodb-platform/cartodb-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
```javascript
{
user_name: '{username}', // Required
type: 'namedmap', // Required
named_map: {
name: '{name_of_map}', // Required, the 'name' of the Named Map that you have created
// Optional
layers: [{
layer_name: "sublayer0", // Optional
interactivity: "column1, column2, ..." // Optional
},
{
layer_name: "sublayer1",
interactivity: "column1, column2, ..."
},
...
],
// Optional
params: {
color: "hex_value",
num: 2
}
}
}
```
**Note:** Instantiating a Named Map over a `createLayer` does not require an API Key and by default, does not include auth tokens. _If_ you defined auth tokens for the Named Map configuration, then you will have to include them.
[CartoDB.js](http://docs.cartodb.com/cartodb-platform/cartodb-js/) has methods for accessing your Named Maps.
1. [layer.setParams()](http://docs.cartodb.com/cartodb-platform/cartodb-js/api-methods/#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript
**Note:** The CartoDB.js `layer.setParams()` function is not supported when using Named maps for Torque.
**Note:** The CartoDB.js `layer.setParams()` function is not supported when using Named Maps for Torque. Alternatively, you can create a [Torque layer in a Named Map](http://bl.ocks.org/iriberri/de37be6406f9cc7cfe5a)
2. [layer.setAuthToken()](http://docs.cartodb.com/cartodb-platform/cartodb-js/api-methods/#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer
### Complete Examples of Named Maps created with CartoDB.js
### Torque Layer in a Named Map
- [Named map selectors with interaction](http://bl.ocks.org/ohasselblad/515a8af1f99d5e690484)
If you are creating a Torque layer in a Named Map without using the Torque.js library, you can apply the Torque layer by applying the following code with CartoDBjs:
- [Named map with interactivity and config file used to create it](http://bl.ocks.org/ohasselblad/d1a45b8ff5e7bd90cd68)
```javascript
// add cartodb layer with one sublayer
cartodb.createLayer(map, {
user_name: '{username}',
type: 'torque',
order: 1,
options: {
query: "",
table_name: "named_map_tutorial_table",
user_name: "{username}",
tile_style: 'Map { -torque-frame-count:512; -torque-animation-duration:10; -torque-time-attribute:"cartodb_id"; -torque-aggregation-function:"count(cartodb_id)"; -torque-resolution:2; -torque-data-aggregation:linear; } #named_map_tutorial_table_copy{ comp-op: lighter; marker-fill-opacity: 0.9; marker-line-color: #FFF; marker-line-width: 1.5; marker-line-opacity: 1; marker-type: ellipse; marker-width: 6; marker-fill: #FF9900; } #named_map_tutorial_table_copy[frame-offset=1] { marker-width:8; marker-fill-opacity:0.45; } #named_map_tutorial_table_copy[frame-offset=2] { marker-width:10; marker-fill-opacity:0.225; }'
},
named_map: {
name: "{namedmap_example}",
layers: [{
layer_name: "t"
}]
}
})
.addTo(map)
.done(function(layer) {
});
}
```
#### Examples of Named Maps created with CartoDB.js
- [Named Map selectors with interaction](http://bl.ocks.org/ohasselblad/515a8af1f99d5e690484)
- [Named Map with interactivity](http://bl.ocks.org/ohasselblad/d1a45b8ff5e7bd90cd68)
- [Toggling sublayers in a Named Map](http://bl.ocks.org/ohasselblad/c1a0f4913610eec53cd3)
## 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
Authenticated users, with an auth token, can use XYZ-based URLs to fetch tiles directly, and instantiate the Named Map as part of the request to your application. You do not have to do any other steps to initialize your map.
To call a template_id in a URL:
`/{template_id}/{layer}/{z}/{x}/{y}.{format}`
For example, a complete URL might appear as:
"https://{username}.cartodb.com/api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.png"
The placeholders indicate the following:
- [`template_id`](http://docs.cartodb.com/cartodb-platform/maps-api/named-maps/#response) is the response of your Named Map.
- layers can be a number (referring to the # layer of your map), all layers of your map, or a list of layers.
- To show just the basemap layer, enter the number value `0` in the layer placeholder "https://{username}.cartodb.com/api/v1/map/named/{template_id}/0/{z}/{x}/{y}.png"
- To show the first layer, enter the number value `1` in the layer placeholder "https://{username}.cartodb.com/api/v1/map/named/{template_id}/1/{z}/{x}/{y}.png"
- To show all layers, enter the value `all` for the layer placeholder "https://{username}.cartodb.com/api/v1/map/named/{template_id}/all/{z}/{x}/{y}.png"
- To show a [list of layers](http://docs.cartodb.com/cartodb-platform/maps-api/anonymous-maps/#blending-and-layer-selection), enter the comma separated layer value as 0,1,2 in the layer placeholder. For example, to show the basemap and the first layer, "https://{username}.cartodb.com/api/v1/map/named/{template_id}/0,1/{z}/{x}/{y}.png"
### Get Mapnik Retina Tiles
Mapnik Retina tiles are not directly supported for Named Maps, so you cannot use the Named Map template_id. To fetch Mapnik Retina tiles, get the [layergroupid](http://docs.cartodb.com/cartodb-platform/maps-api/named-maps/#response-1) to initialize the map.
Instantiate the map by using your `layergroupid` in the token placeholder:
`{token}/{z}/{x}/{y}@{scale_factor}?{x}.{format}`

View File

@@ -1,8 +1,8 @@
# Quickstart
## Anonymous maps
## Anonymous Maps
Here is an example of how to create an anonymous map with JavaScript:
Here is an example of how to create an Anonymous Map with JavaScript:
```javascript
var mapconfig = {
@@ -22,18 +22,18 @@ $.ajax({
type: 'POST',
dataType: 'json',
contentType: 'application/json',
url: 'https://documentation.cartodb.com/api/v1/map',
url: 'https://{username}.cartodb.com/api/v1/map',
data: JSON.stringify(mapconfig),
success: function(data) {
var templateUrl = 'https://documentation.cartodb.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png'
var templateUrl = 'https://{username}.cartodb.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png'
console.log(templateUrl);
}
})
```
## Named maps
## Named Maps
Let's create a named map using some private tables in a CartoDB account.
Let's create a Named Map using some private tables in a CartoDB account.
The following map config sets up a map of European countries that have a white fill color:
```javascript
@@ -56,12 +56,12 @@ The following map config sets up a map of European countries that have a white f
}
```
The map config needs to be sent to CartoDB's Map API using an authenticated call. Here we will use a command line tool called `curl`. For more info about this tool, see [this blog post](http://quickleft.com/blog/command-line-tutorials-curl), or type `man curl` in bash. Using `curl`, and storing the config from above in a file `mapconfig.json`, the call would look like:
The MapConfig needs to be sent to CartoDB's Map API using an authenticated call. Here we will use a command line tool called `curl`. For more info about this tool, see [this blog post](http://quickleft.com/blog/command-line-tutorials-curl), or type `man curl` in bash. Using `curl`, and storing the config from above in a file `MapConfig.json`, the call would look like:
#### Call
```bash
curl 'https://{account}.cartodb.com/api/v1/map/named?api_key=APIKEY' -H 'Content-Type: application/json' -d @mapconfig.json
curl 'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}' -H 'Content-Type: application/json' -d @mapconfig.json
```
To get the `URL` to fetch the tiles you need to instantiate the map, where `template_id` is the template name from the previous response.
@@ -69,7 +69,7 @@ To get the `URL` to fetch the tiles you need to instantiate the map, where `temp
#### Call
```bash
curl -X POST 'https://{account}.cartodb.com/api/v1/map/named/:template_id' -H 'Content-Type: application/json'
curl -X POST 'https://{username}.cartodb.com/api/v1/map/named/{template_id}' -H 'Content-Type: application/json'
```
The response will return JSON with properties for the `layergroupid`, the timestamp (`last_updated`) of the last data modification and some key/value pairs with `metadata` for the `layers`.
@@ -96,5 +96,5 @@ Note: all `layers` in `metadata` will always have a `type` string and a `meta` d
You can use the `layergroupid` to instantiate a URL template for accessing tiles on the client. Here we use the `layergroupid` from the example response above in this URL template:
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
```

View File

@@ -1,28 +1,28 @@
# 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
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
#### Definition
```bash
GET /api/v1/map/static/center/:token/:z/:lat/:lng/:width/:height.:format
GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}
```
#### Params
Param | Description
--- | ---
:token | the layergroupid token from the map instantiation
:z | the zoom level of the map
:lat | the latitude for the center of the map
token | the layergroupid token from the map instantiation
z | the zoom level of the map
lat | the latitude for the center of the map
:format | the format for the image, supported types: `png`, `jpg`
format | the format for the image, supported types: `png`, `jpg`
--- | ---
&#124;_ jpg | will have a default quality of 85.
@@ -31,57 +31,54 @@ Param | Description
#### Definition
```bash
GET /api/v1/map/static/bbox/:token/:bbox/:width/:height.:format`
GET /api/v1/map/static/bbox/{token}/{bbox}/{width}/{height}.{format}`
```
#### 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:
bbox | the bounding box in WGS 84 (EPSG:4326), comma separated values for:
--- | ---
| LowerCorner longitude, in decimal degrees (aka most western)
| LowerCorner latitude, in decimal degrees (aka most southern)
| UpperCorner longitude, in decimal degrees (aka most eastern)
| UpperCorner latitude, in decimal degrees (aka most northern)
:width | the width in pixels for the output image
:height | the height in pixels for the output image
:format | the format for the image, supported types: `png`, `jpg`
:format | the bounding box in WGS 84 (EPSG:4326), comma separated values for:
width | the width in pixels for the output image
height | the height in pixels for the output image
format | the format for the image, supported types: `png`, `jpg`
--- | ---
&#124;_ jpg | will have a default quality of 85.
Note: you can see this endpoint as
```bash
GET /api/v1/map/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format`
GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format}`
```
### Named map
### Named Map
#### Definition
```bash
GET /api/v1/map/static/named/:name/:width/:height.:format
GET /api/v1/map/static/named/{name}/{width}/{height}.{format}
```
#### Params
Param | Description
--- | ---
:name | the name of the named map
:width | the width in pixels for the output image
:height | the height in pixels for the output image
:height | the height in pixels for the output image
name | the name of the Named Map
width | the width in pixels for the output image
height | the height in pixels for the output image
:format | the format for the image, supported types: `png`, `jpg`
format | the 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 in the template](#Arguments), if `view` is not present it will estimate the extent based on the involved tables otherwise it fallback to `"zoom": 1`, `"lng": 0` and `"lat": 0`.
A Named Maps static image will get its constraints from the [`view` argument of the Create Named Map function](http://docs.cartodb.com/cartodb-platform/maps-api/named-maps/#arguments). If `view` is not defined, it will estimate the extent based on the involved tables, otherwise it fallbacks to `"zoom": 1`, `"lng": 0` and `"lat": 0`.
#### Layers
@@ -161,7 +158,7 @@ After instantiating a map from a CartoDB account:
#### Call
```bash
GET /api/v1/map/static/center/4b615ff367e498e770e7d05e99181873:1420231989550.8699/14/40.71502926732618/-73.96039009094238/600/400.png
GET /api/v1/map/static/center/{layergroupid}/{z}/{x}/{y}/{width}/{height}.png
```
#### Response

View File

@@ -1,5 +1,5 @@
function OverviewsMetadataApi(pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
this.pgQueryRunner = pgQueryRunner;
}
module.exports = OverviewsMetadataApi;
@@ -13,7 +13,7 @@ var affectedTableRegexCache = {
};
function prepareSql(sql) {
return sql
return sql && sql
.replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)')
.replace(affectedTableRegexCache.scale_denominator, '0')
.replace(affectedTableRegexCache.pixel_width, '1')
@@ -22,23 +22,25 @@ function prepareSql(sql) {
}
OverviewsMetadataApi.prototype.getOverviewsMetadata = function (username, sql, callback) {
var query = 'SELECT * FROM CDB_Overviews(CDB_QueryTablesText($windshaft$' + prepareSql(sql) + '$windshaft$))';
// FIXME: Currently using internal function _cdb_schema_name
// CDB_Overviews should provide the schema information directly.
var query = 'SELECT *, _cdb_schema_name(base_table)' +
' FROM CDB_Overviews(CDB_QueryTablesText($windshaft$' + prepareSql(sql) + '$windshaft$))';
this.pgQueryRunner.run(username, query, function handleOverviewsRows(err, rows) {
if (err){
var msg = err.message ? err.message : err;
callback(new Error('could not get overviews metadata: ' + msg));
callback(err);
return;
}
var metadata = {};
rows.forEach(function(row) {
var metadata = rows.reduce(function(metadata, row){
var table = row.base_table;
var table_metadata = metadata[table];
if ( !table_metadata ) {
table_metadata = metadata[table] = {};
var schema = row._cdb_schema_name;
if ( !metadata[table] ) {
metadata[table] = {};
}
table_metadata[row.z] = { table: row.overview_table };
});
metadata[table][row.z] = { table: row.overview_table };
metadata[table].schema = schema;
return metadata;
}, {});
return callback(null, metadata);
});
};

View File

@@ -1,96 +0,0 @@
function QueryTablesApi(pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
}
var affectedTableRegexCache = {
bbox: /!bbox!/g,
scale_denominator: /!scale_denominator!/g,
pixel_width: /!pixel_width!/g,
pixel_height: /!pixel_height!/g
};
module.exports = QueryTablesApi;
QueryTablesApi.prototype.getAffectedTablesInQuery = function (username, sql, callback) {
var query = 'SELECT CDB_QueryTablesText($windshaft$' + prepareSql(sql) + '$windshaft$)';
this.pgQueryRunner.run(username, query, function handleAffectedTablesInQueryRows (err, rows) {
if (err){
var msg = err.message ? err.message : err;
callback(new Error('could not fetch source tables: ' + msg));
return;
}
// This is an Array, so no need to split into parts
var tableNames = rows[0].cdb_querytablestext;
return callback(null, tableNames);
});
};
QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (username, sql, callback) {
var query = [
'WITH querytables AS (',
'SELECT * FROM CDB_QueryTablesText($windshaft$' + prepareSql(sql) + '$windshaft$) as tablenames',
')',
'SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max',
'FROM CDB_TableMetadata m',
'WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])'
].join(' ');
this.pgQueryRunner.run(username, query, function handleAffectedTablesAndLastUpdatedTimeRows (err, rows) {
if (err || rows.length === 0) {
var msg = err.message ? err.message : err;
callback(new Error('could not fetch affected tables or last updated time: ' + msg));
return;
}
var result = rows[0];
// This is an Array, so no need to split into parts
var tableNames = result.tablenames;
var lastUpdatedTime = result.max || 0;
callback(null, {
affectedTables: tableNames,
lastUpdatedTime: lastUpdatedTime * 1000
});
});
};
QueryTablesApi.prototype.getLastUpdatedTime = function (username, tableNames, callback) {
if (!Array.isArray(tableNames) || tableNames.length === 0) {
return callback(null, 0);
}
var query = [
'SELECT EXTRACT(EPOCH FROM max(updated_at)) as max',
'FROM CDB_TableMetadata m WHERE m.tabname = any (ARRAY[',
tableNames.map(function(t) { return "'" + t + "'::regclass"; }).join(','),
'])'
].join(' ');
this.pgQueryRunner.run(username, query, function handleLastUpdatedTimeRows (err, rows) {
if (err) {
var msg = err.message ? err.message : err;
return callback(new Error('could not fetch affected tables or last updated time: ' + msg));
}
// when the table has not updated_at means it hasn't been changed so a default last_updated is set
var lastUpdated = 0;
if (rows.length !== 0) {
lastUpdated = rows[0].max || 0;
}
return callback(null, lastUpdated*1000);
});
};
function prepareSql(sql) {
return sql
.replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)')
.replace(affectedTableRegexCache.scale_denominator, '0')
.replace(affectedTableRegexCache.pixel_width, '1')
.replace(affectedTableRegexCache.pixel_height, '1')
;
}

View File

@@ -13,13 +13,9 @@ module.exports = TablesExtentApi;
* `table_name` format as valid input
* @param {Function} callback function(err, result) {Object} result with `west`, `south`, `east`, `north`
*/
TablesExtentApi.prototype.getBounds = function (username, tableNames, callback) {
var estimatedExtentSQLs = tableNames.map(function(tableName) {
var schemaTable = tableName.split('.');
if (schemaTable.length > 1) {
return "ST_EstimatedExtent('" + schemaTable[0] + "', '" + schemaTable[1] + "', 'the_geom_webmercator')";
}
return "ST_EstimatedExtent('" + schemaTable[0] + "', 'the_geom_webmercator')";
TablesExtentApi.prototype.getBounds = function (username, tables, callback) {
var estimatedExtentSQLs = tables.map(function(table) {
return "ST_EstimatedExtent('" + table.schema_name + "', '" + table.table_name + "', 'the_geom_webmercator')";
});
var query = [

View File

@@ -0,0 +1,49 @@
var PSQL = require('cartodb-psql');
function AnalysisStatusBackend() {
}
module.exports = AnalysisStatusBackend;
AnalysisStatusBackend.prototype.getNodeStatus = function (params, callback) {
var nodeId = params.nodeId;
var statusQuery = 'SELECT node_id, status, updated_at FROM cdb_analysis_catalog where node_id = \'' + nodeId + '\'';
var pg = new PSQL(dbParamsFromReqParams(params));
pg.query(statusQuery, function(err, result) {
if (err) {
return callback(err, result);
}
result = result || {};
var rows = result.rows || [];
return callback(null, rows[0] || {
node_id: nodeId,
status: 'unknown'
});
}, true); // use read-only transaction
};
function dbParamsFromReqParams(params) {
var dbParams = {};
if ( params.dbuser ) {
dbParams.user = params.dbuser;
}
if ( params.dbpassword ) {
dbParams.pass = params.dbpassword;
}
if ( params.dbhost ) {
dbParams.host = params.dbhost;
}
if ( params.dbport ) {
dbParams.port = params.dbport;
}
if ( params.dbname ) {
dbParams.dbname = params.dbname;
}
return dbParams;
}

View File

@@ -0,0 +1,19 @@
var camshaft = require('camshaft');
function AnalysisBackend(options) {
var batchConfig = options.batch || {};
batchConfig.endpoint = batchConfig.endpoint || 'http://127.0.0.1:8080/api/v1/sql/job';
batchConfig.inlineExecution = batchConfig.inlineExecution || false;
batchConfig.hostHeaderTemplate = batchConfig.hostHeaderTemplate || '{{=it.username}}.localhost.lan';
this.batchConfig = batchConfig;
}
module.exports = AnalysisBackend;
AnalysisBackend.prototype.create = function(analysisConfiguration, analysisDefinition, callback) {
analysisConfiguration.batch.endpoint = this.batchConfig.endpoint;
analysisConfiguration.batch.inlineExecution = this.batchConfig.inlineExecution;
analysisConfiguration.batch.hostHeaderTemplate = this.batchConfig.hostHeaderTemplate;
camshaft.create(analysisConfiguration, analysisDefinition, callback);
};

View File

@@ -0,0 +1,280 @@
var assert = require('assert');
var _ = require('underscore');
var PSQL = require('cartodb-psql');
var camshaft = require('camshaft');
var step = require('step');
var Timer = require('../stats/timer');
var BBoxFilter = require('../models/filter/bbox');
var DataviewFactory = require('../models/dataview/factory');
function DataviewBackend(analysisBackend) {
this.analysisBackend = analysisBackend;
}
module.exports = DataviewBackend;
DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, params, callback) {
var self = this;
var timer = new Timer();
var dataviewName = params.dataviewName;
var mapConfig;
var dataviewDefinition;
step(
function getMapConfig() {
mapConfigProvider.getMapConfig(this);
},
function _getDataviewDefinition(err, _mapConfig) {
assert.ifError(err);
mapConfig = _mapConfig;
var _dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
if (!_dataviewDefinition) {
throw new Error("Dataview '" + dataviewName + "' does not exists");
}
dataviewDefinition = _dataviewDefinition;
return dataviewDefinition;
},
function loadAnalysis(err) {
assert.ifError(err);
var analysisConfiguration = {
db: {
host: params.dbhost,
port: params.dbport,
dbname: params.dbname,
user: params.dbuser,
pass: params.dbpassword
},
batch: {
username: user,
apiKey: params.api_key
}
};
var sourceId = dataviewDefinition.source.id;
var analysisDefinition = getAnalysisDefinition(mapConfig.obj().analyses, sourceId);
var next = this;
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function(err, analysis) {
if (err) {
return next(err);
}
var sourceId2Node = {};
var rootNode = analysis.getRoot();
if (rootNode.params && rootNode.params.id) {
sourceId2Node[rootNode.params.id] = rootNode;
}
analysis.getSortedNodes().forEach(function(node) {
if (node.params && node.params.id) {
sourceId2Node[node.params.id] = node;
}
});
var node = sourceId2Node[sourceId];
if (!node) {
return next(new Error('Analysis node not found for dataview'));
}
return next(null, node);
});
},
function runDataviewQuery(err, node) {
assert.ifError(err);
var pg = new PSQL(dbParamsFromReqParams(params));
var ownFilter = +params.own_filter;
ownFilter = !!ownFilter;
var query;
if (ownFilter) {
query = node.getQuery();
} else {
var applyFilters = {};
applyFilters[dataviewName] = false;
query = node.getQuery(applyFilters);
}
if (params.bbox) {
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
query = bboxFilter.sql(query);
}
var overrideParams = _.reduce(_.pick(params, 'start', 'end', 'bins'),
function castNumbers(overrides, val, k) {
overrides[k] = Number.isFinite(+val) ? +val : val;
return overrides;
},
{ownFilter: ownFilter}
);
var dataview = DataviewFactory.getDataview(query, dataviewDefinition);
dataview.getResult(pg, overrideParams, this);
},
function returnCallback(err, result) {
return callback(err, result, timer.getTimes());
}
);
};
DataviewBackend.prototype.search = function (mapConfigProvider, user, params, callback) {
var self = this;
var timer = new Timer();
var dataviewName = params.dataviewName;
var mapConfig;
var dataviewDefinition;
step(
function getMapConfig() {
mapConfigProvider.getMapConfig(this);
},
function _getDataviewDefinition(err, _mapConfig) {
assert.ifError(err);
mapConfig = _mapConfig;
var _dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
if (!_dataviewDefinition) {
throw new Error("Dataview '" + dataviewName + "' does not exists");
}
dataviewDefinition = _dataviewDefinition;
return dataviewDefinition;
},
function loadAnalysis(err) {
assert.ifError(err);
var analysisConfiguration = {
db: {
host: params.dbhost,
port: params.dbport,
dbname: params.dbname,
user: params.dbuser,
pass: params.dbpassword
},
batch: {
// TODO load this from configuration
endpoint: 'http://127.0.0.1:8080/api/v1/sql/job',
username: user,
apiKey: params.api_key
}
};
var sourceId = dataviewDefinition.source.id;
var analysisDefinition = getAnalysisDefinition(mapConfig.obj().analyses, sourceId);
var next = this;
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function(err, analysis) {
if (err) {
return next(err);
}
var sourceId2Node = {};
var rootNode = analysis.getRoot();
if (rootNode.params && rootNode.params.id) {
sourceId2Node[rootNode.params.id] = rootNode;
}
analysis.getSortedNodes().forEach(function(node) {
if (node.params && node.params.id) {
sourceId2Node[node.params.id] = node;
}
});
var node = sourceId2Node[sourceId];
if (!node) {
return next(new Error('Analysis node not found for dataview'));
}
return next(null, node);
});
},
function runDataviewQuery(err, node) {
assert.ifError(err);
var pg = new PSQL(dbParamsFromReqParams(params));
var ownFilter = +params.own_filter;
ownFilter = !!ownFilter;
var query;
if (ownFilter) {
query = node.getQuery();
} else {
var applyFilters = {};
applyFilters[dataviewName] = false;
query = node.getQuery(applyFilters);
}
if (params.bbox) {
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
query = bboxFilter.sql(query);
}
var userQuery = params.q;
var dataview = DataviewFactory.getDataview(query, dataviewDefinition);
dataview.search(pg, userQuery, this);
},
function returnCallback(err, result) {
return callback(err, result, timer.getTimes());
}
);
};
function getAnalysisDefinition(mapConfigAnalyses, sourceId) {
mapConfigAnalyses = mapConfigAnalyses || [];
for (var i = 0; i < mapConfigAnalyses.length; i++) {
var analysisGraph = new camshaft.reference.AnalysisGraph(mapConfigAnalyses[i]);
var nodes = analysisGraph.getNodesWithId();
if (nodes.hasOwnProperty(sourceId)) {
return mapConfigAnalyses[i];
}
}
throw new Error('There is no associated analysis for the dataview source id');
}
function getDataviewDefinition(mapConfig, dataviewName) {
var dataviews = mapConfig.dataviews || {};
return dataviews[dataviewName];
}
function dbParamsFromReqParams(params) {
var dbParams = {};
if ( params.dbuser ) {
dbParams.user = params.dbuser;
}
if ( params.dbpassword ) {
dbParams.pass = params.dbpassword;
}
if ( params.dbhost ) {
dbParams.host = params.dbhost;
}
if ( params.dbport ) {
dbParams.port = params.dbport;
}
if ( params.dbname ) {
dbParams.dbname = params.dbname;
}
return dbParams;
}

View File

@@ -1,5 +1,6 @@
var assert = require('assert');
var step = require('step');
var PSQL = require('cartodb-psql');
var _ = require('underscore');
function PgConnection(metadataBackend) {
@@ -99,3 +100,37 @@ PgConnection.prototype.setDBConn = function(dbowner, params, callback) {
}
);
};
/**
* Returns a `cartodb-psql` object for a given username.
* @param {String} username
* @param {Function} callback function({Error}, {PSQL})
*/
PgConnection.prototype.getConnection = function(username, callback) {
var self = this;
var params = {};
require('debug')('cachechan')("getConn1");
step(
function setAuth() {
self.setDBAuth(username, params, this);
},
function setConn(err) {
assert.ifError(err);
self.setDBConn(username, params, this);
},
function openConnection(err) {
assert.ifError(err);
return callback(err, new PSQL({
user: params.dbuser,
pass: params.dbpass,
host: params.dbhost,
port: params.dbport,
dbname: params.dbname
}));
}
);
};

View File

@@ -477,7 +477,11 @@ o.instance = function(template, params) {
var layergroup = JSON.parse(JSON.stringify(template.layergroup));
for (var i=0; i<layergroup.layers.length; ++i) {
var lyropt = layergroup.layers[i].options;
if ( lyropt.cartocss ) {
if ( params.styles && params.styles[i] ) {
// dynamic styling for this layer
lyropt.cartocss = params.styles[i];
} else if ( lyropt.cartocss ) {
lyropt.cartocss = _replaceVars(lyropt.cartocss, all_params);
}
if ( lyropt.sql) {

View File

@@ -1,24 +0,0 @@
var crypto = require('crypto');
function DatabaseTables(dbName, tableNames) {
this.namespace = 't';
this.dbName = dbName;
this.tableNames = tableNames;
}
module.exports = DatabaseTables;
DatabaseTables.prototype.key = function() {
return this.tableNames.map(function(tableName) {
return this.namespace + ':' + shortHashKey(this.dbName + ':' + tableName);
}.bind(this));
};
DatabaseTables.prototype.getCacheChannel = function() {
return this.dbName + ':' + this.tableNames.join(',');
};
function shortHashKey(target) {
return crypto.createHash('sha256').update(target).digest('base64').substring(0,6);
}

View File

@@ -2,18 +2,23 @@ var _ = require('underscore');
var dot = require('dot');
var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider');
var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter');
var AnalysisMapConfigAdapter = require('../models/analysis-mapconfig-adapter');
var templateName = require('../backends/template_maps').templateName;
var queue = require('queue-async');
var LruCache = require("lru-cache");
function NamedMapProviderCache(templateMaps, pgConnection, userLimitsApi, queryTablesApi) {
function NamedMapProviderCache(templateMaps, pgConnection, metadataBackend, userLimitsApi, overviewsAdapter,
turboCartoAdapter) {
this.templateMaps = templateMaps;
this.pgConnection = pgConnection;
this.metadataBackend = metadataBackend;
this.userLimitsApi = userLimitsApi;
this.queryTablesApi = queryTablesApi;
this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter();
this.overviewsAdapter = overviewsAdapter;
this.turboCartoAdapter = turboCartoAdapter;
this.providerCache = new LruCache({ max: 2000 });
}
@@ -29,9 +34,12 @@ NamedMapProviderCache.prototype.get = function(user, templateId, config, authTok
namedMapProviders[providerKey] = new NamedMapMapConfigProvider(
this.templateMaps,
this.pgConnection,
this.metadataBackend,
this.userLimitsApi,
this.queryTablesApi,
this.namedLayersAdapter,
this.overviewsAdapter,
this.turboCartoAdapter,
this.analysisMapConfigAdapter,
user,
templateId,
config,

View File

@@ -14,6 +14,9 @@ var REQUEST_QUERY_PARAMS_WHITELIST = [
'api_key',
'auth_token',
'callback',
'zoom',
'lon',
'lat',
// widgets & filters
'filters', // json
'own_filter', // 0, 1
@@ -197,7 +200,10 @@ BaseController.prototype.send = function(req, res, body, status, headers) {
// jshint maxcomplexity:6
BaseController.prototype.sendError = function(req, res, err, label) {
var allErrors = Array.isArray(err) ? err : [err];
label = label || 'UNKNOWN';
err = allErrors[0] || new Error(label);
allErrors[0] = err;
var statusCode = findStatusCode(err);
@@ -208,7 +214,7 @@ BaseController.prototype.sendError = function(req, res, err, label) {
statusCode = 200;
}
var errorResponseBody = { errors: [errorMessage(err)] };
var errorResponseBody = { errors: allErrors.map(errorMessage) };
this.send(req, res, errorResponseBody, statusCode);
};

View File

@@ -7,8 +7,12 @@ var BaseController = require('./base');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
var DataviewBackend = require('../backends/dataview');
var AnalysisStatusBackend = require('../backends/analysis-status');
var MapStoreMapConfigProvider = require('../models/mapconfig/map_store_provider');
var TablesCacheEntry = require('../cache/model/database_tables_entry');
var QueryTables = require('cartodb-query-tables');
/**
* @param {AuthApi} authApi
@@ -20,14 +24,15 @@ var TablesCacheEntry = require('../cache/model/database_tables_entry');
* @param {WidgetBackend} widgetBackend
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsApi} userLimitsApi
* @param {QueryTablesApi} queryTablesApi
* @param {LayergroupAffectedTables} layergroupAffectedTables
* @param {AnalysisBackend} analysisBackend
* @constructor
*/
function LayergroupController(authApi, pgConnection, mapStore, tileBackend, previewBackend, attributesBackend,
widgetBackend, surrogateKeysCache, userLimitsApi, queryTablesApi, layergroupAffectedTables) {
widgetBackend, surrogateKeysCache, userLimitsApi, layergroupAffectedTables, analysisBackend) {
BaseController.call(this, authApi, pgConnection);
this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.tileBackend = tileBackend;
this.previewBackend = previewBackend;
@@ -35,8 +40,10 @@ function LayergroupController(authApi, pgConnection, mapStore, tileBackend, prev
this.widgetBackend = widgetBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.userLimitsApi = userLimitsApi;
this.queryTablesApi = queryTablesApi;
this.layergroupAffectedTables = layergroupAffectedTables;
this.dataviewBackend = new DataviewBackend(analysisBackend);
this.analysisStatusBackend = new AnalysisStatusBackend();
}
util.inherits(LayergroupController, BaseController);
@@ -78,6 +85,100 @@ LayergroupController.prototype.register = function(app) {
app.get(app.base_url_mapconfig +
'/:token/:layer/widget/:widgetName/search', cors(), userMiddleware,
this.widgetSearch.bind(this));
app.get(app.base_url_mapconfig +
'/:token/dataview/:dataviewName', cors(), userMiddleware,
this.dataview.bind(this));
app.get(app.base_url_mapconfig +
'/:token/dataview/:dataviewName/search', cors(), userMiddleware,
this.dataviewSearch.bind(this));
app.get(app.base_url_mapconfig +
'/:token/analysis/node/:nodeId', cors(), userMiddleware,
this.analysisNodeStatus.bind(this));
};
LayergroupController.prototype.analysisNodeStatus = function(req, res) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveNodeStatus(err) {
assert.ifError(err);
self.analysisStatusBackend.getNodeStatus(req.params, this);
},
function finish(err, nodeStatus, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET NODE STATUS');
} else {
self.sendResponse(req, res, nodeStatus, 200, {
'Cache-Control': 'public,max-age=5',
'Last-Modified': new Date().toUTCString()
});
}
}
);
};
LayergroupController.prototype.dataview = function(req, res) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveDataview(err) {
assert.ifError(err);
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
);
self.dataviewBackend.getDataview(mapConfigProvider, req.context.user, req.params, this);
},
function finish(err, dataview, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET DATAVIEW');
} else {
self.sendResponse(req, res, dataview, 200);
}
}
);
};
LayergroupController.prototype.dataviewSearch = function(req, res) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function searchDataview(err) {
assert.ifError(err);
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
);
self.dataviewBackend.search(mapConfigProvider, req.context.user, req.params, this);
},
function finish(err, searchResult, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET DATAVIEW SEARCH');
} else {
self.sendResponse(req, res, searchResult, 200);
}
}
);
};
LayergroupController.prototype.widget = function(req, res) {
@@ -95,13 +196,13 @@ LayergroupController.prototype.widget = function(req, res) {
);
self.widgetBackend.getWidget(mapConfigProvider, req.params, this);
},
function finish(err, tile, stats) {
function finish(err, widget, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET WIDGET');
} else {
self.sendResponse(req, res, tile, 200);
self.sendResponse(req, res, widget, 200);
}
}
);
@@ -123,13 +224,13 @@ LayergroupController.prototype.widgetSearch = function(req, res) {
);
self.widgetBackend.search(mapConfigProvider, req.params, this);
},
function finish(err, tile, stats) {
function finish(err, searchResult, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET WIDGET');
} else {
self.sendResponse(req, res, tile, 200);
self.sendResponse(req, res, searchResult, 200);
}
}
);
@@ -320,9 +421,8 @@ LayergroupController.prototype.sendResponse = function(req, res, body, status, h
global.logger.warn('ERROR generating cache channel: ' + err);
}
if (!!affectedTables) {
var tablesCacheEntry = new TablesCacheEntry(dbName, affectedTables);
res.set('X-Cache-Channel', tablesCacheEntry.getCacheChannel());
self.surrogateKeysCache.tag(res, tablesCacheEntry);
res.set('X-Cache-Channel', affectedTables.getCacheChannel());
self.surrogateKeysCache.tag(res, affectedTables);
}
self.send(req, res, body, status, headers);
}
@@ -366,17 +466,24 @@ LayergroupController.prototype.getAffectedTables = function(user, dbName, layerg
throw new Error("this request doesn't need an X-Cache-Channel generated");
}
self.queryTablesApi.getAffectedTablesInQuery(user, sql, this); // in addCacheChannel
step(
function getConnection() {
self.pgConnection.getConnection(user, this);
},
function getAffectedTables(err, connection) {
assert.ifError(err);
QueryTables.getAffectedTablesFromQuery(connection, sql, this);
},
this
);
},
function buildCacheChannel(err, tableNames) {
function buildCacheChannel(err, tables) {
assert.ifError(err);
self.layergroupAffectedTables.set(dbName, layergroupId, tables);
self.layergroupAffectedTables.set(dbName, layergroupId, tableNames);
return tableNames;
return tables;
},
function finish(err, affectedTables) {
callback(err, affectedTables);
}
callback
);
};

View File

@@ -2,6 +2,7 @@ var _ = require('underscore');
var assert = require('assert');
var step = require('step');
var windshaft = require('windshaft');
var QueryTables = require('cartodb-query-tables');
var util = require('util');
var BaseController = require('./base');
@@ -13,12 +14,11 @@ var MapConfig = windshaft.model.MapConfig;
var Datasource = windshaft.model.Datasource;
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
var TablesCacheEntry = require('../cache/model/database_tables_entry');
var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter');
var AnalysisMapConfigAdapter = require('../models/analysis-mapconfig-adapter');
var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider');
var CreateLayergroupMapConfigProvider = require('../models/mapconfig/create_layergroup_provider');
var MapConfigOverviewsAdapter = require('../models/mapconfig_overviews_adapter');
/**
* @param {AuthApi} authApi
@@ -26,16 +26,17 @@ var MapConfigOverviewsAdapter = require('../models/mapconfig_overviews_adapter')
* @param {TemplateMaps} templateMaps
* @param {MapBackend} mapBackend
* @param metadataBackend
* @param {QueryTablesApi} queryTablesApi
* @param {OverviewsMetadataApi} overviewsMetadataApi
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsApi} userLimitsApi
* @param {LayergroupAffectedTables} layergroupAffectedTables
* @param {MapConfigOverviewsAdapter} overviewsAdapter
* @param {TurboCartoAdapter} turboCartoAdapter
* @param {AnalysisBackend} analysisBackend
* @constructor
*/
function MapController(authApi, pgConnection, templateMaps, mapBackend, metadataBackend,
queryTablesApi, overviewsMetadataApi,
surrogateKeysCache, userLimitsApi, layergroupAffectedTables) {
surrogateKeysCache, userLimitsApi, layergroupAffectedTables,
overviewsAdapter, turboCartoAdapter, analysisBackend) {
BaseController.call(this, authApi, pgConnection);
@@ -43,14 +44,14 @@ function MapController(authApi, pgConnection, templateMaps, mapBackend, metadata
this.templateMaps = templateMaps;
this.mapBackend = mapBackend;
this.metadataBackend = metadataBackend;
this.queryTablesApi = queryTablesApi;
this.overviewsMetadataApi = overviewsMetadataApi;
this.surrogateKeysCache = surrogateKeysCache;
this.userLimitsApi = userLimitsApi;
this.layergroupAffectedTables = layergroupAffectedTables;
this.turboCartoAdapter = turboCartoAdapter;
this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter(analysisBackend);
this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
this.overviewsAdapter = new MapConfigOverviewsAdapter(this.overviewsMetadataApi);
this.overviewsAdapter = overviewsAdapter;
}
util.inherits(MapController, BaseController);
@@ -131,15 +132,43 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
var self = this;
var mapConfig;
var analysesResults = [];
step(
function setupParams(){
self.req2params(req, this);
},
prepareConfigFn,
function beforeLayergroupCreate(err, requestMapConfig) {
function prepareAnalysisLayers(err, requestMapConfig) {
assert.ifError(err);
var analysisConfiguration = {
db: {
host: req.params.dbhost,
port: req.params.dbport,
dbname: req.params.dbname,
user: req.params.dbuser,
pass: req.params.dbpassword
},
batch: {
username: req.context.user,
apiKey: req.params.api_key
}
};
var filters = {};
if (req.params.filters) {
try {
filters = JSON.parse(req.params.filters);
} catch (e) {
// ignore
}
}
self.analysisMapConfigAdapter.getMapConfig(analysisConfiguration, requestMapConfig, filters, this);
},
function beforeLayergroupCreate(err, requestMapConfig, _analysesResults) {
assert.ifError(err);
var next = this;
analysesResults = _analysesResults;
self.namedLayersAdapter.getLayers(req.context.user, requestMapConfig.layers, self.pgConnection,
function(err, layers, datasource) {
if (err) {
@@ -154,20 +183,35 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
);
},
function addOverviewsInformation(err, requestMapConfig, datasource) {
assert.ifError(err);
var next = this;
self.overviewsAdapter.getLayers(req.context.user, requestMapConfig.layers,
function(err, layers) {
if (err) {
return next(err);
}
assert.ifError(err);
var next = this;
self.overviewsAdapter.getLayers(req.context.user, requestMapConfig.layers, function(err, layers) {
if (err) {
return next(err);
}
if (layers) {
requestMapConfig.layers = layers;
}
return next(null, requestMapConfig, datasource);
}
);
if (layers) {
requestMapConfig.layers = layers;
}
return next(null, requestMapConfig, datasource);
});
},
function parseTurboCarto(err, requestMapConfig, datasource) {
assert.ifError(err);
var next = this;
self.turboCartoAdapter.getLayers(req.context.user, requestMapConfig.layers, function (err, layers) {
if (err) {
return next(err);
}
if (layers) {
requestMapConfig.layers = layers;
}
return next(null, requestMapConfig, datasource);
});
},
function createLayergroup(err, requestMapConfig, datasource) {
assert.ifError(err);
@@ -180,7 +224,7 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
},
function afterLayergroupCreate(err, layergroup) {
assert.ifError(err);
self.afterLayergroupCreate(req, res, mapConfig, layergroup, this);
self.afterLayergroupCreate(req, res, mapConfig, analysesResults, layergroup, this);
},
function finish(err, layergroup) {
if (err) {
@@ -215,9 +259,12 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
mapConfigProvider = new NamedMapMapConfigProvider(
self.templateMaps,
self.pgConnection,
self.metadataBackend,
self.userLimitsApi,
self.queryTablesApi,
self.namedLayersAdapter,
self.overviewsAdapter,
self.turboCartoAdapter,
self.analysisMapConfigAdapter,
cdbuser,
req.params.template_id,
templateParams,
@@ -226,22 +273,6 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
);
mapConfigProvider.getMapConfig(this);
},
function addOverviewsInformation(err, requestMapConfig, rendererParams/*, context*/) {
assert.ifError(err);
var next = this;
self.overviewsAdapter.getLayers(req.context.user, requestMapConfig.layers,
function(err, layers) {
if (err) {
return next(err);
}
if (layers) {
requestMapConfig.layers = layers;
}
return next(null, requestMapConfig, rendererParams);
}
);
},
function createLayergroup(err, mapConfig_, rendererParams) {
assert.ifError(err);
mapConfig = mapConfig_;
@@ -253,7 +284,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
},
function afterLayergroupCreate(err, layergroup) {
assert.ifError(err);
self.afterLayergroupCreate(req, res, mapConfig, layergroup, this);
self.afterLayergroupCreate(req, res, mapConfig, [], layergroup, this);
},
function finishTemplateInstantiation(err, layergroup) {
if (err) {
@@ -262,7 +293,9 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
addWidgetsUrl(req.context.user, layergroup);
addWidgetsUrl(cdbuser, layergroup);
addDataviewsUrls(cdbuser, layergroup, mapConfig.obj());
addAnalysesMetadata(cdbuser, layergroup, mapConfigProvider.analysesResults);
res.set('X-Layergroup-Id', layergroup.layergroupid);
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, mapConfigProvider.getTemplateName()));
@@ -274,7 +307,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
};
MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, layergroup, callback) {
MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, analysesResults, layergroup, callback) {
var self = this;
var username = req.context.user;
@@ -318,43 +351,36 @@ MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, la
var layergroupId = layergroup.layergroupid;
step(
function checkCachedAffectedTables() {
return self.layergroupAffectedTables.hasAffectedTables(dbName, layergroupId);
function getPgConnection() {
self.pgConnection.getConnection(username, this);
},
function getAffectedTablesAndLastUpdatedTime(err, hasCache) {
function getAffectedTablesAndLastUpdatedTime(err, connection) {
assert.ifError(err);
if (hasCache) {
var next = this;
var affectedTables = self.layergroupAffectedTables.get(dbName, layergroupId);
self.queryTablesApi.getLastUpdatedTime(username, affectedTables, function(err, lastUpdatedTime) {
if (err) {
return next(err);
}
return next(null, { affectedTables: affectedTables, lastUpdatedTime: lastUpdatedTime });
});
} else {
self.queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this);
}
QueryTables.getAffectedTablesFromQuery(connection, sql, this);
},
function handleAffectedTablesAndLastUpdatedTime(err, result) {
if (req.profiler) {
req.profiler.done('queryTablesAndLastUpdated');
}
assert.ifError(err);
self.layergroupAffectedTables.set(dbName, layergroupId, result.affectedTables);
// feed affected tables cache so it can be reused from, for instance, layergroup controller
self.layergroupAffectedTables.set(dbName, layergroupId, result);
// last update for layergroup cache buster
layergroup.layergroupid = layergroup.layergroupid + ':' + result.lastUpdatedTime;
layergroup.last_updated = new Date(result.lastUpdatedTime).toISOString();
layergroup.layergroupid = layergroup.layergroupid + ':' + result.getLastUpdatedAt();
layergroup.last_updated = new Date(result.getLastUpdatedAt()).toISOString();
// TODO this should take into account several URL patterns
addWidgetsUrl(username, layergroup);
addDataviewsUrls(username, layergroup, mapconfig.obj());
addAnalysesMetadata(username, layergroup, analysesResults, true);
if (req.method === 'GET') {
var tableCacheEntry = new TablesCacheEntry(dbName, result.affectedTables);
var ttl = global.environment.varnish.layergroupTtl || 86400;
res.set('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
res.set('Last-Modified', (new Date()).toUTCString());
res.set('X-Cache-Channel', tableCacheEntry.getCacheChannel());
if (result.affectedTables && result.affectedTables.length > 0) {
self.surrogateKeysCache.tag(res, tableCacheEntry);
res.set('X-Cache-Channel', result.getCacheChannel());
if (result.tables && result.tables.length > 0) {
self.surrogateKeysCache.tag(res, result);
}
}
@@ -366,6 +392,44 @@ MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, la
);
};
function addAnalysesMetadata(username, layergroup, analysesResults, includeQuery) {
includeQuery = includeQuery || false;
analysesResults = analysesResults || [];
layergroup.metadata.analyses = [];
analysesResults.forEach(function(analysis) {
var nodes = analysis.getSortedNodes();
layergroup.metadata.analyses.push({
nodes: nodes.reduce(function(nodesIdMap, node) {
if (node.params.id) {
var nodeResource = layergroup.layergroupid + '/analysis/node/' + node.id();
nodesIdMap[node.params.id] = {
status: node.getStatus(),
url: getUrls(username, nodeResource)
};
if (includeQuery) {
nodesIdMap[node.params.id].query = node.getQuery();
}
}
return nodesIdMap;
}, {})
});
});
}
function addDataviewsUrls(username, layergroup, mapConfig) {
layergroup.metadata.dataviews = layergroup.metadata.dataviews || {};
var dataviews = mapConfig.dataviews || {};
Object.keys(dataviews).forEach(function(dataviewName) {
var resource = layergroup.layergroupid + '/dataview/' + dataviewName;
layergroup.metadata.dataviews[dataviewName] = {
url: getUrls(username, resource)
};
});
}
function addWidgetsUrl(username, layergroup) {
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers)) {

View File

@@ -9,8 +9,6 @@ var BaseController = require('./base');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
var TablesCacheEntry = require('../cache/model/database_tables_entry');
function NamedMapsController(authApi, pgConnection, namedMapProviderCache, tileBackend, previewBackend,
surrogateKeysCache, tablesExtentApi, metadataBackend) {
BaseController.call(this, authApi, pgConnection);
@@ -44,7 +42,6 @@ NamedMapsController.prototype.sendResponse = function(req, res, resource, header
var self = this;
var dbName = req.params.dbname;
step(
function getAffectedTablesAndLastUpdatedTime() {
namedMapProvider.getAffectedTablesAndLastUpdatedTime(this);
@@ -54,22 +51,21 @@ NamedMapsController.prototype.sendResponse = function(req, res, resource, header
if (err) {
global.logger.log('ERROR generating cache channel: ' + err);
}
if (!result || !!result.affectedTables) {
if (!result || !!result.tables) {
// we increase cache control as we can invalidate it
res.set('Cache-Control', 'public,max-age=31536000');
var lastModifiedDate;
if (Number.isFinite(result.lastUpdatedTime)) {
lastModifiedDate = new Date(result.lastUpdatedTime);
lastModifiedDate = new Date(result.getLastUpdatedAt());
} else {
lastModifiedDate = new Date();
}
res.set('Last-Modified', lastModifiedDate.toUTCString());
var tablesCacheEntry = new TablesCacheEntry(dbName, result.affectedTables);
res.set('X-Cache-Channel', tablesCacheEntry.getCacheChannel());
if (result.affectedTables.length > 0) {
self.surrogateKeysCache.tag(res, tablesCacheEntry);
res.set('X-Cache-Channel', result.getCacheChannel());
if (result.tables.length > 0) {
self.surrogateKeysCache.tag(res, result);
}
}
self.send(req, res, resource, 200);
@@ -144,7 +140,7 @@ NamedMapsController.prototype.staticMap = function(req, res) {
function prepareImageOptions(err, _namedMapProvider) {
assert.ifError(err);
namedMapProvider = _namedMapProvider;
self.getStaticImageOptions(cdbUser, namedMapProvider, this);
self.getStaticImageOptions(cdbUser, req.params, namedMapProvider, this);
},
function getImage(err, imageOpts) {
assert.ifError(err);
@@ -196,9 +192,37 @@ var DEFAULT_ZOOM_CENTER = {
}
};
NamedMapsController.prototype.getStaticImageOptions = function(cdbUser, namedMapProvider, callback) {
function numMapper(n) {
return +n;
}
NamedMapsController.prototype.getStaticImageOptions = function(cdbUser, params, namedMapProvider, callback) {
var self = this;
if ([params.zoom, params.lon, params.lat].map(numMapper).every(Number.isFinite)) {
return callback(null, {
zoom: params.zoom,
center: {
lng: params.lon,
lat: params.lat
}
});
}
if (params.bbox) {
var bbox = params.bbox.split(',').map(numMapper);
if (bbox.length === 4 && bbox.every(Number.isFinite)) {
return callback(null, {
bounds: {
west: bbox[0],
south: bbox[1],
east: bbox[2],
north: bbox[3]
}
});
}
}
step(
function getTemplate() {
namedMapProvider.getTemplate(this);
@@ -209,6 +233,9 @@ NamedMapsController.prototype.getStaticImageOptions = function(cdbUser, namedMap
if (template.view) {
var zoomCenter = templateZoomCenter(template.view);
if (zoomCenter) {
if (Number.isFinite(+params.zoom)) {
zoomCenter.zoom = +params.zoom;
}
return zoomCenter;
}
@@ -231,7 +258,7 @@ NamedMapsController.prototype.getStaticImageOptions = function(cdbUser, namedMap
return next(null);
}
var affectedTables = affectedTablesAndLastUpdate.affectedTables || [];
var affectedTables = affectedTablesAndLastUpdate.tables || [];
if (affectedTables.length === 0) {
return next(null);

View File

@@ -65,6 +65,7 @@ NamedMapsAdminController.prototype.update = function(req, res) {
var cdbuser = req.context.user;
var template;
var tpl_id;
step(
function checkPerms(){
self.authApi.authorizedByAPIKey(cdbuser, req, this);

View File

@@ -0,0 +1,228 @@
var queue = require('queue-async');
var debug = require('debug')('windshaft:analysis');
var camshaft = require('camshaft');
var dot = require('dot');
dot.templateSettings.strip = false;
function AnalysisMapConfigAdapter(analysisBackend) {
this.analysisBackend = analysisBackend;
}
module.exports = AnalysisMapConfigAdapter;
var SKIP_COLUMNS = {
'the_geom': true,
'the_geom_webmercator': true
};
function skipColumns(columnNames) {
return columnNames
.filter(function(columnName) { return !SKIP_COLUMNS[columnName]; });
}
var layerQueryTemplate = dot.template([
'SELECT {{=it._columns}}',
'FROM ({{=it._query}}) _cdb_analysis_query'
].join('\n'));
function layerQuery(node) {
if (node.type === 'source') {
return node.getQuery();
}
var _columns = ['ST_Transform(the_geom, 3857) the_geom_webmercator'].concat(skipColumns(node.getColumns()));
return layerQueryTemplate({ _query: node.getQuery(), _columns: _columns.join(', ') });
}
function appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId) {
var analyses = requestMapConfig.analyses || [];
requestMapConfig.analyses = analyses.map(function(analysisDefinition) {
var analysisGraph = new camshaft.reference.AnalysisGraph(analysisDefinition);
var definition = analysisDefinition;
Object.keys(dataviewsFiltersBySourceId).forEach(function(sourceId) {
definition = analysisGraph.getDefinitionWith(sourceId, {filters: dataviewsFiltersBySourceId[sourceId] });
});
return definition;
});
return requestMapConfig;
}
function shouldAdaptLayers(requestMapConfig) {
return Array.isArray(requestMapConfig.layers) &&
Array.isArray(requestMapConfig.analyses) && requestMapConfig.analyses.length > 0;
}
var DATAVIEW_TYPE_2_FILTER_TYPE = {
aggregation: 'category',
histogram: 'range'
};
function getFilter(dataview, params) {
var type = dataview.type;
return {
type: DATAVIEW_TYPE_2_FILTER_TYPE[type],
column: dataview.options.column,
params: params
};
}
AnalysisMapConfigAdapter.prototype.getMapConfig = function(analysisConfiguration, requestMapConfig, filters, callback) {
// jshint maxcomplexity:7
var self = this;
filters = filters || {};
if (!shouldAdaptLayers(requestMapConfig)) {
return callback(null, requestMapConfig);
}
var dataviewsFilters = filters.dataviews || {};
debug(dataviewsFilters);
var dataviews = requestMapConfig.dataviews || {};
var errors = getDataviewsErrors(dataviews);
if (errors.length > 0) {
return callback(errors);
}
var dataviewsFiltersBySourceId = Object.keys(dataviewsFilters).reduce(function(bySourceId, dataviewName) {
var dataview = dataviews[dataviewName];
if (dataview) {
var sourceId = dataview.source.id;
if (!bySourceId.hasOwnProperty(sourceId)) {
bySourceId[sourceId] = {};
}
bySourceId[sourceId][dataviewName] = getFilter(dataview, dataviewsFilters[dataviewName]);
}
return bySourceId;
}, {});
debug(dataviewsFiltersBySourceId);
debug('mapconfig input', JSON.stringify(requestMapConfig, null, 4));
requestMapConfig = appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId);
function createAnalysis(analysisDefinition, done) {
self.analysisBackend.create(analysisConfiguration, analysisDefinition, done);
}
var analysesQueue = queue(requestMapConfig.analyses.length);
requestMapConfig.analyses.forEach(function(analysis) {
analysesQueue.defer(createAnalysis, analysis);
});
analysesQueue.awaitAll(function(err, analysesResults) {
if (err) {
return callback(err);
}
var sourceId2Node = analysesResults.reduce(function(sourceId2Query, analysis) {
var rootNode = analysis.getRoot();
if (rootNode.params && rootNode.params.id) {
sourceId2Query[rootNode.params.id] = rootNode;
}
analysis.getSortedNodes().forEach(function(node) {
if (node.params && node.params.id) {
sourceId2Query[node.params.id] = node;
}
});
return sourceId2Query;
}, {});
var missingNodesErrors = [];
requestMapConfig.layers = requestMapConfig.layers.map(function(layer, layerIndex) {
if (getLayerSourceId(layer)) {
var layerSourceId = getLayerSourceId(layer);
var layerNode = sourceId2Node[layerSourceId];
if (layerNode) {
var analysisSql = layerQuery(layerNode);
var sqlQueryWrap = layer.options.sql_wrap;
if (sqlQueryWrap) {
analysisSql = sqlQueryWrap.replace(/<%=\s*sql\s*%>/g, analysisSql);
}
layer.options.sql = analysisSql;
var layerDataviews = getLayerDataviews(layer, dataviews);
layer.options.columns = layerDataviews.reduce(function(columns, dataview) {
return columns.concat(getDataviewColumns(dataview));
}, []);
} else {
missingNodesErrors.push(
new Error('Missing analysis node.id="' + layerSourceId +'" for layer='+layerIndex)
);
}
}
return layer;
});
debug('mapconfig output', JSON.stringify(requestMapConfig, null, 4));
if (missingNodesErrors.length > 0) {
return callback(missingNodesErrors);
}
return callback(null, requestMapConfig, analysesResults);
});
};
function getLayerSourceId(layer) {
return layer.options.source && layer.options.source.id;
}
function getDataviewSourceId(dataview) {
return dataview.source && dataview.source.id;
}
function getLayerDataviews(layer, dataviews) {
var layerDataviews = [];
var layerSourceId = getLayerSourceId(layer);
if (layerSourceId) {
var dataviewsList = getDataviewsList(dataviews);
dataviewsList.forEach(function(dataview) {
if (getDataviewSourceId(dataview) === layerSourceId) {
layerDataviews.push(dataview);
}
});
}
return layerDataviews;
}
function getDataviewColumns(dataview) {
var columns = [];
var options = dataview.options;
['column', 'aggregationColumn'].forEach(function(opt) {
if (options.hasOwnProperty(opt)) {
columns.push(options[opt]);
}
});
return columns;
}
function getDataviewsList(dataviews) {
return Object.keys(dataviews).map(function(dataviewKey) { return dataviews[dataviewKey]; });
}
function getDataviewsErrors(dataviews) {
var errors = [];
Object.keys(dataviews).forEach(function(dataviewName) {
var dataview = dataviews[dataviewName];
if (!dataview.hasOwnProperty('source') || !dataview.source.id) {
errors.push(new Error('Dataview "' + dataviewName + '" is missing `source.id` attribute'));
}
if (!dataview.type) {
errors.push(new Error('Dataview "' + dataviewName + '" is missing `type` attribute'));
}
});
return errors;
}

View File

@@ -0,0 +1,278 @@
var _ = require('underscore');
var BaseWidget = require('./base');
var debug = require('debug')('windshaft:widget:aggregation');
var dot = require('dot');
dot.templateSettings.strip = false;
var summaryQueryTpl = dot.template([
'summary AS (',
' SELECT',
' count(1) AS count,',
' sum(CASE WHEN {{=it._column}} IS NULL THEN 1 ELSE 0 END) AS nulls_count',
' FROM ({{=it._query}}) _cdb_aggregation_nulls',
')'
].join('\n'));
var rankedCategoriesQueryTpl = dot.template([
'categories AS(',
' SELECT {{=it._column}} AS category, {{=it._aggregation}} AS value,',
' row_number() OVER (ORDER BY {{=it._aggregation}} desc) as rank',
' FROM ({{=it._query}}) _cdb_aggregation_all',
' GROUP BY {{=it._column}}',
' ORDER BY 2 DESC',
')'
].join('\n'));
var categoriesSummaryQueryTpl = dot.template([
'categories_summary AS(',
' SELECT count(1) categories_count, max(value) max_val, min(value) min_val',
' FROM categories',
')'
].join('\n'));
var rankedAggregationQueryTpl = dot.template([
'SELECT CAST(category AS text), value, false as agg, nulls_count, min_val, max_val, count, categories_count',
' FROM categories, summary, categories_summary',
' WHERE rank < {{=it._limit}}',
'UNION ALL',
'SELECT \'Other\' category, sum(value), true as agg, nulls_count, min_val, max_val, count, categories_count',
' FROM categories, summary, categories_summary',
' WHERE rank >= {{=it._limit}}',
'GROUP BY nulls_count, min_val, max_val, count, categories_count'
].join('\n'));
var aggregationQueryTpl = dot.template([
'SELECT CAST({{=it._column}} AS text) AS category, {{=it._aggregation}} AS value, false as agg,',
' nulls_count, min_val, max_val, count, categories_count',
'FROM ({{=it._query}}) _cdb_aggregation_all, summary, categories_summary',
'GROUP BY category, nulls_count, min_val, max_val, count, categories_count',
'ORDER BY value DESC'
].join('\n'));
var CATEGORIES_LIMIT = 6;
var VALID_OPERATIONS = {
count: [],
sum: ['aggregationColumn']
};
var TYPE = 'aggregation';
/**
{
type: 'aggregation',
options: {
column: 'name',
aggregation: 'count' // it could be, e.g., sum if column is numeric
}
}
*/
function Aggregation(query, options) {
if (!_.isString(options.column)) {
throw new Error('Aggregation expects `column` in widget options');
}
if (!_.isString(options.aggregation)) {
throw new Error('Aggregation expects `aggregation` operation in widget options');
}
if (!VALID_OPERATIONS[options.aggregation]) {
throw new Error("Aggregation does not support '" + options.aggregation + "' operation");
}
var requiredOptions = VALID_OPERATIONS[options.aggregation];
var missingOptions = _.difference(requiredOptions, Object.keys(options));
if (missingOptions.length > 0) {
throw new Error(
"Aggregation '" + options.aggregation + "' is missing some options: " + missingOptions.join(',')
);
}
BaseWidget.apply(this);
this.query = query;
this.column = options.column;
this.aggregation = options.aggregation;
this.aggregationColumn = options.aggregationColumn;
}
Aggregation.prototype = new BaseWidget();
Aggregation.prototype.constructor = Aggregation;
module.exports = Aggregation;
Aggregation.prototype.sql = function(psql, filters, override, callback) {
if (!callback) {
callback = override;
override = {};
}
var _query = this.query;
var aggregationSql;
if (!!override.ownFilter) {
aggregationSql = [
"WITH",
[
summaryQueryTpl({
_query: _query,
_column: this.column
}),
rankedCategoriesQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql()
}),
categoriesSummaryQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
aggregationQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql(),
_limit: CATEGORIES_LIMIT
})
].join('\n');
} else {
aggregationSql = [
"WITH",
[
summaryQueryTpl({
_query: _query,
_column: this.column
}),
rankedCategoriesQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql()
}),
categoriesSummaryQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
rankedAggregationQueryTpl({
_query: _query,
_column: this.column,
_limit: CATEGORIES_LIMIT
})
].join('\n');
}
debug(aggregationSql);
return callback(null, aggregationSql);
};
var aggregationFnQueryTpl = dot.template('{{=it._aggregationFn}}({{=it._aggregationColumn}})');
Aggregation.prototype.getAggregationSql = function() {
return aggregationFnQueryTpl({
_aggregationFn: this.aggregation,
_aggregationColumn: this.aggregationColumn || 1
});
};
Aggregation.prototype.format = function(result) {
var categories = [];
var count = 0;
var nulls = 0;
var minValue = 0;
var maxValue = 0;
var categoriesCount = 0;
if (result.rows.length) {
var firstRow = result.rows[0];
count = firstRow.count;
nulls = firstRow.nulls_count;
minValue = firstRow.min_val;
maxValue = firstRow.max_val;
categoriesCount = firstRow.categories_count;
result.rows.forEach(function(row) {
categories.push(_.omit(row, 'count', 'nulls_count', 'min_val', 'max_val', 'categories_count'));
});
}
return {
count: count,
nulls: nulls,
min: minValue,
max: maxValue,
categoriesCount: categoriesCount,
categories: categories
};
};
var filterCategoriesQueryTpl = dot.template([
'SELECT {{=it._column}} AS category, {{=it._value}} AS value',
'FROM ({{=it._query}}) _cdb_aggregation_search',
'WHERE CAST({{=it._column}} as text) ILIKE {{=it._userQuery}}',
'GROUP BY {{=it._column}}'
].join('\n'));
var searchQueryTpl = dot.template([
'WITH',
'search_unfiltered AS (',
' {{=it._searchUnfiltered}}',
'),',
'search_filtered AS (',
' {{=it._searchFiltered}}',
'),',
'search_union AS (',
' SELECT * FROM search_unfiltered',
' UNION ALL',
' SELECT * FROM search_filtered',
')',
'SELECT category, sum(value) AS value',
'FROM search_union',
'GROUP BY category',
'ORDER BY value desc'
].join('\n'));
Aggregation.prototype.search = function(psql, userQuery, callback) {
var self = this;
var _userQuery = psql.escapeLiteral('%' + userQuery + '%');
// TODO unfiltered will be wrong as filters are already applied at this point
var query = searchQueryTpl({
_searchUnfiltered: filterCategoriesQueryTpl({
_query: this.query,
_column: this.column,
_value: '0',
_userQuery: _userQuery
}),
_searchFiltered: filterCategoriesQueryTpl({
_query: this.query,
_column: this.column,
_value: 'count(1)',
_userQuery: _userQuery
})
});
psql.query(query, function(err, result) {
if (err) {
return callback(err, result);
}
return callback(null, {type: self.getType(), categories: result.rows });
}, true); // use read-only transaction
};
Aggregation.prototype.getType = function() {
return TYPE;
};
Aggregation.prototype.toString = function() {
return JSON.stringify({
_type: TYPE,
_query: this.query,
_column: this.column,
_aggregation: this.aggregation
});
};

View File

@@ -0,0 +1,26 @@
function BaseDataview() {}
module.exports = BaseDataview;
BaseDataview.prototype.getResult = function(psql, override, callback) {
var self = this;
this.sql(psql, override, function(err, query) {
psql.query(query, function(err, result) {
if (err) {
return callback(err, result);
}
result = self.format(result, override);
result.type = self.getType();
return callback(null, result);
}, true); // use read-only transaction
});
};
BaseDataview.prototype.search = function(psql, userQuery, callback) {
return callback(null, this.format({ rows: [] }));
};

View File

@@ -0,0 +1,18 @@
var dataviews = require('./');
var DataviewFactory = {
dataviews: Object.keys(dataviews).reduce(function(allDataviews, dataviewClassName) {
allDataviews[dataviewClassName.toLowerCase()] = dataviews[dataviewClassName];
return allDataviews;
}, {}),
getDataview: function(query, dataviewDefinition) {
var type = dataviewDefinition.type;
if (!this.dataviews[type]) {
throw new Error('Invalid dataview type: "' + type + '"');
}
return new this.dataviews[type](query, dataviewDefinition.options);
}
};
module.exports = DataviewFactory;

View File

@@ -0,0 +1,104 @@
var _ = require('underscore');
var BaseWidget = require('./base');
var debug = require('debug')('windshaft:widget:formula');
var dot = require('dot');
dot.templateSettings.strip = false;
var formulaQueryTpl = dot.template([
'SELECT',
'{{=it._operation}}({{=it._column}}) AS result,',
'(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls WHERE {{=it._column}} IS NULL) AS nulls_count',
'FROM ({{=it._query}}) _cdb_formula'
].join('\n'));
var VALID_OPERATIONS = {
count: true,
avg: true,
sum: true,
min: true,
max: true
};
var TYPE = 'formula';
/**
{
type: 'formula',
options: {
column: 'name',
operation: 'count' // count, sum, avg
}
}
*/
function Formula(query, options) {
if (!_.isString(options.operation)) {
throw new Error('Formula expects `operation` in widget options');
}
if (!VALID_OPERATIONS[options.operation]) {
throw new Error("Formula does not support '" + options.operation + "' operation");
}
if (options.operation !== 'count' && !_.isString(options.column)) {
throw new Error('Formula expects `column` in widget options');
}
BaseWidget.apply(this);
this.query = query;
this.column = options.column || '1';
this.operation = options.operation;
}
Formula.prototype = new BaseWidget();
Formula.prototype.constructor = Formula;
module.exports = Formula;
Formula.prototype.sql = function(psql, filters, override, callback) {
if (!callback) {
callback = override;
override = {};
}
var _query = this.query;
var formulaSql = formulaQueryTpl({
_query: _query,
_operation: this.operation,
_column: this.column
});
debug(formulaSql);
return callback(null, formulaSql);
};
Formula.prototype.format = function(result) {
var formattedResult = {
operation: this.operation,
result: 0,
nulls: 0
};
if (result.rows.length) {
formattedResult.operation = this.operation;
formattedResult.result = result.rows[0].result;
formattedResult.nulls = result.rows[0].nulls_count;
}
return formattedResult;
};
Formula.prototype.getType = function() {
return TYPE;
};
Formula.prototype.toString = function() {
return JSON.stringify({
_type: TYPE,
_query: this.query,
_column: this.column,
_operation: this.operation
});
};

View File

@@ -0,0 +1,297 @@
var _ = require('underscore');
var BaseWidget = require('./base');
var debug = require('debug')('windshaft:dataview:histogram');
var dot = require('dot');
dot.templateSettings.strip = false;
var columnTypeQueryTpl = dot.template(
'SELECT pg_typeof({{=it.column}})::oid FROM ({{=it.query}}) _cdb_histogram_column_type limit 1'
);
var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})");
var BIN_MIN_NUMBER = 6;
var BIN_MAX_NUMBER = 48;
var basicsQueryTpl = dot.template([
'basics AS (',
' SELECT',
' max({{=it._column}}) AS max_val, min({{=it._column}}) AS min_val,',
' avg({{=it._column}}) AS avg_val, count(1) AS total_rows',
' FROM ({{=it._query}}) _cdb_basics',
')'
].join(' \n'));
var overrideBasicsQueryTpl = dot.template([
'basics AS (',
' SELECT',
' max({{=it._end}}) AS max_val, min({{=it._start}}) AS min_val,',
' avg({{=it._column}}) AS avg_val, count(1) AS total_rows',
' FROM ({{=it._query}}) _cdb_basics',
')'
].join('\n'));
var iqrQueryTpl = dot.template([
'iqrange AS (',
' SELECT max(quartile_max) - min(quartile_max) AS iqr',
' FROM (',
' SELECT quartile, max(_cdb_iqr_column) AS quartile_max from (',
' SELECT {{=it._column}} AS _cdb_iqr_column, ntile(4) over (order by {{=it._column}}',
' ) AS quartile',
' FROM ({{=it._query}}) _cdb_rank) _cdb_quartiles',
' WHERE quartile = 1 or quartile = 3',
' GROUP BY quartile',
' ) _cdb_iqr',
')'
].join('\n'));
var binsQueryTpl = dot.template([
'bins AS (',
' SELECT CASE WHEN total_rows = 0 OR iqr = 0',
' THEN 1',
' ELSE GREATEST(',
' LEAST({{=it._minBins}}, CAST(total_rows AS INT)),',
' LEAST(',
' CAST(((max_val - min_val) / (2 * iqr * power(total_rows, 1/3))) AS INT),',
' {{=it._maxBins}}',
' )',
' )',
' END AS bins_number',
' FROM basics, iqrange, ({{=it._query}}) _cdb_bins',
' LIMIT 1',
')'
].join('\n'));
var overrideBinsQueryTpl = dot.template([
'bins AS (',
' SELECT {{=it._bins}} AS bins_number',
')'
].join('\n'));
var nullsQueryTpl = dot.template([
'nulls AS (',
' SELECT',
' count(*) AS nulls_count',
' FROM ({{=it._query}}) _cdb_histogram_nulls',
' WHERE {{=it._column}} IS NULL',
')'
].join('\n'));
var histogramQueryTpl = dot.template([
'SELECT',
' (max_val - min_val) / cast(bins_number as float) AS bin_width,',
' bins_number,',
' nulls_count,',
' avg_val,',
' CASE WHEN min_val = max_val',
' THEN 0',
' ELSE GREATEST(1, LEAST(WIDTH_BUCKET({{=it._column}}, min_val, max_val, bins_number), bins_number)) - 1',
' END AS bin,',
' min({{=it._column}})::numeric AS min,',
' max({{=it._column}})::numeric AS max,',
' avg({{=it._column}})::numeric AS avg,',
' count(*) AS freq',
'FROM ({{=it._query}}) _cdb_histogram, basics, nulls, bins',
'WHERE {{=it._column}} IS NOT NULL',
'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val',
'ORDER BY bin'
].join('\n'));
var TYPE = 'histogram';
/**
{
type: 'histogram',
options: {
column: 'name',
bins: 10 // OPTIONAL
}
}
*/
function Histogram(query, options) {
if (!_.isString(options.column)) {
throw new Error('Histogram expects `column` in widget options');
}
this.query = query;
this.column = options.column;
this.bins = options.bins;
this._columnType = null;
}
Histogram.prototype = new BaseWidget();
Histogram.prototype.constructor = Histogram;
module.exports = Histogram;
var DATE_OIDS = {
1082: true,
1114: true,
1184: true
};
Histogram.prototype.sql = function(psql, override, callback) {
if (!callback) {
callback = override;
override = {};
}
var self = this;
var _column = this.column;
var columnTypeQuery = columnTypeQueryTpl({
column: _column, query: this.query
});
if (this._columnType === null) {
psql.query(columnTypeQuery, function(err, result) {
// assume numeric, will fail later
self._columnType = 'numeric';
if (!err && !!result.rows[0]) {
var pgType = result.rows[0].pg_typeof;
if (DATE_OIDS.hasOwnProperty(pgType)) {
self._columnType = 'date';
}
}
self.sql(psql, override, callback);
}, true); // use read-only transaction
return null;
}
if (this._columnType === 'date') {
_column = columnCastTpl({column: _column});
}
var _query = this.query;
var basicsQuery, binsQuery;
if (override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins')) {
debug('overriding with %j', override);
basicsQuery = overrideBasicsQueryTpl({
_query: _query,
_column: _column,
_start: override.start,
_end: override.end
});
binsQuery = [
overrideBinsQueryTpl({
_bins: override.bins
})
].join(',\n');
} else {
basicsQuery = basicsQueryTpl({
_query: _query,
_column: _column
});
if (override && _.has(override, 'bins')) {
binsQuery = [
overrideBinsQueryTpl({
_bins: override.bins
})
].join(',\n');
} else {
binsQuery = [
iqrQueryTpl({
_query: _query,
_column: _column
}),
binsQueryTpl({
_query: _query,
_minBins: BIN_MIN_NUMBER,
_maxBins: BIN_MAX_NUMBER
})
].join(',\n');
}
}
var histogramSql = [
"WITH",
[
basicsQuery,
binsQuery,
nullsQueryTpl({
_query: _query,
_column: _column
})
].join(',\n'),
histogramQueryTpl({
_query: _query,
_column: _column
})
].join('\n');
debug(histogramSql);
return callback(null, histogramSql);
};
Histogram.prototype.format = function(result, override) {
override = override || {};
var buckets = [];
var binsCount = getBinsCount(override);
var width = getWidth(override);
var binsStart = getBinStart(override);
var nulls = 0;
var avg;
if (result.rows.length) {
var firstRow = result.rows[0];
binsCount = firstRow.bins_number;
width = firstRow.bin_width || width;
avg = firstRow.avg_val;
nulls = firstRow.nulls_count;
binsStart = override.hasOwnProperty('start') ? override.start : firstRow.min;
buckets = result.rows.map(function(row) {
return _.omit(row, 'bins_number', 'bin_width', 'nulls_count', 'avg_val');
});
}
return {
bin_width: width,
bins_count: binsCount,
bins_start: binsStart,
nulls: nulls,
avg: avg,
bins: buckets
};
};
function getBinStart(override) {
return override.start || 0;
}
function getBinsCount(override) {
return override.bins || 0;
}
function getWidth(override) {
var width = 0;
var binsCount = override.bins;
if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) {
width = (override.end - override.start) / binsCount;
}
return width;
}
Histogram.prototype.getType = function() {
return TYPE;
};
Histogram.prototype.toString = function() {
return JSON.stringify({
_type: TYPE,
_column: this.column,
_query: this.query
});
};

View File

@@ -0,0 +1,6 @@
module.exports = {
Aggregation: require('./aggregation'),
Formula: require('./formula'),
Histogram: require('./histogram'),
List: require('./list')
};

View File

@@ -0,0 +1,66 @@
var dot = require('dot');
dot.templateSettings.strip = false;
var BaseWidget = require('./base');
var TYPE = 'list';
var listSqlTpl = dot.template('select {{=it._columns}} from ({{=it._query}}) as _cdb_list');
/**
{
type: 'list',
options: {
columns: ['name', 'description']
}
}
*/
function List(query, options) {
options = options || {};
if (!Array.isArray(options.columns)) {
throw new Error('List expects `columns` array in widget options');
}
BaseWidget.apply(this);
this.query = query;
this.columns = options.columns;
}
List.prototype = new BaseWidget();
List.prototype.constructor = List;
module.exports = List;
List.prototype.sql = function(psql, filters, override, callback) {
if (!callback) {
callback = override;
}
var listSql = listSqlTpl({
_query: this.query,
_columns: this.columns.join(', ')
});
return callback(null, listSql);
};
List.prototype.format = function(result) {
return {
rows: result.rows
};
};
List.prototype.getType = function() {
return TYPE;
};
List.prototype.toString = function() {
return JSON.stringify({
_type: TYPE,
_query: this.query,
_columns: this.columns.join(', ')
});
};

View File

@@ -0,0 +1,122 @@
var debug = require('debug')('windshaft:filter:bbox');
var dot = require('dot');
dot.templateSettings.strip = false;
var filterQueryTpl = dot.template([
'SELECT * FROM ({{=it._sql}}) _cdb_bbox_filter',
'WHERE {{=it._filters}}'
].join('\n'));
var bboxFilterTpl = dot.template(
'{{=it._column}} && ST_Transform(ST_MakeEnvelope({{=it._bbox}}, 4326), {{=it._srid}})'
);
var LATITUDE_MAX_VALUE = 85.0511287798066;
var LONGITUDE_LOWER_BOUND = -180;
var LONGITUDE_UPPER_BOUND = 180;
var LONGITUDE_RANGE = LONGITUDE_UPPER_BOUND - LONGITUDE_LOWER_BOUND;
/**
Definition
{
"type”: "bbox",
"options": {
"column": "the_geom_webmercator",
"srid": 3857
}
}
Params
{
“bbox”: "west,south,east,north"
}
*/
function BBox(filterDefinition, filterParams) {
var bbox = filterParams.bbox;
if (!bbox) {
throw new Error('BBox filter expects to have a bbox param');
}
var bboxElements = bbox.split(',').map(function(e) { return +e; });
validateBboxElements(bboxElements);
this.column = filterDefinition.column || 'the_geom_webmercator';
this.srid = filterDefinition.srid || 3857;
// Latitudes must be within max extent
var south = Math.max(bboxElements[1], -LATITUDE_MAX_VALUE);
var north = Math.min(bboxElements[3], LATITUDE_MAX_VALUE);
// Longitudes crossing 180º need another approach
var adjustedLongitudeRange = adjustLongitudeRange([bboxElements[0], bboxElements[2]]);
var west = adjustedLongitudeRange[0];
var east = adjustedLongitudeRange[1];
this.bboxes = getBoundingBoxes(west, south, east, north);
}
function getBoundingBoxes(west, south, east, north) {
var bboxes = [];
if (east - west >= 360) {
bboxes.push([-180, south, 180, north]);
} else if (west >= -180 && east <= 180) {
bboxes.push([west, south, east, north]);
} else {
bboxes.push([west, south, 180, north]);
bboxes.push([-180, south, east % 180, north]);
}
return bboxes;
}
function validateBboxElements(bboxElements) {
var isNumericBbox = bboxElements
.map(function(n) { return Number.isFinite(n); })
.reduce(function(allFinite, isFinite) {
if (!allFinite) {
return false;
}
return isFinite;
}, true);
if (bboxElements.length !== 4 || !isNumericBbox) {
throw new Error('Invalid bbox filter, expected format="west,south,east,north"');
}
}
function adjustLongitudeRange(we) {
var west = we[0];
west -= LONGITUDE_LOWER_BOUND;
west = west - (LONGITUDE_RANGE * Math.floor(west / LONGITUDE_RANGE)) + LONGITUDE_LOWER_BOUND;
var longitudeRange = Math.min(we[1] - we[0], 360);
return [west, west + longitudeRange];
}
module.exports = BBox;
module.exports.adjustLongitudeRange = adjustLongitudeRange;
module.exports.LATITUDE_MAX_VALUE = LATITUDE_MAX_VALUE;
module.exports.LONGITUDE_MAX_VALUE = LONGITUDE_UPPER_BOUND;
BBox.prototype.sql = function(rawSql) {
var bboxSql = filterQueryTpl({
_sql: rawSql,
_filters: this.bboxes.map(function(bbox) {
return bboxFilterTpl({
_column: this.column,
_bbox: bbox.join(','),
_srid: this.srid
});
}.bind(this)).join(' OR ')
});
debug(bboxSql);
return bboxSql;
};

View File

@@ -5,18 +5,23 @@ var dot = require('dot');
var step = require('step');
var MapConfig = require('windshaft').model.MapConfig;
var templateName = require('../../backends/template_maps').templateName;
var QueryTables = require('cartodb-query-tables');
/**
* @constructor
* @type {NamedMapMapConfigProvider}
*/
function NamedMapMapConfigProvider(templateMaps, pgConnection, userLimitsApi, queryTablesApi, namedLayersAdapter,
function NamedMapMapConfigProvider(templateMaps, pgConnection, metadataBackend, userLimitsApi,
namedLayersAdapter, overviewsAdapter, turboCartoAdapter, analysisMapConfigAdapter,
owner, templateId, config, authToken, params) {
this.templateMaps = templateMaps;
this.pgConnection = pgConnection;
this.metadataBackend = metadataBackend;
this.userLimitsApi = userLimitsApi;
this.queryTablesApi = queryTablesApi;
this.namedLayersAdapter = namedLayersAdapter;
this.turboCartoAdapter = turboCartoAdapter;
this.analysisMapConfigAdapter = analysisMapConfigAdapter;
this.overviewsAdapter = overviewsAdapter;
this.owner = owner;
this.templateName = templateName(templateId);
@@ -36,6 +41,7 @@ function NamedMapMapConfigProvider(templateMaps, pgConnection, userLimitsApi, qu
this.mapConfig = null;
this.rendererParams = null;
this.context = {};
this.analysesResults = [];
}
module.exports = NamedMapMapConfigProvider;
@@ -50,15 +56,29 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
var mapConfig = null;
var datasource = null;
var rendererParams;
var apiKey;
step(
function getTemplate() {
self.getTemplate(this);
},
function prepareParams(err, tpl) {
function prepareDbParams(err, tpl) {
assert.ifError(err);
self.template = tpl;
rendererParams = _.extend({}, self.params, {
user: self.owner
});
self.setDBParams(self.owner, rendererParams, this);
},
function getUserApiKey(err) {
assert.ifError(err);
self.metadataBackend.getUserMapKey(self.owner, this);
},
function prepareParams(err, _apiKey) {
assert.ifError(err);
self.template = tpl;
apiKey = _apiKey;
var templateParams = {};
if (self.config) {
@@ -75,9 +95,36 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
assert.ifError(err);
return self.templateMaps.instance(self.template, templateParams);
},
function prepareLayergroup(err, _mapConfig) {
function prepareAnalysisLayers(err, requestMapConfig) {
assert.ifError(err);
var analysisConfiguration = {
db: {
host: rendererParams.dbhost,
port: rendererParams.dbport,
dbname: rendererParams.dbname,
user: rendererParams.dbuser,
pass: rendererParams.dbpassword
},
batch: {
username: self.owner,
apiKey: apiKey
}
};
var filters = {};
if (self.params.filters) {
try {
filters = JSON.parse(self.params.filters);
} catch (e) {
// ignore
}
}
self.analysisMapConfigAdapter.getMapConfig(analysisConfiguration, requestMapConfig, filters, this);
},
function prepareLayergroup(err, _mapConfig, analysesResults) {
assert.ifError(err);
var next = this;
self.analysesResults = analysesResults || [];
self.namedLayersAdapter.getLayers(self.owner, _mapConfig.layers, self.pgConnection,
function(err, layers, datasource) {
if (err) {
@@ -91,17 +138,42 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
}
);
},
function beforeLayergroupCreate(err, _mapConfig, _datasource) {
function addOverviewsInformation(err, _mapConfig, datasource) {
assert.ifError(err);
var next = this;
self.overviewsAdapter.getLayers(self.owner, _mapConfig.layers, function(err, layers) {
if (err) {
return next(err);
}
if (layers) {
_mapConfig.layers = layers;
}
return next(null, _mapConfig, datasource);
});
},
function parseTurboCarto(err, _mapConfig, datasource) {
assert.ifError(err);
var next = this;
self.turboCartoAdapter.getLayers(self.owner, _mapConfig.layers, function (err, layers) {
if (err) {
return next(err);
}
if (layers) {
_mapConfig.layers = layers;
}
return next(null, _mapConfig, datasource);
});
},
function prepareContextLimits(err, _mapConfig, _datasource) {
assert.ifError(err);
mapConfig = _mapConfig;
datasource = _datasource;
rendererParams = _.extend({}, self.params, {
user: self.owner
});
self.setDBParams(self.owner, rendererParams, this);
},
function prepareContextLimits(err) {
assert.ifError(err);
self.userLimitsApi.getRenderLimits(self.owner, this);
},
function cacheAndReturnMapConfig(err, renderLimits) {
@@ -256,7 +328,16 @@ NamedMapMapConfigProvider.prototype.getAffectedTablesAndLastUpdatedTime = functi
},
function getAffectedTables(err, sql) {
assert.ifError(err);
self.queryTablesApi.getAffectedTablesAndLastUpdatedTime(self.owner, sql, this);
step(
function getConnection() {
self.pgConnection.getConnection(self.owner, this);
},
function getAffectedTables(err, connection) {
assert.ifError(err);
QueryTables.getAffectedTablesFromQuery(connection, sql, this);
},
this
);
},
function finish(err, result) {
self.affectedTablesAndLastUpdate = result;

View File

@@ -3,6 +3,7 @@ var bodyParser = require('body-parser');
var RedisPool = require('redis-mpool');
var cartodbRedis = require('cartodb-redis');
var _ = require('underscore');
var debug = require('debug')('windshaft:cartodb');
var controller = require('./controllers');
@@ -19,7 +20,6 @@ var windshaft = require('windshaft');
var mapnik = windshaft.mapnik;
var TemplateMaps = require('./backends/template_maps.js');
var QueryTablesApi = require('./api/query_tables_api');
var OverviewsMetadataApi = require('./api/overviews_metadata_api');
var UserLimitsApi = require('./api/user_limits_api');
var AuthApi = require('./api/auth_api');
@@ -28,9 +28,15 @@ var NamedMapProviderCache = require('./cache/named_map_provider_cache');
var PgQueryRunner = require('./backends/pg_query_runner');
var PgConnection = require('./backends/pg_connection');
var AnalysisBackend = require('./backends/analysis');
var timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png';
var timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});
var MapConfigOverviewsAdapter = require('./models/mapconfig_overviews_adapter');
var TurboCartoParser = require('./utils/style/turbo-carto-parser');
var TurboCartoAdapter = require('./utils/style/turbo-carto-adapter');
module.exports = function(serverOptions) {
// Make stats client globally accessible
@@ -52,7 +58,6 @@ module.exports = function(serverOptions) {
var metadataBackend = cartodbRedis({pool: redisPool});
var pgConnection = new PgConnection(metadataBackend);
var pgQueryRunner = new PgQueryRunner(pgConnection);
var queryTablesApi = new QueryTablesApi(pgQueryRunner);
var overviewsMetadataApi = new OverviewsMetadataApi(pgQueryRunner);
var userLimitsApi = new UserLimitsApi(metadataBackend, {
limits: {
@@ -138,11 +143,25 @@ module.exports = function(serverOptions) {
var tileBackend = new windshaft.backend.Tile(rendererCache);
var mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend);
var mapBackend = new windshaft.backend.Map(rendererCache, mapStore, mapValidatorBackend);
var analysisBackend = new AnalysisBackend(serverOptions.analysis);
var layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();
app.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
var namedMapProviderCache = new NamedMapProviderCache(templateMaps, pgConnection, userLimitsApi, queryTablesApi);
var overviewsAdapter = new MapConfigOverviewsAdapter(overviewsMetadataApi);
var turboCartoParser = new TurboCartoParser(pgQueryRunner);
var turboCartoAdapter = new TurboCartoAdapter(turboCartoParser);
var namedMapProviderCache = new NamedMapProviderCache(
templateMaps,
pgConnection,
metadataBackend,
userLimitsApi,
overviewsAdapter,
turboCartoAdapter
);
['update', 'delete'].forEach(function(eventType) {
templateMaps.on(eventType, namedMapProviderCache.invalidate.bind(namedMapProviderCache));
});
@@ -166,8 +185,8 @@ module.exports = function(serverOptions) {
new windshaft.backend.Widget(),
surrogateKeysCache,
userLimitsApi,
queryTablesApi,
layergroupAffectedTablesCache
layergroupAffectedTablesCache,
analysisBackend
).register(app);
new controller.Map(
@@ -176,11 +195,12 @@ module.exports = function(serverOptions) {
templateMaps,
mapBackend,
metadataBackend,
queryTablesApi,
overviewsMetadataApi,
surrogateKeysCache,
userLimitsApi,
layergroupAffectedTablesCache
layergroupAffectedTablesCache,
overviewsAdapter,
turboCartoAdapter,
analysisBackend
).register(app);
new controller.NamedMaps(
@@ -212,7 +232,7 @@ function validateOptions(opts) {
// Be nice and warn if configured mapnik version is != instaled mapnik version
if (mapnik.versions.mapnik !== opts.grainstore.mapnik_version) {
console.warn('WARNING: detected mapnik version (' + mapnik.versions.mapnik + ')' +
debug('WARNING: detected mapnik version (' + mapnik.versions.mapnik + ')' +
' != configured mapnik version (' + opts.grainstore.mapnik_version + ')');
}
}

View File

@@ -3,7 +3,7 @@ var _ = require('underscore');
var OverviewsQueryRewriter = require('./utils/overviews_query_rewriter');
var overviewsQueryRewriter = new OverviewsQueryRewriter({
zoom_level: 'CDB_ZoomFromScale(!scale_denominator!)'
zoom_level: 'CDB_ZoomFromScale(!scale_denominator!)'
});
var rendererConfig = _.defaults(global.environment.renderer || {}, {
@@ -24,73 +24,89 @@ rendererConfig.mapnik.queryRewriter = overviewsQueryRewriter;
// Perform keyword substitution in statsd
// See https://github.com/CartoDB/Windshaft-cartodb/issues/153
if ( global.environment.statsd ) {
if ( global.environment.statsd.prefix ) {
if (global.environment.statsd) {
if (global.environment.statsd.prefix) {
var host_token = os.hostname().split('.').reverse().join('.');
global.environment.statsd.prefix = global.environment.statsd.prefix.replace(/:host/, host_token);
}
}
var analysisConfig = _.defaults(global.environment.analysis || {}, {
batch: {
inlineExecution: false,
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
}
});
module.exports = {
bind: {
port: global.environment.port,
host: global.environment.host
},
// This is for inline maps and table maps
base_url: global.environment.base_url_legacy || '/tiles/:table',
// This is for inline maps and table maps
base_url: global.environment.base_url_legacy || '/tiles/:table',
/// @deprecated with Windshaft-0.17.0
///base_url_notable: '/tiles',
/// @deprecated with Windshaft-0.17.0
///base_url_notable: '/tiles',
// This is for Detached maps
//
// "maps" is the official, while
// "tiles/layergroup" is for backward compatibility up to 1.6.x
//
base_url_mapconfig: global.environment.base_url_detached || '(?:/maps|/tiles/layergroup)',
// This is for Detached maps
//
// "maps" is the official, while
// "tiles/layergroup" is for backward compatibility up to 1.6.x
//
base_url_mapconfig: global.environment.base_url_detached || '(?:/maps|/tiles/layergroup)',
base_url_templated: global.environment.base_url_templated || '(?:/maps/named|/tiles/template)',
base_url_templated: global.environment.base_url_templated || '(?:/maps/named|/tiles/template)',
grainstore: {
map: {
grainstore: {
map: {
// TODO: allow to specify in configuration
srid: 3857
},
datasource: global.environment.postgres,
cachedir: global.environment.millstone.cache_basedir,
mapnik_version: global.environment.mapnik_version,
mapnik_tile_format: global.environment.mapnik_tile_format || 'png',
default_layergroup_ttl: global.environment.mapConfigTTL || 7200
},
statsd: global.environment.statsd,
renderCache: {
ttl: rendererConfig.cache_ttl,
statsInterval: rendererConfig.statsInterval
},
renderer: {
mapnik: _.defaults(rendererConfig.mapnik, {
geojson: {
dbPoolParams: {
size: 16,
idleTimeout: 3000,
reapInterval: 1000
},
clipByBox2d: false,
}
}),
torque: rendererConfig.torque,
http: rendererConfig.http
},
// Do not send unwatch on release. See http://github.com/CartoDB/Windshaft-cartodb/issues/161
redis: _.extend(global.environment.redis, {unwatchOnRelease: false}),
enable_cors: global.environment.enable_cors,
varnish_host: global.environment.varnish.host,
varnish_port: global.environment.varnish.port,
varnish_http_port: global.environment.varnish.http_port,
varnish_secret: global.environment.varnish.secret,
varnish_purge_enabled: global.environment.varnish.purge_enabled,
fastly: global.environment.fastly || {},
cache_enabled: global.environment.cache_enabled,
log_format: global.environment.log_format,
useProfiler: global.environment.useProfiler
datasource: global.environment.postgres,
cachedir: global.environment.millstone.cache_basedir,
mapnik_version: global.environment.mapnik_version,
mapnik_tile_format: global.environment.mapnik_tile_format || 'png',
default_layergroup_ttl: global.environment.mapConfigTTL || 7200
},
statsd: global.environment.statsd,
renderCache: {
ttl: rendererConfig.cache_ttl,
statsInterval: rendererConfig.statsInterval
},
renderer: {
mapnik: _.defaults(rendererConfig.mapnik, {
geojson: {
dbPoolParams: {
size: 16,
idleTimeout: 3000,
reapInterval: 1000
},
clipByBox2d: false
}
}),
torque: rendererConfig.torque,
http: rendererConfig.http
},
analysis: {
batch: {
inlineExecution: analysisConfig.batch.inlineExecution,
endpoint: analysisConfig.batch.endpoint,
hostHeaderTemplate: analysisConfig.batch.hostHeaderTemplate
}
},
// Do not send unwatch on release. See http://github.com/CartoDB/Windshaft-cartodb/issues/161
redis: _.extend(global.environment.redis, {unwatchOnRelease: false}),
enable_cors: global.environment.enable_cors,
varnish_host: global.environment.varnish.host,
varnish_port: global.environment.varnish.port,
varnish_http_port: global.environment.varnish.http_port,
varnish_secret: global.environment.varnish.secret,
varnish_purge_enabled: global.environment.varnish.purge_enabled,
fastly: global.environment.fastly || {},
cache_enabled: global.environment.cache_enabled,
log_format: global.environment.log_format,
useProfiler: global.environment.useProfiler
};

View File

@@ -0,0 +1,32 @@
function Timer() {
this.times = {};
}
module.exports = Timer;
Timer.prototype.start = function(label) {
this.timeIt(label, 'start');
};
Timer.prototype.end = function(label) {
this.timeIt(label, 'end');
};
Timer.prototype.timeIt = function(label, what) {
this.times[label] = this.times[label] || {};
this.times[label][what] = Date.now();
};
Timer.prototype.getTimes = function() {
var self = this;
var times = {};
Object.keys(this.times).forEach(function(label) {
var stat = self.times[label];
if (stat.start && stat.end) {
times[label] = Math.max(0, stat.end - stat.start);
}
});
return times;
};

View File

@@ -22,7 +22,7 @@ function overviews_view_for_table(table, overviews_metadata, indent) {
indent = indent || ' ';
for (var z in overviews_metadata) {
if (overviews_metadata.hasOwnProperty(z)) {
if (overviews_metadata.hasOwnProperty(z) && z !== 'schema') {
sorted_overviews.push([z, overviews_metadata[z].table]);
}
}
@@ -39,11 +39,11 @@ function overviews_view_for_table(table, overviews_metadata, indent) {
overview_layers.push(["_vovw_z > " + z_lo, table]);
selects = overview_layers.map(function(condition_table) {
condition = condition_table[0];
ov_table = TableNameParser.parse(condition_table[1]);
ov_table.schema = ov_table.schema || parsed_table.schema;
var ov_identifier = TableNameParser.table_identifier(ov_table);
return indent + "SELECT * FROM " + ov_identifier + ", _vovw_scale WHERE " + condition;
condition = condition_table[0];
ov_table = TableNameParser.parse(condition_table[1]);
ov_table.schema = ov_table.schema || parsed_table.schema;
var ov_identifier = TableNameParser.table_identifier(ov_table);
return indent + "SELECT * FROM " + ov_identifier + ", _vovw_scale WHERE " + condition;
});
return selects.join("\n"+indent+"UNION ALL\n");
@@ -74,14 +74,9 @@ function overviews_view_name(table) {
}
// replace a table name in a query by anoter name
function replace_table_in_query(sql, old_table_name, new_table_name) {
function replace_table_in_query(sql, old_table_name, replacement) {
var old_table = TableNameParser.parse(old_table_name);
var new_table = TableNameParser.parse(new_table_name);
var old_table_ident = TableNameParser.table_identifier(old_table);
var new_table_ident = TableNameParser.table_identifier(new_table);
// text that will be substituted by the table name pattern
var replacement = new_table_ident;
// regular expression prefix (beginning) to match a table name
function pattern_prefix(schema, identifier) {
@@ -95,13 +90,13 @@ function replace_table_in_query(sql, old_table_name, new_table_name) {
return '';
}
} else {
// to match a table name without schema
// name should not begin right after a dot (i.e. have a explicit schema)
// nor be part of another name
// since the pattern matches the first character of the table
// it must be put back in the replacement text
replacement = '$01'+replacement;
return '([^\.a-z0-9_]|^)';
// to match a table name without schema
// name should not begin right after a dot (i.e. have a explicit schema)
// nor be part of another name
// since the pattern matches the first character of the table
// it must be put back in the replacement text
replacement = '$01'+replacement;
return '([^\.a-z0-9_]|^)';
}
}
@@ -127,12 +122,21 @@ function replace_table_in_query(sql, old_table_name, new_table_name) {
function overviews_query(query, overviews, zoom_level_expression) {
var replaced_query = query;
var sql = "WITH\n _vovw_scale AS ( SELECT " + zoom_level_expression + " AS _vovw_z )";
var replacement;
for ( var table in overviews ) {
if (overviews.hasOwnProperty(table)) {
var table_overviews = overviews[table];
var table_view = overviews_view_name(table);
replaced_query = replace_table_in_query(replaced_query, table, table_view);
sql += ",\n " + table_view + " AS (\n" + overviews_view_for_table(table, table_overviews) + "\n )";
var schema = table_overviews.schema;
replacement = "(\n" + overviews_view_for_table(table, table_overviews) + "\n ) AS " + table_view;
replaced_query = replace_table_in_query(replaced_query, table, replacement);
var parsed_table = TableNameParser.parse(table);
if (!parsed_table.schema && schema) {
// replace also the qualified table name, if the table wasn't qualified
parsed_table.schema = schema;
table = TableNameParser.table_identifier(parsed_table);
replaced_query = replace_table_in_query(replaced_query, table, replacement);
}
}
}
if ( replaced_query !== query ) {
@@ -172,11 +176,19 @@ OverviewsQueryRewriter.prototype.query = function(query, data) {
};
OverviewsQueryRewriter.prototype.is_supported_query = function(sql) {
return !!sql.match(
/^\s*SELECT\s+[\*\.a-z0-9_,\s]+?\s+FROM\s+((\"[^"]+\"|[a-z0-9_]+)\.)?(\"[^"]+\"|[a-z0-9_]+)\s*;?\s*$/i
);
var basic_query =
/\s*SELECT\s+[\*a-z0-9_,\s]+?\s+FROM\s+((\"[^"]+\"|[a-z0-9_]+)\.)?(\"[^"]+\"|[a-z0-9_]+)\s*;?\s*/i;
var unwrapped_query = new RegExp("^"+basic_query.source+"$", 'i');
// queries for named maps are wrapped like this:
var wrapped_query = new RegExp(
"^\\s*SELECT\\s+\\*\\s+FROM\\s+\\(" +
basic_query.source +
"\\)\\s+AS\\s+wrapped_query\\s+WHERE\\s+\\d+=1\\s*$",
'i'
);
return !!(sql.match(unwrapped_query) || sql.match(wrapped_query));
};
OverviewsQueryRewriter.prototype.overviews_metadata = function(data) {
return data && data.overviews;
return data && data.overviews;
};

View File

@@ -0,0 +1,55 @@
'use strict';
var dot = require('dot');
dot.templateSettings.strip = false;
function createTemplate(method) {
return dot.template([
'SELECT',
method,
'FROM ({{=it._sql}}) _table_sql WHERE {{=it._column}} IS NOT NULL'
].join('\n'));
}
var methods = {
quantiles: 'CDB_QuantileBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as quantiles',
equal: 'CDB_EqualIntervalBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as equal',
jenks: 'CDB_JenksBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as jenks',
headtails: 'CDB_HeadsTailsBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as headtails'
};
var methodTemplates = Object.keys(methods).reduce(function(methodTemplates, methodName) {
methodTemplates[methodName] = createTemplate(methods[methodName]);
return methodTemplates;
}, {});
function PostgresDatasource (pgQueryRunner, username, query) {
this.pgQueryRunner = pgQueryRunner;
this.username = username;
this.query = query;
}
PostgresDatasource.prototype.getName = function () {
return 'PostgresDatasource';
};
PostgresDatasource.prototype.getRamp = function (column, buckets, method, callback) {
var methodName = methods.hasOwnProperty(method) ? method : 'quantiles';
var template = methodTemplates[methodName];
var query = template({ _column: column, _sql: this.query, _buckets: buckets });
this.pgQueryRunner.run(this.username, query, function (err, result) {
if (err) {
return callback(err);
}
var ramp = result[0][methodName].sort(function(a, b) {
return a - b;
});
return callback(null, ramp);
});
};
module.exports = PostgresDatasource;

View File

@@ -0,0 +1,56 @@
'use strict';
var queue = require('queue-async');
function TurboCartoAdapter(turboCartoParser) {
this.turboCartoParser = turboCartoParser;
}
module.exports = TurboCartoAdapter;
TurboCartoAdapter.prototype.getLayers = function (username, layers, callback) {
var self = this;
if (!layers || layers.length === 0) {
return callback(null, layers);
}
var parseCartoQueue = queue(layers.length);
layers.forEach(function(layer) {
parseCartoQueue.defer(self._parseCartoCss.bind(self), username, layer);
});
parseCartoQueue.awaitAll(function (err, layers) {
if (err) {
return callback(err);
}
return callback(null, layers);
});
};
TurboCartoAdapter.prototype._parseCartoCss = function (username, layer, callback) {
if (isNotLayerToParseCartocss(layer)) {
return process.nextTick(function () {
callback(null, layer);
});
}
this.turboCartoParser.process(username, layer.options.cartocss, layer.options.sql, function (err, cartocss) {
// Ignore turbo-carto errors and continue
if (!err && cartocss) {
layer.options.cartocss = cartocss;
}
callback(null, layer);
});
};
function isNotLayerToParseCartocss(layer) {
if (!layer || !layer.options || !layer.options.cartocss || !layer.options.sql) {
return true;
}
return false;
}

View File

@@ -0,0 +1,15 @@
'use strict';
var turboCarto = require('turbo-carto');
var PostgresDatasource = require('./postgres-datasource');
function TurboCartoParser (pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
}
module.exports = TurboCartoParser;
TurboCartoParser.prototype.process = function (username, cartocss, sql, callback) {
var datasource = new PostgresDatasource(this.pgQueryRunner, username, sql);
turboCarto(cartocss, datasource, callback);
};

1976
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +1,61 @@
{
"private": true,
"name": "windshaft-cartodb",
"version": "2.26.2",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
],
"url": "https://github.com/CartoDB/Windshaft-cartodb",
"license": "BSD-3-Clause",
"repository": {
"type": "git",
"url": "git://github.com/CartoDB/Windshaft-cartodb.git"
},
"author": "Vizzuality <contact@vizzuality.com> (http://vizzuality.com)",
"contributors": [
"Simon Tokumine <simon@vizzuality.com>",
"Javi Santana <jsantana@vizzuality.com>",
"Sandro Santilli <strk@vizzuality.com>"
],
"dependencies": {
"express": "~4.13.3",
"body-parser": "~1.14.0",
"debug": "~2.2.0",
"step-profiler": "~0.2.1",
"node-statsd": "~0.0.7",
"underscore" : "~1.6.0",
"dot": "~1.0.2",
"windshaft": "1.13.2",
"step": "~0.0.6",
"queue-async": "~1.0.7",
"request": "~2.62.0",
"cartodb-redis": "~0.13.0",
"cartodb-psql": "~0.4.0",
"fastly-purge": "~1.0.1",
"redis-mpool": "~0.4.0",
"lru-cache": "2.6.5",
"lzma": "~1.3.7",
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb"
},
"devDependencies": {
"istanbul": "~0.3.6",
"mocha": "~1.21.4",
"nock": "~2.11.0",
"jshint": "~2.6.0",
"redis": "~0.8.6",
"strftime": "~0.8.2",
"semver": "~1.1.4"
},
"scripts": {
"preinstall": "make pre-install",
"test": "make test-all"
},
"engines": {
"node": ">=0.8 <0.11",
"npm": ">=2.14.16"
}
"private": true,
"name": "windshaft-cartodb",
"version": "2.41.1",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
],
"url": "https://github.com/CartoDB/Windshaft-cartodb",
"license": "BSD-3-Clause",
"repository": {
"type": "git",
"url": "git://github.com/CartoDB/Windshaft-cartodb.git"
},
"author": "Vizzuality <contact@vizzuality.com> (http://vizzuality.com)",
"contributors": [
"Simon Tokumine <simon@vizzuality.com>",
"Javi Santana <jsantana@vizzuality.com>",
"Sandro Santilli <strk@vizzuality.com>"
],
"dependencies": {
"body-parser": "~1.14.0",
"camshaft": "0.8.0",
"cartodb-psql": "~0.6.1",
"cartodb-query-tables": "~0.1.0",
"cartodb-redis": "~0.13.0",
"debug": "~2.2.0",
"dot": "~1.0.2",
"express": "~4.13.3",
"fastly-purge": "~1.0.1",
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb",
"lru-cache": "2.6.5",
"lzma": "~1.3.7",
"node-statsd": "~0.0.7",
"queue-async": "~1.0.7",
"redis-mpool": "~0.4.0",
"request": "~2.62.0",
"step": "~0.0.6",
"step-profiler": "~0.3.0",
"turbo-carto": "0.7.1",
"underscore": "~1.6.0",
"windshaft": "1.19.0"
},
"devDependencies": {
"istanbul": "~0.4.3",
"jshint": "~2.6.0",
"mocha": "~1.21.4",
"nock": "~2.11.0",
"redis": "~0.8.6",
"semver": "~1.1.4",
"strftime": "~0.8.2"
},
"scripts": {
"preinstall": "make pre-install",
"test": "make test-all"
},
"engines": {
"node": ">=0.8 <0.11",
"npm": ">=2.14.16"
}
}

View File

@@ -0,0 +1,148 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
var dot = require('dot');
describe('analysis-layers-dataviews', function() {
var multitypeStyleTemplate = dot.template([
"#points['mapnik::geometry_type'=1] {",
" marker-fill-opacity: {{=it._opacity}};",
" marker-line-color: #FFF;",
" marker-line-width: 0.5;",
" marker-line-opacity: {{=it._opacity}};",
" marker-placement: point;",
" marker-type: ellipse;",
" marker-width: 8;",
" marker-fill: {{=it._color}};",
" marker-allow-overlap: true;",
"}",
"#lines['mapnik::geometry_type'=2] {",
" line-color: {{=it._color}};",
" line-width: 2;",
" line-opacity: {{=it._opacity}};",
"}",
"#polygons['mapnik::geometry_type'=3] {",
" polygon-fill: {{=it._color}};",
" polygon-opacity: {{=it._opacity}};",
" line-color: #FFF;",
" line-width: 0.5;",
" line-opacity: {{=it._opacity}};",
"}"
].join('\n'));
function cartocss(color, opacity) {
return multitypeStyleTemplate({
_color: color || '#F11810',
_opacity: Number.isFinite(opacity) ? opacity : 1
});
}
function createMapConfig(layers, dataviews, analysis) {
return {
version: '1.5.0',
layers: layers,
dataviews: dataviews || {},
analyses: analysis || []
};
}
var DEFAULT_MULTITYPE_STYLE = cartocss();
var mapConfig = createMapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
},
"cartocss": DEFAULT_MULTITYPE_STYLE,
"cartocss_version": "2.3.0"
}
}
],
{
pop_max_histogram: {
source: {
id: '2570e105-7b37-40d2-bdf4-1af889598745'
},
type: 'histogram',
options: {
column: 'pop_max'
}
}
},
[
{
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
}
]
);
it('should get histogram dataview', function(done) {
var testClient = new TestClient(mapConfig, 1234);
testClient.getDataview('pop_max_histogram', function(err, dataview) {
assert.ok(!err, err);
assert.equal(dataview.type, 'histogram');
assert.equal(dataview.bins_start, 0);
testClient.drain(done);
});
});
it('should get a filtered histogram dataview', function(done) {
var testClient = new TestClient(mapConfig, 1234);
var params = {
filters: {
dataviews: {
pop_max_histogram: {
min: 2e6
}
}
}
};
testClient.getDataview('pop_max_histogram', params, function(err, dataview) {
assert.ok(!err, err);
assert.equal(dataview.type, 'histogram');
assert.equal(dataview.bins_start, 2008000);
testClient.drain(done);
});
});
it('should skip the filter when sending own_filter=0 for histogram dataview', function(done) {
var testClient = new TestClient(mapConfig, 1234);
var params = {
filters: {
dataviews: {
pop_max_histogram: {
min: 2e6
}
}
},
own_filter: 0
};
testClient.getDataview('pop_max_histogram', params, function(err, dataview) {
assert.ok(!err, err);
assert.equal(dataview.type, 'histogram');
assert.equal(dataview.bins_start, 0);
testClient.drain(done);
});
});
});

View File

@@ -0,0 +1,81 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('analysis-layers-dataviews-geojson', function() {
function createMapConfig(layers, dataviews, analysis) {
return {
version: '1.5.0',
layers: layers,
dataviews: dataviews || {},
analyses: analysis || []
};
}
var CARTOCSS = [
"#points {",
" marker-fill-opacity: 1.0;",
" marker-line-color: #FFF;",
" marker-line-width: 0.5;",
" marker-line-opacity: 1.0;",
" marker-placement: point;",
" marker-type: ellipse;",
" marker-width: 8;",
" marker-fill: red;",
" marker-allow-overlap: true;",
"}"
].join('\n');
var mapConfig = createMapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
},
"cartocss": CARTOCSS,
"cartocss_version": "2.3.0"
}
}
],
{
pop_max_histogram: {
source: {
id: '2570e105-7b37-40d2-bdf4-1af889598745'
},
type: 'histogram',
options: {
column: 'pop_max'
}
}
},
[
{
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
}
]
);
it('should get pop_max column from dataview', function(done) {
var testClient = new TestClient(mapConfig, 1234);
testClient.getTile(0, 0, 0, {format: 'geojson', layers: 0}, function(err, res, geojson) {
assert.ok(!err, err);
assert.ok(Array.isArray(geojson.features));
assert.ok(geojson.features.length > 0);
var feature = geojson.features[0];
assert.ok(feature.properties.hasOwnProperty('pop_max'), 'Missing pop_max property');
testClient.drain(done);
});
});
});

View File

@@ -0,0 +1,672 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
var dot = require('dot');
var debug = require('debug')('windshaft:cartodb:test');
describe('analysis-layers use cases', function() {
var multitypeStyleTemplate = dot.template([
"#points['mapnik::geometry_type'=1] {",
" marker-fill-opacity: {{=it._opacity}};",
" marker-line-color: #FFF;",
" marker-line-width: 0.5;",
" marker-line-opacity: {{=it._opacity}};",
" marker-placement: point;",
" marker-type: ellipse;",
" marker-width: 8;",
" marker-fill: {{=it._color}};",
" marker-allow-overlap: true;",
"}",
"#lines['mapnik::geometry_type'=2] {",
" line-color: {{=it._color}};",
" line-width: 2;",
" line-opacity: {{=it._opacity}};",
"}",
"#polygons['mapnik::geometry_type'=3] {",
" polygon-fill: {{=it._color}};",
" polygon-opacity: {{=it._opacity}};",
" line-color: #FFF;",
" line-width: 0.5;",
" line-opacity: {{=it._opacity}};",
"}"
].join('\n'));
function cartocss(color, opacity) {
return multitypeStyleTemplate({
_color: color || '#F11810',
_opacity: Number.isFinite(opacity) ? opacity : 1
});
}
function mapConfig(layers, dataviews, analysis) {
return {
version: '1.5.0',
layers: layers,
dataviews: dataviews || {},
analysis: analysis || []
};
}
function analysisDef(analysis) {
return JSON.stringify(analysis);
}
var DEFAULT_MULTITYPE_STYLE = cartocss();
var TILE_ANALYSIS_TABLES = { z: 14, x: 8023, y: 6177 };
var useCases = [
{
desc: '1 mapnik layer',
mapConfig: {
version: '1.5.0',
layers: [
{
type: 'cartodb',
options: {
sql: "select * from analysis_rent_listings",
cartocss: DEFAULT_MULTITYPE_STYLE,
cartocss_version: '2.3.0'
}
}
]
}
},
{
desc: '2 mapnik layers',
mapConfig: mapConfig([
{
type: 'cartodb',
options: {
sql: "select * from analysis_banks",
cartocss: cartocss('#2167AB'),
cartocss_version: '2.3.0'
}
},
{
type: 'cartodb',
options: {
sql: "select * from analysis_rent_listings",
cartocss: DEFAULT_MULTITYPE_STYLE,
cartocss_version: '2.3.0'
}
}
])
},
{
desc: 'rent listings + buffer over atm-machines',
mapConfig: mapConfig([
{
type: 'cartodb',
options: {
sql: "select * from analysis_rent_listings",
cartocss: DEFAULT_MULTITYPE_STYLE,
cartocss_version: '2.3.0'
}
},
{
type: 'analysis',
options: {
def: analysisDef({
"type": "buffer",
"params": {
"source": {
"type": "source",
"params": {
"query": "select * from analysis_banks"
}
},
"radius": 250
}
}),
cartocss: cartocss('black', 0.5)
}
}
])
},
{
desc: 'rent listings + point-in-polygon from buffer atm-machines and rent listings',
mapConfig: mapConfig([
{
type: 'cartodb',
options: {
sql: "select * from analysis_rent_listings",
cartocss: DEFAULT_MULTITYPE_STYLE,
cartocss_version: '2.3.0'
}
},
{
type: 'analysis',
options: {
def: analysisDef({
"type": "point-in-polygon",
"params": {
"pointsSource": {
"type": "source",
"params": {
"query": "select * from analysis_rent_listings"
}
},
"polygonsSource": {
"type": "buffer",
"params": {
"source": {
"type": "source",
"params": {
"query": "select * from analysis_banks"
}
},
"radius": 250
}
}
}
}),
cartocss: cartocss('green', 1.0)
}
}
])
},
{
desc: 'point-in-polygon from buffer atm-machines and rent listings + rent listings',
mapConfig: mapConfig([
{
type: 'analysis',
options: {
def: analysisDef({
"type": "point-in-polygon",
"params": {
"pointsSource": {
"type": "source",
"params": {
"query": "select * from analysis_rent_listings"
}
},
"polygonsSource": {
"type": "buffer",
"params": {
"source": {
"type": "source",
"params": {
"query": "select * from analysis_banks"
}
},
"radius": 250
}
}
}
}),
cartocss: cartocss('green', 1.0)
}
},
{
type: 'cartodb',
options: {
sql: "select * from analysis_rent_listings",
cartocss: DEFAULT_MULTITYPE_STYLE,
cartocss_version: '2.3.0'
}
}
])
},
{
desc: 'buffer + point-in-polygon from buffer atm-machines and rent listings + rent listings',
mapConfig: mapConfig([
{
type: 'cartodb',
options: {
sql: "select * from analysis_rent_listings",
cartocss: DEFAULT_MULTITYPE_STYLE,
cartocss_version: '2.3.0'
}
},
{
type: 'analysis',
options: {
def: analysisDef({
"type": "buffer",
"params": {
"source": {
"type": "source",
"params": {
"query": "select * from analysis_banks"
}
},
"radius": 300
}
}),
cartocss: cartocss('magenta', 0.5)
}
},
{
type: 'analysis',
options: {
def: analysisDef({
"type": "point-in-polygon",
"params": {
"pointsSource": {
"type": "source",
"params": {
"query": "select * from analysis_rent_listings"
}
},
"polygonsSource": {
"type": "buffer",
"params": {
"source": {
"type": "source",
"params": {
"query": "select * from analysis_banks"
}
},
"radius": 300
}
}
}
}),
cartocss: cartocss('green', 1.0)
}
}
])
},
{
skip: true,
desc: 'buffer + point-in-polygon from buffer atm-machines and rent listings + rent listings',
mapConfig: mapConfig([
{
type: 'cartodb',
options: {
"source": { id: "a" },
"cartocss": DEFAULT_MULTITYPE_STYLE,
"cartocss_version": "2.3.0"
}
},
{
type: 'cartodb',
options: {
"source": { id: "b1" },
"cartocss": cartocss('green', 1.0),
"cartocss_version": "2.3.0"
}
},
{
type: 'cartodb',
options: {
"source": { id: "b2" },
"cartocss": cartocss('magenta', 0.5),
"cartocss_version": "2.3.0"
}
}
],
[
{
id: "b2",
options: {
def: analysisDef({
"type": "count-in-polygon",
"id": "a0",
"params": {
"columnName": 'count_airbnb',
"pointsSource": {
"type": "source",
"params": {
query: "select * from analysis_rent_listings"
},
dataviews: {
price_histogram: {
type: 'histogram',
options: {
column: 'price'
}
}
}
},
"polygonsSource": {
"id": "b1",
"type": "buffer",
"params": {
"source": {
"id": "b0",
"type": "source",
"params": {
query: "select * from analysis_banks"
}
},
"radius": 250
},
dataviews: {
bank_category: {
type: 'aggregation',
options: {
column: 'bank'
}
}
}
}
},
dataviews: {
count_histogram: {
type: 'histogram',
options: {
column: 'count_airbnb'
}
}
}
}),
cartocss: cartocss('green', 1.0)
}
}
])
},
{
skip: true,
desc: 'I. Distribution centers',
mapConfig: mapConfig(
// layers
[
{
type: 'cartodb',
options: {
"source": { id: "b0" },
"cartocss": [
"#distribution_centers {",
" marker-fill-opacity: 1.0;",
" marker-line-color: #FFF;",
" marker-line-width: 0.5;",
" marker-line-opacity: 0.7;",
" marker-placement: point;",
" marker-type: ellipse;",
" marker-width: 8;",
" marker-fill: blue;",
" marker-allow-overlap: true;",
"}"
].join('\n'),
"cartocss_version": "2.3.0"
}
},
{
type: 'cartodb',
options: {
"source": { id: "a0" },
"cartocss": [
"#shops {",
" marker-fill-opacity: 1.0;",
" marker-line-color: #FFF;",
" marker-line-width: 0.5;",
" marker-line-opacity: 0.7;",
" marker-placement: point;",
" marker-type: ellipse;",
" marker-width: 8;",
" marker-fill: red;",
" marker-allow-overlap: true;",
"}"
].join('\n'),
"cartocss_version": "2.3.0"
}
},
{
type: 'cartodb',
options: {
"source": { id: "a1" },
"cartocss": [
"#routing {",
" line-color: ramp([routing_time], colorbrewer(Reds));",
" line-width: ramp([routing_time], 2, 8);",
" line-opacity: 1.0;",
"}"
].join('\n'),
"cartocss_version": "2.3.0"
}
}
],
// dataviews
{
distribution_center_name_category: {
source: { id: 'b0' },
type: 'aggregation',
options: {
column: 'name'
}
},
time_histogram: {
source: { id: 'a1' },
type: 'histogram',
options: {
column: 'routing_time'
}
},
distance_histogram: {
source: { id: 'a1' },
type: 'histogram',
options: {
column: 'routing_distance'
}
}
},
// analysis
[
{
id: 'a1',
type: 'routing-n-to-n',
params: {
// distanceColumn: 'routing_distance',
// timeColumn: 'routing_time',
originSource: {
id: 'b0',
type: 'source',
params: {
query: 'select * from distribution_centers'
}
},
destinationSource: {
id: 'a0',
type: 'source',
params: {
query: 'select * from shops'
}
}
}
}
]
)
},
{
skip: true,
desc: 'II. Population analysis',
mapConfig: mapConfig(
// layers
[
{
type: 'cartodb',
options: {
"source": { id: "a2" },
"cartocss": [
"#count_in_polygon {",
" polygon-opacity: 1.0",
" line-color: #FFF;",
" line-width: 0.5;",
" line-opacity: 0.7",
" polygon-fill: ramp([estimated_people], colorbrewer(Reds));",
"}"
].join('\n'),
"cartocss_version": "2.3.0"
}
},
{
type: 'cartodb',
options: {
"source": { id: "a0" },
"cartocss": DEFAULT_MULTITYPE_STYLE,
"cartocss_version": "2.3.0"
}
}
],
// dataviews
{
total_population_formula: {
"source": { id: "a3" },
type: 'formula',
options: {
column: 'total_population',
operation: 'sum'
}
},
people_histogram: { // this injects a range filter at `a2` node output
"source": { id: "a2" },
type: 'histogram',
options: {
column: 'estimated_people'
}
},
subway_line_category: { // this injects a category filter at `a0` node output
"source": { id: "a0" },
type: 'aggregation',
options: {
column: 'subway_line'
}
}
},
// analysis
[
{
id: 'a3',
// this will union the polygons, produce just one polygon, and calculate the total population for it
type: 'total-population',
params: {
columnName: 'total_population',
source: {
id: 'a2',
type: 'estimated-population',
params: {
columnName: 'estimated_people',
source: {
id: 'a1',
type: 'trade-area',
params: {
source: {
"id": "a0",
"type": "source",
"params": {
query: "select * from subway_stops"
}
},
kind: 'walk',
time: 300
}
}
}
}
}
}
])
},
{
skip: true,
desc: 'III. Point in polygon',
mapConfig: mapConfig(
// layers
[
{
type: 'cartodb',
options: {
"source": { id: "a1" },
"cartocss": [
"#count_in_polygon {",
" polygon-opacity: 1.0",
" line-color: #FFF;",
" line-width: 0.5;",
" line-opacity: 0.7",
" polygon-fill: ramp([count_people], colorbrewer(Reds));",
"}"
].join('\n'),
"cartocss_version": "2.3.0"
}
}
],
// dataviews
{
age_histogram: {
"source": { id: "a0" },
type: 'histogram',
options: {
column: 'age'
}
},
income_histogram: {
"source": { id: "a0" },
type: 'histogram',
options: {
column: 'income'
}
},
gender_category: {
"source": { id: "a0" },
type: 'aggregation',
options: {
column: 'gender'
}
}
},
// analysis
[
{
"id": "a1",
"type": "count-in-polygon",
"params": {
"columnName": 'count_people',
"pointsSource": {
"id": 'a0',
"type": "source",
"params": {
query: "select the_geom, age, gender, income from people"
}
},
"polygonsSource": {
"id": "b0",
"type": "source",
"params": {
query: "select * from postal_codes"
}
}
}
}
]
)
}
];
useCases.forEach(function(useCase, imageIdx) {
if (!!useCase.skip) {
debug(JSON.stringify(useCase.mapConfig, null, 4));
}
it.skip('should implement use case: "' + useCase.desc + '"', function(done) {
var testClient = new TestClient(useCase.mapConfig, 1234);
var tile = useCase.tile || TILE_ANALYSIS_TABLES;
testClient.getTile(tile.z, tile.x, tile.y, function(err, res, image) {
assert.ok(!err, err);
image.save('/tmp/tests/' + imageIdx + '---' + useCase.desc.replace(/\s/g, '-') + '.png');
assert.equal(image.width(), 256);
testClient.drain(done);
});
});
});
});

View File

@@ -0,0 +1,370 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
var dot = require('dot');
describe('analysis-layers', function() {
var IMAGE_TOLERANCE_PER_MIL = 20;
var multitypeStyleTemplate = dot.template([
"#points['mapnik::geometry_type'=1] {",
" marker-fill-opacity: {{=it._opacity}};",
" marker-line-color: #FFF;",
" marker-line-width: 0.5;",
" marker-line-opacity: {{=it._opacity}};",
" marker-placement: point;",
" marker-type: ellipse;",
" marker-width: 8;",
" marker-fill: {{=it._color}};",
" marker-allow-overlap: true;",
"}",
"#lines['mapnik::geometry_type'=2] {",
" line-color: {{=it._color}};",
" line-width: 2;",
" line-opacity: {{=it._opacity}};",
"}",
"#polygons['mapnik::geometry_type'=3] {",
" polygon-fill: {{=it._color}};",
" polygon-opacity: {{=it._opacity}};",
" line-color: #FFF;",
" line-width: 0.5;",
" line-opacity: {{=it._opacity}};",
"}"
].join('\n'));
function cartocss(color, opacity) {
return multitypeStyleTemplate({
_color: color || '#F11810',
_opacity: Number.isFinite(opacity) ? opacity : 1
});
}
function mapConfig(layers, dataviews, analysis) {
return {
version: '1.5.0',
layers: layers,
dataviews: dataviews || {},
analyses: analysis || []
};
}
var DEFAULT_MULTITYPE_STYLE = cartocss();
var TILE_ANALYSIS_TABLES = { z: 0, x: 0, y: 0 };
var useCases = [
{
desc: 'basic source-id mapnik layer',
fixture: 'basic-source-id-mapnik-layer.png',
mapConfig: mapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
},
"cartocss": DEFAULT_MULTITYPE_STYLE,
"cartocss_version": "2.3.0"
}
}
],
{},
[
{
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
}
]
)
},
{
desc: 'buffer over source',
fixture: 'buffer-over-source.png',
tile: { z: 7, x: 61, y: 47 },
mapConfig: mapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": DEFAULT_MULTITYPE_STYLE,
"cartocss_version": "2.3.0"
}
}
],
{},
[
{
"id": "HEAD",
"type": "buffer",
"params": {
"source": {
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
},
"radius": 50000
}
}
]
)
}
];
useCases.forEach(function(useCase) {
it('should implement use case: "' + useCase.desc + '"', function(done) {
var testClient = new TestClient(useCase.mapConfig, 1234);
var tile = useCase.tile || TILE_ANALYSIS_TABLES;
testClient.getTile(tile.z, tile.x, tile.y, function(err, res, image) {
assert.ok(!err, err);
// To generate images use:
// image.save('/tmp/' + useCase.desc.replace(/\s/g, '-') + '.png');
var fixturePath = './test/fixtures/analysis/' + useCase.fixture;
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, function(err) {
assert.ok(!err, err);
testClient.drain(done);
});
});
});
});
it('should NOT fail for non-authenticated requests when it is just source', function(done) {
var useCase = useCases[0];
// No API key here
var testClient = new TestClient(useCase.mapConfig);
testClient.getLayergroup(function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.metadata.layers.length, 1);
testClient.drain(done);
});
});
it('should fail for non-authenticated requests that has a node other than "source"', function(done) {
var useCase = useCases[1];
// No API key here
var testClient = new TestClient(useCase.mapConfig);
var PERMISSION_DENIED_RESPONSE = {
status: 403,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
testClient.getLayergroup(PERMISSION_DENIED_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.deepEqual(
layergroupResult.errors,
["Analysis requires authentication with API key: permission denied."]
);
testClient.drain(done);
});
});
it('should retrieve enough metadata about analyses', function(done) {
var useCase = useCases[1];
// No API key here
var testClient = new TestClient(useCase.mapConfig, 1234);
testClient.getLayergroup(function(err, layergroupResult) {
assert.ok(!err, err);
assert.ok(
Array.isArray(layergroupResult.metadata.analyses),
'Missing "analyses" array metadata from: ' + JSON.stringify(layergroupResult)
);
var analyses = layergroupResult.metadata.analyses;
assert.equal(analyses.length, 1, 'Invalid number of analyses in metadata');
var nodes = analyses[0].nodes;
var nodesIds = Object.keys(nodes);
assert.deepEqual(nodesIds, ['2570e105-7b37-40d2-bdf4-1af889598745', 'HEAD']);
nodesIds.forEach(function(nodeId) {
var node = nodes[nodeId];
assert.ok(node.hasOwnProperty('url'), 'Missing "url" attribute in node');
assert.ok(node.hasOwnProperty('status'), 'Missing "status" attribute in node');
assert.ok(node.hasOwnProperty('query'), 'Missing "status" attribute in node');
});
testClient.drain(done);
});
});
it('should have analysis metadata when dataviews point to source node', function(done) {
var useCase = {
desc: 'basic source-id mapnik layer',
fixture: 'basic-source-id-mapnik-layer.png',
mapConfig: mapConfig(
[
{
"type": "cartodb",
"options": {
"sql": "select * from populated_places_simple_reduced",
"cartocss": DEFAULT_MULTITYPE_STYLE,
"cartocss_version": "2.3.0"
}
},
{
"type": "cartodb",
"options": {
"source": {
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
},
"cartocss": DEFAULT_MULTITYPE_STYLE,
"cartocss_version": "2.3.0"
}
},
{
"type": "cartodb",
"options": {
"sql": "select * from populated_places_simple_reduced",
"cartocss": DEFAULT_MULTITYPE_STYLE,
"cartocss_version": "2.3.0"
}
}
],
{
pop_max_histogram: {
source: {
id: '2570e105-7b37-40d2-bdf4-1af889598745'
},
type: 'histogram',
options: {
column: 'pop_max'
}
},
scalerank_histogram: {
source: {
id: '2570e105-7b37-40d2-bdf4-1af889598745'
},
type: 'histogram',
options: {
column: 'scalerank'
}
}
},
[
{
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
}
]
)
};
var testClient = new TestClient(useCase.mapConfig, 1234);
testClient.getLayergroup(function(err, layergroupResult) {
assert.ok(!err, err);
assert.ok(
Array.isArray(layergroupResult.metadata.analyses),
'Missing "analyses" array metadata from: ' + JSON.stringify(layergroupResult)
);
var analyses = layergroupResult.metadata.analyses;
assert.equal(analyses.length, 1, 'Invalid number of analyses in metadata');
var nodes = analyses[0].nodes;
var nodesIds = Object.keys(nodes);
assert.deepEqual(nodesIds, ['2570e105-7b37-40d2-bdf4-1af889598745']);
nodesIds.forEach(function(nodeId) {
var node = nodes[nodeId];
assert.ok(node.hasOwnProperty('url'), 'Missing "url" attribute in node');
assert.ok(node.hasOwnProperty('status'), 'Missing "status" attribute in node');
assert.ok(node.hasOwnProperty('query'), 'Missing "status" attribute in node');
});
testClient.drain(done);
});
});
it('should response with custom cache headers for node status endpoints', function(done) {
var useCase = useCases[1];
// No API key here
var testClient = new TestClient(useCase.mapConfig, 1234);
testClient.getNodeStatus('HEAD', function(err, response, nodeStatus) {
assert.ok(!err, err);
assert.equal(nodeStatus.status, 'ready');
var headers = response.headers;
assert.equal(headers['cache-control'], 'public,max-age=5');
var lastModified = new Date(headers['last-modified']);
var tenSecondsInMs = 1e5;
assert.ok(Date.now() - lastModified.getTime() < tenSecondsInMs);
testClient.drain(done);
});
});
it('should wrap queries from analyses', function(done) {
var testClient = new TestClient(mapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
},
"sql_wrap": "SELECT * FROM (<%= sql %>) __wrapped WHERE adm0cap = 1",
"cartocss": DEFAULT_MULTITYPE_STYLE,
"cartocss_version": "2.3.0"
}
}
],
{},
[
{
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
}
]
), 1234);
var tile = TILE_ANALYSIS_TABLES;
testClient.getTile(tile.z, tile.x, tile.y, function(err, res, image) {
assert.ok(!err, err);
var fixturePath = './test/fixtures/analysis/adm0cap-source-id-mapnik-layer.png';
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, function(err) {
assert.ok(!err, err);
testClient.drain(done);
});
});
});
});

View File

@@ -0,0 +1,67 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('analysis-layers error cases', function() {
function createMapConfig(layers, dataviews, analysis) {
return {
version: '1.5.0',
layers: layers,
dataviews: dataviews || {},
analyses: analysis || []
};
}
var ERROR_RESPONSE = {
status: 400,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
it('should handle missing analysis nodes for layers', function(done) {
var mapConfig = createMapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "INVALID-SOURCE-ID"
},
"cartocss": '#polygons { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}
],
{},
[
{
"id": "HEAD",
"type": "buffer",
"params": {
"source": {
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
},
"radius": 50000
}
}
]
);
var testClient = new TestClient(mapConfig, 1234);
testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.errors.length, 1);
assert.equal(layergroupResult.errors[0], 'Missing analysis node.id="INVALID-SOURCE-ID" for layer=0');
testClient.drain(done);
});
});
});

View File

@@ -0,0 +1,245 @@
var assert = require('../../support/assert');
var step = require('step');
var helper = require('../../support/test_helper');
var CartodbWindshaft = require('../../../lib/cartodb/server');
var serverOptions = require('../../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
var LayergroupToken = require('../../../lib/cartodb/models/layergroup_token');
describe('named-maps analysis', function() {
var IMAGE_TOLERANCE_PER_MIL = 20;
var username = 'localhost';
var widgetsTemplateName = 'widgets-template';
var layergroupid;
var layergroup;
var keysToDelete;
beforeEach(function(done) {
keysToDelete = {};
var widgetsTemplate = {
version: '0.0.1',
name: widgetsTemplateName,
layergroup: {
version: '1.5.0',
layers: [
{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": '#buffer { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}
],
dataviews: {
pop_max_histogram: {
source: {
id: 'HEAD'
},
type: 'histogram',
options: {
column: 'pop_max'
}
}
},
analyses: [
{
"id": "HEAD",
"type": "buffer",
"params": {
"source": {
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
},
"radius": 50000
}
}
]
}
};
var template_params = {};
step(
function createTemplate()
{
var next = this;
assert.response(
server,
{
url: '/api/v1/map/named?api_key=1234',
method: 'POST',
headers: {
host: username,
'Content-Type': 'application/json'
},
data: JSON.stringify(widgetsTemplate)
},
{
status: 200
},
function(res, err) {
next(err, res);
}
);
},
function instantiateTemplate(err, res) {
assert.ifError(err);
assert.deepEqual(JSON.parse(res.body), { template_id: widgetsTemplateName });
var next = this;
assert.response(
server,
{
url: '/api/v1/map/named/' + widgetsTemplateName,
method: 'POST',
headers: {
host: username,
'Content-Type': 'application/json'
},
data: JSON.stringify(template_params)
},
{
status: 200
},
function(res) {
next(null, res);
}
);
},
function finish(err, res) {
assert.ifError(err);
layergroup = JSON.parse(res.body);
assert.ok(layergroup.hasOwnProperty('layergroupid'), "Missing 'layergroupid' from: " + res.body);
layergroupid = layergroup.layergroupid;
assert.ok(
Array.isArray(layergroup.metadata.analyses),
'Missing "analyses" array metadata from: ' + res.body
);
var analyses = layergroup.metadata.analyses;
assert.equal(analyses.length, 1, 'Invalid number of analyses in metadata');
var nodes = analyses[0].nodes;
var nodesIds = Object.keys(nodes);
assert.deepEqual(nodesIds, ['2570e105-7b37-40d2-bdf4-1af889598745', 'HEAD']);
nodesIds.forEach(function(nodeId) {
var node = nodes[nodeId];
assert.ok(node.hasOwnProperty('url'), 'Missing "url" attribute in node');
assert.ok(node.hasOwnProperty('status'), 'Missing "status" attribute in node');
assert.ok(!node.hasOwnProperty('query'), 'Unexpected "query" attribute in node');
});
keysToDelete['map_cfg|' + LayergroupToken.parse(layergroup.layergroupid).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
return done();
}
);
});
afterEach(function(done) {
step(
function deleteTemplate(err) {
assert.ifError(err);
var next = this;
assert.response(
server,
{
url: '/api/v1/map/named/' + widgetsTemplateName + '?api_key=1234',
method: 'DELETE',
headers: {
host: username
}
},
{
status: 204
},
function(res, err) {
next(err, res);
}
);
},
function deleteRedisKeys(err) {
assert.ifError(err);
helper.deleteRedisKeys(keysToDelete, done);
}
);
});
it('should be able to retrieve images from analysis', function(done) {
assert.response(
server,
{
url: '/api/v1/map/' + layergroupid + '/6/31/24.png',
method: 'GET',
encoding: 'binary',
headers: {
host: username
}
},
{
status: 200,
headers: {
'Content-Type': 'image/png'
}
},
function(res, err) {
if (err) {
return done(err);
}
var fixturePath = './test/fixtures/analysis/named-map-buffer.png';
assert.imageBufferIsSimilarToFile(res.body, fixturePath, IMAGE_TOLERANCE_PER_MIL, function(err) {
assert.ok(!err, err);
done();
});
}
);
});
it('should be able to retrieve dataviews from analysis', function(done) {
assert.response(
server,
{
url: '/api/v1/map/' + layergroupid + '/dataview/pop_max_histogram',
method: 'GET',
headers: {
host: username
}
},
{
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
},
function(res, err) {
if (err) {
return done(err);
}
var dataview = JSON.parse(res.body);
assert.equal(dataview.type, 'histogram');
assert.equal(dataview.bins_start, 0);
done();
}
);
});
});

View File

@@ -0,0 +1,169 @@
var assert = require('../support/assert');
var step = require('step');
var LayergroupToken = require('../../lib/cartodb/models/layergroup_token');
var testHelper = require(__dirname + '/../support/test_helper');
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
describe('dynamic styling for named maps', function() {
var keysToDelete;
beforeEach(function() {
keysToDelete = {};
});
afterEach(function(done) {
testHelper.deleteRedisKeys(keysToDelete, done);
});
var templateId = 'dynamic-styling-template-1';
var template = {
version: '0.0.1',
name: templateId,
auth: { method: 'open' },
placeholders: {
color: {
type: "css_color",
default: "Reds"
}
},
layergroup: {
version: '1.0.0',
layers: [{
options: {
sql: 'SELECT * FROM test_table',
cartocss: [
'#layer {',
' marker-fill: #000;',
'}'
].join('\n'),
cartocss_version: '2.0.2'
}
}, {
options: {
sql: 'SELECT * FROM test_table',
cartocss: [
'#layer {',
' marker-fill: #000;',
'}'
].join('\n'),
cartocss_version: '2.0.2'
}
}, {
options: {
sql: 'SELECT * FROM test_table',
cartocss: [
'#layer {',
' marker-fill: #000;',
'}'
].join('\n'),
cartocss_version: '2.0.2'
}
}]
}
};
var templateParams = {
styles: {
0: [
'#layer {',
' marker-fill: #fabada;',
'}'
].join('\n'),
2: [
'#layer {',
' marker-fill: #cebada;',
'}'
].join('\n')
}
};
it('should instantiate a template applying cartocss dynamicly', function (done) {
step(
function postTemplate() {
var next = this;
assert.response(server, {
url: '/api/v1/map/named?api_key=1234',
method: 'POST',
headers: { host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(template)
}, {},
function (res, err) {
next(err, res);
});
},
function checkTemplate(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.deepEqual(JSON.parse(res.body), {
template_id: templateId
});
return null;
},
function instantiateTemplate(err) {
assert.ifError(err);
var next = this;
assert.response(server, {
url: '/api/v1/map/named/' + templateId,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(templateParams)
}, {},
function (res, err) {
return next(err, res);
});
},
function checkInstanciation(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
var parsedBody = JSON.parse(res.body);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
assert.equal(parsedBody.metadata.layers[0].meta.cartocss, templateParams.styles['0']);
assert.equal(
parsedBody.metadata.layers[1].meta.cartocss,
template.layergroup.layers[1].options.cartocss
);
assert.equal(parsedBody.metadata.layers[2].meta.cartocss, templateParams.styles['2']);
return parsedBody.layergroupid;
},
function deleteTemplate(err) {
assert.ifError(err);
var next = this;
assert.response(server, {
url: '/api/v1/map/named/' + templateId + '?api_key=1234',
method: 'DELETE',
headers: { host: 'localhost' }
}, {}, function (res, err) {
next(err, res);
});
},
function checkDeleteTemplate(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 204);
assert.ok(!res.body);
return null;
},
function finish(err) {
done(err);
}
);
});
});

View File

@@ -18,7 +18,7 @@ var serverOptions = require('../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
server.setMaxListeners(0);
var TablesCacheEntry = require('../../lib/cartodb/cache/model/database_tables_entry');
var QueryTables = require('cartodb-query-tables');
['/api/v1/map', '/user/localhost/api/v1/map'].forEach(function(layergroup_url) {
@@ -274,9 +274,9 @@ describe(suiteName, function() {
var parsedBody = JSON.parse(res.body);
expected_token = parsedBody.layergroupid.split(':')[0];
helper.checkCache(res);
helper.checkSurrogateKey(res, new TablesCacheEntry('test_windshaft_cartodb_user_1_db', [
'public.test_table',
'public.test_table_2'
helper.checkSurrogateKey(res, new QueryTables.DatabaseTablesEntry([
{dbname: "test_windshaft_cartodb_user_1_db", table_name: "test_table", schema_name: "public"},
{dbname: "test_windshaft_cartodb_user_1_db", table_name: "test_table_2", schema_name: "public"},
]).key().join(' '));

View File

@@ -7,6 +7,7 @@ var _ = require('underscore');
var LayergroupToken = require('../../lib/cartodb/models/layergroup_token');
var PgQueryRunner = require('../../lib/cartodb/backends/pg_query_runner');
var QueryTables = require('cartodb-query-tables');
var CartodbWindshaft = require('../../lib/cartodb/server');
var serverOptions = require('../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
@@ -334,7 +335,7 @@ describe('tests from old api translated to multilayer', function() {
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, {
errors: ["could not get overviews metadata: fake error message"]
errors: ["fake error message"]
});
done();
@@ -360,9 +361,11 @@ describe('tests from old api translated to multilayer', function() {
keysToDelete['map_cfg|' + LayergroupToken.parse(JSON.parse(res.body).layergroupid).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
var runQueryFn = PgQueryRunner.prototype.run;
PgQueryRunner.prototype.run = function(username, query, callback) {
return callback(new Error('failed to query database for affected tables'), []);
var affectedFn = QueryTables.getAffectedTablesFromQuery;
QueryTables.getAffectedTablesFromQuery = function(sql, username, query, callback) {
affectedFn({query: function(query, callback) {
return callback(new Error('fake error message'), []);
}}, username, query, callback);
};
// reset internal cacheChannel cache
@@ -387,7 +390,7 @@ describe('tests from old api translated to multilayer', function() {
},
function(res) {
assert.ok(!res.headers.hasOwnProperty('x-cache-channel'));
PgQueryRunner.prototype.run = runQueryFn;
QueryTables.getAffectedTablesFromQuery = affectedFn;
done();
}
);

View File

@@ -1,3 +1,4 @@
var qs = require('querystring');
var testHelper = require('../support/test_helper');
var RedisPool = require('redis-mpool');
@@ -53,10 +54,18 @@ describe('named maps static view', function() {
templateMaps.delTemplate(username, templateName, done);
});
function getStaticMap(callback) {
function getStaticMap(params, callback) {
if (!callback) {
callback = params;
params = null;
}
var url = '/api/v1/map/static/named/' + templateName + '/640/480.png';
if (params !== null) {
url += '?' + qs.stringify(params);
}
var requestOptions = {
url: url,
method: 'GET',
@@ -163,4 +172,30 @@ describe('named maps static view', function() {
});
});
it('should return override zoom', function (done) {
var view = {
bounds: {
west: 0,
south: 0,
east: 45,
north: 45
},
zoom: 4,
center: {
lng: 40,
lat: 20
}
};
templateMaps.addTemplate(username, createTemplate(view), function (err) {
if (err) {
return done(err);
}
getStaticMap({ zoom: 3 }, function(err, img) {
assert.ok(!err);
img.save('/tmp/static.png');
assert.imageIsSimilarToFile(img, previewFixture('override-zoom'), IMAGE_TOLERANCE, done);
});
});
});
});

View File

@@ -48,63 +48,64 @@ describe('overviews metadata', function() {
it("layers with and without overviews", function(done) {
var layergroup = {
version: '1.0.0',
layers: [overviews_layer, non_overviews_layer]
};
var layergroup = {
version: '1.0.0',
layers: [overviews_layer, non_overviews_layer]
};
var layergroup_url = '/api/v1/map';
var layergroup_url = '/api/v1/map';
var expected_token;
step(
function do_post()
{
var next = this;
assert.response(server, {
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body);
assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid);
expected_token = parsedBody.layergroupid;
next(null, res);
});
},
function do_get_mapconfig(err)
{
assert.ifError(err);
var next = this;
var expected_token;
step(
function do_post()
{
var next = this;
assert.response(server, {
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body);
assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid);
expected_token = parsedBody.layergroupid;
next(null, res);
});
},
function do_get_mapconfig(err)
{
assert.ifError(err);
var next = this;
var mapStore = new windshaft.storage.MapStore({
pool: redisPool,
expire_time: 500000
});
mapStore.load(LayergroupToken.parse(expected_token).token, function(err, mapConfig) {
assert.ifError(err);
assert.deepEqual(non_overviews_layer, mapConfig._cfg.layers[1]);
assert.equal(mapConfig._cfg.layers[0].type, 'cartodb');
assert.ok(mapConfig._cfg.layers[0].options.query_rewrite_data);
var expected_data = {
overviews: {
test_table_overviews: {
1: { table: '_vovw_1_test_table_overviews' },
2: { table: '_vovw_2_test_table_overviews' }
}
}
};
assert.deepEqual(mapConfig._cfg.layers[0].options.query_rewrite_data, expected_data);
});
var mapStore = new windshaft.storage.MapStore({
pool: redisPool,
expire_time: 500000
});
mapStore.load(LayergroupToken.parse(expected_token).token, function(err, mapConfig) {
assert.ifError(err);
assert.deepEqual(non_overviews_layer, mapConfig._cfg.layers[1]);
assert.equal(mapConfig._cfg.layers[0].type, 'cartodb');
assert.ok(mapConfig._cfg.layers[0].options.query_rewrite_data);
var expected_data = {
overviews: {
test_table_overviews: {
schema: 'public',
1: { table: '_vovw_1_test_table_overviews' },
2: { table: '_vovw_2_test_table_overviews' }
}
}
};
assert.deepEqual(mapConfig._cfg.layers[0].options.query_rewrite_data, expected_data);
});
next(err);
},
function finish(err) {
keysToDelete['map_cfg|' + LayergroupToken.parse(expected_token).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
done(err);
}
);
next(err);
},
function finish(err) {
keysToDelete['map_cfg|' + LayergroupToken.parse(expected_token).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
done(err);
}
);
});
});

View File

@@ -0,0 +1,175 @@
var test_helper = require('../support/test_helper');
var assert = require('../support/assert');
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
var LayergroupToken = require('../../lib/cartodb/models/layergroup_token');
var RedisPool = require('redis-mpool');
var step = require('step');
var windshaft = require('windshaft');
describe('overviews metadata for named maps', function() {
// configure redis pool instance to use in tests
var redisPool = new RedisPool(global.environment.redis);
var overviews_layer = {
type: 'cartodb',
options: {
sql: 'SELECT * FROM test_table_overviews',
cartocss: '#layer { marker-fill: black; }',
cartocss_version: '2.3.0'
}
};
var non_overviews_layer = {
type: 'cartodb',
options: {
sql: 'SELECT * FROM test_table',
cartocss: '#layer { marker-fill: black; }',
cartocss_version: '2.3.0'
}
};
var keysToDelete;
beforeEach(function() {
keysToDelete = {};
});
afterEach(function(done) {
test_helper.deleteRedisKeys(keysToDelete, done);
});
var templateId = 'overviews-template-1';
var template = {
version: '0.0.1',
name: templateId,
auth: { method: 'open' },
layergroup: {
version: '1.0.0',
layers: [overviews_layer, non_overviews_layer]
}
};
it("should add overviews data to layers", function(done) {
step(
function postTemplate()
{
var next = this;
assert.response(server, {
url: '/api/v1/map/named?api_key=1234',
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(template)
}, {}, function(res, err) {
next(err, res);
});
},
function checkTemplate(err, res) {
assert.ifError(err);
var next = this;
assert.equal(res.statusCode, 200);
assert.deepEqual(JSON.parse(res.body), {
template_id: templateId
});
next(null);
},
function instantiateTemplate(err) {
assert.ifError(err);
var next = this;
assert.response(server, {
url: '/api/v1/map/named/' + templateId,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
}
}, {},
function(res, err) {
return next(err, res);
});
},
function checkInstanciation(err, res) {
assert.ifError(err);
var next = this;
assert.equal(res.statusCode, 200);
var parsedBody = JSON.parse(res.body);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
assert.ok(parsedBody.layergroupid);
assert.ok(parsedBody.last_updated);
next(null, parsedBody.layergroupid);
},
function checkMapconfig(err, layergroupId)
{
assert.ifError(err);
var next = this;
var mapStore = new windshaft.storage.MapStore({
pool: redisPool,
expire_time: 500000
});
mapStore.load(LayergroupToken.parse(layergroupId).token, function(err, mapConfig) {
assert.ifError(err);
assert.deepEqual(non_overviews_layer, mapConfig._cfg.layers[1]);
assert.equal(mapConfig._cfg.layers[0].type, 'cartodb');
assert.ok(mapConfig._cfg.layers[0].options.query_rewrite_data);
var expected_data = {
overviews: {
test_table_overviews: {
schema: 'public',
1: { table: '_vovw_1_test_table_overviews' },
2: { table: '_vovw_2_test_table_overviews' }
}
}
};
assert.deepEqual(mapConfig._cfg.layers[0].options.query_rewrite_data, expected_data);
});
next(err);
},
function deleteTemplate(err) {
assert.ifError(err);
var next = this;
assert.response(server, {
url: '/api/v1/map/named/' + templateId + '?api_key=1234',
method: 'DELETE',
headers: { host: 'localhost' }
}, {}, function (res, err) {
next(err, res);
});
},
function checkDeleteTemplate(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 204);
assert.ok(!res.body);
return null;
},
function finish(err) {
done(err);
}
);
});
});

View File

@@ -0,0 +1,40 @@
require('../support/test_helper');
var assert = require('../support/assert');
var TestClient = require('../support/test-client');
describe('regressions', function() {
var ERROR_RESPONSE = {
status: 400,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
it('should expose a nice error when missing sql option', function(done) {
var mapConfig = {
version: '1.5.0',
layers: [
{
"type": "cartodb",
"options": {
"cartocss": '#polygons { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}
]
};
var testClient = new TestClient(mapConfig, 1234);
testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.errors.length, 1);
assert.equal(layergroupResult.errors[0], 'Missing sql for layer 0 options');
testClient.drain(done);
});
});
});

View File

@@ -3,8 +3,8 @@ var _ = require('underscore');
var redis = require('redis');
var step = require('step');
var strftime = require('strftime');
var QueryTables = require('cartodb-query-tables');
var NamedMapsCacheEntry = require('../../lib/cartodb/cache/model/named_maps_entry');
var TablesCacheEntry = require('../../lib/cartodb/cache/model/database_tables_entry');
var redis_stats_db = 5;
// Pollute the PG environment to make sure
@@ -1405,7 +1405,8 @@ describe('template_api', function() {
// See https://github.com/CartoDB/Windshaft-cartodb/issues/176
helper.checkCache(res);
var expectedSurrogateKey = [
new TablesCacheEntry('test_windshaft_cartodb_user_1_db', ['public.test_table_private_1']).key(),
new QueryTables.DatabaseTablesEntry([{dbname: 'test_windshaft_cartodb_user_1_db', schema_name: 'public',
table_name: 'test_table_private_1'}]).key(),
new NamedMapsCacheEntry('localhost', template_acceptance_open.name).key()
].join(' ');
helper.checkSurrogateKey(res, expectedSurrogateKey);
@@ -1488,7 +1489,8 @@ describe('template_api', function() {
// See https://github.com/CartoDB/Windshaft-cartodb/issues/176
helper.checkCache(res);
var expectedSurrogateKey = [
new TablesCacheEntry('test_windshaft_cartodb_user_1_db', ['public.test_table_private_1']).key(),
new QueryTables.DatabaseTablesEntry([{dbname: 'test_windshaft_cartodb_user_1_db', schema_name: 'public',
table_name: 'test_table_private_1'}]).key(),
new NamedMapsCacheEntry('localhost', template_acceptance_open.name).key()
].join(' ');
helper.checkSurrogateKey(res, expectedSurrogateKey);

View File

@@ -0,0 +1,128 @@
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
var IMAGE_TOLERANCE_PER_MIL = 20;
function imageCompareFn(fixture, done) {
return function(err, res, image) {
assert.ok(!err, err);
assert.imageIsSimilarToFile(image, './test/fixtures/' + fixture, IMAGE_TOLERANCE_PER_MIL, done);
};
}
function makeMapconfig(cartocss) {
return {
"version": "1.4.0",
"layers": [
{
"type": 'mapnik',
"options": {
"cartocss_version": '2.3.0',
"sql": [
'SELECT test_table.*, _prices.price FROM test_table JOIN (' +
' SELECT 1 AS cartodb_id, 10.00 AS price',
' UNION',
' SELECT 2, 10.50',
' UNION',
' SELECT 3, 11.00',
' UNION',
' SELECT 4, 12.00',
' UNION',
' SELECT 5, 21.00',
') _prices ON _prices.cartodb_id = test_table.cartodb_id'
].join('\n'),
"cartocss": cartocss
}
}
]
};
}
describe('turbo-carto for anonymous maps', function() {
describe('parsing ramp function with colorbrewer for greens and mapnik renderer', function () {
beforeEach(function () {
var turboCartocss = '#layer { marker-fill: ramp([price], colorbrewer(Greens)); }';
this.testClient = new TestClient(makeMapconfig(turboCartocss));
});
afterEach(function (done) {
this.testClient.drain(done);
});
it('should get a tile with turbo-carto parsed properly', function (done) {
var fixturePath = 'test_turbo_carto_greens_13_4011_3088.png';
this.testClient.getTile(13, 4011, 3088, imageCompareFn(fixturePath, done));
});
});
describe('parsing ramp function with colorbrewer for reds and mapnik renderer', function () {
beforeEach(function () {
var turboCartocss = '#layer { marker-fill: ramp([price], colorbrewer(Reds)); }';
this.testClient = new TestClient(makeMapconfig(turboCartocss));
});
afterEach(function (done) {
this.testClient.drain(done);
});
it('should get a tile with turbo-carto parsed properly', function (done) {
var fixtureFileName = 'test_turbo_carto_reds_13_4011_3088.png';
this.testClient.getTile(13, 4011, 3088, imageCompareFn(fixtureFileName, done));
});
});
describe('parsing ramp function with colorbrewer for greens and toque renderer', function () {
var mapConfig = {
version: '1.2.0',
layers: [
{
type: 'torque',
options: {
sql: "SELECT * FROM populated_places_simple_reduced where the_geom" +
" && ST_MakeEnvelope(-90, 0, 90, 65)",
cartocss: [
'Map {',
' buffer-size:0;',
' -torque-frame-count:1;',
' -torque-animation-duration:30;',
' -torque-time-attribute:"cartodb_id";',
' -torque-aggregation-function:"count(cartodb_id)";',
' -torque-resolution:1;',
' -torque-data-aggregation:linear;',
'};',
'#populated_places_simple_reduced {',
' comp-op: multiply;',
' marker-fill-opacity: 1;',
' marker-line-color: #FFF;',
' marker-line-width: 0;',
' marker-line-opacity: 1;',
' marker-type: rectangle;',
' marker-width: 3;',
' marker-fill: ramp([pop_max], colorbrewer(Greens));',
'};'
].join(' '),
cartocss_version: '2.3.0'
}
}
]
};
beforeEach(function () {
this.testClient = new TestClient(mapConfig);
});
afterEach(function (done) {
this.testClient.drain(done);
});
it('should get a tile with turbo-carto parsed properly', function (done) {
var z = 2;
var x = 2;
var y = 1;
var pngFixture = 'torque/populated_places_simple_reduced-turbo-carto-' + [z, x, y].join('.') + '.png';
this.testClient.getTile(z, x, y, { layers: 0, format: 'torque.png' }, imageCompareFn(pngFixture, done));
});
});
});

View File

@@ -0,0 +1,236 @@
var assert = require('../../support/assert');
var step = require('step');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup_token');
var testHelper = require('../../support/test_helper');
var CartodbWindshaft = require('../../../lib/cartodb/server');
var serverOptions = require('../../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
var mapnik = require('windshaft').mapnik;
var IMAGE_TOLERANCE_PER_MIL = 10;
describe('turbo-carto for named maps', function() {
var keysToDelete;
beforeEach(function() {
keysToDelete = {};
});
afterEach(function(done) {
testHelper.deleteRedisKeys(keysToDelete, done);
});
var templateId = 'turbo-carto-template-1';
var template = {
version: '0.0.1',
name: templateId,
auth: { method: 'open' },
placeholders: {
color: {
type: "css_color",
default: "Reds"
}
},
layergroup: {
version: '1.0.0',
layers: [{
options: {
sql: [
'SELECT test_table.*, _prices.price FROM test_table JOIN (' +
' SELECT 1 AS cartodb_id, 10.00 AS price',
' UNION',
' SELECT 2, 10.50',
' UNION',
' SELECT 3, 11.00',
' UNION',
' SELECT 4, 12.00',
' UNION',
' SELECT 5, 21.00',
') _prices ON _prices.cartodb_id = test_table.cartodb_id'
].join('\n'),
cartocss: [
'#layer {',
' marker-fill: ramp([price], colorbrewer(<%= color %>));',
' marker-allow-overlap:true;',
'}'
].join('\n'),
cartocss_version: '2.0.2'
}
}
]
}
};
var templateParamsReds = { color: 'Reds' };
var templateParamsBlues = { color: 'Blues' };
it('should create a template with turbo-carto parsed properly', function (done) {
step(
function postTemplate() {
var next = this;
assert.response(server, {
url: '/api/v1/map/named?api_key=1234',
method: 'POST',
headers: { host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(template)
}, {},
function (res, err) {
next(err, res);
});
},
function checkTemplate(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.deepEqual(JSON.parse(res.body), {
template_id: templateId
});
return null;
},
function instantiateTemplateWithReds(err) {
assert.ifError(err);
var next = this;
assert.response(server, {
url: '/api/v1/map/named/' + templateId,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(templateParamsReds)
}, {},
function(res, err) {
return next(err, res);
});
},
function checkInstanciationWithReds(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
var parsedBody = JSON.parse(res.body);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
assert.ok(parsedBody.layergroupid);
assert.ok(parsedBody.last_updated);
return parsedBody.layergroupid;
},
function requestTileReds(err, layergroupId) {
assert.ifError(err);
var next = this;
assert.response(server, {
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
method: 'GET',
headers: { host: 'localhost' },
encoding: 'binary'
}, {},
function(res, err) {
next(err, res);
});
},
function checkTileReds(err, res) {
assert.ifError(err);
var next = this;
assert.equal(res.statusCode, 200);
assert.equal(res.headers['content-type'], 'image/png');
var fixturePath = './test/fixtures/turbo-carto-named-maps-reds.png';
var image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, next);
},
function instantiateTemplateWithBlues(err) {
assert.ifError(err);
var next = this;
assert.response(server, {
url: '/api/v1/map/named/' + templateId,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(templateParamsBlues)
}, {},
function(res, err) {
return next(err, res);
});
},
function checkInstanciationWithBlues(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
var parsedBody = JSON.parse(res.body);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
assert.ok(parsedBody.layergroupid);
assert.ok(parsedBody.last_updated);
return parsedBody.layergroupid;
},
function requestTileBlues(err, layergroupId) {
assert.ifError(err);
var next = this;
assert.response(server, {
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
method: 'GET',
headers: { host: 'localhost' },
encoding: 'binary'
}, {},
function(res, err) {
next(err, res);
});
},
function checkTileBlues(err, res) {
assert.ifError(err);
var next = this;
assert.equal(res.statusCode, 200);
assert.equal(res.headers['content-type'], 'image/png');
var fixturePath = './test/fixtures/turbo-carto-named-maps-blues.png';
var image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, next);
},
function deleteTemplate(err) {
assert.ifError(err);
var next = this;
assert.response(server, {
url: '/api/v1/map/named/' + templateId + '?api_key=1234',
method: 'DELETE',
headers: { host: 'localhost' }
}, {}, function (res, err) {
next(err, res);
});
},
function checkDeleteTemplate(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 204);
assert.ok(!res.body);
return null;
},
function finish(err) {
done(err);
}
);
});
});

View File

@@ -0,0 +1,75 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
function makeMapconfig(cartocss) {
return {
"version": "1.4.0",
"layers": [
{
"type": 'mapnik',
"options": {
"cartocss_version": '2.3.0',
"sql": [
'SELECT test_table.*, _prices.price FROM test_table JOIN (' +
' SELECT 1 AS cartodb_id, 10.00 AS price',
' UNION',
' SELECT 2, 10.50',
' UNION',
' SELECT 3, 11.00',
' UNION',
' SELECT 4, 12.00',
' UNION',
' SELECT 5, 21.00',
') _prices ON _prices.cartodb_id = test_table.cartodb_id'
].join('\n'),
"cartocss": cartocss
}
}
]
};
}
describe('turbo-carto regressions', function() {
var cartocss = [
"/** simple visualization */",
"",
"Map {",
" buffer-size: 256;",
"}",
"",
"#county_points_with_population{",
" marker-fill-opacity: 0.1;",
" marker-line-color:#FFFFFF;//#CF1C90;",
" marker-line-width: 0;",
" marker-line-opacity: 0.3;",
" marker-placement: point;",
" marker-type: ellipse;",
" //marker-comp-op: overlay;",
" marker-width: [price];",
" [zoom=5]{marker-width: [price]*2;}",
" [zoom=6]{marker-width: [price]*4;}",
" marker-fill: #000000;",
" marker-allow-overlap: true;",
" ",
"",
"}"
].join('\n');
beforeEach(function () {
this.testClient = new TestClient(makeMapconfig(cartocss));
});
afterEach(function (done) {
this.testClient.drain(done);
});
it('should accept // comments', function(done) {
this.testClient.getTile(0, 0, 0, function(err) {
assert.ok(!err, err);
done();
});
});
});

View File

@@ -114,6 +114,34 @@ describe('histogram widgets', function() {
testClient.drain(done);
});
});
it("should expose a filtered histogram using dataviews for filtering", function(done) {
var params = {
filters: {
dataviews: {
country_places_histogram: { min: 4000000 }
}
}
};
var testClient = new TestClient(histogramMapConfig);
testClient.getWidget('country_places_histogram', params, function(err, res) {
if (err) {
return done(err);
}
var histogram = JSON.parse(res.body);
// notice min value
assert.deepEqual(histogram.bins[0], {
bin: 0,
freq: 62,
min: 4000000,
max: 9276403,
avg: 5815009.596774193
});
testClient.drain(done);
});
});
});
});

View File

@@ -113,7 +113,7 @@ describe('named-maps widgets', function() {
}
);
},
function fetchTile(err, res) {
function finish(err, res) {
assert.ifError(err);
layergroup = JSON.parse(res.body);

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

View File

@@ -73,12 +73,13 @@ describe('MapConfigOverviewsAdapter', function() {
assert.equal(layers[0].options.cartocss_version, cartocss_version);
assert.ok(layers[0].options.query_rewrite_data);
var expected_data = {
overviews: {
test_table_overviews: {
1: { table: '_vovw_1_test_table_overviews' },
2: { table: '_vovw_2_test_table_overviews' }
overviews: {
test_table_overviews: {
schema: 'public',
1: { table: '_vovw_1_test_table_overviews' },
2: { table: '_vovw_2_test_table_overviews' }
}
}
}
};
assert.deepEqual(layers[0].options.query_rewrite_data, expected_data);
done();

View File

@@ -7,20 +7,18 @@ var cartodbRedis = require('cartodb-redis');
var PgConnection = require('../../lib/cartodb/backends/pg_connection');
var PgQueryRunner = require('../../lib/cartodb/backends/pg_query_runner');
var QueryTablesApi = require('../../lib/cartodb/api/query_tables_api');
var OverviewsMetadataApi = require('../../lib/cartodb/api/overviews_metadata_api');
describe('OverviewsMetadataApi', function() {
var queryTablesApi, overviewsMetadataApi;
var overviewsMetadataApi;
before(function() {
var redisPool = new RedisPool(global.environment.redis);
var metadataBackend = cartodbRedis({pool: redisPool});
var pgConnection = new PgConnection(metadataBackend);
var pgQueryRunner = new PgQueryRunner(pgConnection);
queryTablesApi = new QueryTablesApi(pgQueryRunner);
overviewsMetadataApi = new OverviewsMetadataApi(pgQueryRunner);
});
@@ -42,8 +40,9 @@ describe('OverviewsMetadataApi', function() {
assert.deepEqual(result, {
'test_table_overviews': {
1: { table: '_vovw_1_test_table_overviews' },
2: { table: '_vovw_2_test_table_overviews' }
schema: 'public',
1: { table: '_vovw_1_test_table_overviews' },
2: { table: '_vovw_2_test_table_overviews' }
}
});

View File

@@ -1,55 +0,0 @@
require('../support/test_helper');
var assert = require('assert');
var RedisPool = require('redis-mpool');
var cartodbRedis = require('cartodb-redis');
var PgConnection = require('../../lib/cartodb/backends/pg_connection');
var PgQueryRunner = require('../../lib/cartodb/backends/pg_query_runner');
var QueryTablesApi = require('../../lib/cartodb/api/query_tables_api');
describe('QueryTablesApi', function() {
var queryTablesApi;
before(function() {
var redisPool = new RedisPool(global.environment.redis);
var metadataBackend = cartodbRedis({pool: redisPool});
var pgConnection = new PgConnection(metadataBackend);
var pgQueryRunner = new PgQueryRunner(pgConnection);
queryTablesApi = new QueryTablesApi(pgQueryRunner);
});
// Check test/support/sql/windshaft.test.sql to understand where the values come from.
it('should return an object with affected tables array and last updated time', function(done) {
var query = 'select * from test_table';
queryTablesApi.getAffectedTablesAndLastUpdatedTime('localhost', query, function(err, result) {
assert.ok(!err, err);
assert.deepEqual(result, {
affectedTables: [ 'public.test_table' ],
lastUpdatedTime: 1234567890123
});
done();
});
});
it('should work with private tables', function(done) {
var query = 'select * from test_table_private_1';
queryTablesApi.getAffectedTablesAndLastUpdatedTime('localhost', query, function(err, result) {
assert.ok(!err, err);
assert.deepEqual(result, {
affectedTables: [ 'public.test_table_private_1' ],
lastUpdatedTime: 1234567890123
});
done();
});
});
});

View File

@@ -0,0 +1,71 @@
require('../support/test_helper');
var assert = require('assert');
var RedisPool = require('redis-mpool');
var cartodbRedis = require('cartodb-redis');
var PgConnection = require('../../lib/cartodb/backends/pg_connection');
var QueryTables = require('cartodb-query-tables');
describe('QueryTables', function() {
var connection;
before(function(done) {
var redisPool = new RedisPool(global.environment.redis);
var metadataBackend = cartodbRedis({pool: redisPool});
var pgConnection = new PgConnection(metadataBackend);
pgConnection.getConnection('localhost', function(err, pgConnection) {
if (err) {
return done(err);
}
connection = pgConnection;
return done();
});
});
// Check test/support/sql/windshaft.test.sql to understand where the values come from.
it('should return an object with affected tables array and last updated time', function(done) {
var query = 'select * from test_table';
QueryTables.getAffectedTablesFromQuery(connection, query, function(err, result) {
assert.ok(!err, err);
assert.equal(result.getLastUpdatedAt(), 1234567890123);
assert.equal(result.tables.length, 1);
assert.deepEqual(result.tables[0], {
dbname: 'test_windshaft_cartodb_user_1_db',
schema_name: 'public',
table_name: 'test_table',
updated_at: new Date(1234567890123)
});
done();
});
});
it('should work with private tables', function(done) {
var query = 'select * from test_table_private_1';
QueryTables.getAffectedTablesFromQuery(connection, query, function(err, result) {
assert.ok(!err, err);
assert.equal(result.getLastUpdatedAt(), 1234567890123);
assert.equal(result.tables.length, 1);
assert.deepEqual(result.tables[0], {
dbname: 'test_windshaft_cartodb_user_1_db',
schema_name: 'public',
table_name: 'test_table_private_1',
updated_at: new Date(1234567890123)
});
done();
});
});
});

View File

@@ -71,6 +71,9 @@ if test x"$PREPARE_PGSQL" = xyes; then
dropdb "${TEST_DB}"
createdb -Ttemplate_postgis -EUTF8 "${TEST_DB}" || die "Could not create test database"
curl -L -s https://raw.githubusercontent.com/CartoDB/camshaft/master/test/fixtures/cdb_analysis_catalog.sql -o sql/cdb_analysis_catalog.sql
cat sql/cdb_analysis_catalog.sql | psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
cat sql/windshaft.test.sql sql/gadm4.sql |
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
@@ -78,11 +81,15 @@ if test x"$PREPARE_PGSQL" = xyes; then
sed "s/:TESTPASS/${TESTPASS}/" |
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
psql -c "CREATE LANGUAGE plpythonu;" ${TEST_DB}
curl -L -s https://github.com/CartoDB/cartodb-postgresql/raw/cdb/scripts-available/CDB_QueryStatements.sql -o sql/CDB_QueryStatements.sql
curl -L -s https://github.com/CartoDB/cartodb-postgresql/raw/cdb/scripts-available/CDB_QueryTables.sql -o sql/CDB_QueryTables.sql
cat sql/CDB_QueryStatements.sql sql/CDB_QueryTables.sql sql/CDB_Overviews.sql |
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
cat sql/_CDB_QueryStatements.sql | psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
SQL_SCRIPTS='CDB_QueryTables CDB_CartodbfyTable CDB_TableMetadata CDB_ForeignTable CDB_UserTables CDB_ColumnNames CDB_ZoomFromScale CDB_Overviews CDB_QuantileBins CDB_JenksBins CDB_HeadsTailsBins CDB_EqualIntervalBins'
for i in ${SQL_SCRIPTS}
do
curl -L -s https://github.com/CartoDB/cartodb-postgresql/raw/master/scripts-available/$i.sql -o sql/$i.sql
cat sql/$i.sql | sed -e 's/cartodb\./public./g' -e "s/''cartodb''/''public''/g" \
| psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
done
fi

View File

@@ -1,46 +0,0 @@
-- Mockup for CDB_Overviews
CREATE OR REPLACE FUNCTION CDB_Overviews(table_names regclass[])
RETURNS TABLE(base_table regclass, z integer, overview_table regclass)
AS $$
BEGIN
IF (SELECT 'test_table_overviews'::regclass = ANY (table_names)) THEN
RETURN QUERY
SELECT 'test_table_overviews'::regclass AS base_table, 1 AS z, '_vovw_1_test_table_overviews'::regclass AS overview_table
UNION ALL
SELECT 'test_table_overviews'::regclass AS base_table, 2 AS z, '_vovw_2_test_table_overviews'::regclass AS overview_table;
ELSE
RETURN;
END IF;
END
$$ LANGUAGE PLPGSQL;
CREATE OR REPLACE FUNCTION CDB_ZoomFromScale(scaleDenominator numeric) RETURNS int AS $$
BEGIN
CASE
WHEN scaleDenominator > 500000000 THEN RETURN 0;
WHEN scaleDenominator <= 500000000 AND scaleDenominator > 200000000 THEN RETURN 1;
WHEN scaleDenominator <= 200000000 AND scaleDenominator > 100000000 THEN RETURN 2;
WHEN scaleDenominator <= 100000000 AND scaleDenominator > 50000000 THEN RETURN 3;
WHEN scaleDenominator <= 50000000 AND scaleDenominator > 25000000 THEN RETURN 4;
WHEN scaleDenominator <= 25000000 AND scaleDenominator > 12500000 THEN RETURN 5;
WHEN scaleDenominator <= 12500000 AND scaleDenominator > 6500000 THEN RETURN 6;
WHEN scaleDenominator <= 6500000 AND scaleDenominator > 3000000 THEN RETURN 7;
WHEN scaleDenominator <= 3000000 AND scaleDenominator > 1500000 THEN RETURN 8;
WHEN scaleDenominator <= 1500000 AND scaleDenominator > 750000 THEN RETURN 9;
WHEN scaleDenominator <= 750000 AND scaleDenominator > 400000 THEN RETURN 10;
WHEN scaleDenominator <= 400000 AND scaleDenominator > 200000 THEN RETURN 11;
WHEN scaleDenominator <= 200000 AND scaleDenominator > 100000 THEN RETURN 12;
WHEN scaleDenominator <= 100000 AND scaleDenominator > 50000 THEN RETURN 13;
WHEN scaleDenominator <= 50000 AND scaleDenominator > 25000 THEN RETURN 14;
WHEN scaleDenominator <= 25000 AND scaleDenominator > 12500 THEN RETURN 15;
WHEN scaleDenominator <= 12500 AND scaleDenominator > 5000 THEN RETURN 16;
WHEN scaleDenominator <= 5000 AND scaleDenominator > 2500 THEN RETURN 17;
WHEN scaleDenominator <= 2500 AND scaleDenominator > 1500 THEN RETURN 18;
WHEN scaleDenominator <= 1500 AND scaleDenominator > 750 THEN RETURN 19;
WHEN scaleDenominator <= 750 AND scaleDenominator > 500 THEN RETURN 20;
WHEN scaleDenominator <= 500 AND scaleDenominator > 250 THEN RETURN 21;
WHEN scaleDenominator <= 250 AND scaleDenominator > 100 THEN RETURN 22;
WHEN scaleDenominator <= 100 THEN RETURN 23;
END CASE;
END
$$ LANGUAGE plpgsql IMMUTABLE;

View File

@@ -1,14 +0,0 @@
-- Return an array of statements found in the given query text
--
-- Regexp curtesy of Hubert Lubaczewski (depesz)
-- Implemented in plpython for performance reasons
--
CREATE OR REPLACE FUNCTION CDB_QueryStatements(query text)
RETURNS SETOF TEXT AS $$
import re
pat = re.compile( r'''((?:[^'"$;]+|"[^"]*"|'[^']*'|(\$[^$]*\$).*?\2)+)''', re.DOTALL )
for match in pat.findall(query):
cleaned = match[0].strip()
if ( cleaned ):
yield cleaned
$$ language 'plpythonu' IMMUTABLE STRICT;

View File

@@ -1,78 +0,0 @@
-- Return an array of table names scanned by a given query
--
-- Requires PostgreSQL 9.x+
--
CREATE OR REPLACE FUNCTION CDB_QueryTablesText(query text)
RETURNS text[]
AS $$
DECLARE
exp XML;
tables text[];
rec RECORD;
rec2 RECORD;
BEGIN
tables := '{}';
FOR rec IN SELECT CDB_QueryStatements(query) q LOOP
IF NOT ( rec.q ilike 'select%' or rec.q ilike 'with%' ) THEN
--RAISE WARNING 'Skipping %', rec.q;
CONTINUE;
END IF;
BEGIN
EXECUTE 'EXPLAIN (FORMAT XML, VERBOSE) ' || rec.q INTO STRICT exp;
EXCEPTION WHEN others THEN
-- TODO: if error is 'relation "xxxxxx" does not exist', take xxxxxx as
-- the affected table ?
RAISE WARNING 'CDB_QueryTables cannot explain query: % (%: %)', rec.q, SQLSTATE, SQLERRM;
RAISE EXCEPTION '%', SQLERRM;
CONTINUE;
END;
-- Now need to extract all values of <Relation-Name>
-- RAISE DEBUG 'Explain: %', exp;
FOR rec2 IN WITH
inp AS (
SELECT
xpath('//x:Relation-Name/text()', exp, ARRAY[ARRAY['x', 'http://www.postgresql.org/2009/explain']]) as x,
xpath('//x:Relation-Name/../x:Schema/text()', exp, ARRAY[ARRAY['x', 'http://www.postgresql.org/2009/explain']]) as s
)
SELECT unnest(x)::text as p, unnest(s)::text as sc from inp
LOOP
-- RAISE DEBUG 'tab: %', rec2.p;
-- RAISE DEBUG 'sc: %', rec2.sc;
tables := array_append(tables, format('%s.%s', quote_ident(rec2.sc), quote_ident(rec2.p)));
END LOOP;
-- RAISE DEBUG 'Tables: %', tables;
END LOOP;
-- RAISE DEBUG 'Tables: %', tables;
-- Remove duplicates and sort by name
IF array_upper(tables, 1) > 0 THEN
WITH dist as ( SELECT DISTINCT unnest(tables)::text as p ORDER BY p )
SELECT array_agg(p) from dist into tables;
END IF;
--RAISE DEBUG 'Tables: %', tables;
return tables;
END
$$ LANGUAGE 'plpgsql' VOLATILE STRICT;
-- Keep CDB_QueryTables with same signature for backwards compatibility.
-- It should probably be removed in the future.
CREATE OR REPLACE FUNCTION CDB_QueryTables(query text)
RETURNS name[]
AS $$
BEGIN
RETURN CDB_QueryTablesText(query)::name[];
END
$$ LANGUAGE 'plpgsql' VOLATILE STRICT;

View File

@@ -0,0 +1,16 @@
-- DUMMY IMPLEMENTATION
-- Ref: https://github.com/CartoDB/cartodb-postgresql/blob/master/scripts-available/CDB_QueryStatements.sql
-- Originally implemented in plpython for performance reasons
-- Return an array of statements found in the given query text
--
-- Regexp curtesy of Hubert Lubaczewski (depesz)
--
CREATE OR REPLACE FUNCTION CDB_QueryStatements(query text)
RETURNS SETOF TEXT AS $$
with matches as (
select regexp_matches($1, $regexp$((?:[^'"$;]+|"[^"]*"|'[^']*'|(\$[^$]*\$).*?\2)+)$regexp$, 'g') as m
)
select btrim(m[1]) from matches
$$
LANGUAGE SQL IMMUTABLE STRICT;

View File

@@ -332,3 +332,298 @@ INSERT INTO _vovw_2_test_table_overviews VALUES
INSERT INTO _vovw_1_test_table_overviews VALUES
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.314252', 1, 'Hawai', 'Calle de Pérez Galdós 9, Madrid, Spain', '0101000020E610000000000000000020C00000000000004440', '0101000020110F000076491621312319C122D4663F1DCC5241');
-- analysis tables -----------------------------------------------
ALTER TABLE cdb_analysis_catalog OWNER TO :TESTUSER;
--
-- TOC entry 804 (class 1259 OID 13870252)
-- Name: analysis_banks; Type: TABLE; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
--
CREATE TABLE analysis_banks (
cartodb_id bigint NOT NULL,
the_geom geometry(Geometry,4326),
the_geom_webmercator geometry(Geometry,3857),
bank text
);
ALTER TABLE analysis_banks OWNER TO :TESTUSER;
--
-- TOC entry 803 (class 1259 OID 13870250)
-- Name: analysis_banks_cartodb_id_seq; Type: SEQUENCE; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
--
CREATE SEQUENCE analysis_banks_cartodb_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER TABLE analysis_banks_cartodb_id_seq OWNER TO :TESTUSER;
--
-- TOC entry 5784 (class 0 OID 0)
-- Dependencies: 803
-- Name: analysis_banks_cartodb_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
--
ALTER SEQUENCE analysis_banks_cartodb_id_seq OWNED BY analysis_banks.cartodb_id;
--
-- TOC entry 802 (class 1259 OID 13870235)
-- Name: analysis_rent_listings; Type: TABLE; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
--
CREATE TABLE analysis_rent_listings (
cartodb_id bigint NOT NULL,
the_geom geometry(Geometry,4326),
the_geom_webmercator geometry(Geometry,3857),
price double precision
);
ALTER TABLE analysis_rent_listings OWNER TO :TESTUSER;
--
-- TOC entry 801 (class 1259 OID 13870233)
-- Name: analysis_rent_listings_cartodb_id_seq; Type: SEQUENCE; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
--
CREATE SEQUENCE analysis_rent_listings_cartodb_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER TABLE analysis_rent_listings_cartodb_id_seq OWNER TO :TESTUSER;
--
-- TOC entry 5786 (class 0 OID 0)
-- Dependencies: 801
-- Name: analysis_rent_listings_cartodb_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
--
ALTER SEQUENCE analysis_rent_listings_cartodb_id_seq OWNED BY analysis_rent_listings.cartodb_id;
--
-- TOC entry 5612 (class 2604 OID 13870258)
-- Name: cartodb_id; Type: DEFAULT; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
--
ALTER TABLE ONLY analysis_banks ALTER COLUMN cartodb_id SET DEFAULT nextval('analysis_banks_cartodb_id_seq'::regclass);
--
-- TOC entry 5611 (class 2604 OID 13870241)
-- Name: cartodb_id; Type: DEFAULT; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
--
ALTER TABLE ONLY analysis_rent_listings ALTER COLUMN cartodb_id SET DEFAULT nextval('analysis_rent_listings_cartodb_id_seq'::regclass);
--
-- TOC entry 5778 (class 0 OID 13870252)
-- Dependencies: 804
-- Data for Name: analysis_banks; Type: TABLE DATA; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
--
COPY analysis_banks (cartodb_id, the_geom, the_geom_webmercator, bank) FROM stdin;
1 0101000020E61000000AEC3F6BE5A80DC09EF022E20C364440 0101000020110F0000AD24852BA63019C13215440E02CC5241 BBVA
2 0101000020E61000005AB3C72EE6A40DC02A499181E3364440 0101000020110F0000DE3E9A22412D19C1B059CF80F1CC5241 Santander
3 0101000020E6100000FD52FCA1E1960DC0DD48F7ADB9354440 0101000020110F00008C1CE860592119C12B37FC3BA5CB5241 BBVA
4 0101000020E61000008A52C823B69F0DC02579E6E0B4354440 0101000020110F0000DC41553AD92819C170BBE0E09FCB5241 Santander
5 0101000020E6100000A6C9EAA399B00DC00A31DF64BD354440 0101000020110F000041C09F2D313719C1EDC9C960A9CB5241 Santander
6 0101000020E6100000D2AD3EB570970DC01F85791E1B354440 0101000020110F00001D0E73E4D22119C14730F65AF4CA5241 BBVA
7 0101000020E6100000B2F9AF3A6FA40DC0BA91F3D44C364440 0101000020110F0000B071A11BDC2C19C16DD5026649CC5241 BBVA
8 0101000020E61000000F1B8134EE8E0DC067D8E65807374440 0101000020110F000033B46EB0981A19C10322017E19CD5241 BBVA
9 0101000020E6100000F12A179193880DC07EBCEDABC2354440 0101000020110F0000C11C4B2F331519C1B2B8FD43AFCB5241 BBVA
10 0101000020E61000004B602775E0A50DC0037374A875364440 0101000020110F00004B5097B1152E19C1481948F276CC5241 BBVA
11 0101000020E6100000F796A5DDEE880DC05337CD960A364440 0101000020110F0000ACE39CB9801519C1F843077FFFCB5241 Santander
12 0101000020E610000041BC5F214A920DC0AFCE2B5507354440 0101000020110F000027682406731D19C15145B148DECA5241 BBVA
13 0101000020E6100000CC2EFD14A0AF0DC00550C5AE47354440 0101000020110F000095956F3A5D3619C11CACED1026CB5241 BBVA
14 0101000020E6100000B113F5FFF28E0DC01075C9419A354440 0101000020110F00005E9EE8C29C1A19C1DB38342E82CB5241 BBVA
15 0101000020E6100000997EFF432F990DC0E6D8677BF1364440 0101000020110F00007E0667274E2319C1ACE7B01801CD5241 Santander
\.
--
-- TOC entry 5787 (class 0 OID 0)
-- Dependencies: 803
-- Name: analysis_banks_cartodb_id_seq; Type: SEQUENCE SET; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
--
SELECT pg_catalog.setval('analysis_banks_cartodb_id_seq', 15, true);
--
-- TOC entry 5776 (class 0 OID 13870235)
-- Dependencies: 802
-- Data for Name: analysis_rent_listings; Type: TABLE DATA; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
--
COPY analysis_rent_listings (cartodb_id, the_geom, the_geom_webmercator, price) FROM stdin;
1 0101000020E61000006300CEA9C4920DC0FCB8FEFB04354440 0101000020110F0000EA3B5C17DB1D19C135CF17AADBCA5241 81
2 0101000020E6100000F49AD93201910DC0E37AB970F8354440 0101000020110F00009BD483A95B1C19C11FC4D53FEBCB5241 169
3 0101000020E610000096F9FBAA0F880DC03974BF03B8354440 0101000020110F00006891BA29C31419C1055F8260A3CB5241 163
4 0101000020E61000009F08C1D17C930DC0651427FA68364440 0101000020110F00006C35BB7E771E19C1837381CC68CC5241 127
5 0101000020E61000000A7B7928EAB40DC0425EE239C4364440 0101000020110F00000C6ADB3EDB3A19C14FDB889ACECC5241 58
6 0101000020E610000095B232CF20A00DC0584C660044354440 0101000020110F0000979587D2332919C177DEB3F521CB5241 184
7 0101000020E61000001E8C81FF328F0DC031AE47DFB2364440 0101000020110F00006634761DD31A19C13723EB3DBBCC5241 109
8 0101000020E61000008A014F8FD79D0DC0B1E8EA3376364440 0101000020110F000021A20DC5422719C15A18E08D77CC5241 87
9 0101000020E61000006ECAA9C393970DC0F71C39950A364440 0101000020110F0000A3016DAAF02119C19C71447DFFCB5241 132
10 0101000020E6100000FB434A9D57A60DC0F9FD273C3F354440 0101000020110F00008E7BC3E47A2E19C1798082A41CCB5241 134
11 0101000020E6100000C597512EFE8C0DC032DE0DCDFD344440 0101000020110F0000F51B6C6AF31819C15512CCA6D3CA5241 167
12 0101000020E610000025E84904589F0DC00BC88CB0BA364440 0101000020110F000024C7054A892819C1C84EBBF6C3CC5241 135
13 0101000020E61000002917E524E69E0DC0EE4915018C354440 0101000020110F00008011BC93282819C153F8E34772CB5241 160
14 0101000020E6100000617E8939B1A40DC05EF963BEF0344440 0101000020110F000071896E28142D19C160872616C5CA5241 185
15 0101000020E6100000B20B809602A30DC0514274FE37354440 0101000020110F00007D63FC6AA62B19C1DC88B19014CB5241 94
16 0101000020E6100000191978861B9C0DC05C902E6C45354440 0101000020110F0000EBC9ACA6C92519C10A76818B23CB5241 172
17 0101000020E610000098D6FCD1F2AC0DC01ED4EEFCAE354440 0101000020110F0000FA7E3A3C173419C1DFFAA34E99CB5241 154
18 0101000020E6100000F9C7CB37DC970DC0B7E914C94E354440 0101000020110F0000795D5C332E2219C1FAAE47FD2DCB5241 183
19 0101000020E61000002D0263B27B940DC09D26F27AC4354440 0101000020110F0000C4616AF64F1F19C1D2868548B1CB5241 77
20 0101000020E6100000BD682E39DA8D0DC0DA3ADC937A364440 0101000020110F000038D83D4CAE1919C143A35B6F7CCC5241 196
21 0101000020E6100000165B41DEB2A90DC0D0D1F568A4354440 0101000020110F00008B9179A8543119C1EAADBA818DCB5241 74
22 0101000020E6100000DECD65EC4A8D0DC08D309F6B1F354440 0101000020110F000063E4D797341919C1A1034827F9CA5241 164
23 0101000020E6100000D14DF59EC1AD0DC086C17713C7354440 0101000020110F0000E8ED02DFC63419C15FACD82DB4CB5241 180
24 0101000020E61000002A41F69FA7900DC063FF9C14AC364440 0101000020110F0000DDF34D960F1C19C1070119AAB3CC5241 62
25 0101000020E61000001E390026BA8B0DC0B506A19336354440 0101000020110F00005620FE36E01719C109E9F5FB12CB5241 103
26 0101000020E61000001856B89F9E890DC0AE3A805FA7364440 0101000020110F0000F4E717FF151619C102619069AECC5241 132
27 0101000020E61000006244348FA7AD0DC093992086E1354440 0101000020110F0000204AB0BCB03419C10E2515AFD1CB5241 64
28 0101000020E6100000C249D99ADA970DC0D7CF322454364440 0101000020110F00005303A5D42C2219C1463AA98D51CC5241 142
29 0101000020E61000001E36C089F2930DC0098310FAF2344440 0101000020110F0000E9293E79DB1E19C134C7D593C7CA5241 149
30 0101000020E610000084DC236A48970DC08AA0D30E37354440 0101000020110F0000A4E5D3ABB02119C12A57638513CB5241 190
31 0101000020E610000068C42C3D6DAE0DC0E316EF8262354440 0101000020110F000007FF5AA0583519C1CEF97EFE43CB5241 132
32 0101000020E6100000F0FCDE54BCA70DC03311579A9F354440 0101000020110F0000CE2C83DAA92F19C120A8E62488CB5241 180
33 0101000020E6100000B5A43D9E809B0DC059B84FF6F1364440 0101000020110F000035B5A016462519C15C07D2A101CD5241 160
34 0101000020E6100000D4D36C80629E0DC0249F8DFC8B354440 0101000020110F0000F241EAC5B82719C1904ED64272CB5241 95
35 0101000020E61000008BEA878AF1A50DC05DA77AA5B9354440 0101000020110F0000BBD0E633242E19C15D7E8432A5CB5241 108
36 0101000020E610000022D3CD1F62B40DC08AC22B852E364440 0101000020110F0000C8E240B6673A19C1DCF9E99427CC5241 168
37 0101000020E6100000EBAC4CB637950DC0E989CFA0D8364440 0101000020110F0000883BDDA4EF1F19C15478CE5DE5CC5241 80
38 0101000020E61000007CEFD63A8A960DC006B8BDD1C8364440 0101000020110F00003728B0250F2119C169FD71BAD3CC5241 168
39 0101000020E6100000756B387CCFA70DC084C36711F7354440 0101000020110F0000CEB4ED1EBA2F19C13F1EE7B7E9CB5241 56
40 0101000020E6100000A47B2BAC4E960DC0820DC4B760354440 0101000020110F00008BCCAF90DC2019C1AE4248FE41CB5241 162
41 0101000020E6100000D5645E0C31990DC0F535DB974F364440 0101000020110F00007E8BFFAA4F2319C1B884AA7A4CCC5241 102
42 0101000020E6100000E3B6E0EA41B40DC02ADE56A899364440 0101000020110F0000FFC4D55B4C3A19C1A602351C9FCC5241 174
43 0101000020E61000006C513CE4E3B00DC010B3C38659364440 0101000020110F00002817653D703719C14258A88F57CC5241 97
44 0101000020E61000006B6DF64366A10DC0C980E41EB2364440 0101000020110F0000CFE47B3B482A19C1EFCB4567BACC5241 63
45 0101000020E61000007031674EA8960DC073B9E40B22354440 0101000020110F0000A608EEB0282119C159033215FCCA5241 159
46 0101000020E610000002328782CB890DC0A6462A1EAF364440 0101000020110F000008823D1E3C1619C14C749B0DB7CC5241 141
47 0101000020E61000009694069312A70DC09438822A79354440 0101000020110F0000922DC0AD192F19C113F51A445DCB5241 185
48 0101000020E610000081F9078F75AE0DC069F4C3ADCD364440 0101000020110F00003F393EB15F3519C1D4F16926D9CC5241 113
49 0101000020E6100000E348615820AC0DC0D165849A50364440 0101000020110F0000ACEC8F7A643319C14D783D9B4DCC5241 124
50 0101000020E6100000378F35768FAD0DC085A048F310364440 0101000020110F0000BFF95B459C3419C12A94C89706CC5241 62
51 0101000020E6100000F25E60BD14B40DC0FA9EFA964F354440 0101000020110F0000EB4840FD253A19C1FF36F6E22ECB5241 109
52 0101000020E6100000B73BFD3FB6AF0DC0A3D844C97B364440 0101000020110F0000AD1F370E703619C1B9278EC87DCC5241 149
53 0101000020E6100000FF1181969EB10DC0136E39E5A0364440 0101000020110F0000D10A15CD0E3819C1828B712FA7CC5241 176
54 0101000020E6100000426F39BD459A0DC0A5505E4CB7354440 0101000020110F000074615DA93A2419C106D4EF93A2CB5241 90
55 0101000020E6100000E2B0F14972A80DC0AFA4D0BF84354440 0101000020110F00008817D563443019C1202D06306ACB5241 162
56 0101000020E6100000ADE43CA9F79A0DC0C24A1417D9364440 0101000020110F0000C4944EC5D12419C1D0B5C2E1E5CC5241 80
57 0101000020E6100000AD6B99A9818B0DC07E06699A33364440 0101000020110F00007EE2C43DB01719C12BBF9D402DCC5241 172
58 0101000020E610000098272DE67B960DC08EC657CCF4344440 0101000020110F0000352CE4F9022119C16660F49BC9CA5241 177
59 0101000020E6100000DEAA210E56A90DC0B45845C72B354440 0101000020110F00003689FED4053119C1F0FE50F006CB5241 172
60 0101000020E61000004CAD1CF03B8F0DC07A4C4F1EED364440 0101000020110F000048EE2CB5DA1A19C1E236513AFCCC5241 122
\.
--
-- TOC entry 5788 (class 0 OID 0)
-- Dependencies: 801
-- Name: analysis_rent_listings_cartodb_id_seq; Type: SEQUENCE SET; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
--
SELECT pg_catalog.setval('analysis_rent_listings_cartodb_id_seq', 60, true);
--
-- TOC entry 5618 (class 2606 OID 13870260)
-- Name: analysis_banks_pkey; Type: CONSTRAINT; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
--
ALTER TABLE ONLY analysis_banks
ADD CONSTRAINT analysis_banks_pkey PRIMARY KEY (cartodb_id);
--
-- TOC entry 5614 (class 2606 OID 13870243)
-- Name: analysis_rent_listings_pkey; Type: CONSTRAINT; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
--
ALTER TABLE ONLY analysis_rent_listings
ADD CONSTRAINT analysis_rent_listings_pkey PRIMARY KEY (cartodb_id);
--
-- TOC entry 5619 (class 1259 OID 13870261)
-- Name: analysis_banks_the_geom_idx; Type: INDEX; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
--
CREATE INDEX analysis_banks_the_geom_idx ON analysis_banks USING gist (the_geom);
--
-- TOC entry 5620 (class 1259 OID 13870262)
-- Name: analysis_banks_the_geom_webmercator_idx; Type: INDEX; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
--
CREATE INDEX analysis_banks_the_geom_webmercator_idx ON analysis_banks USING gist (the_geom_webmercator);
--
-- TOC entry 5615 (class 1259 OID 13870244)
-- Name: analysis_rent_listings_the_geom_idx; Type: INDEX; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
--
CREATE INDEX analysis_rent_listings_the_geom_idx ON analysis_rent_listings USING gist (the_geom);
--
-- TOC entry 5616 (class 1259 OID 13870245)
-- Name: analysis_rent_listings_the_geom_webmercator_idx; Type: INDEX; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886; Tablespace:
--
CREATE INDEX analysis_rent_listings_the_geom_webmercator_idx ON analysis_rent_listings USING gist (the_geom_webmercator);
--
-- TOC entry 5783 (class 0 OID 0)
-- Dependencies: 804
-- Name: analysis_banks; Type: ACL; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
--
REVOKE ALL ON TABLE analysis_banks FROM PUBLIC;
REVOKE ALL ON TABLE analysis_banks FROM :TESTUSER;
GRANT ALL ON TABLE analysis_banks TO :TESTUSER;
GRANT SELECT ON TABLE analysis_banks TO :PUBLICUSER;
--
-- TOC entry 5785 (class 0 OID 0)
-- Dependencies: 802
-- Name: analysis_rent_listings; Type: ACL; Schema: public; Owner: development_cartodb_user_359a4d9f-a063-4130-9674-799e90960886
--
REVOKE ALL ON TABLE analysis_rent_listings FROM PUBLIC;
REVOKE ALL ON TABLE analysis_rent_listings FROM :TESTUSER;
GRANT ALL ON TABLE analysis_rent_listings TO :TESTUSER;
GRANT SELECT ON TABLE analysis_rent_listings TO :PUBLICUSER;
-- Completed on 2016-02-29 12:50:53 CET
--
-- PostgreSQL database dump complete
--
--
GRANT SELECT, UPDATE, INSERT, DELETE ON cdb_analysis_catalog TO :TESTUSER;

View File

@@ -2,6 +2,9 @@
var qs = require('querystring');
var step = require('step');
var urlParser = require('url');
var mapnik = require('windshaft').mapnik;
var LayergroupToken = require('../../lib/cartodb/models/layergroup_token');
@@ -10,11 +13,12 @@ var helper = require('./test_helper');
var CartodbWindshaft = require('../../lib/cartodb/server');
var serverOptions = require('../../lib/cartodb/server_options');
serverOptions.analysis.batch.inlineExecution = true;
var server = new CartodbWindshaft(serverOptions);
function TestClient(mapConfig) {
function TestClient(mapConfig, apiKey) {
this.mapConfig = mapConfig;
this.apiKey = apiKey;
this.keysToDelete = {};
}
@@ -114,6 +118,356 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) {
);
};
TestClient.prototype.getDataview = function(dataviewName, params, callback) {
var self = this;
if (!callback) {
callback = params;
params = {};
}
var extraParams = {};
if (this.apiKey) {
extraParams.api_key = this.apiKey;
}
if (params && params.filters) {
extraParams.filters = JSON.stringify(params.filters);
}
var url = '/api/v1/map';
if (Object.keys(extraParams).length > 0) {
url += '?' + qs.stringify(extraParams);
}
var layergroupId;
step(
function createLayergroup() {
var next = this;
assert.response(server,
{
url: url,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(self.mapConfig)
},
{
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
},
function(res, err) {
if (err) {
return next(err);
}
var parsedBody = JSON.parse(res.body);
var expectedDataviewsURLS = {
http: "/api/v1/map/" + parsedBody.layergroupid + "/dataview/" + dataviewName
};
assert.ok(parsedBody.metadata.dataviews[dataviewName]);
assert.ok(
parsedBody.metadata.dataviews[dataviewName].url.http.match(expectedDataviewsURLS.http)
);
return next(null, parsedBody.layergroupid);
}
);
},
function getDataviewResult(err, _layergroupId) {
assert.ifError(err);
var next = this;
layergroupId = _layergroupId;
var urlParams = {
own_filter: params.hasOwnProperty('own_filter') ? params.own_filter : 1
};
if (params && params.bbox) {
urlParams.bbox = params.bbox;
}
if (self.apiKey) {
urlParams.api_key = self.apiKey;
}
url = '/api/v1/map/' + layergroupId + '/dataview/' + dataviewName + '?' + qs.stringify(urlParams);
assert.response(server,
{
url: url,
method: 'GET',
headers: {
host: 'localhost'
}
},
{
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
},
function(res, err) {
if (err) {
return next(err);
}
next(null, JSON.parse(res.body));
}
);
},
function finish(err, res) {
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
self.keysToDelete['user:localhost:mapviews:global'] = 5;
return callback(err, res);
}
);
};
TestClient.prototype.getTile = function(z, x, y, params, callback) {
var self = this;
if (!callback) {
callback = params;
params = {};
}
var url = '/api/v1/map';
if (this.apiKey) {
url += '?' + qs.stringify({api_key: this.apiKey});
}
var layergroupId;
step(
function createLayergroup() {
var next = this;
assert.response(server,
{
url: url,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(self.mapConfig)
},
{
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
},
function(res, err) {
if (err) {
return next(err);
}
return next(null, JSON.parse(res.body).layergroupid);
}
);
},
function getTileResult(err, _layergroupId) {
assert.ifError(err);
var next = this;
layergroupId = _layergroupId;
url = '/api/v1/map/' + layergroupId + '/';
var layers = params.layers;
if (layers !== undefined) {
layers = Array.isArray(layers) ? layers : [layers];
url += layers.join(',') + '/';
}
var format = params.format || 'png';
url += [z,x,y].join('/');
url += '.' + format;
if (self.apiKey) {
url += '?' + qs.stringify({api_key: self.apiKey});
}
var request = {
url: url,
method: 'GET',
headers: {
host: 'localhost'
}
};
var expectedResponse = {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
var isPng = format.match(/png$/);
if (isPng) {
request.encoding = 'binary';
expectedResponse.headers['Content-Type'] = 'image/png';
}
assert.response(server, request, expectedResponse, function(res, err) {
assert.ifError(err);
var obj;
if (isPng) {
obj = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
} else {
obj = JSON.parse(res.body);
}
next(null, res, obj);
});
},
function finish(err, res, image) {
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
self.keysToDelete['user:localhost:mapviews:global'] = 5;
return callback(err, res, image);
}
);
};
TestClient.prototype.getLayergroup = function(expectedResponse, callback) {
var self = this;
if (!callback) {
callback = expectedResponse;
expectedResponse = {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
}
var url = '/api/v1/map';
if (this.apiKey) {
url += '?' + qs.stringify({api_key: this.apiKey});
}
assert.response(server,
{
url: url,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(self.mapConfig)
},
expectedResponse,
function(res, err) {
if (err) {
return callback(err);
}
var parsedBody = JSON.parse(res.body);
if (parsedBody.layergroupid) {
self.keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
self.keysToDelete['user:localhost:mapviews:global'] = 5;
}
return callback(null, parsedBody);
}
);
};
TestClient.prototype.getNodeStatus = function(nodeName, callback) {
var self = this;
var url = '/api/v1/map';
if (this.apiKey) {
url += '?' + qs.stringify({api_key: this.apiKey});
}
var layergroupId;
var nodes = {};
step(
function createLayergroup() {
var next = this;
assert.response(server,
{
url: url,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(self.mapConfig)
},
{
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
},
function(res, err) {
if (err) {
return next(err);
}
var parsedBody = JSON.parse(res.body);
nodes = parsedBody.metadata.analyses.reduce(function(nodes, analysis) {
return Object.keys(analysis.nodes).reduce(function(nodes, nodeName) {
var node = analysis.nodes[nodeName];
nodes[nodeName] = node.url.http;
return nodes;
}, nodes);
}, nodes);
return next(null, parsedBody.layergroupid);
}
);
},
function getNodeStatusResult(err, _layergroupId) {
assert.ifError(err);
var next = this;
layergroupId = _layergroupId;
url = urlParser.parse(nodes[nodeName]).path;
if (self.apiKey) {
url += '?' + qs.stringify({api_key: self.apiKey});
}
var request = {
url: url,
method: 'GET',
headers: {
host: 'localhost'
}
};
var expectedResponse = {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
assert.response(server, request, expectedResponse, function(res, err) {
assert.ifError(err);
next(null, res, JSON.parse(res.body));
});
},
function finish(err, res, image) {
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
self.keysToDelete['user:localhost:mapviews:global'] = 5;
return callback(err, res, image);
}
);
};
TestClient.prototype.drain = function(callback) {
helper.deleteRedisKeys(this.keysToDelete, callback);
};

View File

@@ -63,7 +63,15 @@ function checkCache(res) {
function checkSurrogateKey(res, expectedKey) {
assert.ok(res.headers.hasOwnProperty('surrogate-key'));
assert.equal(res.headers['surrogate-key'], expectedKey);
function createSet(keys, key) {
keys[key] = true;
return keys;
}
var keys = res.headers['surrogate-key'].split(' ').reduce(createSet, {});
var expectedKeys = expectedKey.split(' ').reduce(createSet, {});
assert.deepEqual(keys, expectedKeys);
}
var redisClient;

View File

@@ -3,11 +3,11 @@ require('../../support/test_helper');
var assert = require('assert');
var OverviewsQueryRewriter = require('../../../lib/cartodb/utils/overviews_query_rewriter');
var overviewsQueryRewriter = new OverviewsQueryRewriter({
zoom_level: 'ZoomLevel()'
zoom_level: 'ZoomLevel()'
});
function normalize_whitespace(txt) {
return txt.replace(/\s+/g, " ").trim();
return txt.replace(/\s+/g, " ").trim();
}
// compare SQL statements ignoring whitespace
@@ -17,413 +17,462 @@ function assertSameSql(sql1, sql2) {
describe('Overviews query rewriter', function() {
it('does not alter queries if no overviews data is present', function(done){
var sql = "SELECT * FROM table1";
var overviews_sql = overviewsQueryRewriter.query(sql);
assert.equal(overviews_sql, sql);
overviews_sql = overviewsQueryRewriter.query(sql, {});
assert.equal(overviews_sql, sql);
overviews_sql = overviewsQueryRewriter.query(sql, { overviews: {} });
assert.equal(overviews_sql, sql);
done();
});
it('does not alter queries if no overviews data is present', function(){
var sql = "SELECT * FROM table1";
var overviews_sql = overviewsQueryRewriter.query(sql);
assert.equal(overviews_sql, sql);
overviews_sql = overviewsQueryRewriter.query(sql, {});
assert.equal(overviews_sql, sql);
overviews_sql = overviewsQueryRewriter.query(sql, { overviews: {} });
assert.equal(overviews_sql, sql);
});
it('does not alter queries which don\'t use overviews', function(done){
var sql = "SELECT * FROM table1";
var data = {
overviews: {
table2: {
0: { table: 'table2_ov0' },
1: { table: 'table2_ov1' },
4: { table: 'table2_ov4' }
it('does not alter queries which don\'t use overviews', function(){
var sql = "SELECT * FROM table1";
var data = {
overviews: {
table2: {
0: { table: 'table2_ov0' },
1: { table: 'table2_ov1' },
4: { table: 'table2_ov4' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
done();
});
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
});
// jshint multistr:true
// jshint multistr:true
it('generates query with single overview layer for level 0', function(done){
var sql = "SELECT * FROM table1";
var data = {
overviews: {
table1: {
0: { table: 'table1_ov0' }
it('generates query with single overview layer for level 0', function(){
var sql = "SELECT * FROM table1";
var data = {
overviews: {
table1: {
0: { table: 'table1_ov0' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
_vovw_table1 AS (\
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 0\
)\
SELECT * FROM _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
it('generates query with single overview layer for level >0', function(done){
var sql = "SELECT * FROM table1";
var data = {
overviews: {
table1: {
2: { table: 'table1_ov2' }
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (\
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 0\
) AS _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
});
it('generates query with single overview layer for level >0', function(){
var sql = "SELECT * FROM table1";
var data = {
overviews: {
table1: {
2: { table: 'table1_ov2' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
_vovw_table1 AS (\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
)\
SELECT * FROM _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
) AS _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
});
it('generates query with multiple overview layers for all levels up to N', function(done){
var sql = "SELECT * FROM table1";
var data = {
overviews: {
table1: {
0: { table: 'table1_ov0' },
1: { table: 'table1_ov1' },
2: { table: 'table1_ov2' },
3: { table: 'table1_ov3' }
it('generates query with multiple overview layers for all levels up to N', function(){
var sql = "SELECT * FROM table1";
var data = {
overviews: {
table1: {
0: { table: 'table1_ov0' },
1: { table: 'table1_ov1' },
2: { table: 'table1_ov2' },
3: { table: 'table1_ov3' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
_vovw_table1 AS (\
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
UNION ALL\
SELECT * FROM table1_ov1, _vovw_scale WHERE _vovw_z = 1\
UNION ALL\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z = 2\
UNION ALL\
SELECT * FROM table1_ov3, _vovw_scale WHERE _vovw_z = 3\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 3\
)\
SELECT * FROM _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (\
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
UNION ALL\
SELECT * FROM table1_ov1, _vovw_scale WHERE _vovw_z = 1\
UNION ALL\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z = 2\
UNION ALL\
SELECT * FROM table1_ov3, _vovw_scale WHERE _vovw_z = 3\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 3\
) AS _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
});
it('generates query with multiple overview layers for random levels', function(done){
var sql = "SELECT * FROM table1";
var data = {
overviews: {
table1: {
0: { table: 'table1_ov0' },
1: { table: 'table1_ov1' },
6: { table: 'table1_ov6' }
it('generates query with multiple overview layers for random levels', function(){
var sql = "SELECT * FROM table1";
var data = {
overviews: {
table1: {
0: { table: 'table1_ov0' },
1: { table: 'table1_ov1' },
6: { table: 'table1_ov6' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
_vovw_table1 AS (\
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
UNION ALL\
SELECT * FROM table1_ov1, _vovw_scale WHERE _vovw_z = 1\
UNION ALL\
SELECT * FROM table1_ov6, _vovw_scale WHERE _vovw_z > 1 AND _vovw_z <= 6\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 6\
)\
SELECT * FROM _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (\
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
UNION ALL\
SELECT * FROM table1_ov1, _vovw_scale WHERE _vovw_z = 1\
UNION ALL\
SELECT * FROM table1_ov6, _vovw_scale WHERE _vovw_z > 1 AND _vovw_z <= 6\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 6\
) AS _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
});
it('generates query for a table with explicit schema', function(done){
var sql = "SELECT * FROM public.table1";
var data = {
overviews: {
'public.table1': {
2: { table: 'table1_ov2' }
it('generates query for a table with explicit schema', function(){
var sql = "SELECT * FROM public.table1";
var data = {
overviews: {
'public.table1': {
2: { table: 'table1_ov2' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
_vovw_table1 AS (\
SELECT * FROM public.table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM public.table1, _vovw_scale WHERE _vovw_z > 2\
)\
SELECT * FROM _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (\
SELECT * FROM public.table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM public.table1, _vovw_scale WHERE _vovw_z > 2\
) AS _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
});
it('generates query for a table with explicit schema in the overviews info', function(done){
var sql = "SELECT * FROM public.table1";
var data = {
overviews: {
'public.table1': {
2: { table: 'table1_ov2' }
it('generates query for a table with explicit schema in the overviews info', function(){
var sql = "SELECT * FROM public.table1";
var data = {
overviews: {
'public.table1': {
2: { table: 'table1_ov2' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
_vovw_table1 AS (\
SELECT * FROM public.table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM public.table1, _vovw_scale WHERE _vovw_z > 2\
)\
SELECT * FROM _vovw_table1\
";
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (\
SELECT * FROM public.table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM public.table1, _vovw_scale WHERE _vovw_z > 2\
) AS _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
assertSameSql(overviews_sql, expected_sql);
});
it('generates query for a table that needs quoting with explicit schema', function(done){
var sql = "SELECT * FROM public.\"table 1\"";
var data = {
overviews: {
'public."table 1"': {
2: { table: '"table 1_ov2"' }
it('uses schema name from overviews', function(){
var sql = "SELECT * FROM public.table1";
var data = {
overviews: {
'table1': {
schema: 'public',
2: { table: 'table1_ov2' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
\"_vovw_table 1\" AS (\
SELECT * FROM public.\"table 1_ov2\", _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM public.\"table 1\", _vovw_scale WHERE _vovw_z > 2\
)\
SELECT * FROM \"_vovw_table 1\"\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
) AS _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
});
it('generates query for a table with explicit schema that needs quoting', function(done){
var sql = "SELECT * FROM \"user-1\".table1";
var data = {
overviews: {
'"user-1".table1': {
2: { table: 'table1_ov2' }
it('ignores schema name from overviews if not necessary', function(){
var sql = "SELECT * FROM table1";
var data = {
overviews: {
'table1': {
schema: 'public',
2: { table: 'table1_ov2' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
_vovw_table1 AS (\
SELECT * FROM \"user-1\".table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM \"user-1\".table1, _vovw_scale WHERE _vovw_z > 2\
)\
SELECT * FROM _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
it('generates query for a table with explicit schema both needing quoting', function(done){
var sql = "SELECT * FROM \"user-1\".\"table 1\"";
var data = {
overviews: {
'"user-1"."table 1"': {
2: { table: '"table 1_ov2"' }
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
) AS _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
});
it('uses redundant schema information', function(){
var sql = "SELECT * FROM public.table1";
var data = {
overviews: {
'public.table1': {
schema: 'public',
2: { table: 'table1_ov2' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
\"_vovw_table 1\" AS (\
SELECT * FROM \"user-1\".\"table 1_ov2\", _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM \"user-1\".\"table 1\", _vovw_scale WHERE _vovw_z > 2\
)\
SELECT * FROM \"_vovw_table 1\"\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (\
SELECT * FROM public.table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM public.table1, _vovw_scale WHERE _vovw_z > 2\
) AS _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
});
it('generates query using overviews for queries with selected columns', function(done){
var sql = "SELECT column1, column2, column3 FROM table1";
var data = {
overviews: {
table1: {
2: { table: 'table1_ov2' }
it('generates query for a table that needs quoting with explicit schema', function(){
var sql = "SELECT * FROM public.\"table 1\"";
var data = {
overviews: {
'public."table 1"': {
2: { table: '"table 1_ov2"' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
_vovw_table1 AS (\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
)\
SELECT column1, column2, column3 FROM _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (\
SELECT * FROM public.\"table 1_ov2\", _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM public.\"table 1\", _vovw_scale WHERE _vovw_z > 2\
) AS \"_vovw_table 1\"\
";
assertSameSql(overviews_sql, expected_sql);
});
it('generates query using overviews for queries with selected columns and all columns', function(done){
var sql = "SELECT table1.*, column1, column2, column3 FROM table1";
var data = {
overviews: {
table1: {
2: { table: 'table1_ov2' }
it('generates query for a table with explicit schema that needs quoting', function(){
var sql = "SELECT * FROM \"user-1\".table1";
var data = {
overviews: {
'"user-1".table1': {
2: { table: 'table1_ov2' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
_vovw_table1 AS (\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
)\
SELECT _vovw_table1.*, column1, column2, column3 FROM _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (\
SELECT * FROM \"user-1\".table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM \"user-1\".table1, _vovw_scale WHERE _vovw_z > 2\
) AS _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
});
it('generates query using overviews for queries with a semicolon', function(done){
var sql = "SELECT table1.*, column1, column2, column3 FROM table1;";
var data = {
overviews: {
table1: {
2: { table: 'table1_ov2' }
it('generates query for a table with explicit schema both needing quoting', function(){
var sql = "SELECT * FROM \"user-1\".\"table 1\"";
var data = {
overviews: {
'"user-1"."table 1"': {
2: { table: '"table 1_ov2"' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
_vovw_table1 AS (\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
)\
SELECT _vovw_table1.*, column1, column2, column3 FROM _vovw_table1;\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (\
SELECT * FROM \"user-1\".\"table 1_ov2\", _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM \"user-1\".\"table 1\", _vovw_scale WHERE _vovw_z > 2\
) AS \"_vovw_table 1\"\
";
assertSameSql(overviews_sql, expected_sql);
});
it('generates query using overviews for queries with extra whitespace', function(done){
var sql = " SELECT table1.* , column1,column2, column3 FROM table1 ";
var data = {
overviews: {
table1: {
2: { table: 'table1_ov2' }
it('generates query using overviews for queries with selected columns', function(){
var sql = "SELECT column1, column2, column3 FROM table1";
var data = {
overviews: {
table1: {
2: { table: 'table1_ov2' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z ),\
_vovw_table1 AS (\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
)\
SELECT _vovw_table1.* , column1,column2, column3 FROM _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
done();
});
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT column1, column2, column3 FROM (\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
) AS _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
});
it('does not alter queries which have not the simple supported form', function(done){
var sql = "SELECT * FROM table1 WHERE column1='x'";
var data = {
overviews: {
table1: {
2: { table: 'table1_ov2' }
it('generates query using overviews for queries with a semicolon', function(){
var sql = "SELECT column1, column2, column3 FROM table1;";
var data = {
overviews: {
table1: {
2: { table: 'table1_ov2' }
}
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
sql = "SELECT * FROM table1 JOIN table2 ON (table1.col1=table2.col1)";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT column1, column2, column3 FROM (\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
) AS _vovw_table1;\
";
assertSameSql(overviews_sql, expected_sql);
});
sql = "SELECT a+b AS c FROM table1";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
it('generates query using overviews for queries with extra whitespace', function(){
var sql = " SELECT column1,column2, column3 FROM table1 ";
var data = {
overviews: {
table1: {
2: { table: 'table1_ov2' }
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT column1,column2, column3 FROM (\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z <= 2\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
) AS _vovw_table1\
";
assertSameSql(overviews_sql, expected_sql);
});
sql = "SELECT f(a) AS b FROM table1";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
it('does not alter queries which have not the simple supported form', function(){
var sql = "SELECT * FROM table1 WHERE column1='x'";
var data = {
overviews: {
table1: {
2: { table: 'table1_ov2' }
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
sql = "SELECT * FROM table1 AS x";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
sql = "SELECT * FROM table1 JOIN table2 ON (table1.col1=table2.col1)";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
sql = "WITH a AS (1) SELECT * FROM table1";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
sql = "SELECT a+b AS c FROM table1";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
sql = "SELECT * FROM table1 WHERE a=1";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
sql = "SELECT f(a) AS b FROM table1";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
sql = "\
SELECT table1.* FROM table1 \
JOIN areas ON ST_Intersects(table1.the_geom, areas.the_geom) \
WHERE areas.name='A' \
";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
sql = "SELECT * FROM table1 AS x";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
done();
});
sql = "WITH a AS (1) SELECT * FROM table1";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
sql = "SELECT * FROM table1 WHERE a=1";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
sql = "\
SELECT table1.* FROM table1 \
JOIN areas ON ST_Intersects(table1.the_geom, areas.the_geom) \
WHERE areas.name='A' \
";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
sql = "SELECT table1.*, column1, column2, column3 FROM table1";
overviews_sql = overviewsQueryRewriter.query(sql, data);
assert.equal(overviews_sql, sql);
});
it('generates overviews for wrapped query', function(){
var sql = "SELECT * FROM (SELECT * FROM table1) AS wrapped_query WHERE 1=1";
var data = {
overviews: {
table1: {
0: { table: 'table1_ov0' },
1: { table: 'table1_ov1' },
2: { table: 'table1_ov2' }
}
}
};
var overviews_sql = overviewsQueryRewriter.query(sql, data);
var expected_sql = "\
WITH\
_vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\
SELECT * FROM (SELECT * FROM (\
SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\
UNION ALL\
SELECT * FROM table1_ov1, _vovw_scale WHERE _vovw_z = 1\
UNION ALL\
SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z = 2\
UNION ALL\
SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\
) AS _vovw_table1) AS wrapped_query WHERE 1=1\
";
assertSameSql(overviews_sql, expected_sql);
});
});

View File

@@ -1,8 +0,0 @@
Deprecated tools
================
All tools and scripts found in this directory are deprecated and no longer maintained.
Use at your own peril.
In future releases they might get removed.

View File

@@ -1,81 +0,0 @@
#!/usr/bin/env node
var path = require('path');
var grainstore = require('../node_modules/windshaft/node_modules/grainstore');
var mapnik = require('mapnik');
var redis = require('redis');
function usage(me, exitcode) {
console.log("Usage: " + me + " <database_name> <table_name> [<target_mapnik_version>]");
process.exit(exitcode);
}
var node_path = process.argv.shift();
var script_path = process.argv.shift();
var me = path.basename(script_path);
var database_name = process.argv.shift()
var table_name = process.argv.shift()
var MAPNIK_VERSION = process.argv.shift()
if ( ! MAPNIK_VERSION ) {
MAPNIK_VERSION = mapnik.versions.mapnik;
}
if ( ! database_name || ! table_name) {
usage(me, 1);
}
var REDIS_PORT = 6379; // TODO: make a command line parameter
var dbnum = 0;
var mml_store = new grainstore.MMLStore({port:REDIS_PORT}, {mapnik_version:MAPNIK_VERSION});
var failures = [];
var client = redis.createClient(REDIS_PORT, 'localhost');
client.on('connect', function() {
client.select(dbnum);
client.keys('map_style|' + database_name + '|' + table_name, function(err, matches) {
processNext = function() {
if ( ! matches.length ) process.exit(failures.length);
var k = matches.shift();
if ( /map_style\|.*\|.*\|/.test(k) ) {
//console.warn("Key " + k + " is EXTENDED, skipping");
processNext();
}
var out = 'map_style|' + database_name + '|' + table_name + ': ';
var mml_builder = mml_store.mml_builder({
dbname:database_name,
table:table_name},
function(err, payload) {
if ( err ) {
console.warn(out + err.message);
failures.push(k); processNext();
}
else {
mml_builder.resetStyle(function(err, data) {
if ( err ) {
console.warn(out + err.message);
failures.push(k);
}
else console.log(out + 'OK');
processNext();
}, true);
}
});
};
processNext();
});
});

View File

@@ -1,40 +0,0 @@
#!/bin/sh
verbose=no
tiler_url=http://dev.localhost.lan:8181/tiles/layergroup
# This is for direct windshaft connection
#tiler_url=http://dev.localhost.lan:8083/database/cartodb_dev_user_1_db/layergroup
while test -n "$1"; do
if test "$1" = "-v"; then
verbose=yes
elif test -z "$cfg"; then
cfg="$1"
else
tiler_url="$1"
fi
shift
done
if test -z "$cfg"; then
echo "Usage: $0 [-v] <config_file> [<tiler_url>]" >&2
echo "Default <tiler_url> is ${tiler_url}" >&2
exit 1
fi
cmd="curl -skH Content-Type:application/json --data-binary @- ${tiler_url}"
if test x${verbose} = xyes; then
cmd="${cmd} -v"
fi
res=`cat ${cfg} | tr '\n' ' ' | ${cmd}`
if test $? -gt 0; then
echo "curl command failed: ${cmd}"
fi
if test x${verbose} = xyes; then
echo "${res}"
fi
tok=`echo "$res" | sed 's/.*"layergroupid":"\([^"]*\)".*/\1/'`
echo $tok

View File

@@ -1,49 +0,0 @@
#!/bin/sh
verbose=no
tiler_url=http://dev.localhost.lan:8181/tiles/template
apikey=${CDB_APIKEY}
while test -n "$1"; do
if test "$1" = "-v"; then
verbose=yes
elif test "$1" = "-k"; then
shift
apikey="$1"
elif test "$1" = "-u"; then
shift
tiler_url="$1"
elif test -z "$cfg"; then
cfg="$1"
else
echo "Unused parameter $1" >&2
fi
shift
done
if test -z "$cfg"; then
echo "Usage: $0 [-v] [-k <api_key>] [-u <tiler_url>] <template_config>" >&2
echo "Default <tiler_url> is ${tiler_url}" >&2
echo "Default <api_key> is read from CDB_APIKEY env variable" >&2
exit 1
fi
cmd="curl -skH Content-Type:application/json --data-binary @- ${tiler_url}?api_key=${apikey}"
if test x${verbose} = xyes; then
cmd="${cmd} -v"
fi
res=`cat ${cfg} | tr '\n' ' ' | ${cmd}`
if test $? -gt 0; then
echo "curl command failed: ${cmd}"
fi
if test x${verbose} = xyes; then
echo "${res}"
fi
# Successful response contains no space
echo "$res" | grep " " && { echo $res && exit 1; }
tok=`echo "$res" | sed 's/.*"template_id":"\([^"]*\)".*/\1/'`
echo $tok

View File

@@ -1,45 +0,0 @@
#!/bin/sh
verbose=no
tiler_url=http://dev.localhost.lan:8181/tiles/template
apikey=${CDB_APIKEY}
while test -n "$1"; do
if test "$1" = "-v"; then
verbose=yes
elif test "$1" = "-k"; then
shift
apikey="$1"
elif test "$1" = "-u"; then
shift
tiler_url="$1"
elif test -z "$tpl"; then
tpl="$1"
else
echo "Unused parameter $1" >&2
fi
shift
done
if test -z "$tpl"; then
echo "Usage: $0 [-v] [-k <api_key>] [-u <tiler_url>] <template_id>" >&2
echo "Default <tiler_url> is ${tiler_url}" >&2
echo "Default <api_key> is read from CDB_APIKEY env variable" >&2
exit 1
fi
cmd="curl -X DELETE -skH Content-Type:application/json ${tiler_url}/${tpl}?api_key=${apikey}"
if test x${verbose} = xyes; then
cmd="${cmd} -v"
fi
res=`${cmd}`
if test $? -gt 0; then
echo "curl command failed: ${cmd}"
fi
if test x${verbose} = xyes; then
echo "${res}"
fi
tok=`echo "$res" | sed 's/.*"template_id":"\([^"]*\)".*/\1/'`
echo $tok

View File

@@ -1,11 +0,0 @@
{"version":"1.0.1",
"layers":[{
"type":"cartodb",
"options":{
"sql":"select 1 as id, ST_SetSRID(ST_MakePoint(0,0),3857) as the_geom_webmercator",
"cartocss":"#style{ marker-width: 12;}",
"cartocss_version":"2.1.1",
"Interactivity":"id"
}
}]
}

View File

@@ -1,11 +0,0 @@
{"version":"1.0.1",
"layers":[{
"type":"cartodb",
"options":{
"sql":"select 1 as id, ST_Transform(ST_SetSRID(ST_MakePoint(x/1000,x/2000),4326),3857) as the_geom_webmercator FROM generate_series(-170000,170000) x",
"cartocss":"#style{ marker-width: 12;}",
"cartocss_version":"2.1.1",
"Interactivity":"id"
}
}]
}

View File

@@ -1,10 +0,0 @@
{"version":"1.0.1",
"layers":[{
"type":"torque",
"options":{
"sql":"select 1 as id, ST_SetSRID(ST_MakePoint(0,0),3857) as the_geom_webmercator",
"cartocss":"Map{ -torque-time-attribute:'id'; -torque-aggregation-function:'count(id)'; -torque-frame-count:2; -torque-resolution:2}",
"cartocss_version": "2.1.1"
}
}]
}

Some files were not shown because too many files have changed in this diff Show More