Compare commits

..

160 Commits

Author SHA1 Message Date
Daniel García Aubert
0606fca484 Release 2.22.0 2016-02-08 18:31:28 +01:00
Daniel García Aubert
e6812ef6c1 Upgrade Windshaft version to 1.8.3 2016-02-08 18:26:26 +01:00
Daniel García Aubert
260e5ec25f tubs next version 2016-02-05 15:09:32 +01:00
Daniel García Aubert
097f68f98c Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb 2016-02-05 15:05:15 +01:00
Daniel García Aubert
45d72b2bc6 Release 2.21.1 2016-02-05 15:03:34 +01:00
Raul Ochoa
82d4c20586 Merge pull request #371 from CartoDB/widgets-tests
Widgets tests
2016-02-05 14:58:07 +01:00
Raul Ochoa
03e3f7f13c Merge pull request #370 from CartoDB/fix-geojson-config
Fixed default geojson config
2016-02-05 14:53:43 +01:00
Raul Ochoa
b571b39b38 Aggregations in their own file 2016-02-05 13:32:38 +01:00
Raul Ochoa
f42d20f2c3 Histograms in their own file 2016-02-05 13:24:39 +01:00
Raul Ochoa
74cb876771 Move list to own file 2016-02-05 13:01:34 +01:00
Raul Ochoa
d78e01b7a4 Extract getWidget to TestClient 2016-02-05 12:59:33 +01:00
Raul Ochoa
73478ed0e9 Rename widgets tests file 2016-02-05 12:36:25 +01:00
Daniel García Aubert
887d71a9ad Fixed default geojson config 2016-02-05 12:35:03 +01:00
Daniel García Aubert
13c3fbae70 Stubs next version 2016-02-04 19:35:23 +01:00
Daniel García Aubert
0b6845235a Release 2.21.0 2016-02-04 19:31:57 +01:00
Raul Ochoa
d2558197d2 Merge pull request #369 from CartoDB/geojson-renderer
Geojson renderer
2016-02-04 19:26:41 +01:00
Raul Ochoa
d005521aa4 Upgrade windshaft version 2016-02-04 19:06:54 +01:00
Daniel García Aubert
336aaa3840 Updated travis config in order to use npm@2 2016-02-04 17:24:02 +01:00
Daniel García Aubert
edbdd95f79 Upgrades npm version to 2.14.16 2016-02-04 17:09:30 +01:00
Daniel García Aubert
bb5a8fd0bf Merge branch 'geojson-renderer' of github.com:CartoDB/Windshaft-cartodb into geojson-renderer 2016-02-04 17:02:14 +01:00
Daniel García Aubert
3284e709c3 Regenerate npm-shrinkwrap 2016-02-04 17:00:44 +01:00
Raul Ochoa
d33ae29211 Revert "Fixed missing map key for named layers"
This reverts commit a4041524a3.
2016-02-04 16:26:32 +01:00
Daniel García Aubert
da51a173d7 Upgrades windshaft to 1.8.1 2016-02-04 15:46:13 +01:00
Raul Ochoa
425ec83209 Use a valid SPDX license expression 2016-02-04 15:22:08 +01:00
Daniel García Aubert
971b77451d Merge branch 'geojson-renderer' of github.com:CartoDB/Windshaft-cartodb into geojson-renderer 2016-02-04 12:46:52 +01:00
Daniel García Aubert
6407101709 Upgrade to Windshaft 1.8.0 2016-02-04 12:40:53 +01:00
Daniel
89033d2cd4 Merge pull request #367 from CartoDB/metadata-refactor
Added property 'enableLayerMetadata' to config files
2016-02-03 15:37:05 +01:00
Daniel García Aubert
08d43a8620 Added property 'enableLayerMetadata' to config files 2016-02-02 16:34:58 +01:00
Daniel García Aubert
8601a67e97 Improved assertions for multilayer tests 2016-02-01 18:39:34 +01:00
Daniel García Aubert
640500a0e3 Updated config to use Windshaft's geojson renderer 2016-02-01 17:12:50 +01:00
Daniel García Aubert
6ee1f1a8bf Fixed bad assertion in multilayer tests 2016-02-01 16:31:03 +01:00
Daniel García Aubert
a4041524a3 Fixed missing map key for named layers 2016-02-01 16:29:36 +01:00
Carlos Matallín
c3b17df3e7 Merge pull request #366 from CartoDB/docs-minor-clarification-added
Docs minor clarification added
2016-02-01 14:47:20 +01:00
csobier
5e77c50102 changed relative links to absolute links 2016-01-29 16:46:42 -05:00
csobier
1620cbc8df modified title 2016-01-29 12:11:21 -05:00
csobier
912b8f6ff4 fixed title of named maps 2016-01-29 12:07:47 -05:00
csobier
486a55ed7f edited intro text and fixed typo 2016-01-29 11:52:17 -05:00
csobier
af50af325d added links to new mapconfig, updated named map intro, and added cartodbjs named map examples 2016-01-29 11:46:03 -05:00
Daniel García Aubert
feef31d1bf Merge branch 'geojson-renderer' of github.com:CartoDB/Windshaft-cartodb into geojson-renderer 2016-01-29 15:36:47 +01:00
Raul Ochoa
d6ecb8c793 Remove trailing whitespace 2016-01-28 19:44:25 +01:00
Raul Ochoa
e4e7d6c840 Stubs next version 2016-01-20 12:26:16 +01:00
Raul Ochoa
d266d9a590 Release 2.20.0 2016-01-20 12:25:12 +01:00
Raul Ochoa
1e8525162a Bump version 2016-01-20 12:22:56 +01:00
Raul Ochoa
5fed5afe1f Update news 2016-01-20 12:22:24 +01:00
Raul Ochoa
f8e4deb4b9 Merge pull request #364 from CartoDB/upgrade-windshaft
Upgrade windshaft to 1.7.0
2016-01-20 11:45:03 +01:00
Raul Ochoa
9c2fe9eac4 Upgrade windshaft to 1.7.0 2016-01-20 11:39:53 +01:00
Raul Ochoa
d53a293f28 Merge pull request #363 from CartoDB/fix-statsd-key
Change redis pool name to report with a valid statsd key
2016-01-19 13:26:48 +01:00
Raul Ochoa
1dea84f9bf Change redis pool name to report with a valid statsd key 2016-01-19 13:00:02 +01:00
Raul Ochoa
e72bd77265 Merge branch 'master' into geojson-renderer
Conflicts:
	npm-shrinkwrap.json
	package.json
2016-01-18 14:52:01 +01:00
Raul Ochoa
8b50097a12 Merge pull request #361 from CartoDB/mapconfig-extension
Mapconfig widgets extension
2016-01-18 14:39:35 +01:00
Raul Ochoa
fbc1bccb48 Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
2016-01-18 14:32:33 +01:00
Raul Ochoa
b5b881e662 Regenerate npm-shrinkwrap.json 2016-01-18 14:24:23 +01:00
Raul Ochoa
30e479094f Fix tests for windshaft master 2016-01-18 14:08:01 +01:00
Raul Ochoa
2b3244440f Merge remote-tracking branch 'origin/master' into mapconfig-extension 2016-01-15 18:31:30 +01:00
Raul Ochoa
ba4db870e4 Merge pull request #362 from CartoDB/prepare-tests-for-next-windshaft
Prepare tests for next windshaft
2016-01-15 18:30:50 +01:00
Raul Ochoa
d3f5b03f13 Validate current torque metadata keys 2016-01-15 17:25:09 +01:00
Raul Ochoa
e00661aa34 Remove substitution tokens in attributes service test 2016-01-15 17:17:20 +01:00
Raul Ochoa
3afb7a0eb3 Adds some notes about unsupported endpoints 2016-01-15 17:02:09 +01:00
Raul Ochoa
1eb69ae3d1 Remove make target to update windshaft 2016-01-15 16:02:02 +01:00
Raul Ochoa
5eeaa0272c Use master branch from windshaft and regenerate npm-shrinkwrap.json 2016-01-15 16:01:01 +01:00
Raul Ochoa
3e601cc2e6 Use geojson renderer branch from windshaft 2016-01-15 15:53:40 +01:00
Raul Ochoa
3e9f2a1319 Merge branch 'master' into mapconfig-extension 2016-01-14 18:45:30 +01:00
Raul Ochoa
45acdd9f39 fix json, 2nd attempt 2016-01-14 18:16:31 +01:00
Raul Ochoa
62b1df970a fix json 2016-01-14 18:16:12 +01:00
Raul Ochoa
1aac4316ab Merge pull request #360 from CartoDB/travis-container-based
Travis container based
2016-01-13 20:40:00 +01:00
Raul Ochoa
97d3bed1d2 test with pg 9.4 2016-01-13 20:33:12 +01:00
Raul Ochoa
19216eaa88 Use create language instead of extension 2016-01-13 20:21:54 +01:00
Raul Ochoa
3780aed1b7 Check with no postgresql-plpython-9.3 at all 2016-01-13 20:14:04 +01:00
Raul Ochoa
04a2d1d33c Remove postgresql-plpython-9.3 2016-01-13 20:11:24 +01:00
Raul Ochoa
18278da4bb Create language instead of extension 2016-01-13 19:36:01 +01:00
Raul Ochoa
553c64bd8b Create extension for plpython 2016-01-13 19:32:50 +01:00
Raul Ochoa
74abee2700 Use apt addon instead of apt-get install 2016-01-13 19:23:30 +01:00
Raul Ochoa
8ea159e0a1 Remove sudo calls 2016-01-13 19:14:58 +01:00
Raul Ochoa
353919239d Attempt to use travis' container based builds 2016-01-13 18:59:33 +01:00
Raul Ochoa
9e74e8633a Merge pull request #359 from CartoDB/query-runner-improvements
Query runner improvements
2016-01-13 18:56:26 +01:00
Raul Ochoa
98611be544 Merge pull request #351 from CartoDB/install_instructions
Install instructions
2016-01-12 16:39:58 +01:00
Raul Ochoa
3b7ff2285c Merge pull request #356 from CartoDB/http-409-status-on-template-limit-reached
Send 409 error code when maximum number of templates limit is reached
2016-01-12 16:14:37 +01:00
Raul Ochoa
8203c878f4 Send 409 error code when maximum number of templates limit is reached
Closes #346
2016-01-12 15:53:16 +01:00
Carlos Matallín
4f4480dc9b Merge pull request #339 from CartoDB/newdocs
docs: split Map-API.md in different files
2016-01-07 15:38:31 +01:00
Carlos Matallín
16e1abe376 replicate #355 2016-01-07 15:33:16 +01:00
Carlos Matallín
2d0ebc821f Merge pull request #355 from CartoDB/353-named-maps-limit
added limit amount regarding named maps
2016-01-07 10:41:22 +01:00
csobier
67e921017c added limit amount regarding named maps 2016-01-06 08:44:53 -05:00
Paul Norman
7ed96ef0bb Add a list of packages and postgres instructions 2015-12-30 23:37:12 -08:00
Paul Norman
6f7bbe4ff5 Move install instructions to their own file 2015-12-30 17:29:19 -08:00
Raul Ochoa
74898e4261 Integration tests for QueryTablesApi
Again this is more about how it would be possible to use it isolated.
2015-12-30 17:48:24 +01:00
Raul Ochoa
c664d5392c Adds some tests about how to use PgQueryRunner isolated 2015-12-30 17:48:00 +01:00
Raul Ochoa
76cbc2f863 Improve PgQueryRunner public run method
Last param is callback function, receiving err + result, no need to
keep passing two functions, the query handler and the final callback.

It should be easier to understand now as query handler was in a position
where it had to know about receiving a callback, that was exposing an
implementation detail of PgQueryRunner.
2015-12-30 17:44:49 +01:00
Raul Ochoa
69fdaca41f Merge pull request #350 from CartoDB/fix-tests
Do not test `all` layers in blend suite
2015-12-30 16:04:58 +01:00
Raul Ochoa
5ac327272f Do not test all layers. Test is also present in windshaft suite. 2015-12-30 15:52:52 +01:00
Raul Ochoa
53a2d52523 Merge pull request #349 from CartoDB/pnorman-patch-1
Fix readme typo
2015-12-29 12:55:06 +01:00
Paul Norman
cd847adfb3 Fix readme typo
[no ci]
2015-12-28 11:53:54 -08:00
Raul Ochoa
e65dc2d790 Merge branch 'master' into mapconfig-extension 2015-12-22 12:59:39 +01:00
Raul Ochoa
e0d8f5afac Tests for named maps + torque layers with params instantiation 2015-12-21 20:01:44 +01:00
Raul Ochoa
7d2f543284 Allow column_type query string param 2015-12-02 18:50:11 +01:00
Raul Ochoa
ad566d8ff0 Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-11-24 13:36:39 +01:00
Raul Ochoa
67ba517a0e Stubs next version 2015-11-23 16:08:32 +01:00
Raul Ochoa
9a01c8b26e Release 2.19.1 2015-11-23 16:07:37 +01:00
Raul Ochoa
b40fd29228 Merge pull request #344 from CartoDB/upgrade-windshaft
Upgrade windshaft
2015-11-23 16:03:16 +01:00
Raul Ochoa
152440e611 Upgrade windshaft to 1.6.1
Regenerates npm-shrinkwrap.json, including bump in torque.js@2.11.4
2015-11-23 15:51:57 +01:00
Raul Ochoa
ea2a94be88 Fix news formatting 2015-11-23 15:51:00 +01:00
Raul Ochoa
0a38549c55 Regenerate npm-shrinkwrap.json 2015-11-23 11:00:55 +01:00
Raul Ochoa
e3b25f3080 Dependencies installation example 2015-11-19 12:50:34 +01:00
Raul Ochoa
1de4753daa fix tests 2015-11-16 18:34:36 +01:00
Raul Ochoa
d9614cc1c5 Add search endpoint 2015-11-16 13:15:01 +01:00
Raul Ochoa
37ff13493c Use mapconfig-extension from windshaft 2015-11-16 12:23:05 +01:00
Raul Ochoa
9264f4a668 Re-adds npm-shrinkwrap.json 2015-11-16 12:13:54 +01:00
Raul Ochoa
e254379244 Use windshaft's histogram branch 2015-11-13 10:35:52 +01:00
Raul Ochoa
4c851a0d09 Remove npm-shrinkwrap.json to ease development 2015-11-13 10:35:30 +01:00
Raul Ochoa
a84dd7cd29 Add more url params 2015-11-12 19:45:49 +01:00
Raul Ochoa
86abc392a1 Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-11-12 11:38:42 +01:00
Raul Ochoa
9e00cb3309 Stubs next version 2015-11-12 11:30:58 +01:00
Raul Ochoa
6d55544d70 Release 2.19.0 2015-11-12 11:29:50 +01:00
Raul Ochoa
d8e38e7e81 Merge pull request #343 from CartoDB/upgrade-mapnik
Upgrade windshaft/mapnik
2015-11-11 16:34:10 +01:00
Raul Ochoa
3dbcf5f293 Upgrade windshaft/mapnik 2015-11-11 16:28:21 +01:00
Raul Ochoa
7d230cc15d Widgets returning two results 2015-11-04 17:21:33 +01:00
Raul Ochoa
2320b0b852 Reference Windshaft's notes on Installing Mapnik 2015-11-03 16:56:37 +01:00
Raul Ochoa
837f99f31c Restrict npm supported versions to >=1.2.1 <2.0.0 2015-11-03 16:55:42 +01:00
Raul Ochoa
663703abae Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-11-02 11:13:34 +01:00
Raul Ochoa
5844c031c9 Stubs next version 2015-11-02 10:55:57 +01:00
Carlos Matallín
4bc8c57729 docs: remove code-title 2015-10-30 11:25:42 +01:00
Raul Ochoa
2c4cb6d42e Adds some tests for per widget bbox filters 2015-10-29 17:18:14 +01:00
Carlos Matallín
b2cd421e2e docs: rearrange overview 2015-10-29 16:59:51 +01:00
Raul Ochoa
8a81828a3d Enable bbox query string param 2015-10-29 16:25:56 +01:00
Raul Ochoa
8e568d0f20 Adapt tests to new widgets format 2015-10-29 13:19:49 +01:00
Raul Ochoa
3b9759d5e4 Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-10-28 19:41:44 +01:00
Carlos Matallín
277c0ee818 docs: fixes 2015-10-28 17:38:46 +01:00
Carlos Matallín
a3c7b3fc35 docs: fixes 2015-10-28 17:31:20 +01:00
Carlos Matallín
034b492788 docs: static maps 2015-10-28 17:02:16 +01:00
Carlos Matallín
cd00680c80 docs: static maps 2015-10-28 17:00:22 +01:00
Carlos Matallín
cc0ebf70a7 docs: static maps 2015-10-28 16:55:11 +01:00
Carlos Matallín
8087f838ef docs: named maps 2015-10-28 16:46:50 +01:00
Carlos Matallín
3eda1750cc docs: named maps 2015-10-28 16:26:33 +01:00
Carlos Matallín
2c35e27095 docs: anonymous_maps 2015-10-28 16:07:04 +01:00
Carlos Matallín
90487819bf docs: anonymous_maps 2015-10-28 16:02:23 +01:00
Carlos Matallín
50a943c131 docs: quickstart 2015-10-28 15:53:39 +01:00
Carlos Matallín
36b91180e4 docs: quickstart 2015-10-28 15:51:31 +01:00
Carlos Matallín
df44a84bfc Merge branch 'master' into newdocs 2015-10-28 15:31:35 +01:00
Raul Ochoa
e5afdb1e04 Adds test combining two widgets + filters 2015-10-28 12:21:43 +01:00
Raul Ochoa
cf2774c852 Fix test description 2015-10-28 12:06:06 +01:00
Raul Ochoa
36369068e1 Use match instead of full url 2015-10-27 19:11:29 +01:00
Raul Ochoa
af0812e990 Fix jshint 2015-10-27 19:08:46 +01:00
Raul Ochoa
0285f015e2 Pass filters within params 2015-10-27 13:26:11 +01:00
Raul Ochoa
473b20596a Adds helper function to retrieve widgets 2015-10-27 13:26:11 +01:00
Raul Ochoa
fda405f35d Unify widgets tests 2015-10-27 13:20:15 +01:00
Raul Ochoa
4047cb82b9 Merge pull request #340 from CartoDB/fix-widget-urls
Adds trailing slash to API endpoint in widget URLs
2015-10-26 17:52:55 +01:00
Pablo Alonso Garcia
7b57d22444 Added trailing slash to API endpoint in widget URLs 2015-10-26 17:50:50 +01:00
Raul Ochoa
50e63bf83d Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-10-26 17:06:13 +01:00
Raul Ochoa
8a9e257cdb Adds target to Makefile to just remove and re-install windshaft dep 2015-10-26 16:51:21 +01:00
Raul Ochoa
3a05d8c2e8 Widgets inside layer options 2015-10-26 16:40:39 +01:00
Raul Ochoa
8b222914c5 Add URL to widgets with their endpoints 2015-10-26 14:35:51 +01:00
Raul Ochoa
6d9182aba8 Rename widgets endpoint to honour its name 2015-10-26 13:42:06 +01:00
Raul Ochoa
f9d3e419a0 Renames list -> histogram 2015-10-26 11:12:26 +01:00
Raul Ochoa
4b0ecb1251 Adds histogram support, a bit of code duplication 2015-10-26 11:10:59 +01:00
Raul Ochoa
36a6af3266 Replaces List with Widget backend and changes list access to be layer based 2015-10-26 10:23:56 +01:00
Carlos Matallín
5e0c9377f2 newdocs: fix titles 2015-10-22 15:02:01 +02:00
Carlos Matallín
8db1ad6f19 newdocs: split files 2015-10-22 13:28:08 +02:00
Raul Ochoa
52dbe14af2 Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-10-13 16:06:04 +02:00
Raul Ochoa
9bc95a6071 List endpoint for layergroups 2015-10-06 19:47:44 +02:00
Raul Ochoa
b80e80bd61 Do not pass MapStore as it is no longer needed in Attributes backend 2015-10-06 19:46:52 +02:00
Raul Ochoa
59fb1dea54 Start using mapconfig-extension branch from windshaft 2015-10-06 18:55:27 +02:00
47 changed files with 3890 additions and 2148 deletions

View File

@@ -1,10 +1,17 @@
sudo: false
addons:
postgresql: "9.3"
postgresql: "9.4"
apt:
packages:
- postgresql-plpython-9.4
- pkg-config
- libcairo2-dev
- libjpeg8-dev
- libgif-dev
before_install:
- sudo apt-get update
- sudo apt-get install -y pkg-config libcairo2-dev libjpeg8-dev libgif-dev
- sudo apt-get install postgresql-plpython-9.3
- npm install -g npm@2
- createdb template_postgis
- psql -c "CREATE EXTENSION postgis" template_postgis

57
INSTALL.md Normal file
View File

@@ -0,0 +1,57 @@
# Installing Windshaft-CartoDB #
## Requirements ##
Make sure that you have the requirements needed. These are
- Core
- Node.js >=0.8
- npm >=1.2.1 <2.0.0
- PostgreSQL >8.3.x, PostGIS >1.5.x
- Redis >2.4.0 (http://www.redis.io)
- Mapnik 2.0.1, 2.0.2, 2.1.0, 2.2.0, 2.3.0. See [Installing Mapnik](https://github.com/CartoDB/Windshaft#installing-mapnik).
- Windshaft: check [Windshaft dependencies and installation notes](https://github.com/CartoDB/Windshaft#dependencies)
- libcairo2-dev, libpango1.0-dev, libjpeg8-dev and libgif-dev for server side canvas support
- For cache control (optional)
- CartoDB 0.9.5+ (for `CDB_QueryTables`)
- Varnish (http://www.varnish-cache.org)
- For running the testsuite
- ImageMagick (http://www.imagemagick.org)
On Ubuntu 14.04 the dependencies can be installed with
```shell
sudo apt-get update
sudo apt-get install -y make g++ pkg-config git-core \
libgif-dev libjpeg-dev libcairo2-dev \
libhiredis-dev redis-server \
nodejs nodejs-legacy npm \
postgresql-9.3-postgis-2.1 postgresql-plpython-9.3 postgresql-server-dev-9.3
```
On Ubuntu 12.04 the [cartodb/cairo PPA](https://launchpad.net/~cartodb/+archive/ubuntu/cairo) may be useful.
## PostGIS setup ##
A `template_postgis` database is expected. One can be set up with
```shell
createdb --owner postgres --template template0 template_postgis
psql -d template_postgis -c 'CREATE EXTENSION postgis;'
```
## Build/install ##
To fetch and build all node-based dependencies, run:
```
npm install
```
Note that the ```npm install``` step will populate the node_modules/
directory with modules, some of which being compiled on demand. If you
happen to have startup errors you may need to force rebuilding those
modules. At any time just wipe out the node_modules/ directory and run
```npm install``` again.

70
NEWS.md
View File

@@ -1,5 +1,61 @@
# Changelog
## 2.22.0
Released 2016-02-08
Announcements:
- Upgrades windshaft to [1.8.3](https://github.com/CartoDB/Windshaft/releases/tag/1.8.3)
## 2.21.1
Released 2016-02-05
Bug fixes:
- Added default config for geojson renderer
## 2.21.0
Released 2016-02-04
Announcements:
- Upgrades windshaft to [1.8.2](https://github.com/CartoDB/Windshaft/releases/tag/1.8.2)
## 2.20.0
Released 2016-01-20
Bug fixes:
- Change redis pool name to report with a valid statsd key #363
Improvements:
- Query runner improvements #359
Unsupported:
- Widgets endpoints
- Layer filters
Note: API for unsupported list might change in the future, use at your own peril.
## 2.19.1
Released 2015-11-23
Announcements:
- Upgrades windshaft to [1.6.1](https://github.com/CartoDB/Windshaft/releases/tag/1.6.1)
## 2.19.0
Released 2015-11-12
Announcements:
- Upgrades windshaft to [1.6.0](https://github.com/CartoDB/Windshaft/releases/tag/1.6.0)
## 2.18.0
Released 2015-11-02
@@ -787,7 +843,7 @@ Released 2014-03-10
Enhancements:
- Set statsd prefix for all endpoints
- Set statsd prefix for all endpoints
- Respond with a permission denied on attempt to access map tiles waiving
signature of someone who had not left any (#170)
- Do not log an error on GET / (#177)
@@ -827,7 +883,7 @@ Released 2014-02-27
Enhancements:
- Upgrades windshaft to 0.19.1 with many performance improvements,
See node_modules/windshaft/NEWS
See node_modules/windshaft/NEWS
- Improve speed of instanciating a map (#147, #159, #165)
- Give meaningful error on attempts to use map tokens
with attribute service (#156)
@@ -916,7 +972,7 @@ Bug fixes:
Released 2014-01-30
Bug fixes:
Bug fixes:
* layergroup accept both map_key and api_key (#91)
* Fix public instanciation of signed template accessing private data (#114)
@@ -1039,7 +1095,7 @@ Released 2013-10-03
"[ zoom > 3]" CartoCSS snippets (note the space)
* Fix backward compatibility handling of sqlapi.host configuration (#82)
* Fix error for invalid text-name in CartoCSS (#81)
* Do not let anonymous requests use authorized renderer caches
* Do not let anonymous requests use authorized renderer caches
## 1.3.4
@@ -1086,7 +1142,7 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain
* Multilayer API changes
* Layers passed by index in grid fetching url
* Interactivity only specified in layergroup config
* Embed cache_buster within token
* Embed cache_buster within token
* Use ISO format for last_modified timestamp
* Expected LZMA encoding changed to base64
@@ -1140,7 +1196,7 @@ Released DD//MM//YY
Released DD//MM//YY
* Reduce default extent to allow for consistent proj4 round-tripping
* Reduce default extent to allow for consistent proj4 round-tripping
* Enhance reset_styles script to use full configuration (#62)
* Have reset_styles script also drop extended keys (#58)
* Fix example postgis parameter for simplifying input geoms (#63)
@@ -1190,7 +1246,7 @@ Released (30/10/12)
* Autodetect target mapnik version and let config override it
* Add tools/reset_styles script to batch-reset (and optionally convert) styles
* Configurable logging format (#4)
* Detailed error on missing user metadata
* Detailed error on missing user metadata
* Properly handle unauthenticated requests for metadata
* Accept "api_key" in addition to "map_key",
both in query_string and POST body (#38)

View File

@@ -13,23 +13,9 @@ This is the [CartoDB Maps API](http://docs.cartodb.com/cartodb-platform/maps-api
* provides a link to varnish high speed cache
* provides a [template maps API](https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Template-maps.md)
Requirements
------------
- Core
- Node.js >=0.8
- npm >=1.2.1
- PostgreSQL >8.3.x, PostGIS >1.5.x
- Redis >2.4.0 (http://www.redis.io)
- Mapnik 2.0.1, 2.0.2, 2.1.0, 2.2.0, 2.3.0. See Installing Mapnik.
- Windshaft: check [Windshaft dependencies and installation notes](https://github.com/CartoDB/Windshaft#dependencies)
- libcairo2-dev, libpango1.0-dev, libjpeg8-dev and libgif-dev for server side canvas support
- For cache control (optional)
- CartoDB 0.9.5+ (for `CDB_QueryTables`)
- Varnish (http://www.varnish-cache.org)
- For running the testsuite
- ImageMagick (http://www.imagemagick.org)
Install
-------
See [INSTALL.md](INSTALL.md) for detailed installation instructions.
Configure
---------
@@ -40,22 +26,6 @@ see ```./configure --help``` to see available options.
Look at lib/cartodb/server_options.js for more on config
Build/install
-------------
To fetch and build all node-based dependencies, run:
```
git clone
npm install
```
Note that the ```npm install``` step will populate the node_modules/
directory with modules, some of which being compiled on demand. If you
happen to have startup errors you may need to force rebuilding those
modules. At any time just wipe out the node_modules/ directory and run
```npm install``` again.
Upgrading
---------
@@ -93,7 +63,6 @@ Examples
[CartoDB's Map Gallery](http://cartodb.com/gallery/) showcases several examples of visualisations built on top of this.
Contributing
---

View File

@@ -13,7 +13,7 @@ var config = {
// Base URLs for the APIs
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
//
//
// Base url for the Templated Maps API
// "/api/v1/map/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x
@@ -33,7 +33,7 @@ var config = {
// to be able to navigate the map without a reload ?
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in milliseconds
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: false
@@ -147,7 +147,24 @@ var config = {
// memory. If we want to enforce this behaviour we have to implement a cache eviction policy for the
// internal cache.
cacheOnTimeout: true
},
geojson: {
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
},
// 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
}
},
http: {
timeout: 2000, // the timeout in ms for a http tile request
@@ -232,7 +249,7 @@ var config = {
serviceId: 'wadus_service_id'
}
// If useProfiler is true every response will be served with an
// X-Tiler-Profile header containing elapsed timing for various
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
,useProfiler:true
// Settings for the health check available at /health
@@ -250,7 +267,10 @@ var config = {
// whether it should intercept tile render errors an act based on them, enabled by default.
onTileErrorStrategy: true,
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true
cdbQueryTablesFromPostgres: true,
// whether in mapconfig is available stats & metadata for each layer
layerMetadata: true
}
};

View File

@@ -13,7 +13,7 @@ var config = {
// Base URLs for the APIs
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
//
//
// Base url for the Templated Maps API
// "/api/v1/map/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x
@@ -34,7 +34,7 @@ var config = {
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: true
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
@@ -141,7 +141,24 @@ var config = {
// memory. If we want to enforce this behaviour we have to implement a cache eviction policy for the
// internal cache.
cacheOnTimeout: true
},
geojson: {
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
},
// 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
}
},
http: {
timeout: 2000, // the timeout in ms for a http tile request
@@ -226,7 +243,7 @@ var config = {
serviceId: 'wadus_service_id'
}
// If useProfiler is true every response will be served with an
// X-Tiler-Profile header containing elapsed timing for various
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
,useProfiler:false
,serverMetadata: {
@@ -250,7 +267,9 @@ var config = {
// whether it should intercept tile render errors an act based on them, enabled by default.
onTileErrorStrategy: true,
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true
cdbQueryTablesFromPostgres: true,
// whether in mapconfig is available stats & metadata for each layer
layerMetadata: false
}
};

View File

@@ -13,7 +13,7 @@ var config = {
// Base URLs for the APIs
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
//
//
// Base url for the Templated Maps API
// "/api/v1/maps/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x
@@ -34,7 +34,7 @@ var config = {
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: true
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms (:res[X-Tiler-Profiler]) -> :res[Content-Type]'
@@ -141,7 +141,24 @@ var config = {
// memory. If we want to enforce this behaviour we have to implement a cache eviction policy for the
// internal cache.
cacheOnTimeout: true
},
geojson: {
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
},
// 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
}
},
http: {
timeout: 2000, // the timeout in ms for a http tile request
@@ -226,7 +243,7 @@ var config = {
serviceId: 'wadus_service_id'
}
// If useProfiler is true every response will be served with an
// X-Tiler-Profile header containing elapsed timing for various
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
,useProfiler:true
,serverMetadata: {
@@ -250,7 +267,9 @@ var config = {
// whether it should intercept tile render errors an act based on them, enabled by default.
onTileErrorStrategy: true,
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true
cdbQueryTablesFromPostgres: true,
// whether in mapconfig is available stats & metadata for each layer
layerMetadata: true
}
};

View File

@@ -13,7 +13,7 @@ var config = {
// Base URLs for the APIs
//
// See https://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
//
//
// Base url for the Templated Maps API
// "/api/v1/map/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x
@@ -34,7 +34,7 @@ var config = {
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: false
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
@@ -141,6 +141,22 @@ var config = {
// memory. If we want to enforce this behaviour we have to implement a cache eviction policy for the
// internal cache.
cacheOnTimeout: true
},
geojson: {
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
},
// 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
}
},
http: {
@@ -228,7 +244,7 @@ var config = {
serviceId: 'wadus_service_id'
}
// If useProfiler is true every response will be served with an
// X-Tiler-Profile header containing elapsed timing for various
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
,useProfiler:true
// Settings for the health check available at /health
@@ -246,7 +262,9 @@ var config = {
// whether it should intercept tile render errors an act based on them, enabled by default.
onTileErrorStrategy: true,
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true
cdbQueryTablesFromPostgres: true,
// whether in mapconfig is available stats & metadata for each layer
layerMetadata: true
}
};

View File

@@ -1,977 +1,19 @@
## Maps API
# Maps API
The CartoDB Maps API allows you to generate maps based on data hosted in your CartoDB account and you can apply custom SQL and CartoCSS to the data. The API generates a XYZ-based URL to fetch Web Mercator projected tiles using web clients such as [Leaflet](http://leafletjs.com), [Google Maps](https://developers.google.com/maps/), or [OpenLayers](http://openlayers.org/).
The CartoDB Maps API allows you to generate maps based on data hosted in your CartoDB account and apply custom SQL and CartoCSS to the data. The API generates a XYZ-based URL to fetch Web Mercator projected tiles, using web clients such as [Leaflet](http://leafletjs.com), [Google Maps](https://developers.google.com/maps/), or [OpenLayers](http://openlayers.org/).
You can create two types of maps with the Maps API:
- **Anonymous maps**
You can create maps using your CartoDB public data. Any client can change the read-only SQL and CartoCSS parameters that generate the map tiles. These maps can be created from a JavaScript application alone and no authenticated calls are needed. See [this CartoDB.js example]({{ '/cartodb-platform/cartodb-js.html' | prepend: site.baseurl }}).
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**
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.
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.
## Quickstart
## Documentation
### Anonymous maps
Here is an example of how to create an anonymous map with JavaScript:
```javascript
var mapconfig = {
"version": "1.3.1",
"layers": [{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
"sql": "select * from european_countries_e"
}
}]
}
$.ajax({
crossOrigin: true,
type: 'POST',
dataType: 'json',
contentType: 'application/json',
url: 'https://documentation.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'
console.log(templateUrl);
}
})
```
### Named maps
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
{
"version": "0.0.1",
"name": "test",
"auth": {
"method": "open"
},
"layergroup": {
"layers": [{
"type": "mapnik",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
"sql": "select * from european_countries_e"
}
}]
}
}
```
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:
<div class="code-title notitle code-request"></div>
```bash
curl 'https://{account}.cartodb.com/api/v1/map/named?api_key=APIKEY' -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.
<div class="code-title notitle code-request"></div>
```bash
curl -X POST 'https://{account}.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`.
Note: all `layers` in `metadata` will always have a `type` string and a `meta` dictionary with the key/value pairs.
Here is an example response:
```javascript
{
"layergroupid": "c01a54877c62831bb51720263f91fb33:0",
"last_updated": "1970-01-01T00:00:00.000Z",
"metadata": {
"layers": [
{
"type": "mapnik",
"meta": {}
}
]
}
}
```
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
```
## General Concepts
The following concepts are the same for every endpoint in the API except when it's noted explicitly.
### Auth
By default, users do not have access to private tables in 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`.
### Errors
Errors are reported using standard HTTP codes and extended information encoded in JSON with this format:
```javascript
{
"errors": [
"access forbidden to table TABLE"
]
}
```
If you use JSONP, the 200 HTTP code is always returned so the JavaScript client can receive errors from the JSON object.
### CORS support
All the endpoints, which might be accessed using a web browser, add CORS headers and allow OPTIONS method.
## 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)
### Instantiate
#### Definition
<div class="code-title notitle code-request"></div>
```html
POST /api/v1/map
```
#### Params
```javascript
{
"version": "1.3.0",
"layers": [{
"type": "mapnik",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
"sql": "select * from european_countries_e",
"interactivity": ["cartodb_id", "iso3"]
}
}]
}
```
Should be a [Mapconfig](https://github.com/CartoDB/Windshaft/blob/0.44.1/doc/MapConfig-1.3.0.md).
#### Response
The response includes:
- **layergroupid**
The ID for that map, used to compose the URL for the tiles. The final URL is:
```html
https://{account}.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.
#### Example
<div class="code-title code-request with-result">REQUEST</div>
```bash
curl 'https://documentation.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json
```
<div class="code-title">RESPONSE</div>
```javascript
{
"layergroupid": "c01a54877c62831bb51720263f91fb33:0",
"last_updated": "1970-01-01T00:00:00.000Z",
"metadata": {
"layers": [
{
"type": "mapnik",
"meta": {}
}
]
},
"cdn_url": {
"http": "http://cdb.com",
"https": "https://cdb.com"
}
}
```
##### Retrieve resources from the layergroup
###### Mapnik tiles can be accessed using:
These tiles will get just the mapnik layers. To get individual layers see next section.
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png
```
###### Individual layers
The MapConfig specification holds the layers definition in a 0-based index. Layers can be requested individually in different formats depending on the layer type.
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
```
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
```
###### Attributes defined in `attributes` section:
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/attributes/:feature_id
```
Which returns JSON with the attributes defined, like:
```javascript
{ "c": 1, "d": 2 }
```
###### Blending and layer selection
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/: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:
- `all` alias
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
```
- Filter by layer index
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
```
Some notes about filtering:
- Invalid index values or out of bounds indexes will end in `Invalid layer filtering` errors.
- Once a mapnik layer is selected, all mapnik layers will get blended. As this may change in the future **it is
recommended** to always select all mapnik layers if you want to select at least one so you will get a consistent
behavior in the future.
- Ordering is not considered. So right now filtering layers 0,3,4 is the very same thing as filtering 3,4,0. As this
may change in the future **it is recommended** to always select the layers in ascending order so you will get a
consistent behavior in the future.
### Create JSONP
The JSONP endpoint is provided in order to allow web browsers access which don't support CORS.
#### Definition
<div class="code-title notitle code-request"></div>
```bash
GET /api/v1/map?callback=method
```
#### Params
- **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.
#### Example
<div class="code-title code-request with-result">REQUEST</div>
```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"
```
<div class="code-title">RESPONSE</div>
```javascript
callback({
layergroupid: "d9034c133262dfb90285cea26c5c7ad7:0",
cdn_url: {
"http": "http://cdb.com",
"https": "https://cdb.com"
},
last_updated: "1970-01-01T00:00:00.000Z"
})
```
### 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.
## 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. Two other big differences are: you can create named maps from private data and that users without an API Key can see them even though they are from that private data.
The main two differences compared to anonymous maps are:
- **auth layer**
This allows you to control who is able to see the map based on a token auth
- **templates**
Since the MapConfig is static it can contain some variables so the client can modify the map's appearance using those variables.
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).
### Create
#### Definition
<div class="code-title notitle code-request"></div>
```html
POST /api/v1/map/named
```
#### Params
- **api_key** is required
<div class="code-title">template.json</div>
```javascript
{
"version": "0.0.1",
"name": "template_name",
"auth": {
"method": "token",
"valid_tokens": [
"auth_token1",
"auth_token2"
]
},
"placeholders": {
"color": {
"type": "css_color",
"default": "red"
},
"cartodb_id": {
"type": "number",
"default": 1
}
},
"layergroup": {
"version": "1.0.1",
"layers": [
{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: <%= color %>; }",
"sql": "select * from european_countries_e WHERE cartodb_id = <%= cartodb_id %>"
}
}
]
},
"view": {
"zoom": 4,
"center": {
"lng": 0,
"lat": 0
},
"bounds": {
"west": -45,
"south": -45,
"east": 45,
"north": 45
}
}
}
```
##### Arguments
- **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 (_).
- **auth**:
- **method** `"token"` or `"open"` (the default if no `"method"` is given).
- **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 documentation](https://github.com/CartoDB/Windshaft/blob/0.44.1/doc/MapConfig-1.3.0.md) 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.
- **zoom** The zoom level to use
- **center**
- **lng** The longitude to use for the center
- **lat** The latitude to use for the center
- **bounds**
- **west**: LowerCorner longitude for the bounding box, in decimal degrees (aka most western)
- **south**: LowerCorner latitude for the bounding box, in decimal degrees (aka most southern)
- **east**: UpperCorner longitude for the bounding box, in decimal degrees (aka most eastern)
- **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
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.
##### Example
```javascript
<%= 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.
#### Placeholder Types
The placeholder type will determine the kind of escaping for the associated value. Supported types are:
- **sql_literal** internal single-quotes will be sql-escaped
- **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.
When using templates, be very careful about your selections as they can give broad access to your data if they are defined losely.
<div class="code-title code-request with-result">REQUEST</div>
```html
curl -X POST \
-H 'Content-Type: application/json' \
-d @template.json \
'https://documentation.cartodb.com/api/v1/map/named?api_key=APIKEY'
```
<div class="code-title">RESPONSE</div>
```javascript
{
"template_id":"name",
}
```
### Instantiate
Instantiating a map allows you to get the information needed to fetch tiles. That temporal map is an anonymous map.
#### Definition
<div class="code-title notitle code-request"></div>
```html
POST /api/v1/map/named/:template_name
```
#### Param
- **auth_token** optional, but required when `"method"` is set to `"token"`
```javascript
// params.json
{
"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)
- **auth_token** *optional* if the named map needs auth
#### Example
You can initialize a template map by passing all of the required parameters in a POST to `/api/v1/map/named/:template_name`.
Valid credentials will be needed if required by the template.
<div class="code-title code-request with-result">REQUEST</div>
```bash
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'
```
<div class="code-title">Response</div>
```javascript
{
"layergroupid": "docs@fd2861af@c01a54877c62831bb51720263f91fb33:123456788",
"last_updated": "2013-11-14T11:20:15.000Z"
}
```
<div class="code-title">Error</div>
```javascript
{
"errors" : ["Some error string here"]
}
```
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
<div class="code-title notitle code-request"></div>
```bash
GET /api/v1/map/named/:template_name/jsonp
```
#### Params
- **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
<div class="code-title code-request with-result">REQUEST</div>
```bash
curl 'https://documentation.cartodb.com/api/v1/map/named/:template_name/jsonp?auth_token=AUTH_TOKEN&callback=callback&config=template_params_json'
```
<div class="code-title">RESPONSE</div>
```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"
})
```
### Update
#### Definition
<div class="code-title notitle code-request"></div>
```bash
PUT /api/v1/map/named/:template_name
```
#### Params
- **api_key** is required
#### Response
Same as updating a map.
#### Other Info
Updating a named map removes all the named map instances so they need to be initialized again.
#### Example
<div class="code-title code-request with-result">REQUEST</div>
```bash
curl -X PUT \
-H 'Content-Type: application/json' \
-d @template.json \
'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY'
```
<div class="code-title">RESPONSE</div>
```javascript
{
"template_id": "@template_name"
}
```
If any template has the same name, it will be updated.
If a template with the same name does NOT exist, a 400 HTTP response is generated with an error in this format:
```javascript
{
"errors" : ["error string here"]
}
```
### Delete
Delete the specified template map from the server and it disables any previously initialized versions of the map.
#### Definition
<div class="code-title notitle code-request"></div>
```bash
DELETE /api/v1/map/named/:template_name
```
#### Params
- **api_key** is required
#### Example
<div class="code-title code-request">REQUEST</div>
```bash
curl -X DELETE 'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY'
```
<div class="code-title">RESPONSE</div>
```javascript
{
"errors" : ["Some error string here"]
}
```
On success, a 204 (No Content) response will be issued. Otherwise a 4xx response with an error will be returned.
### Listing Available Templates
This allows you to get a list of all available templates.
#### Definition
<div class="code-title notitle code-request"></div>
```bash
GET /api/v1/map/named/
```
#### Params
- **api_key** is required
#### Example
<div class="code-title code-request with-result">REQUEST</div>
```bash
curl -X GET 'https://documentation.cartodb.com/api/v1/map/named?api_key=APIKEY'
```
<div class="code-title with-result">RESPONSE</div>
```javascript
{
"template_ids": ["@template_name1","@template_name2"]
}
```
<div class="code-title">ERROR</div>
```javascript
{
"errors" : ["Some error string here"]
}
```
### Getting a Specific Template
This gets the definition of a template.
#### Definition
<div class="code-title notitle code-request"></div>
```bash
GET /api/v1/map/named/:template_name
```
#### Params
- **api_key** is required
#### Example
<div class="code-title code-request with-result">REQUEST</div>
```bash
curl -X GET 'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY'
```
<div class="code-title with-result">RESPONSE</div>
```javascript
{
"template": {...} // see template.json above
}
```
<div class="code-title">ERROR</div>
```javascript
{
"errors" : ["Some error string here"]
}
```
### Use with CartoDB.js
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.
```js
var layerSource = {
user_name: '{your_user_name}',
type: 'namedmap',
named_map: {
name: '{template_name}',
layers: [{
layer_name: "layer1",
interactivity: "column1, column2, ..."
}]
}
}
cartodb.createLayer('map_dom_id',layerSource)
.addTo(map_object);
```
[CartoDB.js](http://docs.cartodb.com/cartodb-platform/cartodb-js.html) has methods for accessing your named maps.
1. [layer.setParams()](http://docs.cartodb.com/cartodb-platform/cartodb-js.html#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript
2. [layer.setAuthToken()](http://docs.cartodb.com/cartodb-platform/cartodb-js.html#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer
## 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.
### 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.
#### Zoom + center
##### Definition
<div class="code-title notitle code-request"></div>
```bash
GET /api/v1/map/static/center/:token/:z/:lat/:lng/:width/:height.:format
```
##### Params
* **:token**: the layergroupid token from the map instantiation
* **:z**: the zoom level of the map
* **:lat**: the latitude for the center of the map
* **:lng**: the longitude for the center of the 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`
* **jpg** will have a default quality of 85.
#### Bounding Box
##### Definition
<div class="code-title notitle code-request"></div>
```bash
GET /api/v1/map/static/bbox/:token/:bbox/:width/:height.:format`
```
##### Params
* **:token**: the layergroupid token from the map instantiation
* **: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`
* **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`
```
#### Named map
##### Definition
<div class="code-title notitle code-request"></div>
```bash
GET /api/v1/map/static/named/:name/:width/:height.:format
```
##### Params
* **: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`
* **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`.
####Layers
The Static Maps API allows for multiple layers of incorporation into the `MapConfig` to allow for maximum versatility in creating a static map. The examples below were used to generate the static image example in the next section, and appear in the specific order designated.
**Basemaps**
```javascript
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
]
}
},
```
By manipulating the `"urlTemplate"` custom basemaps can be used in generating static images. Supported map types for the Static Maps API are:
'http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png',
'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
**Mapnik**
```javascript
{
"type": "mapnik",
"options": {
"sql": "select null::geometry the_geom_webmercator",
"cartocss": "#layer {\n\tpolygon-fill: #FF3300;\n\tpolygon-opacity: 0;\n\tline-color: #333;\n\tline-width: 0;\n\tline-opacity: 0;\n}",
"cartocss_version": "2.2.0"
}
},
```
**CartoDB**
As described in the [Mapconfig documentation](https://github.com/CartoDB/Windshaft/blob/0.44.1/doc/MapConfig-1.3.0.md), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility.
```javascript
{
"type": "cartodb",
"options": {
"sql": "select * from park",
"cartocss": "/** simple visualization */\n\n#park{\n polygon-fill: #229A00;\n polygon-opacity: 0.7;\n line-color: #FFF;\n line-width: 0;\n line-opacity: 1;\n}",
"cartocss_version": "2.1.1"
}
},
```
Additionally, static images from Torque maps and other map layers can be used together to generate highly customizable and versatile static maps.
#### Caching
It is important to note that generated images are cached from the live data referenced with the `layergroupid token` on the specified CartoDB account. This means that if the data changes, the cached image will also change. When linking dynamically, it is important to take into consideration the state of the data and longevity of the static image to avoid broken images or changes in how the image is displayed. To obtain a static snapshot of the map as it is today and preserve the image long-term regardless of changes in data, the image must be saved and stored locally.
#### Limits
* While images can encompass an entirety of a map, the default limit for pixel range is 8192 x 8192.
* Image resolution by default is set to 72 DPI
* JPEG quality by default is 85%
* Timeout limits for generating static maps are the same across the CartoDB Editor and Platform. It is important to ensure timely processing of queries.
### Examples
After instantiating a map from a CartoDB account:
<div class="code-title code-request with-result">REQUEST</div>
```bash
GET /api/v1/map/static/center/4b615ff367e498e770e7d05e99181873:1420231989550.8699/14/40.71502926732618/-73.96039009094238/600/400.png
```
#### Response
<p class="wrap-border"><img src="https://raw.githubusercontent.com/namessanti/Pictures/master/static_api.png" alt="static-api"/></p>
#### MapConfig
For this map, the multiple layers, order, and stylings are defined by the MapConfig.
```javascript
{
"version": "1.3.0",
"layers": [
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
]
}
},
{
"type": "mapnik",
"options": {
"sql": "select null::geometry the_geom_webmercator",
"cartocss": "#layer {\n\tpolygon-fill: #FF3300;\n\tpolygon-opacity: 0;\n\tline-color: #333;\n\tline-width: 0;\n\tline-opacity: 0;\n}",
"cartocss_version": "2.2.0"
}
},
{
"type": "cartodb",
"options": {
"sql": "select * from park",
"cartocss": "/** simple visualization */\n\n#park{\n polygon-fill: #229A00;\n polygon-opacity: 0.7;\n line-color: #FFF;\n line-width: 0;\n line-opacity: 1;\n}",
"cartocss_version": "2.1.1"
}
},
{
"type": "cartodb",
"options": {
"sql": "select * from residential_zoning_2009",
"cartocss": "/** simple visualization */\n\n#residential_zoning_2009{\n polygon-fill: #c7eae5;\n polygon-opacity: 1;\n line-color: #FFF;\n line-width: 0.2;\n line-opacity: 0.5;\n}",
"cartocss_version": "2.1.1"
}
},
{
"type": "cartodb",
"options": {
"sql": "select * from nycha_developments_july2011",
"cartocss": "/** simple visualization */\n\n#nycha_developments_july2011{\n polygon-fill: #ef3b2c;\n polygon-opacity: 0.7;\n line-color: #FFF;\n line-width: 0;\n line-opacity: 1;\n}",
"cartocss_version": "2.1.1"
}
}
]
}
```
* [Quickstart](quickstart.md)
* [General Concepts](general_concepts.md)
* [Anonymous Maps](anonymous_maps.md)
* [Named Maps](named_maps.md)
* [Static Maps API](static_maps_api.md)

View File

@@ -17,8 +17,8 @@ Windshaft-CartoDB adds the following attributes in the response object
```json
{
http: 'http://cdn_url.com/'
https: 'https://secure.cdn_url.com/'
"http": "http://cdn_url.com/",
"https": "https://secure.cdn_url.com/"
}
```

192
docs/anonymous_maps.md Normal file
View File

@@ -0,0 +1,192 @@
# 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)
## Instantiate
#### Definition
```html
POST /api/v1/map
```
#### Params
```javascript
{
"version": "1.3.0",
"layers": [{
"type": "mapnik",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
"sql": "select * from european_countries_e",
"interactivity": ["cartodb_id", "iso3"]
}
}]
}
```
See [MapConfig File Formats](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/) for details.
#### Response
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`
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.
### Example
#### Call
```bash
curl 'https://documentation.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json
```
#### Response
```javascript
{
"layergroupid": "c01a54877c62831bb51720263f91fb33:0",
"last_updated": "1970-01-01T00:00:00.000Z",
"metadata": {
"layers": [
{
"type": "mapnik",
"meta": {}
}
]
},
"cdn_url": {
"http": "http://cdb.com",
"https": "https://cdb.com"
}
}
```
### Retrieve resources from the layergroup
#### Mapnik tiles can be accessed using
These tiles will get just the mapnik layers. To get individual layers see next section.
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png
```
#### Individual layers
The MapConfig specification holds the layers definition in a 0-based index. Layers can be requested individually in different formats depending on the layer type.
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
```
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
```
#### Attributes defined in `attributes` section
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/attributes/:feature_id
```
Which returns JSON with the attributes defined, like:
```javascript
{ "c": 1, "d": 2 }
```
#### Blending and layer selection
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/: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:
- `all` alias
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
```
- Filter by layer index
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
```
Some notes about filtering:
- Invalid index values or out of bounds indexes will end in `Invalid layer filtering` errors.
- Once a mapnik layer is selected, all mapnik layers will get blended. As this may change in the future **it is
recommended** to always select all mapnik layers if you want to select at least one so you will get a consistent
behavior in the future.
- Ordering is not considered. So right now filtering layers 0,3,4 is the very same thing as filtering 3,4,0. As this
may change in the future **it is recommended** to always select the layers in ascending order so you will get a
consistent behavior in the future.
## Create JSONP
The JSONP endpoint is provided in order to allow web browsers access which don't support CORS.
#### Definition
```bash
GET /api/v1/map?callback=method
```
#### Params
Param | Description
--- | ---
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.
### Example
#### 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"
```
#### Response
```javascript
callback({
layergroupid: "d9034c133262dfb90285cea26c5c7ad7:0",
cdn_url: {
"http": "http://cdb.com",
"https": "https://cdb.com"
},
last_updated: "1970-01-01T00:00:00.000Z"
})
```
## 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.

27
docs/general_concepts.md Normal file
View File

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

472
docs/named_maps.md Normal file
View File

@@ -0,0 +1,472 @@
# 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.
The main two differences compared to anonymous maps are:
- **auth layer**
This allows you to control who is able to see the map based on a token auth
- **templates**
Since the MapConfig is static it can contain some variables so the client can modify the map's appearance using those variables.
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).
**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.
## Create
#### Definition
```html
POST /api/v1/map/named
```
#### Params
Params | Description
--- | ---
api_key | is required
#### template.json
```javascript
{
"version": "0.0.1",
"name": "template_name",
"auth": {
"method": "token",
"valid_tokens": [
"auth_token1",
"auth_token2"
]
},
"placeholders": {
"color": {
"type": "css_color",
"default": "red"
},
"cartodb_id": {
"type": "number",
"default": 1
}
},
"layergroup": {
"version": "1.0.1",
"layers": [
{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: <%= color %>; }",
"sql": "select * from european_countries_e WHERE cartodb_id = <%= cartodb_id %>"
}
}
]
},
"view": {
"zoom": 4,
"center": {
"lng": 0,
"lat": 0
},
"bounds": {
"west": -45,
"south": -45,
"east": 45,
"north": 45
}
}
}
```
#### Arguments
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 (_).
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;_ zoom | The zoom level to use
&#124;_ center |
--- | ---
&#124;_ &#124;_ lng | The longitude to use for the center
&#124;_ &#124;_ lat | The latitude to use for the center
&#124;_ bounds |
--- | ---
&#124;_ &#124;_ west | LowerCorner longitude for the bounding box, in decimal degrees (aka most western)
&#124;_ &#124;_ south | LowerCorner latitude for the bounding box, in decimal degrees (aka most southern)
&#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
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.
#### Example
```javascript
<%= 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.
### Placeholder Types
The placeholder type will determine the kind of escaping for the associated value. Supported types are:
Types | Description
--- | ---
sql_literal | internal single-quotes will be sql-escaped
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.
When using templates, be very careful about your selections as they can give broad access to your data if they are defined losely.
#### Call
```html
curl -X POST \
-H 'Content-Type: application/json' \
-d @template.json \
'https://documentation.cartodb.com/api/v1/map/named?api_key=APIKEY'
```
#### Response
```javascript
{
"template_id":"name",
}
```
## Instantiate
Instantiating a map allows you to get the information needed to fetch tiles. That temporal map is an anonymous map.
#### Definition
```html
POST /api/v1/map/named/:template_name
```
#### Param
Param | Description
--- | ---
auth_token | optional, but required when `"method"` is set to `"token"`
```javascript
// params.json
{
"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)
- **auth_token** *optional* if the named map needs auth
### Example
You can initialize a template map by passing all of the required parameters in a POST to `/api/v1/map/named/:template_name`.
Valid credentials will be needed if required by the template.
#### Call
```bash
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'
```
#### Response
```javascript
{
"layergroupid": "docs@fd2861af@c01a54877c62831bb51720263f91fb33:123456788",
"last_updated": "2013-11-14T11:20:15.000Z"
}
```
#### Error
```javascript
{
"errors" : ["Some error string here"]
}
```
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"
})
```
## Update
#### Definition
```bash
PUT /api/v1/map/named/:template_name
```
#### Params
Param | Description
--- | ---
api_key | is required
#### Response
Same as updating a map.
### Other Info
Updating a named map removes all the named map instances so they need to be initialized again.
### Example
#### Call
```bash
curl -X PUT \
-H 'Content-Type: application/json' \
-d @template.json \
'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY'
```
#### Response
```javascript
{
"template_id": "@template_name"
}
```
If any template has the same name, it will be updated.
If a template with the same name does NOT exist, a 400 HTTP response is generated with an error in this format:
```javascript
{
"errors" : ["error string here"]
}
```
## Delete
Delete the specified template map from the server and it disables any previously initialized versions of the map.
#### Definition
```bash
DELETE /api/v1/map/named/:template_name
```
#### Params
Param | Description
--- | ---
api_key | is required
### Example
#### Call
```bash
curl -X DELETE 'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY'
```
#### Response
```javascript
{
"errors" : ["Some error string here"]
}
```
On success, a 204 (No Content) response will be issued. Otherwise a 4xx response with an error will be returned.
## Listing Available Templates
This allows you to get a list of all available templates.
#### Definition
```bash
GET /api/v1/map/named/
```
#### Params
Param | Description
--- | ---
api_key | is required
### Example
#### Call
```bash
curl -X GET 'https://documentation.cartodb.com/api/v1/map/named?api_key=APIKEY'
```
#### Response
```javascript
{
"template_ids": ["@template_name1","@template_name2"]
}
```
#### Error
```javascript
{
"errors" : ["Some error string here"]
}
```
## Getting a Specific Template
This gets the definition of a template.
#### Definition
```bash
GET /api/v1/map/named/:template_name
```
#### Params
Param | Description
--- | ---
api_key | is required
### Example
#### Call
```bash
curl -X GET 'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY'
```
#### Response
```javascript
{
"template": {...} // see template.json above
}
```
#### Error
```javascript
{
"errors" : ["Some error string here"]
}
```
## 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.
```js
var layerSource = {
user_name: '{your_user_name}',
type: 'namedmap',
named_map: {
name: '{template_name}',
layers: [{
layer_name: "layer1",
interactivity: "column1, column2, ..."
}]
}
}
cartodb.createLayer('map_dom_id',layerSource)
.addTo(map_object);
```
[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
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
- [Named map selectors with interaction](http://bl.ocks.org/ohasselblad/515a8af1f99d5e690484)
- [Named map with interactivity and config file used to create it](http://bl.ocks.org/ohasselblad/d1a45b8ff5e7bd90cd68)
- [Toggling sublayers in a Named Map](http://bl.ocks.org/ohasselblad/c1a0f4913610eec53cd3)

100
docs/quickstart.md Normal file
View File

@@ -0,0 +1,100 @@
# Quickstart
## Anonymous maps
Here is an example of how to create an anonymous map with JavaScript:
```javascript
var mapconfig = {
"version": "1.3.1",
"layers": [{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
"sql": "select * from european_countries_e"
}
}]
}
$.ajax({
crossOrigin: true,
type: 'POST',
dataType: 'json',
contentType: 'application/json',
url: 'https://documentation.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'
console.log(templateUrl);
}
})
```
## Named maps
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
{
"version": "0.0.1",
"name": "test",
"auth": {
"method": "open"
},
"layergroup": {
"layers": [{
"type": "mapnik",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
"sql": "select * from european_countries_e"
}
}]
}
}
```
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:
#### Call
```bash
curl 'https://{account}.cartodb.com/api/v1/map/named?api_key=APIKEY' -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.
#### Call
```bash
curl -X POST 'https://{account}.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`.
Note: all `layers` in `metadata` will always have a `type` string and a `meta` dictionary with the key/value pairs.
#### Response
```javascript
{
"layergroupid": "c01a54877c62831bb51720263f91fb33:0",
"last_updated": "1970-01-01T00:00:00.000Z",
"metadata": {
"layers": [
{
"type": "mapnik",
"meta": {}
}
]
}
}
```
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
```

224
docs/static_maps_api.md Normal file
View File

@@ -0,0 +1,224 @@
# 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.
## 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.
### Zoom + center
#### Definition
```bash
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
:format | the format for the image, supported types: `png`, `jpg`
--- | ---
&#124;_ jpg | will have a default quality of 85.
### Bounding Box
#### Definition
```bash
GET /api/v1/map/static/bbox/:token/:bbox/:width/:height.:format`
```
#### Params
Param | Description
--- | ---
:token | the layergroupid token from the map instantiation
: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:
--- | ---
&#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`
```
### Named map
#### Definition
```bash
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
: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`.
#### Layers
The Static Maps API allows for multiple layers of incorporation into the `MapConfig` to allow for maximum versatility in creating a static map. The examples below were used to generate the static image example in the next section, and appear in the specific order designated.
**Basemaps**
```javascript
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
]
}
}
```
By manipulating the `"urlTemplate"` custom basemaps can be used in generating static images. Supported map types for the Static Maps API are:
```javascript
'http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png',
'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
```
**Mapnik**
```javascript
{
"type": "mapnik",
"options": {
"sql": "select null::geometry the_geom_webmercator",
"cartocss": "#layer {\n\tpolygon-fill: #FF3300;\n\tpolygon-opacity: 0;\n\tline-color: #333;\n\tline-width: 0;\n\tline-opacity: 0;\n}",
"cartocss_version": "2.2.0"
}
},
```
**CartoDB**
As described in the [MapConfig File Format](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility.
```javascript
{
"type": "cartodb",
"options": {
"sql": "select * from park",
"cartocss": "/** simple visualization */\n\n#park{\n polygon-fill: #229A00;\n polygon-opacity: 0.7;\n line-color: #FFF;\n line-width: 0;\n line-opacity: 1;\n}",
"cartocss_version": "2.1.1"
}
}
```
Additionally, static images from Torque maps and other map layers can be used together to generate highly customizable and versatile static maps.
### Caching
It is important to note that generated images are cached from the live data referenced with the `layergroupid token` on the specified CartoDB account. This means that if the data changes, the cached image will also change. When linking dynamically, it is important to take into consideration the state of the data and longevity of the static image to avoid broken images or changes in how the image is displayed. To obtain a static snapshot of the map as it is today and preserve the image long-term regardless of changes in data, the image must be saved and stored locally.
### Limits
* While images can encompass an entirety of a map, the default limit for pixel range is 8192 x 8192.
* Image resolution by default is set to 72 DPI
* JPEG quality by default is 85%
* Timeout limits for generating static maps are the same across the CartoDB Editor and Platform. It is important to ensure timely processing of queries.
## Examples
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
```
#### Response
<p class="wrap-border"><img src="https://raw.githubusercontent.com/namessanti/Pictures/master/static_api.png" alt="static-api"/></p>
### MapConfig
For this map, the multiple layers, order, and stylings are defined by the MapConfig.
```javascript
{
"version": "1.3.0",
"layers": [
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
]
}
},
{
"type": "mapnik",
"options": {
"sql": "select null::geometry the_geom_webmercator",
"cartocss": "#layer {\n\tpolygon-fill: #FF3300;\n\tpolygon-opacity: 0;\n\tline-color: #333;\n\tline-width: 0;\n\tline-opacity: 0;\n}",
"cartocss_version": "2.2.0"
}
},
{
"type": "cartodb",
"options": {
"sql": "select * from park",
"cartocss": "/** simple visualization */\n\n#park{\n polygon-fill: #229A00;\n polygon-opacity: 0.7;\n line-color: #FFF;\n line-width: 0;\n line-opacity: 1;\n}",
"cartocss_version": "2.1.1"
}
},
{
"type": "cartodb",
"options": {
"sql": "select * from residential_zoning_2009",
"cartocss": "/** simple visualization */\n\n#residential_zoning_2009{\n polygon-fill: #c7eae5;\n polygon-opacity: 1;\n line-color: #FFF;\n line-width: 0.2;\n line-opacity: 0.5;\n}",
"cartocss_version": "2.1.1"
}
},
{
"type": "cartodb",
"options": {
"sql": "select * from nycha_developments_july2011",
"cartocss": "/** simple visualization */\n\n#nycha_developments_july2011{\n polygon-fill: #ef3b2c;\n polygon-opacity: 0.7;\n line-color: #FFF;\n line-width: 0;\n line-opacity: 1;\n}",
"cartocss_version": "2.1.1"
}
}
]
}
```

View File

@@ -13,26 +13,22 @@ module.exports = QueryTablesApi;
QueryTablesApi.prototype.getAffectedTablesInQuery = function (username, sql, callback) {
var query = 'SELECT CDB_QueryTablesText($windshaft$' + prepareSql(sql) + '$windshaft$)';
this.pgQueryRunner.run(username, query, handleAffectedTablesInQueryRows, callback);
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);
});
};
function handleAffectedTablesInQueryRows(err, rows, callback) {
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;
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',
@@ -42,28 +38,26 @@ QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (usernam
'WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])'
].join(' ');
this.pgQueryRunner.run(username, query, handleAffectedTablesAndLastUpdatedTimeRows, callback);
};
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;
}
function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) {
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];
var result = rows[0];
// This is an Array, so no need to split into parts
var tableNames = result.tablenames;
// This is an Array, so no need to split into parts
var tableNames = result.tablenames;
var lastUpdatedTime = result.max || 0;
var lastUpdatedTime = result.max || 0;
callback(null, {
affectedTables: tableNames,
lastUpdatedTime: lastUpdatedTime * 1000
callback(null, {
affectedTables: tableNames,
lastUpdatedTime: lastUpdatedTime * 1000
});
});
}
};
QueryTablesApi.prototype.getLastUpdatedTime = function (username, tableNames, callback) {
if (!Array.isArray(tableNames) || tableNames.length === 0) {
@@ -77,23 +71,21 @@ QueryTablesApi.prototype.getLastUpdatedTime = function (username, tableNames, ca
'])'
].join(' ');
this.pgQueryRunner.run(username, query, handleLastUpdatedTimeRows, callback);
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 handleLastUpdatedTimeRows(err, rows, callback) {
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)')

View File

@@ -35,19 +35,17 @@ TablesExtentApi.prototype.getBounds = function (username, tableNames, callback)
"FROM ext"
].join(' ');
this.pgQueryRunner.run(username, query, handleBoundsResult, callback);
this.pgQueryRunner.run(username, query, function handleBoundsResult (err, rows) {
if (err) {
var msg = err.message ? err.message : err;
return callback(new Error('could not fetch source tables: ' + msg));
}
var result = null;
if (rows.length > 0) {
result = {
bounds: rows[0]
};
}
callback(null, result);
});
};
function handleBoundsResult(err, rows, callback) {
if (err) {
var msg = err.message ? err.message : err;
return callback(new Error('could not fetch source tables: ' + msg));
}
var result = null;
if (rows.length > 0) {
result = {
bounds: rows[0]
};
}
callback(null, result);
}

View File

@@ -8,8 +8,14 @@ function PgQueryRunner(pgConnection) {
module.exports = PgQueryRunner;
PgQueryRunner.prototype.run = function(username, query, queryHandler, callback) {
/**
* Runs `query` with `username`'s PostgreSQL role, callback receives error and rows array.
*
* @param {String} username
* @param {String} query
* @param {Function} callback function({Error}, {Array}) second argument is guaranteed to be an array
*/
PgQueryRunner.prototype.run = function(username, query, callback) {
var self = this;
var params = {};
@@ -33,8 +39,7 @@ PgQueryRunner.prototype.run = function(username, query, queryHandler, callback)
});
psql.query(query, function(err, resultSet) {
resultSet = resultSet || {};
var rows = resultSet.rows || [];
queryHandler(err, rows, callback);
return callback(err, resultSet.rows || []);
});
}
);

View File

@@ -21,7 +21,7 @@ var util = require('util');
// @param opts TemplateMap options. Supported elements:
// 'max_user_templates' limit on the number of per-user
//
//
//
function TemplateMaps(redis_pool, opts) {
if (!(this instanceof TemplateMaps)) {
return new TemplateMaps();
@@ -41,7 +41,7 @@ function TemplateMaps(redis_pool, opts) {
//
// Map templates are owned by a user that specifies access permissions
// for their instances.
//
//
// We have the following datastores:
//
// 1. User templates: set of per-user map templates
@@ -197,7 +197,7 @@ function templateDefaults(template) {
// @param template layergroup template, see
// http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps#template-format
//
// @param callback function(err, tpl_id)
// @param callback function(err, tpl_id)
// Return template identifier (only valid for given user)
//
o.addTemplate = function(owner, template, callback) {
@@ -224,8 +224,10 @@ o.addTemplate = function(owner, template, callback) {
function installTemplateIfDoesNotExist(err, numberOfTemplates) {
assert.ifError(err);
if ( limit && numberOfTemplates >= limit ) {
throw new Error("User '" + owner + "' reached limit on number of templates " +
"("+ numberOfTemplates + "/" + limit + ")");
var limitReachedError = new Error("User '" + owner + "' reached limit on number of templates (" +
numberOfTemplates + "/" + limit + ")");
limitReachedError.http_status = 409;
throw limitReachedError;
}
self._redisCmd('HSETNX', [ userTemplatesKey, templateName, JSON.stringify(template) ], this);
},
@@ -294,7 +296,7 @@ o.delTemplate = function(owner, tpl_id, callback) {
// http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps#template-format
//
// @param callback function(err)
//
//
o.updTemplate = function(owner, tpl_id, template, callback) {
var self = this;
@@ -353,7 +355,7 @@ o.updTemplate = function(owner, tpl_id, template, callback) {
//
// @param callback function(err, tpl_id_list)
// Returns a list of template identifiers
//
//
o.listTemplates = function(owner, callback) {
this._redisCmd('HKEYS', [ this.key_usr_tpl({owner:owner}) ], callback);
};
@@ -420,7 +422,7 @@ o.isAuthorized = function(template, authTokens) {
// Only the ones found in the template's placeholders object
// will be used, with missing ones taking default values.
//
// @returns a layergroup configuration
// @returns a layergroup configuration
//
// @throws Error on malformed template or parameter
//
@@ -429,7 +431,7 @@ var _reNumber = /^([-+]?[\d\.]?\d+([eE][+-]?\d+)?)$/,
_reCSSColorVal = /^#[0-9a-fA-F]{3,6}$/;
function _replaceVars (str, params) {
//return _.template(str, params); // lazy way, possibly dangerous
//return _.template(str, params); // lazy way, possibly dangerous
// Construct regular expressions for each param
Object.keys(params).forEach(function(k) {
str = str.replace(new RegExp("<%=\\s*" + k + "\\s*%>", "g"), params[k]);

View File

@@ -13,7 +13,17 @@ var REQUEST_QUERY_PARAMS_WHITELIST = [
'map_key',
'api_key',
'auth_token',
'callback'
'callback',
// widgets & filters
'filters', // json
'own_filter', // 0, 1
'bbox', // w,s,e,n
'bins', // number
'start', // number
'end', // number
'column_type', // string
// widgets search
'q'
];
function BaseController(authApi, pgConnection) {
@@ -191,7 +201,7 @@ BaseController.prototype.sendError = function(req, res, err, label) {
var statusCode = findStatusCode(err);
debug('[%s ERROR] -- %d: %s', label, statusCode, err);
debug('[%s ERROR] -- %d: %s, %s', label, statusCode, err, err.stack);
// If a callback was requested, force status to 200
if (req.query && req.query.callback) {

View File

@@ -17,6 +17,7 @@ var TablesCacheEntry = require('../cache/model/database_tables_entry');
* @param {TileBackend} tileBackend
* @param {PreviewBackend} previewBackend
* @param {AttributesBackend} attributesBackend
* @param {WidgetBackend} widgetBackend
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsApi} userLimitsApi
* @param {QueryTablesApi} queryTablesApi
@@ -24,13 +25,14 @@ var TablesCacheEntry = require('../cache/model/database_tables_entry');
* @constructor
*/
function LayergroupController(authApi, pgConnection, mapStore, tileBackend, previewBackend, attributesBackend,
surrogateKeysCache, userLimitsApi, queryTablesApi, layergroupAffectedTables) {
widgetBackend, surrogateKeysCache, userLimitsApi, queryTablesApi, layergroupAffectedTables) {
BaseController.call(this, authApi, pgConnection);
this.mapStore = mapStore;
this.tileBackend = tileBackend;
this.previewBackend = previewBackend;
this.attributesBackend = attributesBackend;
this.widgetBackend = widgetBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.userLimitsApi = userLimitsApi;
this.queryTablesApi = queryTablesApi;
@@ -66,6 +68,72 @@ LayergroupController.prototype.register = function(app) {
app.get(app.base_url_mapconfig +
'/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format', cors(), userMiddleware,
this.bbox.bind(this));
// Undocumented/non-supported API endpoint methods.
// Use at your own peril.
app.get(app.base_url_mapconfig +
'/:token/:layer/widget/:widgetName', cors(), userMiddleware,
this.widget.bind(this));
app.get(app.base_url_mapconfig +
'/:token/:layer/widget/:widgetName/search', cors(), userMiddleware,
this.widgetSearch.bind(this));
};
LayergroupController.prototype.widget = function(req, res) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveList(err) {
assert.ifError(err);
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
);
self.widgetBackend.getWidget(mapConfigProvider, req.params, this);
},
function finish(err, tile, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET WIDGET');
} else {
self.sendResponse(req, res, tile, 200);
}
}
);
};
LayergroupController.prototype.widgetSearch = function(req, res) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveList(err) {
assert.ifError(err);
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
);
self.widgetBackend.search(mapConfigProvider, req.params, this);
},
function finish(err, tile, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET WIDGET');
} else {
self.sendResponse(req, res, tile, 200);
}
}
);
};
LayergroupController.prototype.attributes = function(req, res) {

View File

@@ -306,6 +306,9 @@ MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, la
layergroup.layergroupid = layergroup.layergroupid + ':' + result.lastUpdatedTime;
layergroup.last_updated = new Date(result.lastUpdatedTime).toISOString();
// TODO this should take into account several URL patterns
addWidgetsUrl(username, layergroup);
if (req.method === 'GET') {
var tableCacheEntry = new TablesCacheEntry(dbName, result.affectedTables);
var ttl = global.environment.varnish.layergroupTtl || 86400;
@@ -324,3 +327,34 @@ MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, la
}
);
};
function addWidgetsUrl(username, layergroup) {
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers)) {
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
if (layer.widgets) {
Object.keys(layer.widgets).forEach(function(widgetName) {
var resource = layergroup.layergroupid + '/' + layerIndex + '/widget/' + widgetName;
layer.widgets[widgetName].url = getUrls(username, resource);
});
}
return layer;
});
}
}
function getUrls(username, resource) {
var cdnUrl = global.environment.serverMetadata && global.environment.serverMetadata.cdn_url;
if (cdnUrl) {
return {
http: 'http://' + cdnUrl.http + '/' + username + '/api/v1/map/' + resource,
https: 'https://' + cdnUrl.https + '/' + username + '/api/v1/map/' + resource
};
} else {
var port = global.environment.port;
return {
http: 'http://' + username + '.' + 'localhost.lan:' + port + '/api/v1/map/' + resource
};
}
}

View File

@@ -36,7 +36,7 @@ module.exports = function(serverOptions) {
global.statsClient = StatsClient.getInstance(serverOptions.statsd);
var redisPool = new RedisPool(_.defaults(global.environment.redis, {
name: 'windshaft:server',
name: 'windshaft-server',
unwatchOnRelease: false,
noReadyCheck: true
}));
@@ -131,7 +131,7 @@ module.exports = function(serverOptions) {
var rendererStatsReporter = new RendererStatsReporter(rendererCache, rendererCacheOpts.statsInterval);
rendererStatsReporter.start();
var attributesBackend = new windshaft.backend.Attributes(mapStore);
var attributesBackend = new windshaft.backend.Attributes();
var previewBackend = new windshaft.backend.Preview(rendererCache);
var tileBackend = new windshaft.backend.Tile(rendererCache);
var mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend);
@@ -161,6 +161,7 @@ module.exports = function(serverOptions) {
tileBackend,
previewBackend,
attributesBackend,
new windshaft.backend.Widget(),
surrogateKeysCache,
userLimitsApi,
queryTablesApi,

View File

@@ -61,7 +61,16 @@ module.exports = {
statsInterval: rendererConfig.statsInterval
},
renderer: {
mapnik: rendererConfig.mapnik,
mapnik: _.defaults(rendererConfig.mapnik, {
geojson: {
dbPoolParams: {
size: 16,
idleTimeout: 3000,
reapInterval: 1000
},
clipByBox2d: false,
}
}),
torque: rendererConfig.torque,
http: rendererConfig.http
},

2436
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,13 @@
{
"private": true,
"name": "windshaft-cartodb",
"version": "2.18.0",
"version": "2.22.0",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
],
"url": "https://github.com/CartoDB/Windshaft-cartodb",
"licenses": [{
"type": "BSD",
"url": "https://github.com/CartoDB/Windshaft-cartodb/blob/master/LICENCE"
}],
"license": "BSD-3-Clause",
"repository": {
"type": "git",
"url": "git://github.com/CartoDB/Windshaft-cartodb.git"
@@ -29,7 +26,7 @@
"node-statsd": "~0.0.7",
"underscore" : "~1.6.0",
"dot": "~1.0.2",
"windshaft": "~1.5.0",
"windshaft": "1.8.3",
"step": "~0.0.6",
"queue-async": "~1.0.7",
"request": "~2.62.0",
@@ -56,6 +53,6 @@
},
"engines": {
"node": ">=0.8 <0.11",
"npm": ">=1.2.1"
"npm": ">=2.14.16"
}
}

View File

@@ -111,6 +111,6 @@ describe('health checks', function () {
done();
});
});
});
});

View File

@@ -52,14 +52,14 @@ describe(suiteName, function() {
{ options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' +
' from test_table limit 2',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss_version: '2.0.1',
interactivity: 'cartodb_id'
} },
{ options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, -5e6, 0) as the_geom_webmercator' +
' from test_table limit 2 offset 2',
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss_version: '2.0.2',
interactivity: 'cartodb_id'
} }
@@ -104,7 +104,7 @@ describe(suiteName, function() {
// Check X-Cache-Channel
cc = res.headers['x-cache-channel'];
assert.ok(cc);
assert.ok(cc);
var dbname = test_database;
assert.equal(cc.substring(0, dbname.length), dbname);
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
@@ -198,7 +198,7 @@ describe(suiteName, function() {
{ options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' +
' from test_table limit 2',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss_version: '2.0.1'
} }
]
@@ -211,7 +211,7 @@ describe(suiteName, function() {
assert.response(server, {
url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {host: 'localhost'}
headers: {host: 'localhost'}
}, {}, function(res, err) { next(err, res); });
},
function do_check_create(err, res) {
@@ -245,7 +245,7 @@ describe(suiteName, function() {
]
};
var expected_token;
var expected_token;
step(
function do_create_get()
{
@@ -253,7 +253,7 @@ describe(suiteName, function() {
assert.response(server, {
url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {host: 'localhost'}
headers: {host: 'localhost'}
}, {}, function(res, err) { next(err, res); });
},
function do_check_create(err, res) {
@@ -329,7 +329,7 @@ describe(suiteName, function() {
{ options: {
sql: 'select 1 as cartodb_id, ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!))' +
' as the_geom_webmercator from test_table limit 1',
cartocss: '#layer { polygon-fill:red; }',
cartocss: '#layer { polygon-fill:red; }',
cartocss_version: '2.0.1',
interactivity: 'cartodb_id'
} }
@@ -413,7 +413,7 @@ describe(suiteName, function() {
// Check X-Cache-Channel
var cc = res.headers['x-cache-channel'];
assert.ok(cc);
assert.ok(cc);
var dbname = test_database;
assert.equal(cc.substring(0, dbname.length), dbname);
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
@@ -485,8 +485,8 @@ describe(suiteName, function() {
{ options: {
sql: 'select 1 as cartodb_id, !pixel_height! as h,' +
' ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
cartocss: '#layer { polygon-fill:red; }',
cartocss_version: '2.0.1'
cartocss: '#layer { polygon-fill:red; }',
cartocss_version: '2.0.1'
} }
]
};
@@ -578,8 +578,8 @@ describe(suiteName, function() {
{ options: {
sql: 'select 1 as cartodb_id, !pixel_height! as h' +
'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
cartocss: '#layer { polygon-fit:red; }',
cartocss_version: '2.0.1'
cartocss: '#layer { polygon-fit:red; }',
cartocss_version: '2.0.1'
} }
]
};
@@ -606,8 +606,8 @@ describe(suiteName, function() {
layers: [
{ options: {
sql: 'select bogus(0,0) as the_geom_webmercator',
cartocss: '#layer { polygon-fill:red; }',
cartocss_version: '2.0.1'
cartocss: '#layer { polygon-fill:red; }',
cartocss_version: '2.0.1'
} }
]
};
@@ -633,13 +633,13 @@ describe(suiteName, function() {
layers: [
{ options: {
sql: 'select * from test_table_private_1 where cartodb_id=1',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss_version: '2.1.0',
interactivity: 'cartodb_id'
} },
{ options: {
sql: 'select * from test_table_private_1 where cartodb_id=2',
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss_version: '2.1.0',
interactivity: 'cartodb_id'
} }
@@ -684,7 +684,7 @@ describe(suiteName, function() {
// Check X-Cache-Channel
var cc = res.headers['x-cache-channel'];
assert.ok(cc);
assert.ok(cc);
var dbname = test_database;
assert.equal(cc.substring(0, dbname.length), dbname);
next(err);
@@ -780,7 +780,7 @@ describe(suiteName, function() {
layers: [
{ options: {
sql: 'select * from test_table where cartodb_id=1',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss_version: '2.1.0',
interactivity: 'cartodb_id'
} }
@@ -830,7 +830,7 @@ describe(suiteName, function() {
// Check X-Cache-Channel
var cc = res.headers['x-cache-channel'];
assert.ok(cc, "Missing X-Cache-Channel");
assert.ok(cc, "Missing X-Cache-Channel");
var dbname = test_database;
assert.equal(cc.substring(0, dbname.length), dbname);
return null;
@@ -965,7 +965,7 @@ describe(suiteName, function() {
layers: [
{ options: {
sql: "select 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator",
cartocss: '#layer { point-transform:"scale(20)"; }',
cartocss: '#layer { point-transform:"scale(20)"; }',
cartocss_version: '2.0.1'
} }
]
@@ -1031,7 +1031,7 @@ describe(suiteName, function() {
layers: [
{ options: {
sql: "select * from test_table_private_1 LIMIT 0",
cartocss: '#layer { marker-fill:red; }',
cartocss: '#layer { marker-fill:red; }',
cartocss_version: '2.0.1'
} }
]
@@ -1105,7 +1105,7 @@ describe(suiteName, function() {
layers: [
{ options: {
sql: sql,
cartocss: '#layer { marker-fill:red; }',
cartocss: '#layer { marker-fill:red; }',
cartocss_version: '2.0.1'
} }
]
@@ -1194,7 +1194,7 @@ describe(suiteName, function() {
layers: [
{ options: {
sql: "select *, 'SQLAPINOANSWER' from test_table",
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss_version: '2.1.0'
} }
]
@@ -1318,7 +1318,7 @@ describe(suiteName, function() {
}
);
});
});
});

View File

@@ -310,8 +310,8 @@ describe('tests from old api translated to multilayer', function() {
it("creates layergroup fails when postgresql queries fail to figure affected tables in query", function(done) {
var runQueryFn = PgQueryRunner.prototype.run;
PgQueryRunner.prototype.run = function(username, query, queryHandler, callback) {
return queryHandler(new Error('fake error message'), [], callback);
PgQueryRunner.prototype.run = function(username, query, callback) {
return callback(new Error('fake error message'), []);
};
var layergroup = singleLayergroupConfig('select * from gadm4', '#gadm4 { marker-fill: red; }');
@@ -365,8 +365,8 @@ describe('tests from old api translated to multilayer', function() {
keysToDelete['user:localhost:mapviews:global'] = 5;
var runQueryFn = PgQueryRunner.prototype.run;
PgQueryRunner.prototype.run = function(username, query, queryHandler, callback) {
return queryHandler(new Error('failed to query database for affected tables'), [], callback);
PgQueryRunner.prototype.run = function(username, query, callback) {
return callback(new Error('failed to query database for affected tables'), []);
};
// reset internal cacheChannel cache

View File

@@ -86,7 +86,7 @@ describe('blend http fallback', function() {
};
var filteredLayersSuite = [
['all'], // layers displayed: 2 + 4, skipping 3 as it fails
//['all'], // layers displayed: 2 + 4, skipping 3 as it fails
[0, 4],
[0, 3], // skips layer 3 as it fails
[1, 2],

View File

@@ -400,12 +400,11 @@ describe('multilayer', function() {
function jsonp_test(body) {
assert.ok(body.layergroupid);
expected_token = LayergroupToken.parse(body.layergroupid).token;
assert.deepEqual(body.metadata, {
layers: [
{ type: "mapnik", "meta":{} },
{ type: "mapnik", "meta":{} }
]
});
assert.ok(body.metadata.layers.length === 2);
assert.ok(body.metadata.layers[0].type === 'mapnik');
assert.ok(body.metadata.layers[0].meta);
assert.ok(body.metadata.layers[1].type === 'mapnik');
assert.ok(body.metadata.layers[1].meta);
didRunJsonCallback = true;
}
eval(res.body);
@@ -1278,4 +1277,3 @@ describe('multilayer', function() {
});
});

View File

@@ -64,46 +64,6 @@ describe('multilayer error cases', function() {
});
});
// See https://github.com/CartoDB/Windshaft/issues/154
it("mapnik tokens cannot be used with attributes service", function(done) {
var layergroup = {
version: '1.1.0',
layers: [
{ options: {
sql: 'select cartodb_id, 1 as n, the_geom, !bbox! as b from test_table limit 1',
cartocss: '#layer { marker-fill:red }',
cartocss_version: '2.0.1',
attributes: { id:'cartodb_id', columns:['n'] },
geom_column: 'the_geom'
} }
]
};
step(
function do_post()
{
var next = this;
assert.response(server, {
url: '/database/windshaft_test/layergroup',
method: 'POST',
headers: {'Content-Type': 'application/json; charset=utf-8' },
data: JSON.stringify(layergroup)
}, {}, function(res, err) { next(err, res); });
},
function do_check(err, res) {
assert.equal(res.statusCode, 400, res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.errors);
assert.equal(parsed.errors.length, 1);
var msg = parsed.errors[0];
assert.ok(msg.match(/Attribute service cannot be activated/), msg);
return null;
},
function finish(err) {
done(err);
}
);
});
it("layergroup with no cartocss_version", function(done) {
var layergroup = {
version: '1.0.0',

View File

@@ -374,4 +374,3 @@ describe('multilayer interactivity and layers order', function() {
chaosScenarios.forEach(testInteractivityLayersOrderScenario);
});

View File

@@ -216,8 +216,10 @@ describe('torque', function() {
assert.ok(tm0,
'No layer 0 in "torque" in metadata:' + JSON.stringify(tm));
var expectedTorqueMetadata = {"start":0,"end":86400000,"data_steps":2,"column_type":"date"};
assert.deepEqual(tm0, expectedTorqueMetadata);
assert.deepEqual(meta.layers[0].meta, expectedTorqueMetadata);
Object.keys(expectedTorqueMetadata).forEach(function(k) {
assert.equal(tm0[k], expectedTorqueMetadata[k]);
assert.equal(meta.layers[0].meta[k], expectedTorqueMetadata[k]);
});
return null;
},
function do_get_tile(err)
@@ -416,7 +418,7 @@ describe('torque', function() {
assert.response(server, {
url: '/database/windshaft_test/layergroup',
method: 'POST',
headers: {'Content-Type': 'application/json' },
headers: {'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
}, {}, function(res) { next(null, res); });
},

View File

@@ -65,7 +65,7 @@ describe('template_api', function() {
{ options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, -5e6, 0) as the_geom_webmercator' +
' from test_table limit 2 offset 2',
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss_version: '2.0.2',
interactivity: 'cartodb_id'
} }
@@ -326,8 +326,8 @@ describe('template_api', function() {
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(tmpl)
};
assert.response(server, post_request, {}, function(res) {
next(null, res);
assert.response(server, post_request, {}, function(res) {
next(null, res);
});
},
function testCORS() {
@@ -338,12 +338,12 @@ describe('template_api', function() {
headers: {host: 'localhost', 'Content-Type': 'application/json' }
},{
status: 200
}, function(res) {
}, function(res) {
var parsed = JSON.parse(res.body);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
assert.ok(_.isEqual(parsed.cdn_url, global.environment.serverMetadata.cdn_url));
next(null);
next(null);
});
},
function deleteTemplate(err) {
@@ -720,7 +720,7 @@ describe('template_api', function() {
layers: [
{ options: {
sql: "select * from test_table_private_1 LIMIT 0",
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss_version: '2.0.2',
interactivity: 'cartodb_id'
} }
@@ -848,7 +848,7 @@ describe('template_api', function() {
},
function checkTile(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200,
assert.equal(res.statusCode, 200,
'Unexpected error for authorized instance: ' + res.statusCode + ' -- ' + res.body);
assert.equal(res.headers['content-type'], "image/png");
return null;
@@ -869,7 +869,7 @@ describe('template_api', function() {
},
function checkForeignSignerError(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 403,
assert.equal(res.statusCode, 403,
'Unexpected error for authorized instance: ' + res.statusCode + ' -- ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('errors'),
@@ -1036,7 +1036,7 @@ describe('template_api', function() {
},
function checkTile_fetchOnRestart(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200,
assert.equal(res.statusCode, 200,
'Unexpected error for authorized instance: ' + res.statusCode + ' -- ' + res.body);
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
var cc = res.headers['x-cache-channel'];
@@ -1056,7 +1056,7 @@ describe('template_api', function() {
},
function checkCacheChannel(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200,
assert.equal(res.statusCode, 200,
'Unexpected error for authorized instance: ' + res.statusCode + ' -- ' + res.body);
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
var cc = res.headers['x-cache-channel'];
@@ -1112,7 +1112,7 @@ describe('template_api', function() {
layers: [
{ options: {
sql: "select * from test_table_private_1 where cartodb_id in ( 5,6 )",
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss_version: '2.0.2',
attributes: { id:'cartodb_id', columns: ['name', 'address'] }
} }
@@ -1224,7 +1224,7 @@ describe('template_api', function() {
},
function checkAttribute(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200,
assert.equal(res.statusCode, 200,
'Unexpected error for authorized getAttributes: ' + res.statusCode + ' -- ' + res.body);
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
return null;
@@ -1277,7 +1277,7 @@ describe('template_api', function() {
layers: [
{ options: {
sql: "select * from test_table_private_1 LIMIT 0",
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss_version: '2.0.2',
interactivity: 'cartodb_id'
} }
@@ -1347,7 +1347,7 @@ describe('template_api', function() {
layers: [
{ options: {
sql: "select * from test_table_private_1 LIMIT 0",
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss_version: '2.0.2',
interactivity: 'cartodb_id'
} }
@@ -1430,7 +1430,7 @@ describe('template_api', function() {
layers: [
{ options: {
sql: "select * from test_table_private_1 LIMIT 0",
cartocss: '#layer { marker-fill: <%= color %>; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill: <%= color %>; marker-allow-overlap:true; }',
cartocss_version: '2.0.2',
interactivity: 'cartodb_id'
} }
@@ -1503,8 +1503,8 @@ describe('template_api', function() {
{ options: {
sql: 'select 1 as cartodb_id, !pixel_height! as h,' +
' ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
cartocss: '#layer { polygon-fill:red; }',
cartocss_version: '2.0.1'
cartocss: '#layer { polygon-fill:red; }',
cartocss_version: '2.0.1'
} }
]
};
@@ -1516,7 +1516,7 @@ describe('template_api', function() {
};
var statskey = "user:localhost:mapviews";
var redis_stats_client = redis.createClient(global.environment.redis.port);
var template_id; // will be set on template post
var template_id; // will be set on template post
var now = strftime("%Y%m%d", new Date());
var errors = [];
step(
@@ -1621,7 +1621,7 @@ describe('template_api', function() {
layers: [
{ options: {
sql: "select * from test_table_private_1 LIMIT 0",
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss_version: '2.0.2',
interactivity: 'cartodb_id'
} }
@@ -1947,4 +1947,140 @@ describe('template_api', function() {
});
var torqueParamsScenarios = [
{
templateParams: {},
expectedTile: [{"x__uint8":125,"y__uint8":159,"vals__uint8":[2],"dates__uint16":[0]}]
},
{
templateParams: { namesFilter: "'Hawai'" },
expectedTile: [{"x__uint8":125,"y__uint8":159,"vals__uint8":[1],"dates__uint16":[0]}]
}
];
torqueParamsScenarios.forEach(function(scenario) {
it("can instantiate with torque layer and params=" + JSON.stringify(scenario.templateParams), function(done) {
var torqueParamsTemplate = {
version: '0.0.1',
name: 'acceptance_torque_params',
auth: {
method: 'open'
},
placeholders: {
namesFilter: {
"type": "sql_ident",
"default": "'Hawai', 'El Estocolmo'"
}
},
layergroup: {
version: '1.4.0',
layers: [
{
type: 'torque',
options: {
sql: "select * from test_table_private_1 where name in (<%= namesFilter %>)",
cartocss: "Map { -torque-frame-count:1; -torque-resolution:1; " +
"-torque-aggregation-function:'count(*)'; -torque-time-attribute:'cartodb_id'; }",
cartocss_version: '2.0.2'
}
}
]
}
};
var layergroupIdToDelete;
step(
function createTemplate() {
var next = this;
var createTemplateRequest = {
url: '/api/v1/map/named?api_key=1234',
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(torqueParamsTemplate)
};
assert.response(
server,
createTemplateRequest,
{
status: 200
},
function(res, err) {
next(err, res);
}
);
},
function instantiateTemplate(err, res) {
assert.ifError(err);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('template_id'), "Missing 'template_id' from response: " + res.body);
var templateId = parsed.template_id;
var instantiatePostRequest = {
url: '/api/v1/map/named/' + templateId,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(scenario.templateParams)
};
var next = this;
assert.response(
server,
instantiatePostRequest,
{
status: 200
},
function(res, err) {
return next(err, res);
}
);
},
function requestTile(err, res) {
assert.ifError(err);
var layergroupId = JSON.parse(res.body).layergroupid;
layergroupIdToDelete = LayergroupToken.parse(layergroupId).token;
var torqueTileRequest = {
url: '/api/v1/map/' + layergroupId + '/0/0/0/0.torque.json',
method: 'GET',
headers: {
host: 'localhost'
}
};
var next = this;
assert.response(
server,
torqueTileRequest,
{
status: 200
},
function(res, err) {
return next(err, res);
}
);
},
function validateTileAndFinish(err, res) {
if (err) {
return done(err);
}
keysToDelete['map_cfg|' + layergroupIdToDelete] = 0;
keysToDelete['map_tpl|localhost'] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
assert.deepEqual(
JSON.parse(res.body),
scenario.expectedTile
);
done();
}
);
});
});
});

View File

@@ -0,0 +1,74 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('aggregation widgets', function() {
var aggregationMapConfig = {
version: '1.5.0',
layers: [
{
type: 'mapnik',
options: {
sql: 'select * from populated_places_simple_reduced',
cartocss: '#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }',
cartocss_version: '2.3.0',
widgets: {
country_places_count: {
type: 'aggregation',
options: {
column: 'adm0_a3',
aggregation: 'count'
}
}
}
}
}
]
};
it("should expose an aggregation", function(done) {
var testClient = new TestClient(aggregationMapConfig);
testClient.getWidget('country_places_count', { own_filter: 0 }, function(err, res) {
if (err) {
return done(err);
}
var aggregation = JSON.parse(res.body);
assert.equal(aggregation.categories.length, 6);
assert.deepEqual(aggregation.categories[0], { value: 769, category: 'USA', agg: false });
testClient.drain(done);
});
});
describe('filters', function() {
describe('category', function () {
it("should expose a filtered aggregation", function (done) {
var params = {
filters: {
layers: [
{country_places_count: {accept: ['CAN']}}
]
}
};
var testClient = new TestClient(aggregationMapConfig);
testClient.getWidget('country_places_count', params, function (err, res) {
if (err) {
return done(err);
}
var aggregation = JSON.parse(res.body);
assert.equal(aggregation.categories.length, 1);
assert.deepEqual(aggregation.categories[0], { value: 256, category: 'CAN', agg: false });
testClient.drain(done);
});
});
});
});
});

View File

@@ -0,0 +1,120 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('histogram widgets', function() {
it("should expose layer histogram", function(done) {
var histogramMapConfig = {
version: '1.5.0',
layers: [
{
type: 'mapnik',
options: {
sql: 'select * from populated_places_simple_reduced',
cartocss: '#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }',
cartocss_version: '2.3.0',
widgets: {
pop_max: {
type: 'histogram',
options: {
column: 'pop_max'
}
}
}
}
}
]
};
var testClient = new TestClient(histogramMapConfig);
testClient.getWidget('pop_max', function(err, res) {
if (err) {
return done(err);
}
var histogram = JSON.parse(res.body);
assert.ok(histogram.bins.length);
testClient.drain(done);
});
});
describe('filters', function() {
describe('range', function() {
var histogramMapConfig = {
version: '1.5.0',
layers: [
{
type: 'mapnik',
options: {
sql: 'select * from populated_places_simple_reduced',
cartocss: '#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }',
cartocss_version: '2.3.0',
widgets: {
country_places_histogram: {
type: 'histogram',
options: {
column: 'pop_max'
}
}
}
}
}
]
};
it("should expose an histogram", function(done) {
var testClient = new TestClient(histogramMapConfig);
testClient.getWidget('country_places_histogram', { own_filter: 0 }, 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: 6497, min: 0, max: 742572, avg: 113511.16823149147 }
);
testClient.drain(done);
});
});
it("should expose a filtered histogram", function(done) {
var params = {
filters: {
layers: [
{
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

@@ -0,0 +1,51 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('list widgets', function() {
it("should expose layer list", function(done) {
var listWidgetMapConfig = {
version: '1.5.0',
layers: [
{
type: 'mapnik',
options: {
sql: 'select * from test_table',
cartocss: '#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }',
cartocss_version: '2.3.0',
widgets: {
names: {
type: 'list',
options: {
columns: ['name']
}
}
}
}
}
]
};
var testClient = new TestClient(listWidgetMapConfig);
testClient.getWidget('names', function(err, res) {
if (err) {
return done(err);
}
var expectedList = [
{name:"Hawai"},
{name:"El Estocolmo"},
{name:"El Rey del Tallarín"},
{name:"El Lacón"},
{name:"El Pico"}
];
assert.deepEqual(JSON.parse(res.body).rows, expectedList);
testClient.drain(done);
});
});
});

View File

@@ -0,0 +1,162 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('widget filters', function() {
describe('combine widget filters', function() {
var combinedWidgetsMapConfig = {
version: '1.5.0',
layers: [
{
type: 'mapnik',
options: {
sql: 'select * from populated_places_simple_reduced',
cartocss: '#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }',
cartocss_version: '2.3.0',
widgets: {
country_places_count: {
type: 'aggregation',
options: {
column: 'adm0_a3',
aggregation: 'count'
}
},
country_places_histogram: {
type: 'histogram',
options: {
column: 'pop_max'
}
}
}
}
}
]
};
it("should expose a filtered aggregation", function(done) {
var params = {
filters: {
layers: [
{
country_places_count: { reject: ['CHN'] }
}
]
}
};
var testClient = new TestClient(combinedWidgetsMapConfig);
testClient.getWidget('country_places_count', params, function(err, res) {
if (err) {
return done(err);
}
var aggregation = JSON.parse(res.body);
// first one would be CHN if reject filter wasn't applied
assert.deepEqual(aggregation.categories[0], { value: 769, category: "USA", agg: false });
// confirm 'CHN' was filtered out (reject)
assert.equal(aggregation.categories.reduce(function(sum, row) {
return sum + (row.category === 'CHN' ? 1 : 0);
}, 0), 0);
testClient.drain(done);
});
});
it("should expose a filtered aggregation", function(done) {
var params = {
filters: {
layers: [
{
country_places_count: { reject: ['CHN'] },
country_places_histogram: { min: 7000000 }
}
]
}
};
var testClient = new TestClient(combinedWidgetsMapConfig);
testClient.getWidget('country_places_count', params, function(err, res) {
if (err) {
return done(err);
}
var aggregation = JSON.parse(res.body);
// first one would be CHN if reject filter wasn't applied
assert.deepEqual(aggregation.categories[0], { value: 4, category: 'IND', agg: false });
// confirm 'CHN' was filtered out (reject)
assert.equal(aggregation.categories.reduce(function(sum, row) {
return sum + (row.category === 'CHN' ? 1 : 0);
}, 0), 0);
testClient.drain(done);
});
});
it("should allow to filter by bounding box a filtered aggregation", function(done) {
var params = {
filters: {
layers: [
{
country_places_histogram: { min: 50000 }
}
]
},
bbox: '-20,0,45,60'
};
var testClient = new TestClient(combinedWidgetsMapConfig);
testClient.getWidget('country_places_count', params, function(err, res) {
if (err) {
return done(err);
}
var aggregation = JSON.parse(res.body);
// first one would be CHN if reject filter wasn't applied
assert.deepEqual(aggregation.categories[0], { value: 96, category: "RUS", agg: false });
// confirm 'CHN' was filtered out (reject)
assert.equal(aggregation.categories.reduce(function(sum, row) {
return sum + (row.category === 'CHN' ? 1 : 0);
}, 0), 0);
testClient.drain(done);
});
});
it("should allow to filter by bounding box a filtered aggregation, with reject", function(done) {
var params = {
filters: {
layers: [
{
country_places_count: { reject: ['RUS'] },
country_places_histogram: { min: 50000 }
}
]
},
bbox: '-20,0,45,60'
};
var testClient = new TestClient(combinedWidgetsMapConfig);
testClient.getWidget('country_places_count', params, function(err, res) {
if (err) {
return done(err);
}
var aggregation = JSON.parse(res.body);
// first one would be CHN if reject filter wasn't applied
assert.deepEqual(aggregation.categories[0], { value: 77, category: "TUR", agg: false });
// confirm 'CHN' was filtered out (reject)
assert.equal(aggregation.categories.reduce(function(sum, row) {
return sum + (row.category === 'CHN' ? 1 : 0);
}, 0), 0);
testClient.drain(done);
});
});
});
});

View File

@@ -0,0 +1,46 @@
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');
describe('PgQueryRunner', function() {
var queryRunner;
before(function() {
var redisPool = new RedisPool(global.environment.redis);
var metadataBackend = cartodbRedis({pool: redisPool});
var pgConnection = new PgConnection(metadataBackend);
queryRunner = new PgQueryRunner(pgConnection);
});
it('should work for happy case', function(done) {
var query = 'select cartodb_id from test_table limit 3';
queryRunner.run('localhost', query, function(err, result) {
assert.ok(!err, err);
assert.ok(Array.isArray(result));
assert.equal(result.length, 3);
done();
});
});
it('should receive rows array even on error', function(done) {
var query = 'select __error___ from test_table';
queryRunner.run('localhost', query, function(err, result) {
assert.ok(err);
assert.ok(Array.isArray(result));
assert.equal(result.length, 0);
done();
});
});
});

View File

@@ -0,0 +1,55 @@
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,78 @@
require('../support/test_helper');
var assert = require('assert');
var redis = require('redis');
var RedisPool = require('redis-mpool');
var TemplateMaps = require('../../lib/cartodb/backends/template_maps');
describe('TemplateMaps limits', function() {
var OWNER = 'username';
var templateCounter = 0;
function templateUniqueName() {
return 'tpl_' + templateCounter++;
}
function createTemplate() {
return {
version: '0.0.1',
name: templateUniqueName(),
layergroup: {
layers: [
{
type: 'plain',
options: {
color: 'blue'
}
}
]
}
};
}
var redisClient = redis.createClient(global.environment.redis.port);
var redisPool = new RedisPool(global.environment.redis);
afterEach(function(done) {
redisClient.del('map_tpl|' + OWNER, done);
});
it('should allow to create templates when there is no limit in options', function(done) {
var templateMaps = new TemplateMaps(redisPool);
templateMaps.addTemplate(OWNER, createTemplate(), function(err, templateName, template) {
assert.ok(!err, err);
assert.ok(template);
templateMaps.addTemplate(OWNER, createTemplate(), function(err, templateName, template) {
assert.ok(!err, err);
assert.ok(template);
done();
});
});
});
it('should allow to create templates with limit in options', function(done) {
var templateMaps = new TemplateMaps(redisPool, {max_user_templates: 1});
templateMaps.addTemplate(OWNER, createTemplate(), function(err, templateName, template) {
assert.ok(!err, err);
assert.ok(template);
done();
});
});
it('should fail to create more templates than allowed by options', function(done) {
var templateMaps = new TemplateMaps(redisPool, {max_user_templates: 1});
templateMaps.addTemplate(OWNER, createTemplate(), function(err, templateName, template) {
assert.ok(!err, err);
assert.ok(template);
templateMaps.addTemplate(OWNER, createTemplate(), function(err) {
assert.ok(err);
assert.equal(err.http_status, 409);
done();
});
});
});
});

View File

@@ -7,8 +7,8 @@ module.exports = function(opts) {
var config = {
redis_pool: {
max: 10,
idleTimeoutMillis: 1,
max: 10,
idleTimeoutMillis: 1,
reapIntervalMillis: 1,
port: global.environment.redis.port
}

View File

@@ -72,15 +72,15 @@ if test x"$PREPARE_PGSQL" = xyes; then
createdb -Ttemplate_postgis -EUTF8 "${TEST_DB}" || die "Could not create test database"
cat sql/windshaft.test.sql sql/gadm4.sql |
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
sed "s/:TESTUSER/${TESTUSER}/" |
sed "s/:TESTPASS/${TESTPASS}/" |
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
sed "s/:TESTUSER/${TESTUSER}/" |
sed "s/:TESTPASS/${TESTPASS}/" |
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
psql -c "CREATE EXTENSION 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
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 |
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
@@ -98,7 +98,7 @@ HMSET rails:users:localhost id ${TESTUSERID} \
SADD rails:users:localhost:map_key 1235
EOF
# A user configured as with cartodb-2.5.0+
# A user configured as with cartodb-2.5.0+
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
HMSET rails:users:cartodb250user id ${TESTUSERID} \
database_name "${TEST_DB}" \

View File

@@ -1,6 +1,6 @@
--
-- Windshaft test database
--
--
-- To use run ../prepare_db.sh
-- NOTE: requires a postgis template called template_postgis
--

119
test/support/test-client.js Normal file
View File

@@ -0,0 +1,119 @@
'use strict';
var qs = require('querystring');
var step = require('step');
var LayergroupToken = require('../../lib/cartodb/models/layergroup_token');
var assert = require('./assert');
var helper = require('./test_helper');
var CartodbWindshaft = require('../../lib/cartodb/server');
var serverOptions = require('../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
function TestClient(mapConfig) {
this.mapConfig = mapConfig;
this.keysToDelete = {};
}
module.exports = TestClient;
TestClient.prototype.getWidget = function(widgetName, params, callback) {
var self = this;
if (!callback) {
callback = params;
params = {};
}
var url = '/api/v1/map';
if (params && params.filters) {
url += '?' + qs.stringify({ filters: JSON.stringify(params.filters) });
}
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 expectedWidgetURLS = {
http: "/api/v1/map/" + parsedBody.layergroupid + "/0/widget/" + widgetName
};
assert.ok(parsedBody.metadata.layers[0].widgets[widgetName]);
assert.ok(
parsedBody.metadata.layers[0].widgets[widgetName].url.http.match(expectedWidgetURLS.http)
);
return next(null, parsedBody.layergroupid);
}
);
},
function getWidgetResult(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;
}
url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '?' + 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, res);
}
);
},
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.drain = function(callback) {
helper.deleteRedisKeys(this.keysToDelete, callback);
};

View File

@@ -30,7 +30,7 @@ describe('req2params', function() {
baseController = new BaseController(authApi, pgConnection);
});
it('can be found in server_options', function(){
assert.ok(_.isFunction(baseController.req2params));
});