From b1fb838b194497afb597101d50c7a476f37d6efd Mon Sep 17 00:00:00 2001 From: csubira Date: Wed, 21 Feb 2018 19:22:02 +0100 Subject: [PATCH 01/84] Add new structure to docs folder --- docs/examples/01-example.md | 1 + docs/guides/01-quickstart.md | 100 +++ docs/guides/02-general-concepts.md | 27 + docs/guides/03-anonymous-maps.md | 396 ++++++++++++ docs/guides/04-named-maps.md | 568 ++++++++++++++++++ docs/guides/05-static-maps-API.md | 226 +++++++ docs/guides/06-tile-aggregation.md | 187 ++++++ docs/guides/07-MapConfig-file-format.md | 270 +++++++++ .../08-MapConfig-aggregation-extension.md | 64 ++ .../guides/09-MapConfig-analyses-extension.md | 95 +++ .../10-MapConfig-dataviews-extension.md | 279 +++++++++ .../11-Mapconfig-named-maps-extension.md | 58 ++ docs/reference/01-routes.md | 114 ++++ docs/support/01-metrics.md | 43 ++ 14 files changed, 2428 insertions(+) create mode 100644 docs/examples/01-example.md create mode 100644 docs/guides/01-quickstart.md create mode 100644 docs/guides/02-general-concepts.md create mode 100644 docs/guides/03-anonymous-maps.md create mode 100644 docs/guides/04-named-maps.md create mode 100644 docs/guides/05-static-maps-API.md create mode 100644 docs/guides/06-tile-aggregation.md create mode 100644 docs/guides/07-MapConfig-file-format.md create mode 100644 docs/guides/08-MapConfig-aggregation-extension.md create mode 100644 docs/guides/09-MapConfig-analyses-extension.md create mode 100644 docs/guides/10-MapConfig-dataviews-extension.md create mode 100644 docs/guides/11-Mapconfig-named-maps-extension.md create mode 100644 docs/reference/01-routes.md create mode 100644 docs/support/01-metrics.md diff --git a/docs/examples/01-example.md b/docs/examples/01-example.md new file mode 100644 index 00000000..5bc9ae3e --- /dev/null +++ b/docs/examples/01-example.md @@ -0,0 +1 @@ +## Example 1 \ No newline at end of file diff --git a/docs/guides/01-quickstart.md b/docs/guides/01-quickstart.md new file mode 100644 index 00000000..03de7527 --- /dev/null +++ b/docs/guides/01-quickstart.md @@ -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://{username}.carto.com/api/v1/map', + data: JSON.stringify(mapconfig), + success: function(data) { + var templateUrl = 'https://{username}.carto.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 CARTO 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 MapConfig needs to be sent to CARTO's Map API using an authenticated call. Here we will use a command line tool called `curl`. For more info about this tool, see [this blog post](http://quickleft.com/blog/command-line-tutorials-curl), or type `man curl` in bash. Using `curl`, and storing the config from above in a file `MapConfig.json`, the call would look like: + +##### Call + +```bash +curl 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}' -H 'Content-Type: application/json' -d @mapconfig.json +``` + +To get the `URL` to fetch the tiles you need to instantiate the map, where `template_id` is the template name from the previous response. + +##### Call + +```bash +curl -X POST 'https://{username}.carto.com/api/v1/map/named/{template_id}' -H 'Content-Type: application/json' +``` + +The response will return JSON with properties for the `layergroupid`, the timestamp (`last_updated`) of the last data modification and some key/value pairs with `metadata` for the `layers`. + +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://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png +``` diff --git a/docs/guides/02-general-concepts.md b/docs/guides/02-general-concepts.md new file mode 100644 index 00000000..4eeec096 --- /dev/null +++ b/docs/guides/02-general-concepts.md @@ -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 CARTO. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a Named Map). + +To execute an authorized request, `api_key=YOURAPIKEY` should be added to the request URL. The param can be also passed as POST param. Using HTTPS is mandatory when you are performing requests that include your `api_key`. + +### Errors + +Errors are reported using standard HTTP codes and extended information encoded in JSON with this format: + +```javascript +{ + "errors": [ + "access forbidden to table TABLE" + ] +} +``` + +If you use JSONP, the 200 HTTP code is always returned so the JavaScript client can receive errors from the JSON object. + +### CORS Support + +All the endpoints, which might be accessed using a web browser, add CORS headers and allow OPTIONS method. \ No newline at end of file diff --git a/docs/guides/03-anonymous-maps.md b/docs/guides/03-anonymous-maps.md new file mode 100644 index 00000000..56d61502 --- /dev/null +++ b/docs/guides/03-anonymous-maps.md @@ -0,0 +1,396 @@ +## Anonymous Maps + +Anonymous Maps allows you to instantiate a map given SQL and CartoCSS. It also allows you to add interaction capabilities using [UTF Grid.](https://github.com/mapbox/utfgrid-spec). +Alternatively, you can get the data for the map (geometry and attributes for each layer) using vector tiles (in which case CartoCSS is not required). + + +### Instantiate + +##### 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.carto.com/carto-engine/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://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png` +updated_at | The ISO date of the last time the data involved in the query was updated. +metadata | Includes information about the layers. +cdn_url | URLs to fetch the data using the best CDN for your zone. + +**Improved response metadata** + +Originally, you needed to concantenate the `layergroupid` with the correct domain and the path for the tiles. +Now, for convenience, the layergroup includes the final URLs in two formats: +1. Leaflet's urlTemplate alike: useful when working with raster tiles or with libraries with an API similar to Leaflet's one. +1. [TileJSON spec](https://github.com/mapbox/tilejson-spec): useful when working with Mapbox GL or any other library that supports TileJSON. + +#### Example + +##### Call + +```bash +curl 'https://{username}.carto.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": {} + } + ], + "tilejson": { + "raster": { + "tilejson": "2.2.0", + "tiles": [ + "http://a.cdb.com/c01a54877c62831bb51720263f91fb33/{z}/{x}/{y}.png", + "http://b.cdb.com/c01a54877c62831bb51720263f91fb33/{z}/{x}/{y}.png" + ] + } + }, + "url": { + "raster": { + "urlTemplate": "http://{s}.cdb.com/c01a54877c62831bb51720263f91fb33/{z}/{x}/{y}.png", + "subdomains": ["a", "b"] + } + } + }, + "cdn_url": { + "http": "http://cdb.com", + "https": "https://cdb.com", + "templates": { + "http": { "subdomains": ["a","b"], "url": "http://{s}.cdb.com" }, + "https": { "subdomains": ["a","b"], "url": "https://{s}.example.com" }, + } + } +} +``` + +### Map Tile Rendering + +Map tiles are used to create the graphic representation of your map in a web browser. Tiles can be requested either as pre-rendered *raster* tiles (images) or as *vector* map data to be rendered by the client (browser). + +- **Raster**: If a tile is requested as a raster image format, like PNG, the map will be rendered on the server, using the CartoCSS styles defined in the layers of the map. It is necessary that all the layers of a map define CartoCSS styles in order to obtain raster tiles. Raster tiles are made up of 256x256 pixels; to avoid graphic quality issues tiles should be used unscaled to represent the zoom level (Z) for which they are requested. In order to render tiles, data will be retrieved from the database (in vector format) on the server-side. + +- **Vector**: Tiles can also be requested as MVT (Mapbox Vector Tiles). In this case, only the geospatial vector data, without any styling, is returned. These tiles should be processed in the client-side to render the map. In this case layers do not need to define CartoCSS, as any rendering and styling will be performed on the client side. The vector data of a tile represents real-world geometries by defining the vertices of points, lines or polygons in a tile-specific coordinate system. + +### Retrieve resources from the layergroup + +When you have a layergroup, there are several resources for retrieving layergoup details such as, accessing Mapnik tiles, getting individual layers, accessing defined Attributes, and blending and layer selection. + +#### Raster tiles + +These raster tiles are PNG images that represent only the Mapnik layers of a map. See [individual layers](#individual-layers) for details about how to retrieve other layers. + +```bash +https://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png +``` + +#### Mapbox Vector Tiles (MVT) + +[Mapbox Vector Tiles (MVT)](https://www.mapbox.com/vector-tiles/specification/) are map tiles that transfer geographic vector data to the client-side. Browser performance is fast since you can pan and zoom without having to query the server. + +CARTO uses Web Graphics Library (WebGL) to process MVT files on the browser. This is useful since WebGL is compatible with most web browsers, include support for multiple client-side mapping engines, and do not require additional information from the server; which makes it more efficient for rendering map tiles. However, you can use any implementation tool for processing MVT files. + +The following examples describe how to fetch MVT tiles with a cURL request. + +##### MVT and Windshaft + +CARTO uses Windshaft as the map tiler library to render multilayer maps with the Maps API. You can use Windshaft to request MVT using the same layer type that is used for requesting raster tiles (Mapnik layer). Simply change the file format `.mvt` in the URL. + + +```bash +https://{username}.cartodb.com/api/v1/map/HASH/:layer/{z}/{x}/{y}.mvt +``` + +The following example instantiates an anonymous map with layer options: + +```bash +{ + user_name: 'mycartodbuser', + sublayers: [{ + sql: "SELECT * FROM table_name"; + cartocss: '#layer { marker-fill: #F0F0F0; }' + }], + maps_api_template: 'https://{user}.cartodb.com' // Optional +} +``` + +**Note**: If no layer type is specified, Mapnik tiles are used by default. To access MVT tiles, specify `https://{username}.cartodb.com/api/v1/map/HASH/{z}/{x}/{y}.mvt` as the `maps_api_template` variable. + +**Tip:** If you are using [Named Maps](https://carto.com/docs/carto-engine/maps-api/named-maps/) to instantiate a layer, indicate the MVT file format and layer in the response: + +```bash +https://{username}.cartodb.com/api/v1/map/named/:templateId/:layer/{z}/{x}/{y}.mvt +``` + +For all layers in a Named Map, you must indicate Mapnik as the layer filter: + +```bash +https://{username}.cartodb.com/api/v1/map/named/:templateId/mapnik/{z}/{x}/{y}.mvt +``` + +##### Layergroup Filter for MVT Tiles + +To filter layers using Windshaft, use the following request where layers are numbered: + +```bash +https://{username}.cartodb.com/api/v1/map/HASH/0,1,2/{z}/{x}/{y}.mvt +``` + +To request all layers, remove the layergroup filter parameter: + +```bash +https://{username}.cartodb.com/api/v1/map/HASH/{z}/{x}/{y}.mvt +``` + +To filter a specific layer: + +```bash +https://{username}.cartodb.com/api/v1/map/HASH/2/{z}/{x}/{y}.mvt +``` + +##### Example 1: MVT Tiles with Windshaft, CARTO.js, and MapboxGL + +1) Import the required libraries: + +```bash + + + +``` + +2) Configure Map Client: + +```bash +mapboxgl.accessToken = '{yourMapboxToken}'; +``` + +3) Create Map Object (Mapbox): + +```bash +var map = new mapboxgl.Map({ +container: 'map', +zoom: 1, +minZoom: 0, +maxZoom: 18, +center: [30, 0] +}); +``` + +4) Define Layer Options (CARTO): + +```bash +var layerOptions = { +user_name: "{username}", +sublayers: [{ +sql: "SELECT * FROM {table_name}", +cartocss: "...", + }] +}; +``` + +5) Request Tiles (from CARTO) and Set to Map Object (Mapbox): + +**Note:** By default, [CARTO core functions](https://carto.com/docs/carto-engine/carto-js/core-api/) retrieve URLs for fully rendered tiles. You must replace the default format (.png) with the MVT format (.mvt). + + +```bash +cartodb.Tiles.getTiles(layerOptions, function(result, err) { +var tiles = result.tiles.map(function(tileUrl) { +return tileUrl +.replace('{s}', 'a') +.replace(/\.png/, '.mvt'); +}); +map.setStyle(simpleStyle(tiles)); +}); +``` + +##### Example 2: MVT Libraries with Windshaft and MapboxGL + +When you are not including CARTO.js to implement MVT tiles, you must use the `map.setStyle` parameter to specify vector map rendering. + +1) Import the required libraries: + +```bash + + +``` + +2) Configure Map Client: + +```bash +mapboxgl.accessToken = '{yourMapboxToken}'; +``` + +3) Create Map Object (Mapbox): + +```bash +var map = new mapboxgl.Map({ +container: 'map', +zoom: 1, +minZoom: 0, +maxZoom: 18, +center: [30, 0] +}); +``` + +4) Set the Style + +```bash +map.setStyle({ + "version": 7, + "glyphs": "...", + "constants": {...}, + "sources": { + "cartodb": { + "type": "vector", + "tiles": [ "http://{username}.cartodb.com/api/v1/map/named/templateId/mapnik/{z}/{x}/{y}.mvt" + ], + "maxzoom": 18 + } + }, + "layers": [{...}] +}); +``` + +**Tip:** If you are using MapboxGL, see the following resource for additional information. + +- [MapboxGL API Reference](https://www.mapbox.com/mapbox-gl-js/api/) +- [MapboxGL Style Specifications](https://www.mapbox.com/mapbox-gl-js/style-spec/) +- [Example of MapboxGL Implementation](https://www.mapbox.com/mapbox-gl-js/examples/) + +#### Individual layers + +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://{username}.carto.com/api/v1/map/{layergroupid}/{layer}/{z}/{x}/{y}.grid.json +``` + +In this case, `layer` as 0 returns the UTF grid tiles/attributes for layer 0, the only layer in the example MapConfig. + +If the MapConfig had a Torque layer at index 1 it could be possible to request it with: + +```bash +https://{username}.carto.com/api/v1/map/{layergroupid}/1/{z}/{x}/{y}.torque.json +``` + +#### Attributes defined in `attributes` section + +```bash +https://{username}.carto.com/api/v1/map/{layergroupid}/{layer}/attributes/{feature_id} +``` + +Which returns JSON with the attributes defined, such as: + +```javascript +{ "c": 1, "d": 2 } +``` + +#### Blending and layer selection + +```bash +https://{username}.carto.com/api/v1/map/{layergroupid}/{layer_filter}/{z}/{x}/{y}.png +``` + +Note: currently format is limited to `png`. + +`layer_filter` can be used to select some layers to be rendered together. `layer_filter` supports two formats: + +- `all` alias + +Using `all` as `layer_filter` will blend all layers in the layergroup + +```bash +https://{username}.carto.com/api/v1/map/{layergroupid}/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://{username}.carto.com/api/v1/map/{layergroupid}/0,3,4/{z}/{x}/{y}.png +``` + +Some notes about filtering: + + - Invalid index values or out of bounds indexes will end in `Invalid layer filtering` errors. + - Ordering is not considered. So right now filtering layers 0,3,4 is the very same thing as filtering 3,4,0. As this may change in the future, **it is recommended** to always select the layers in ascending order so that you will always get consistent behavior. + +### Create JSONP + +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://{username}.carto.com/api/v1/map?callback=callback&config=%7B%22version%22%3A%221.0.1%22%2C%22layers%22%3A%5B%7B%22type%22%3A%22cartodb%22%2C%22options%22%3A%7B%22sql%22%3A%22select+%2A+from+european_countries_e%22%2C%22cartocss%22%3A%22%23european_countries_e%7B+polygon-fill%3A+%23FF6600%3B+%7D%22%2C%22cartocss_version%22%3A%222.3.0%22%2C%22interactivity%22%3A%5B%22cartodb_id%22%5D%7D%7D%5D%7D" +``` + +##### Response + +```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, or sometimes longer. If an Anonymous Map expires and tiles are requested from it, an error will be raised. This could happen if a user leaves a map open and after time, returns to the map and attempts to interact with it in a way that requires new tiles (e.g. zoom). The client will need to go through the steps of creating the map again to fix the problem. diff --git a/docs/guides/04-named-maps.md b/docs/guides/04-named-maps.md new file mode 100644 index 00000000..fb91adb3 --- /dev/null +++ b/docs/guides/04-named-maps.md @@ -0,0 +1,568 @@ +## 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 uploading a MapConfig file to CARTO servers, to select data from your CARTO user database by using SQL, and specifying the CartoCSS for your map. + +The response back from the API provides the template_id of your Named Map as the `name` (the identifier of your Named Map), which is the name that you specified in the MapConfig. You can which you can then use to create your Named Map details, or [fetch XYZ tiles](#fetching-xyz-tiles-for-named-maps) directly for Named Maps. + +**Tip:** You can also use a Named Map that you created (which is defined by its `name`), to create a map using CARTO.js. This is achieved by adding the [`namedmap` type](http://docs.carto.com/carto-engine/carto-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map. + +The main differences, compared to Anonymous Maps, is that Named Maps include: + +- **auth token** + This allows you to control who is able to see the map based on an auth token, and create a secure Named Map with password-protection. + +- **template map** + The template map is static and may contain placeholders, enabling you to modify your maps appearance by using variables. Templates maps are persistent with no preset expiration. They can only be created, or deleted, by a CARTO user with a valid API KEY (See [auth argument](#arguments)). + + Uploading a MapConfig creates a Named Map. MapConfigs are uploaded to the server by sending the server a "template".json file, which contain the [MapConfig specifications](http://docs.carto.com/carto-engine/maps-api/mapconfig/). + +**Note:** There is a limit of 4,096 Named Maps allowed per account. If you need to create more Named Maps, it is recommended to use a single Named Map and change the variables using [placeholders](#placeholder-format), instead of uploading multiple [Named Map MapConfigs](http://docs.carto.com/carto-engine/maps-api/mapconfig/#named-map-layer-options). + +### Create + +##### Definition + +```html +POST /api/v1/map/named +``` + +##### Params + +Params | Description +--- | --- +api_key | is required +MapConfig | a [Named Map MapConfig](http://docs.carto.com/carto-engine/maps-api/mapconfig/#named-map-layer-options) is required to create a Named Map + +##### template.json + +The `name` argument defines how to name this "template_name".json. Note that there are some requirements for how to name a Named Map template. See the [`name`](#arguments) argument description for details. + +```javascript +{ + "version": "0.0.1", + "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 + }, + "preview_layers": { + "0": true, + "layer1": false + } + } +} +``` + +##### Arguments + +Params | Description +--- | --- +name | There can only be _one_ template with the same name for any user. Valid names start with a letter or a number, and only contain letters, numbers, dashes (-), or underscores (_). _This is specific to the name of your Named Map that is specified in the `name` property of the template file_. + +auth | +--- | --- +|_ method | `"token"` or `"open"` (`"open"` is the default if no method is specified. Use `"token"` to password-protect your map) +|_ valid_tokens | when `"method"` is set to `"token"`, the values listed here allow you to instantiate the Named Map. See this [example](http://docs.carto.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map. +placeholders | Placeholders are variables that can be placed in your template.json file's SQL or CartoCSS. +layergroup | the layergroup configurations, as specified in the template. See [MapConfig File Format](http://docs.carto.com/carto-engine/maps-api/mapconfig/) for more information. +view (optional) | extra keys to specify the view area for the map. It can be used to have a static preview of a Named Map without having to instantiate it. It is possible to specify it with `center` + `zoom` or with a bounding box `bbox`. Center+zoom takes precedence over bounding box. Also it is possible to choose which layers are visible or not with `preview_layers` indicating its visibility by layer index or id (visible by default). +--- | --- +|_ 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) + + +#### Placeholder Format + +Placeholders are variables that can be placed in your template.json file. Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergroup configurations](http://docs.carto.com/carto-engine/maps-api/mapconfig/#layergroup-configurations). + +Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced inside the Named Maps API. + +##### Example + +```javascript +<%= my_color %> +``` + +The set of supported placeholders for a template need to be explicitly defined with a specific type, and default value, for each placeholder. + +#### Placeholder Types + +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 loosely. + +##### Call + +This is the call for creating the Named Map. It is sending the template.json file to the service, and the server responds with the template id. + +```bash +curl -X POST \ + -H 'Content-Type: application/json' \ + -d @template.json \ + 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}' +``` + +##### Response + +The response back from the API provides the name of your MapConfig as a template, enabling you to edit the Named Map details by inserting your variables into the template where placeholders are defined, and create custom queries using SQL. + +```javascript +{ + "template_id":"name" +} +``` + +### Instantiate + +Instantiating a Named Map allows you to fetch the map tiles. You can use the Maps API to instantiate, or use the CARTO.js `createLayer()` function. The result is an Anonymous Map. + +##### Definition + +```html +POST /api/v1/map/named/{template_name} +``` + +##### Param + +Param | Description +--- | --- +auth_token | `"token"` or `"open"` (`"open"` is the default if not specified. Use `"token"` to password-protect your map) + +```javascript +// params.json, this is required if the Named Map allows variables (if placeholders were defined in the template.json by the user) +{ + "color": "#ff0000", + "cartodb_id": 3 +} +``` + +The fields you pass as `params.json` depend on the variables allowed by the Named Map. If there are variables missing, it will raise an error (HTTP 400). + +**Note:** It is required that you include a `params.json` file to instantiate a Named Map that contains variables, even if you have no fields to pass and the JSON is empty. (This is specific to when a Named Map allows variables (if placeholders were defined in the template.json by the user). + +##### Example + +You can initialize a template map by passing all of the required parameters in a POST to `/api/v1/map/named/{template_name}`. + +Valid auth token will be needed, if required by the template. + + +##### Call + +```bash +curl -X POST \ + -H 'Content-Type: application/json' \ + -d @params.json \ + 'https://{username}.carto.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 Maps](http://docs.carto.com/carto-engine/maps-api/anonymous-maps/)). + +### Update + +##### Definition + +```bash +PUT /api/v1/map/named/{template_name} +``` + +##### Params + +Param | Description +--- | --- +api_key | is required + +##### Response + +Same as updating a map. + +#### Other Information + +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://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}' +``` + +##### 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 + +Deletes the specified template map from the server, and disables any previously initialized versions of the map. + +##### Definition + +```bash +DELETE /api/v1/map/named/{template_name} +``` + +##### Params + +Param | Description +--- | --- +api_key | is required + +#### Example + +##### Call + +```bash +curl -X DELETE 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}' +``` + +##### 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://{username}.carto.com/api/v1/map/named?api_key={api_key}' +``` + +##### Response + +```javascript +{ + "template_ids": ["@template_name1","@template_name2"] +} +``` + +##### Error + +```javascript +{ + "errors" : ["Some error string here"] +} +``` + +### Get Template Definition + +This gets the definition of a requested template. + +##### Definition + +```bash +GET /api/v1/map/named/{template_name} +``` + +##### Params + +Param | Description +--- | --- +api_key | is required + +#### Example + +##### Call + +```bash +curl -X GET 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}' +``` + +##### Response + +```javascript +{ + "template": {...} // see [template.json](#templatejson) +} +``` + +##### Error + +```javascript +{ + "errors" : ["Some error string here"] +} +``` + +### JSONP for Named Maps + +If using a [JSONP](https://en.wikipedia.org/wiki/JSONP) (for old browsers) request, there is a special endpoint used to initialize and create a Named Map. + +##### Definition + +```bash +GET /api/v1/map/named/{template_name}/jsonp +``` + +##### Params + +Params | Description +--- | --- +auth_token | `"token"` or `"open"` (`"open"` is the default if no method is specified. Use `"token"` to password-protect your map) +params | Encoded JSON with the params (variables) needed for the Named Map +lmza | You can use an LZMA compressed file instead of a params JSON file +callback | JSON callback name + +##### Call + +```bash +curl 'https://{username}.carto.com/api/v1/map/named/{template_name}/jsonp?auth_token={auth_token}&callback=callback&config=template_params_json' +``` + +##### Response + +```javascript +callback({ + "layergroupid":"c01a54877c62831bb51720263f91fb33:0", + "last_updated":"1970-01-01T00:00:00.000Z" + "cdn_url": { + "http": "http://cdb.com", + "https": "https://cdb.com" + } +}) +``` + +This takes the `callback` function (required), `auth_token` if the template needs auth, and `config` which is the variable for the template (in cases where it has variables). + +```javascript +url += "config=" + encodeURIComponent( +JSON.stringify({ color: 'red' }); +``` + +The response is: + +```javascript +callback({ + layergroupid: "dev@744bd0ed9b047f953fae673d56a47b4d:1390844463021.1401", + last_updated: "2014-01-27T17:41:03.021Z" +}) +``` + +### CARTO.js for Named Maps + +You can use a Named Map that you created (which is defined by its `name`), to create a map using CARTO.js. This is achieved by adding the [`namedmap` type](http://docs.carto.com/carto-engine/carto-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map. + +```javascript +{ + user_name: '{username}', // Required + type: 'namedmap', // Required + named_map: { + name: '{name_of_map}', // Required, the 'name' of the Named Map that you have created + // Optional + layers: [{ + layer_name: "sublayer0", // Optional + interactivity: "column1, column2, ..." // Optional + }, + { + layer_name: "sublayer1", + interactivity: "column1, column2, ..." + }, + ... + ], + // Optional + params: { + color: "hex_value", + num: 2 + } + } +} +``` + +**Note:** Instantiating a Named Map over a `createLayer` does not require an API Key and by default, does not include auth tokens. _If_ you defined auth tokens for the Named Map configuration, then you will have to include them. + +[CARTO.js](http://docs.carto.com/carto-engine/carto-js/) has methods for accessing your Named Maps. + +1. [layer.setParams()](http://docs.carto.com/carto-engine/carto-js/api-methods/#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript + + **Note:** The CARTO.js `layer.setParams()` function is not supported when using Named Maps for Torque. Alternatively, you can create a [Torque layer in a Named Map](http://bl.ocks.org/iriberri/de37be6406f9cc7cfe5a) + +2. [layer.setAuthToken()](http://docs.carto.com/carto-engine/carto-js/api-methods/#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer + +#### Torque Layer in a Named Map + +If you are creating a Torque layer in a Named Map without using the Torque.js library, you can apply the Torque layer by applying the following code with CARTO.js: + +```javascript + // add cartodb layer with one sublayer + cartodb.createLayer(map, { + user_name: '{username}', + type: 'torque', + order: 1, + options: { + query: "", + table_name: "named_map_tutorial_table", + user_name: "{username}", + tile_style: 'Map { -torque-frame-count:512; -torque-animation-duration:10; -torque-time-attribute:"cartodb_id"; -torque-aggregation-function:"count(cartodb_id)"; -torque-resolution:2; -torque-data-aggregation:linear; } #named_map_tutorial_table_copy{ comp-op: lighter; marker-fill-opacity: 0.9; marker-line-color: #FFF; marker-line-width: 1.5; marker-line-opacity: 1; marker-type: ellipse; marker-width: 6; marker-fill: #FF9900; } #named_map_tutorial_table_copy[frame-offset=1] { marker-width:8; marker-fill-opacity:0.45; } #named_map_tutorial_table_copy[frame-offset=2] { marker-width:10; marker-fill-opacity:0.225; }' + + }, + named_map: { + name: "{namedmap_example}", + layers: [{ + layer_name: "t" + }] + } + }) + .addTo(map) + .done(function(layer) { + + }); +} +``` + +##### Examples of Named Maps created with CARTO.js + +- [Named Map selectors with interaction](http://bl.ocks.org/andy-esch/515a8af1f99d5e690484) + +- [Named Map with interactivity](http://bl.ocks.org/andy-esch/d1a45b8ff5e7bd90cd68) + +- [Toggling sublayers in a Named Map](http://bl.ocks.org/andy-esch/c1a0f4913610eec53cd3) + +### Fetching XYZ Tiles for Named Maps + +Optionally, authenticated users can fetch projected tiles (XYZ tiles or Mapnik Retina tiles) for your Named Map. + +#### Fetch XYZ Tiles Directly with a URL + +Authenticated users, with an auth token, can use XYZ-based URLs to fetch tiles directly, and instantiate the Named Map as part of the request to your application. You do not have to do any other steps to initialize your map. + +To call a template_id in a URL: + +`/{template_id}/{layer}/{z}/{x}/{y}.{format}` + +For example, a complete URL might appear as: + +"https://{username}.carto.com/api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.png" + +The placeholders indicate the following: + +- [`template_id`](http://docs.carto.com/carto-engine/maps-api/named-maps/#response) is the response of your Named Map. +- layers can be a number (referring to the ## layer of your map), all layers of your map, or a list of layers. + - To show just the basemap layer, enter the number value `0` in the layer placeholder "https://{username}.carto.com/api/v1/map/named/{template_id}/0/{z}/{x}/{y}.png" + - To show the first layer, enter the number value `1` in the layer placeholder "https://{username}.carto.com/api/v1/map/named/{template_id}/1/{z}/{x}/{y}.png" + - To show all layers, enter the value `all` for the layer placeholder "https://{username}.carto.com/api/v1/map/named/{template_id}/all/{z}/{x}/{y}.png" + - To show a [list of layers](http://docs.carto.com/carto-engine/maps-api/anonymous-maps/#blending-and-layer-selection), enter the comma separated layer value as 0,1,2 in the layer placeholder. For example, to show the basemap and the first layer, "https://{username}.carto.com/api/v1/map/named/{template_id}/0,1/{z}/{x}/{y}.png" + + +#### Get Mapnik Retina Tiles + +Mapnik Retina tiles are not directly supported for Named Maps, so you cannot use the Named Map template_id. To fetch Mapnik Retina tiles, get the [layergroupid](http://docs.carto.com/carto-engine/maps-api/named-maps/#response-1) to initialize the map. + +Instantiate the map by using your `layergroupid` in the token placeholder: + + `{token}/{z}/{x}/{y}@{scale_factor}?{x}.{format}` diff --git a/docs/guides/05-static-maps-API.md b/docs/guides/05-static-maps-API.md new file mode 100644 index 00000000..d36d83b0 --- /dev/null +++ b/docs/guides/05-static-maps-API.md @@ -0,0 +1,226 @@ +## 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` +--- | --- +|_ 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` +--- | --- +|_ 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 + +format | the format for the image, supported types: `png`, `jpg` +--- | --- +|_ jpg | will have a default quality of 85. + +A Named Maps static image will get its constraints from the [`view` argument of the Create Named Map function](http://docs.carto.com/carto-engine/maps-api/named-maps/#arguments). If `view` is not defined, it will estimate the extent based on the involved tables, otherwise it fallbacks to `"zoom": 1`, `"lng": 0` and `"lat": 0`. + +##### Layers + +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" + } +}, +``` + +**CARTO** + +As described in the [MapConfig File Format](http://docs.carto.com/carto-engine/maps-api/mapconfig/), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility. + +```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 CARTO account. This means that if the data changes, the cached image will also change. When linking dynamically, it is important to take into consideration the state of the data and longevity of the static image to avoid broken images or changes in how the image is displayed. To obtain a static snapshot of the map as it is today and preserve the image long-term regardless of changes in data, the image must be saved and stored locally. + +#### Limits + +* While images can encompass an entirety of a map, the limit for pixel range is 8192 x 8192. +* Image resolution is set to 72 DPI +* JPEG quality is 85% +* Timeout limits for generating static maps are the same across CARTO Builder and CARTO Engine. It is important to ensure timely processing of queries. +* If you are publishing your map as a static image with the API, you must manually add [attributions](https://carto.com/attribution) for your static map image. For example, add the following attribution code: + +{% highlight javascript %} +attribution: '© OpenStreetMap contributors, © CARTO +{% endhighlight %} + +### Examples + +After instantiating a map from a CARTO account: + +##### Call + +```bash + GET /api/v1/map/static/center/{layergroupid}/{z}/{x}/{y}/{width}/{height}.png +``` + +##### Response + +

static-api

+ +#### 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" + } + } + ] +} +``` diff --git a/docs/guides/06-tile-aggregation.md b/docs/guides/06-tile-aggregation.md new file mode 100644 index 00000000..ef615b2e --- /dev/null +++ b/docs/guides/06-tile-aggregation.md @@ -0,0 +1,187 @@ +## Tile Aggregation + +To be able to represent a large amount of data (say, hundred of thousands to millions of points) in a tile. This can be useful both for raster tiles (where the aggregation reduces the number of features to be rendered) and vector tiles (the tile contais less features). + +Aggregation is available only for point geometries. During aggregation the points are grouped using a grid; all the points laying in the same cell of the grid are summarized in a single aggregated result point. + - The position of the aggregated point is controlled by the `placement` parameter. + - The aggregated rows always contain at least a column, named `_cdb_feature_count`, which contains the number of the original points that the aggregated point represents. + +#### Special default aggregation + +When no placement or columns are specified a special default aggregation is performed. + +This special mode performs only spatial aggregation (using a grid defined by the requested tile and the resolution, parameter, as all the other cases), and returns a _random_ record from each group (grid cell) with all its columns and an additional `_cdb_features_count` with the number of features in the group. + +Regarding the randomness of the sample: currently we use the row with the minimum `cartodb_id` value in each group. + +The rationale behind having this special aggregation with all the original columns is to provide a mostly transparent way to handle large datasets without having to provide special map configurations for those cases (i.e. preserving the logic used to produce the maps with smaller datasets). [Overviews have been used so far with this intent](https://carto.com/docs/tips-and-tricks/back-end-data-performance/), but they are inflexible. + +#### User defined aggregations + +When either a explicit placement or columns are requested we no longer use the special, query; we use one determined by the placement (which will default to "centroid"), and it will have as columns only the aggregated columns specified, in addition to `_cdb_features_count`, which is always present. + +We might decide in the future to allow sampling column values for any of the different placement modes. + +#### Behaviour for raster and vector tiles + +The vector tiles from a vector-only map will be aggregated by default. +However, Raster tiles (or vector tiles from a map which defines CartoCSS styles) will be aggregated only upon request. + +Aggregation that would otherwise occur can be disabled by passing an `aggregation=false` parameter to the map instantiation HTTP call. + +To control how aggregation is performed, an aggregation option can be added to the layer: + +```json +{ + "layers": [ + { + "options": { + "sql": "SELECT * FROM data", + "aggregation": { + "placement": "centroid", + "columns": { + "value": { + "aggregate_function": "sum", + "aggregated_column": "value" + } + } + } + } + } + ] +} +``` + +Even if aggregation is explicitly requested it may not be activated, e.g., if the geometries are not points +or the whole dataset is too small. The map instantiation response contains metadata that informs if any particular +layer will be aggregated when tiles are requested, both for vector (mvt) and raster (png) tiles. + +```json +{ + "layergroupid": "7b97b6e76590fef889b63edd2efb1c79:1513608333045", + "metadata": { + "layers": [ + { + "type": "mapnik", + "id": "layer0", + "meta": { + "stats": { + "estimatedFeatureCount": 6232136 + }, + "aggregation": { + "png": true, + "mvt": true + } + } + } + ] + } +} +``` + +### Aggregation parameters + +The aggregation parameters for a layer are defined inside an `aggregation` option of the layer: + +```json +{ + "layers": [ + { + "options": { + "sql": "SELECT * FROM data", + "aggregation": {"...": "..."} + } + } + ] +} +``` + +#### `placement` + +Determines the kind of aggregated geometry generated: + +##### `point-sample` + +This is the default placement. It will place the aggregated point at a random sample of the grouped points, +like the default aggregation does. No other attribute is sampled, though, the point will contain the aggregated attributes determined by the `columns` parameter. + +##### `point-grid` + +Generates points at the center of the aggregation grid cells (squares). + +##### `centroid` + +Generates points with the averaged coordinated of the grouped points (i.e. the points inside each grid cell). + +#### `columns` + +The aggregated attributes defined by `columns` are computed by a applying an _aggregate function_ to all the points in each group. +Valid aggregate functions are `sum`, `avg` (average), `min` (minimum), `max` (maximum) and `mode` (the most frequent value in the group). +The values to be aggregated are defined by the _aggregated column_ of the source data. The column keys define the name of the resulting column in the aggregated dataset. + +For example here we define three aggregate attributes named `total`, `max_price` and `price` which are all computed with the same column, `price`, +of the original dataset applying three different aggregate functions. + +```json +{ + "columns": { + "total": { "aggregate_function": "sum", "aggregated_column": "price" }, + "max_price": { "aggregate_function": "max", "aggregated_column": "price" }, + "price": { "aggregate_function": "avg", "aggregated_column": "price" } + } +} +``` + +> Note that you can use the original column names as names of the result, but all the result column names must be unique. In particular, the names `cartodb_id`, `the_geom`, `the_geom_webmercator` and `_cdb_feature_count` cannot be used for aggregated columns, as they correspond to columns always present in the result. + +#### `resolution` + +Defines the cell-size of the spatial aggregation grid. This is equivalent to the [CartoCSS `-torque-resolution`](https://carto.com/docs/carto-engine/cartocss/properties-for-torque/#-torque-resolution-float) property of Torque maps. + +The aggregation cells are `resolution`×`resolution` pixels in size, where pixels here are defined to be 1/256 of the (linear) size of a tile. +The default value is 1, so that aggregation coincides with raster pixels. A value of 2 would make each cell to be 4 (2×2) pixels, and a value of +0.5 would yield 4 cells per pixel. In teneral values less than 1 produce sub-pixel precision. + +> Note that is independent of the number of pixels for raster tile or the coordinate resolution (mvt_extent) of vector tiles. + + +#### `threshold` + +This is the minimum number of (estimated) rows in the dataset (query results) for aggregation to be applied. If the number of rows estimate is less than the threshold aggregation will be disabled for the layer; the instantiation response will reflect that and tiles will be generated without aggregation. + +#### Example + +```json +{ + "version": "1.7.0", + "extent": [-20037508.5, -20037508.5, 20037508.5, 20037508.5], + "srid": 3857, + "maxzoom": 18, + "minzoom": 3, + "layers": [ + { + "type": "mapnik", + "options": { + "sql": "select * from table", + "cartocss": "#table { marker-width: [total]; marker-fill: ramp(value, (red, green, blue), jenks); }", + "cartocss_version": "2.3.0", + "aggregation": { + "placement": "centroid", + "columns": { + "value": { + "aggregate_function": "avg", + "aggregated_column": "value" + }, + "total": { + "aggregate_function": "sum", + "aggregated_column": "value" + } + }, + "resolution": 2, + "threshold": 500000 + } + } + } + ] +} +``` diff --git a/docs/guides/07-MapConfig-file-format.md b/docs/guides/07-MapConfig-file-format.md new file mode 100644 index 00000000..2f8068a9 --- /dev/null +++ b/docs/guides/07-MapConfig-file-format.md @@ -0,0 +1,270 @@ +{% comment %} +The original resource for this was: +https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.4.0.md. However this is internal documenation only. This file (07-mapconfig.md) contains select content from the Windshaft internal doc. *I instructed @rochoa to add new Doc issues if/when they make a change to this content - so that the public docs can also be updated. +{% endcomment %} + +## MapConfig File Format + +CARTO uses Windshaft as the map tiler library to render multilayer maps with the [Maps API]({{ site.baseurl }}/carto-engine/maps-api/). The MapConfig file is where these Windshaft layers are stored and applied. You can configure tiles and use the MapConfig document to request different resources for your map. + +This section describes the MapConfig specifications, and required formats, when using the Maps API. + +### Layergroup Configurations + +The following MapConfig Layergroup configurations are applied using the [RFC 4627](http://www.ietf.org/rfc/rfc4627.txt) JSON format. + +Layergroup Configuration | Description | Optional or Required? +--- | --- +`version` | Spec version to use for validation.

**Note:** The default value is `"1.0.0"`. | Optional +`extent` | The default map extent for the map projection.

**Note:** Currently, only webmercator is supported. | Optional +`srid` | The spatial reference identifier for the map. The default is `3857`. | Optional +`maxzoom` | The maximum zoom level for your map. A request beyond the defined maxzoom returns a 404 error.

**Note:** The default value is undefined (infinite). | Optional +`minzoom` | The minimum zoom level for your map. A request beyond the defined minzoom returns a 404 error.

**Note:** The default value is `0`. | Optional +`layers` | Defines the layer type, and the layers, in rendering order.

**Note:** The following layers options are available: | +--- | --- + type | A string value that defines the layer type. You can define up to four values:

`mapnik`, rasterized tiles

`cartodb`, an alias for mapnik (for backward compatibility)

`torque`, render vector tiles in torque format

`http`, load tiles over HTTP

`plain`, color or background image url

`named`, use a Named Map as a layer | Required + options | An object value that sets different options for each layer type.

**Note:** Options that are not defined in different layers will be discarded. | Required + +#### Example of MapConfig + +{% highlight json %} +{ + "version": "1.7.0", + "extent": [-20037508.5, -20037508.5, 20037508.5, 20037508.5], + "srid": 3857, + "maxzoom": 18, + "minzoom": 3, + "layers": [ + { + "type": "mapnik", + "options": { + "sql": "select * from table", + "cartocss": "#table { marker-placement: point; }", + "cartocss_version": "2.3.0" + } + } + ] +} +{% endhighlight %} + +--- + +### Mapnik Layer Options + +If you are using Mapnik as a layer resource, the following configurations are required in your MapConfig file. + +Mapnik Layer Option | Description | Optional or Required? +--- | --- +`sql` | A string value, the SQL request to the user database that will fetch the rendered data.

**Tip:** The SQL request should include the following Mapnik layer configurations: `geom_column`, `interactivity`, and `attributes`, as described in this section.

**Note:** The SQL request may contain substitutions tokens, such as `!bbox!`, `!pixel_width!`, and `!pixel_height!`. It is suggested to define the layergroup `minzoom` and `extent` variables to prevent errors. | Required +`cartocss` | A string value, specifying the CartoCSS style to render the tiles. If this is not present, only vector tiles can be requested for this layer. For a map to be valid either all the layers or none of them must have CartoCSS style.

**Note:** The CartoCSS specification is dependent on the layer type. For details, see [mapnik-reference.json](https://github.com/mapnik/mapnik-reference). | Optional +`cartocss_version` | A string value, specifying the CartoCSS style version of the CartoCSS attribute.

**Note:** The CartoCSS version is specific to the layer type. | Optional +`geom_column` | The name of the column containing the geometry. The default is `the_geom_webmercator`.

*You must specify this value as part of the Mapnik layer `SQL`configuration. | *Optional +`geom_type` | Defines the type of column as either `geometry` (the default) or `raster`.

**Note:** `geom_type` is not compatible with the Mapnik layer `interactivity` option. | Optional +`raster_band` | Defines the raster band (this option is only applicable when the `geom_type=raster`. The default value is `0`.

**Note:** If the default, or no value is specified, raster bands are interpreted as either: grayscale (for single bands), RGB (for 3 bands), or RGBA (for 4 bands). | Optional +`srid` | The spatial reference identifier for the geometry column. The default is `3857`. | Optional +`affected_tables` | A string of values containing the tables that the Mapnik layer `SQL` configuration is using. This value is used if there is a problem guessing what the affected tables are from the SQL configuration (i.e. when using PL/SQL functions). | Optional +`interactivity` | A string of values that contains the fields rendered inside grid.json. All the parameters should be exposed as a result of executing the Mapnik layer `SQL` query.

**Note:** `interactivity` is not compatible with the Mapnik layer `geom_type` option. For example, you cannot create a layergroup instance with a raster layer by defining the `geom_type=raster`.

*You must specify this value as part of the Mapnik layer `SQL` configuration. | *Optional +`attributes` | The id and column values returned by the Mapnik attributes service. (This option is disabled if no configuration is defined).

*You must specify this value as part of the Mapnik layer `SQL`configuration.| *Optional +--- | --- + id | The key value used to fetch columns. | Required + columns | A string of values (columns) returned by the Mapnik attribute service. | Required + +#### Example of Mapnik MapConfig + +{% highlight json %} +{ + "type": "mapnik", + "options": { + "sql": "select * from table", + "cartocss": "#layer { marker-placement: point; }", + "cartocss_version": "2.3.0", + "geom_column": "the_geom_webmercator", + "geom_type": "geometry", + "interactivity": [ "column1", "column2", "..."], + "attributes": { + "id": "cartodb_id", + "columns": ["column1", "column2"] + } + } +} +{% endhighlight %} + +### Torque Layer Options + +If you are using Torque as a layer resource, the following configurations are required in your MapConfig file. For more details about Torque layers in general, see the [Torque API]({{ site.baseurl }}/carto-engine/torque/torqueapi/#torque-api) documentation. + +Torque Layer Option | Description | Optional or Required? +--- | --- +`sql` | A string value, the SQL request to the user database that will fetch the rendered data.

**Tip:** The SQL request should include the following Torque layer configurations: `geom_column`, `interactivity`, and `attributes`, as described in this section. | Required +`cartocss` | A string value, specifying the CartoCSS style to render the tiles.

**Note:** The CartoCSS specification is dependent on the layer type. For details, see [Torque cartocss-reference.js](https://github.com/CartoDB/torque/blob/master/lib/torque/cartocss_reference.js).| Required +`cartocss_version` | A string value, specifying the CartoCSS style version of the CartoCSS attribute.

**Note:** The CartoCSS version is specific to the layer type. | Required +`step` | The number of [animation steps]({{ site.baseurl }}/carto-engine/cartocss/properties-for-torque/#torque-frame-count-number) to render when requesting a torque.png tile. The default value is `0`. | Optional +`geom_column` | The name of the column containing the geometry. The default is `the_geom_webmercator`.

*You must specify this value as part of the Torque layer `SQL`configuration. | *Optional +`srid` | The spatial reference identifier for the geometry column. The default is `3857`. | Optional +`affected_tables` | A string of values containing the tables that the Mapnik layer `SQL` configuration is using. This value is used if there is a problem guessing what the affected tables are from the SQL configuration (i.e. when using PL/SQL functions). | Optional +`attributes` | The id and column values returned by the Torque attributes service. (This option is disabled if no configuration is defined).

*You must specify this value as part of the Torque layer `SQL`configuration.| *Optional +--- | --- + id | The key value used to fetch columns. | Required + columns | A string of values (columns) returned by the Torque attribute service. | Required + +#### Example of Torque MapConfig + +{% highlight json %} +{ + "type": "torque", + "options": { + "sql": "select * from table", + "cartocss": "#layer { ... }", + "cartocss_version": "1.0.0", + "geom_column": "the_geom_webmercator" + } +} +{% endhighlight %} + +### HTTP Layer Options + +If you are using an HTTP destination as the resource for a map layer, the following configurations are required in your MapConfig file. + +HTTP Layer Option | Description | Optional or Required? +--- | --- +`urlTemplate` | A string value, end URL, from where the tile data is retrieved. _URLs must be included in the configuration whitelist to be valid._

**Note:** The {String} value includes:

`{z}` as the zoom level

`{x} and {y}` as the tile coordinates

Optionally, the subdomain `{s}` may be included as part of the `urlTemplate` configuration. Otherwise, you can define the `subdomains` separately, as shown below. | Required +`subdomains` | A string of values used to retrieve tiles from different subdomains. The default value is [`a`, `b`, `c`] when `{s}` is defined in the `urlTemplate` configuration. Otherwise, the default value is `[ ]`.

**Note:** The subdomains value will consistently replace the `{s}` value defined in the `urlTemplate`.| Optional +`tms` | A boolean value that specifies whether the tile is using Tile Map Service format. The default value is `false`.

**Note:** If the value is `true`, the TMS inverses the Y axis numbering for tiles. | Optional + +#### Example of HTTP MapConfig + +{% highlight json %} +{ + "type": "http", + "options": { + "urlTemplate": "http://{s}.example.com/{z}/{x}/{y}.png", + "subdomains": ["a", "b", "c"], + "tms": false + } +} +{% endhighlight %} + +### Plain Layer Options + +If you are using plain layer options as your map resource, the following configurations are required in your MapConfig file. + +_**Note:** At least one of the plain layer options (either `color` or `imageUrl`) must be defined. If both options are defined, only `color` is used as part of the plain layer configuration._ + +Plain Layer Option | Description | Optional or Required? +--- | --- +`color` | A string value of numbers that defines the valid colors to include. The default value is `null`. Valid colors include:

- A string value that includes CSS colors (i.e. `blue`) or a hex color string (i.e. `#0000ff`)

- An integer array of r,g,b values (i.e. `[255,0,0]`)

- An integer array of r,g,b,a values (i.e. `[255,0,0,128]`)

* If **only** the `color` value is used for a plain layer, this value is Required.

* If **both** `color` and `imageUrl` are defined, only the `color` value is used for the plain layer configuration.| *Both +`imageUrl` | A string value, end URL, from where the image is retrieved. The default value is `null`.

* If **only** the `imageUrl` value is used for a plain layer, this value is Required.

* If `color` is defined, this `imageUrl` value is ignored. | *Both + +#### Example of Plain MapConfig + +{% highlight json %} +{ + "type": "plain", + "options": { + "color": "blue", + "imageUrl": "http://example.com/background.png" + } +} +{% endhighlight %} + +### Named Map Layer Options + +You can use a [Named Map]({{ site.baseurl }}/carto-engine/maps-api/named-maps/#named-maps) as a map layer. Note the following limitations before referencing the MapConfig options for a Named Map layer. + +_**Limitations:**_ + +- A Named Map will not allow you to have `named` type layers inside of your template layergroup's layers definition +- A `named` layer does not allow Named Maps from other accounts. You can only use Named Maps from the _same_ user account + +If you are using `named` layer options as your map resource, the following configurations are required in your MapConfig file. + +Named Layer Option | Description | Optional or Required? +--- | --- +`name` | A string value, the name for the Named Map to use. | Required +`config` | An object, the replacement values for the Named Map's template placeholders. | Optional +`auth_tokens` | Strings array, the authorized tokens in case the Named Map has auth method set to `token`. | Optional + +#### Example of Named MapConfig + +{% highlight json %} +{ + "type": "named", + "options": { + "name": "world_borders", + "config": { + "color": "#000" + }, + "auth_tokens": ["token1", "token2"] + } +} +{% endhighlight %} + +### Aggregation Options + +The data used to render tiles, or contained in the tiles (for the case of vector tiles), can be spatially [aggregated](https://carto.com/docs/carto-engine/maps-api/named-maps/) under some circumstances. + +An `aggregation` attribute can be used in the layer `options` to control the aggregation. A value of `false` will disable aggregation for the layer. Otherwise, an object can be passed with the following aggregation parameters: + +Parameter|Description|Default value +`placement`|Determines the kind of aggregated geometry generated ("point-sample", "point-grind" or "centroid").|"centroid" +`columns`|Defines aggregated columns; each one by an "aggregate_function" ("sum", "avg", "min, "max", "mode", "count") and "aggregated_column" name.| +`resolution`|Defines the cell-size of the spatial aggregation grid.|1 (for 256x256 cells per tile) +`threshold`|Minimum rows in the dataset to apply aggregation. + +#### Example of Aggregation MapConfig + +{% highlight json %} +{ + "version": "1.7.0", + "extent": [-20037508.5, -20037508.5, 20037508.5, 20037508.5], + "srid": 3857, + "maxzoom": 18, + "minzoom": 3, + "layers": [ + { + "type": "mapnik", + "options": { + "sql": "select * from table", + "cartocss": "#table { marker-width: [total]; marker-fill: ramp(value, (red, green, blue), jenks); }", + "cartocss_version": "2.3.0", + "aggregation": { + "placement": "centroid",s + "columns": { + "value": { + "aggregate_function": "avg", + "aggregated_column": "value" + }, + "total": { + "aggregate_function": "sum", + "aggregated_column": "value" + } + }, + "resolution": 2, // Aggregation cell is 2x2 pixels + "threshold": 500000 + } + } + } + ] +} +{% endhighlight %} + +### MapConfig Requirements + +All of these are MapConfig requirements for [Anonymous Maps]({{ site.baseurl }}/carto-engine/maps-api/anonymous-maps/#retrieve-resources-from-the-layergroup). + +- Identified by `{z}/{x}/{y}` path + +- If applicable, additionally identified by `LAYER_NUMBER` + +- Can be of different formats: + - png + - grid.json + - torque.json + +- Static images/previews + - With a center or a bounding box + +- Attributes + -Identified by LAYER_NUMBER and FEATURE_ID + +**Tip:** The MapConfig file may be extended for specific uses. For example, [Windshaft-CartoDB](https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/MultiLayer-API.md) defines the addition of a `stat_tag` element in the config. This extension is also covered as part of the [Named Map Layer Options](#named-map-layer-options). \ No newline at end of file diff --git a/docs/guides/08-MapConfig-aggregation-extension.md b/docs/guides/08-MapConfig-aggregation-extension.md new file mode 100644 index 00000000..9dfb4dfa --- /dev/null +++ b/docs/guides/08-MapConfig-aggregation-extension.md @@ -0,0 +1,64 @@ +## MapConfig Aggregation Extension + +### 1. Purpose + +This specification describes an extension for +[MapConfig 1.7.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.7.0.md) version. + + +### 2. Changes over specification + +This extension introduces a new layer options for aggregated data tile generation. + +#### 2.1 Aggregation options + +The layer options attribute is extended with a new optional `aggregation` attribute. +The value of this attribute can be `false` to explicitly disable aggregation for the layer. + +```javascript +{ + aggregation: { + + // OPTIONAL + // string, defines the placement of aggregated geometries. Can be one of: + // * "point-sample", the default places geometries at a sample point (one of the aggregated geometries) + // * "point-grid" places geometries at the center of the aggregation grid cells + // * "centroid" places geometriea at the average position of the aggregated points + // See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/aggregation.md#placement for more details + placement: "point-sample", + + // OPTIONAL + // object, defines the columns of the aggregated datasets. Each property corresponds to a columns name and + // should contain an object with two properties: "aggregate_function" (one of "sum", "max", "min", "avg", "mode" or "count"), + // and "aggregated_column" (the name of a column of the original layer query or "*") + // A column defined as `"_cdb_features_count": {"aggregate_function": "count", aggregated_column: "*"}` + // is always generated in addition to the defined columns. + // The column names `cartodb_id`, `the_geom`, `the_geom_webmercator` and `_cdb_feature_count` cannot be used + // for aggregated columns, as they correspond to columns always present in the result. + // See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/aggregation.md#columns for more details + columns: { + "aggregated_column_1": { + "aggregate_function": "sum", + "aggregated_column": "original_column_1" + } + }, + + // OPTIONAL + // Number, defines the cell-size of the spatial aggregation grid as a pixel resolution power of two (1/4, 1/2,... 2, 4, 16) + // to scale from 256x256 pixels; the default is 1 corresponding to 256x256 cells per tile. + // See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/aggregation.md#resolution for more details + resolution: 1, + + // OPTIONAL + // Number, the minimum number of (estimated) rows in the dataset (query results) for aggregation to be applied. + // See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/aggregation.md#threshold for more details + threshold: 500000 + } +} +``` + +### History + +#### 1.0.0 + + - Initial version diff --git a/docs/guides/09-MapConfig-analyses-extension.md b/docs/guides/09-MapConfig-analyses-extension.md new file mode 100644 index 00000000..53500e32 --- /dev/null +++ b/docs/guides/09-MapConfig-analyses-extension.md @@ -0,0 +1,95 @@ +## MapConfig Analyses Extension + +### 1. Purpose + +This specification describes an extension for +[MapConfig 1.4.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.4.0.md) version. + + +### 2. Changes over specification + +This extension targets layers with `sql` option, including layer types: `cartodb`, `mapnik`, and `torque`. + +It extends MapConfig with a new attribute: `analyses`. + +#### 2.1 Analyses attribute + +The new analyses attribute must be an array of analyses as per [camshaft](https://github.com/CartoDB/camshaft). Each +analysis must adhere to the [camshaft-reference](https://github.com/CartoDB/camshaft/blob/0.8.0/reference/versions/0.7.0/reference.json) specification. + +Each node can have an id that can be later references to consume the query from MapConfig's layers. + +Basic analyses example: + +```javascript +[ + { + // REQUIRED + // string, `id` free identifier that can be reference from any layer + "id": "HEAD", + // REQUIRED + // string, `type` camshaft's analysis type + "type": "source", + // REQUIRED + // object, `params` will depend on `type`, check camshaft-reference for more information + "params": { + "query": "select * from your_table" + } + } +] +``` + +### 2.2. Integration with layers + +As pointed before an analysis node id can be referenced from layers to consume its output query. + +The layer consuming the output must reference it with the following option: + +``` +{ + "options": { + // REQUIRED + // object, `source` as in the future we might want to have other source options + "source": { + // REQUIRED + // string, `id` the analysis node identifier + "id": "HEAD" + } + } +} +``` + +#### 2.3. Complete example + +``` +{ + "version": "1.4.0", + "layers": [ + { + "type": "cartodb", + "options": { + "source": { + "id": "HEAD" + }, + "cartocss": "...", + "cartocss_version": "2.3.0" + } + } + ], + "analyses": [ + { + "id": "HEAD", + "type": "source", + "params": { + "query": "select * from your_table" + } + } + ] +} +``` + +### History + +#### 1.0.0 + + - Initial version diff --git a/docs/guides/10-MapConfig-dataviews-extension.md b/docs/guides/10-MapConfig-dataviews-extension.md new file mode 100644 index 00000000..7a5ab59a --- /dev/null +++ b/docs/guides/10-MapConfig-dataviews-extension.md @@ -0,0 +1,279 @@ +## MapConfig Dataviews Extension + +### 1. Purpose + +This specification describes an extension for +[MapConfig 1.4.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.4.0.md) version. + + +### 2. Changes over specification + +This extension depends on Analyses extension. It extends MapConfig with a new attribute: `dataviews`. + +It makes possible to get tabular data from analysis nodes: aggregated lists, aggregations, and histograms. + +#### 2.1. Dataview types + +##### Aggregation + +An aggregation is a list with aggregated results by a column and a given aggregation function. + +Definition +``` +{ + // REQUIRED + // string, `type` the aggregation type + “type”: “aggregation”, + // REQUIRED + // object, `options` dataview params + “options”: { + // REQUIRED + // string, `column` column name to aggregate by + “column”: “country”, + // REQUIRED + // string, `aggregation` operation to perform + “aggregation”: “count” + // OPTIONAL + // string, `aggregationColumn` column value to aggregate + // This param is required when `aggregation` is different than "count" + “aggregationColumn”: “population” + } +} +``` + +Expected output +``` +{ + "type": "aggregation", + "categories": [ + { + "category": "foo", + "value": 100 + }, + { + "category": "bar", + "value": 200 + } + ] +} +``` + +##### Histograms + +Histograms represent the data distribution for a column. + +Definition +``` +{ + // REQUIRED + // string, `type` the histogram type + “type”: “histogram”, + // REQUIRED + // object, `options` dataview params + “options”: { + // REQUIRED + // string, `column` column name to aggregate by + “column”: “name”, + // OPTIONAL + // number, `bins` how many buckets the histogram should use + “bins”: 10 + } +} +``` + +Expected output +``` +{ + "type": "histogram", + "bins": [{"bin": 0, "start": 2, "end": 2, "min": 2, "max": 2, "freq": 1}, null, null, {"bin": 3, "min": 40, "max": 44, "freq": 2}, null], + "width": 10 +} +``` + +##### Formula + +Formulas given a final value representing the whole dataset. + +Definition +``` +{ + // REQUIRED + // string, `type` the formula type + “type”: “formula”, + // REQUIRED + // object, `options` dataview params + “options”: { + // REQUIRED + // string, `column` column name to aggregate by + “column”: “name”, + // REQUIRED + // string, `aggregation` operation to perform + “operation”: “count” + } +} +``` + +Operation must be: “min”, “max”, “count”, “avg”, or “sum”. + +Result +``` +{ + "type": "formula", + "operation": "count", + "result": 1000, + "nulls": 0 +} +``` + + +#### 2.2 Dataviews attribute + +The new dataviews attribute must be a dictionary of dataviews. + +An analysis node id can be referenced from dataviews to consume its output query. + + +The layer consuming the output must reference it with the following option: + +``` +{ + // REQUIRED + // object, `source` as in the future we might want to have other source options + "source": { + // REQUIRED + // string, `id` the analysis node identifier + "id": "HEAD" + } +} +``` + +#### 2.3. Complete example + +``` +{ + "version": "1.4.0", + "layers": [ + { + "type": "cartodb", + "options": { + "source": { + "id": "HEAD" + }, + "cartocss": "...", + "cartocss_version": "2.3.0" + } + } + ], + "dataviews" { + "basic_histogram": { + "source": { + "id": "HEAD" + }, + "type": "histogram", + "options": { + "column": "pop_max" + } + } + }, + "analyses": [ + { + "id": "HEAD", + "type": "source", + "params": { + "query": "select * from your_table" + } + } + ] +} +``` + +#### 3. Filters + +Camshaft's analyses expose a filtering capability and `aggregation` and `histogram` dataviews get them for free with + this extension. Filters are available with the very dataview id, so if you have a "basic_histogram" histogram dataview + you can filter with a range filter with "basic_histogram" name. + + +#### 3.1 Filter types + +##### Category + +Allows to remove results that are not contained within a set of elements. +Initially this filter can be applied to a `numeric` or `text` columns. + +Params + +``` +{ + “accept”: [“Spain”, “Germany”] + “reject”: [“Japan”] +} +``` + +##### Range filter + +Allows to remove results that don’t satisfy numeric min and max values. +Filter is applied to a numeric column. + +Params + +``` +{ + “min”: 0, + “max”: 1000 +} +``` + +#### 3.2. How to apply filters + +Filters must be applied at map instantiation time. + +With :mapconfig as a valid MapConfig and with :filters (a valid JSON) as: + +##### Anonymous map + +`GET /api/v1/map?config=:mapconfig&filters=:filters` + +`POST /api/v1/map?filters=:filters` +with `BODY=:mapconfig` + +If in the future we need to support a bigger filters param and it doesn’t fit in the query string, + we might solve it by accepting: + +`POST /api/v1/map` +with `BODY={“config”: :mapconfig, “filters”: :filters}` + +##### Named map + +Assume :params (a valid JSON) as named maps params, like in: `{“color”: “red”}` + +`GET /api/v1/named/:name/jsonp?config=:params&filters=:filters&callback=cb` + +`POST /api/v1/named/:name?filters=:filters` +with `BODY=:params` + +If, again, in the future we need to support a bigger filters param that doesn’t fit in the query string, + we might solve it by accepting: + +`POST /api/v1/named/:name` +with `BODY={“config”: :params, “filters”: :filters}` + + +#### 3.3 Bounding box special filter + +A bounding box filter allows to remove results that don’t satisfy a geospatial range. + +The bounding box special filter is available per dataview and there is no need to create a bounding box definition as +it’s always possible to apply a bbox filter per dataview. + +A dataview can get its result filtered by bounding box by sending a bbox param in the query string, +param must be in the form `west,south,east,north`. + +So applying a bbox filter to a dataview looks like: +GET /api/v1/map/:layergroupid/dataview/:dataview_name?bbox=-90,-45,90,45 + +### History + +#### 1.0.0-alpha + + - WIP document diff --git a/docs/guides/11-Mapconfig-named-maps-extension.md b/docs/guides/11-Mapconfig-named-maps-extension.md new file mode 100644 index 00000000..001ec847 --- /dev/null +++ b/docs/guides/11-Mapconfig-named-maps-extension.md @@ -0,0 +1,58 @@ +## MapConfig Named Maps Extension + +### 1. Purpose + +This specification describes an extension for +[MapConfig 1.3.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.3.0.md) version. + + +### 2. Changes over specification + +This extension introduces a new layer type so it's possible to use a Named Map by its name as a layer. + +#### 2.1 Named layers definition + +```javascript +{ + // REQUIRED + // string, `named` is the only supported value + type: "named", + + // REQUIRED + // object, set `named` map layers configuration + options: { + + // REQUIRED + // string, the name for the Named Map to use + name: "world_borders", + + // OPTIONAL + // object, the replacement values for the Named Map's template placeholders + // See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md#instantiate-1 for more details + config: { + "color": "#000" + }, + + // OPTIONAL + // string array, the authorized tokens in case the Named Map has auth method set to `token` + // See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md#named-maps-1 for more details + auth_tokens: [ + "token1", + "token2" + ] + } +} +``` + +#### 2.2 Limitations + +1. A Named Map will not allow to have `named` type layers inside their templates layergroup's layers definition. +2. A `named` layer does not allow Named Maps form other accounts, it's only possible to use Named Maps from the very +same user account. + + +### History + +#### 1.0.0 + + - Initial version diff --git a/docs/reference/01-routes.md b/docs/reference/01-routes.md new file mode 100644 index 00000000..0ebe732b --- /dev/null +++ b/docs/reference/01-routes.md @@ -0,0 +1,114 @@ +This document list all routes available in Windshaft-cartodb Maps API server. + +## Routes list + +1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:z/:x/:y@:scale_factor?x.:format {:user(f),:token(f),:z(f),:x(f),:y(f),:scale_factor(t),:format(f)} (1)` +
Notes: Mapnik retina tiles [0] + +1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:z/:x/:y.:format {:user(f),:token(f),:z(f),:x(f),:y(f),:format(f)} (1)` +
Notes: Mapnik tiles [0] + +1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/:z/:x/:y.(:format) {:user(f),:token(f),:layer(f),:z(f),:x(f),:y(f),:format(f)} (1)` +
Notes: Per :layer rendering based on :format [0] + +1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/attributes/:fid {:user(f),:token(f),:layer(f),:fid(f)} (1)` +
Notes: Endpoint for info windows data, alternative for sql api when tables are private [0] + +1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/center/:token/:z/:lat/:lng/:width/:height.:format {:user(f),:token(f),:z(f),:lat(f),:lng(f),:width(f),:height(f),:format(f)} (1)` +
Notes: Static Maps API [0] + +1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format {:user(f),:token(f),:west(f),:south(f),:east(f),:north(f),:width(f),:height(f),:format(f)} (1)` +
Notes: Static Maps API [0] + +1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/widget/:widgetName {:user(f),:token(f),:layer(f),:widgetName(f)} (1)` +
Notes: By :widgetName per :layer widget [0] + +1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/widget/:widgetName/search {:user(f),:token(f),:layer(f),:widgetName(f)} (1)` +
Notes: By :widgetName per :layer widget search [0] + +1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)` +
Notes: Map instantiation [0] + +1. `POST (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)` +
Notes: Map instantiation [0] + +1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/jsonp {:user(f),:template_id(f)} (1)` +
Notes: Named maps JSONP instantiation [1] + +1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` +
Notes: Instantiate named map [1] + +1. `OPTIONS (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)` +
Notes: CORS [0] + +1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/:layer/:z/:x/:y.(:format) {:user(f),:template_id(f),:layer(f),:z(f),:x(f),:y(f),:0(f),:format(f)} (1)` +
Notes: Per :layer fixed URL named map tiles [1] + +1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/named/:template_id/:width/:height.:format {:user(f),:template_id(f),:width(f),:height(f),:format(f)} (1)` +
Notes: Static map for named maps [1] + +1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)` +
Notes: Create named map (w/ API KEY) [1] + +1. `PUT (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` +
Notes: Update a named map (w/ API KEY) [1] + +1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` +
Notes: Named map retrieval (w/ API KEY) [1] + +1. `DELETE (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` +
Notes: Delete named map (w/ API KEY) [1] + +1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)` +
Notes: List named maps (w/ API KEY) [1] + +1. `OPTIONS (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` +
Notes: CORS [1] + +1. `GET /health {} (1)` +
Notes: Health check + +1. `GET / {} (1)` +
Notes: Welcome message + +1. `GET /version {} (1)` +
Notes: Return relevant module versions: mapnik, grainstore, etc + + +## Optional deprecated routes + +- [0] `/tiles/layergroup` is deprecated and `/api/v1/map` should be used but we keep it for now. +- [1] `/tiles/template` is deprecated and `/api/v1/map/named` should be used but we keep it for now. + +## How to generate the list of routes + +Something like the following patch should do the trick + +```javascript +diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js +index 5f62850..bca377d 100644 +--- a/lib/cartodb/server.js ++++ b/lib/cartodb/server.js +@@ -215,6 +215,20 @@ module.exports = function(serverOptions) { + * END Routing + ******************************************************************************************************************/ + ++ var format = require('util').format; ++ var routesNotes = app._router.stack ++ .filter(function(handler) { return !!handler.route; }) ++ .map(function(handler) { ++ return format("\n1. `%s %s {%s} (1)`\n
Notes: [DEPRECATED]? ", ++ Object.keys(handler.route.methods)[0].toUpperCase(), ++ handler.route.path, ++ handler.keys.map(function(k) { ++ return format(':%s(%s)', k.name, k.optional ? 't' : 'f'); ++ }).join(',') ++ ); ++ }); ++ console.log(routesNotes.join('\n')); ++ + return app; + }; + + +``` diff --git a/docs/support/01-metrics.md b/docs/support/01-metrics.md new file mode 100644 index 00000000..bf845b7a --- /dev/null +++ b/docs/support/01-metrics.md @@ -0,0 +1,43 @@ +## Metrics + +Windshaft-cartodb metrics +========================= +See [Windshaft metrics documentation](https://github.com/CartoDB/Windshaft/blob/master/doc/metrics.md) to understand the full picture. + +The next list includes the API endpoints, each endpoint may have several inner timers, some of them are displayed within this list as subitems. Find the description for them in the Inner timers section. +## Timers +- **windshaft-cartodb.flush_cache**: time to flush the tile and sql cache +- **windshaft-cartodb.get_template**: time to retrieve an specific template +- **windshaft-cartodb.delete_template**: time to delete an specific template +- **windshaft-cartodb.get_template_list**: time to retrieve the list of owned templates +- **windshaft-cartodb.instance_template_post**: time to create a template via HTTP POST +- **windshaft-cartodb.instance_template_get**: time to create a template via HTTP GET + + TemplateMaps_instance + + createLayergroup + +There are some endpoints that are not being tracked: +- Adding a template +- Updating a template + +### Inner timers +Again, each inner timer may have several inner timers. + +- **addCacheChannel**: time to add X-Cache-Channel header based on table last modifications +- **LZMA decompress**: time to decompress request params with LZMA +- **TemplateMaps_instance**: time to retrieve a map template instance, see *getTemplate* and *authorizedByCert* +- **affectedTables**: time to check what are the affected tables for adding the cache channel, see *addCacheChannel* +- **authorize**: time to authorize a request, see *authorizedByAPIKey*, *authorizedByCert*, *authorizedBySigner* +- **authorizedByCert**: time to authorize a template instantiation +- **findLastUpdated**: time to retrieve the last update time for a list of tables, see *affectedTables* +- **generateCacheChannel**: time to generate the headers for the cache channel based on the request, see *addCacheChannel* +- **getSignerMapKey**: time to retrieve from redis the authorized user for a template map +- **getTablePrivacy**: time to retrieve from redis the privacy of a table +- **getTemplate**: time to retrieve from redis the template for a map +- **getUserMapKey**: time to retrieve from redis the user key for a map +- **incMapviewCount**: time to incremenent in redis the map views +- **mapStore_load**: time to retrieve from redis a map configuration +- **req2params.setup**: time to prepare the params from a request, see *req2params* in Windshaft documentation +- **setDBAuth**: time to retrieve from redis and set db user and db password from a user +- **setDBConn**: time to retrieve from redis and set db host and db name from a user +- **setDBParams**: time to prepare all db params to be able to connect/query a database, see *setDBAuth* and *setDBConn* +- **tablePrivacy_getUserDBName**: time to retrieve from redis the database for a user From 9259d0477153ef298227771002c0964a738a6ee4 Mon Sep 17 00:00:00 2001 From: csubira Date: Wed, 28 Feb 2018 11:22:23 +0100 Subject: [PATCH 02/84] Remove routes md --- docs/reference/01-routes.md | 39 ------------------------------------- 1 file changed, 39 deletions(-) diff --git a/docs/reference/01-routes.md b/docs/reference/01-routes.md index 0ebe732b..06cc173c 100644 --- a/docs/reference/01-routes.md +++ b/docs/reference/01-routes.md @@ -73,42 +73,3 @@ This document list all routes available in Windshaft-cartodb Maps API server. 1. `GET /version {} (1)`
Notes: Return relevant module versions: mapnik, grainstore, etc - - -## Optional deprecated routes - -- [0] `/tiles/layergroup` is deprecated and `/api/v1/map` should be used but we keep it for now. -- [1] `/tiles/template` is deprecated and `/api/v1/map/named` should be used but we keep it for now. - -## How to generate the list of routes - -Something like the following patch should do the trick - -```javascript -diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js -index 5f62850..bca377d 100644 ---- a/lib/cartodb/server.js -+++ b/lib/cartodb/server.js -@@ -215,6 +215,20 @@ module.exports = function(serverOptions) { - * END Routing - ******************************************************************************************************************/ - -+ var format = require('util').format; -+ var routesNotes = app._router.stack -+ .filter(function(handler) { return !!handler.route; }) -+ .map(function(handler) { -+ return format("\n1. `%s %s {%s} (1)`\n
Notes: [DEPRECATED]? ", -+ Object.keys(handler.route.methods)[0].toUpperCase(), -+ handler.route.path, -+ handler.keys.map(function(k) { -+ return format(':%s(%s)', k.name, k.optional ? 't' : 'f'); -+ }).join(',') -+ ); -+ }); -+ console.log(routesNotes.join('\n')); -+ - return app; - }; - - -``` From 9d2473bbe30402a06231de6800619606af0e1aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1igo=20Medina?= Date: Thu, 12 Apr 2018 23:40:39 +0200 Subject: [PATCH 03/84] Create 01-support-options.md --- docs/support/01-support-options.md | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 docs/support/01-support-options.md diff --git a/docs/support/01-support-options.md b/docs/support/01-support-options.md new file mode 100644 index 00000000..9ba4b47f --- /dev/null +++ b/docs/support/01-support-options.md @@ -0,0 +1,33 @@ +## Support Options + +Feeling stuck? There are many ways to find help. + +* Ask a question on [GIS StackExchange](https://gis.stackexchange.com/questions/tagged/carto) using the `CARTO` tag. +* [Report an issue](https://github.com/CartoDB/cartodb/issues) in Github. +* Engine Plan customers have additional access to enterprise-level support through CARTO's support representatives. + +If you just want to describe an issue or share an idea, just + +### Issues on Github + +If you think you may have found a bug, or if you have a feature request that you would like to share with the Maps API team, please [open an issue](https://github.com/CartoDB/Windshaft-cartodb/issues/new). + +### Community support on GIS Stack Exchange + +GIS Stack Exchange is the most popular community in the geospatial industry. This is a collaboratively-edited question and answer site for geospatial programmers and technicians. It is a fantastic resource for asking technical questions about developing and maintaining your application. + + +When posting a new question, please consider the following: + +* Read the GIS Stack Exchange [help](https://gis.stackexchange.com/help) and [how to ask](https://gis.stackexchange.com/help/how-to-ask) pages for guidelines and tips about posting questions. +* Be very clear about your question in the subject. A clear explanation helps those trying to answer your question, as well as those who may be looking for information in the future. +* Be informative in your post. Details, code snippets, logs, screenshots, etc. help others to understand your problem. +* Use code that demonstrates the problem. It is very hard to debug errors without sample code to reproduce the problem. + +### Engine Plan Customers + +Engine Plan customers have additional support options beyond general community support. As per your account Terms of Service, you have access to enterprise-level support through CARTO's support representatives available at [enterprise-support@carto.com](mailto:enterprise-support@carto.com) + +In order to speed up the resolution of your issue, provide as much information as possible (even if it is a link from community support). This allows our engineers to investigate your problem as soon as possible. + +If you are not yet CARTO customer, browse our [plans & pricing](https://carto.com/pricing/) and find the right plan for you. From 3cbf7412093940a636c49b7513035ccc41e5690c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1igo=20Medina?= Date: Thu, 12 Apr 2018 23:41:54 +0200 Subject: [PATCH 04/84] Create 02-contribute.md --- docs/support/02-contribute.md | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 docs/support/02-contribute.md diff --git a/docs/support/02-contribute.md b/docs/support/02-contribute.md new file mode 100644 index 00000000..a8bbfa80 --- /dev/null +++ b/docs/support/02-contribute.md @@ -0,0 +1,36 @@ +## Contribute + +CARTO platform is an open-source ecosystem. You can read about the [fundamentals]({{site.fundamental_docs}}/components/) of CARTO architecture and its components. +We are more than happy to receive your contributions to the code and the documentation as well. + +## Filling a ticket + +If you want to open a new issue in our repository, please follow these instructions: + +1. Descriptive title. +2. Write a good description, it always helps. +3. Specify the steps to reproduce the problem. +4. Try to add an example showing the problem. + +## Contributing code + +Best part of open source, collaborate in Maps API code!. We like hearing from you, so if you have any bug fixed, or a new feature ready to be merged, those are the steps you should follow: + +1. Fork the repository. +2. Create a new branch in your forked repository. +3. Commit your changes. Add new tests if it is necessary. +4. Open a pull request. +5. Any of the maintainers will take a look. +6. If everything works, it will merged and released \o/. + +If you want more detailed information, this [GitHub guide](https://guides.github.com/activities/contributing-to-open-source/) is a must. + +## Completing documentation + +Maps API documentation is located in ```docs/```. That folder is the content that appears in the [Developer Center](https://carto.com/developer-center/maps-api/). Just follow the instructions described in [contributing code](#contributing-code) and after accepting your pull request, we will make it appear online :). + +**Tip:** A convenient, easy way of proposing changes in documentation is by using the GitHub editor directly on the web. You can easily create a branch with your changes and make a PR from there. + +## Submitting contributions + +You will need to sign a Contributor License Agreement (CLA) before making a submission. [Learn more here](https://carto.com/contributions). From 69f41a0200462d0948faba5b7a3c006dcf6205a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1igo=20Medina?= Date: Thu, 12 Apr 2018 23:58:47 +0200 Subject: [PATCH 05/84] Update and rename 01-metrics.md to 04-metrics.md --- docs/support/{01-metrics.md => 04-metrics.md} | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) rename docs/support/{01-metrics.md => 04-metrics.md} (93%) diff --git a/docs/support/01-metrics.md b/docs/support/04-metrics.md similarity index 93% rename from docs/support/01-metrics.md rename to docs/support/04-metrics.md index bf845b7a..71914f55 100644 --- a/docs/support/01-metrics.md +++ b/docs/support/04-metrics.md @@ -1,11 +1,10 @@ ## Metrics -Windshaft-cartodb metrics -========================= -See [Windshaft metrics documentation](https://github.com/CartoDB/Windshaft/blob/master/doc/metrics.md) to understand the full picture. +See [metrics guide](https://github.com/CartoDB/Windshaft/blob/master/doc/metrics.md) to understand the full picture. The next list includes the API endpoints, each endpoint may have several inner timers, some of them are displayed within this list as subitems. Find the description for them in the Inner timers section. -## Timers + +### Timers - **windshaft-cartodb.flush_cache**: time to flush the tile and sql cache - **windshaft-cartodb.get_template**: time to retrieve an specific template - **windshaft-cartodb.delete_template**: time to delete an specific template From 2e19bf5b17ad0c09c65039f1156a68549e895b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1igo=20Medina?= Date: Fri, 13 Apr 2018 00:00:30 +0200 Subject: [PATCH 06/84] Create 03-rate-limiting.md --- docs/support/03-rate-limiting.md | 124 +++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 docs/support/03-rate-limiting.md diff --git a/docs/support/03-rate-limiting.md b/docs/support/03-rate-limiting.md new file mode 100644 index 00000000..82d66823 --- /dev/null +++ b/docs/support/03-rate-limiting.md @@ -0,0 +1,124 @@ +## Rate limiting + +Rate limits ensure that CARTO platform is not flooded with so many requests it does not have the time and resources to service them all. + +Of course, there is nothing we can do to prevent people from actually sending as many requests to our platform as they want, but requests over a user's rate limit will be acknowledged with an error so that the sender understands they need to lower the rate at which requests are sent before they are serviced again. + +Currently, Maps API is affected by rate limiting. + +### Per user and endpoint + +Rate limit is on a per-user basis (or more accurately described, per user access) and by endpoint. For example, suppose you have 2 different apps (with 2 different maps) and both call to the same endpoint that allows 100 requests per second. Both apps/maps "share" 100 requests per second regardless the map calling to this endpoint. + + +### How it works + +We are using the [generic cell rate algorithm](https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm), a [leaky bucket](https://en.wikipedia.org/wiki/Leaky_bucket) algorithm type. + +The main keys to keep in mind about this algorithm and our implementation are: +- We allow a request every a certain time period +``` +If an endpoint has a limit of 5 requests per second, you will have a request available every 200ms and when you spend all the available requests, you will need to wait 200ms to have another available request, instead of 1 second +``` +- Most of the endpoints are limited per second +``` +If an endpoint has a limit of 5 requests per second, after a second without requests, you will have at least 5 available requests +``` +- Most of the endpoints allow an initial burst equal to the number of requests per second +``` +If an endpoint has a limit of 5 requests per second, initially you will have 5 available requests +``` + +### Caches + +In computing, a cache is a high-speed data storage layer which stores data, typically a set of data, so that future requests for that data are served up faster than by accessing the original location. + +CARTO caching allows you to efficiently reuse previously retrieved or computed data, as the data in a cache is stored by CARTO in fast access hardware in combination with specific software to manage this. + +Resources accessed by caches don't count against the limits. That is, any request that is handled by any cache layer is out of limits. You can always know which resources are served through cache looking at the `X-Cache` HTTP Header. + + +### HTTP Headers and Response Codes + +When an application exceeds the rate limit for a given API endpoint, the API will return an HTTP `429 Too Many Requests` error. + +Use the HTTP headers in order to understand where the application is at for a given rate limit, on the method that was just utilized. Note that the HTTP headers are contextual. That is, they indicate the rate limit for the user context. If you have multiple apps (maps) accessing to their resources with the same user, HTTP headers are related to that user. + +- **Carto-Rate-Limit-Limit**: total allowed requests +- **Carto-Rate-Limit-Remaining**: remaining requests +- **Retry-After**: seconds until next available request (returns `-1` if the current request is allowed) +- **Carto-Rate-Limit-Reset**: seconds until the limit will reset to its maximum capacity + +### Tips + +We only have 1 tip: +- If you receive a rate limit error, you must wait the seconds indicated by the `Retry-After` HTTP header (most of the time will be 1 second) + +### Rate Limits Chart + +Below, you can find the values of the rate limit by user account type and endpoint. Note that endpoints not listed in the chart are disabled by default. + +#### Enterprise plans + +|Endpoint |Request |Time period |Burst | +| :--- | ---: | ---: | ---: | +| GET /api/v1/map
POST /api/v1/map |10 |1 |10 | +| GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}
GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format} |2 |1 |2 | +| GET /api/v1/map/static/named/{template_id}/{width}/{height}.{format} |10 |1 |10 | +| GET /api/v1/map/analyses/catalog |1 |1 |1 | +| GET /api/v1/map/{token}/{layer}/widget/{dataviewName}
GET /api/v1/map/{token}/dataview/{dataviewName} |20 |1 |20 | +| GET /{token}/{layer}/widget/{dataviewName}/search
GET /{token}/dataview/{dataviewName}/search |1 |1 |1 | +| GET /api/v1/map/{token}/analysis/node/{nodeId} |1 |1 |1 | +| GET /api/v1/map/{token}/{z}/{x}/{y}@{scale_factor}?x.{format}
GET /api/v1/map/{token}/{z}/{x}/{y}.{format}
GET /api/v1/map/{token}/{layer}/{z}/{x}/{y}.{format} |120
1200 |1
60 |120
600 | +| GET /api/v1/map/{token}/{layer}/attributes/{fid} |4 |1 |4 | +| GET /api/v1/map/named |1 |1 |1 | +| POST /api/v1/map/named |2 |1 |2 | +| GET /api/v1/map/named/{template_id} |15 |1 |15 | +| POST /api/v1/map/named/{template_id}
GET /api/v1/map/named/{template_id}/jsonp |2 |1 |2 | +| PUT /api/v1/map/named/{template_id} |15 |1 |15 | +| DELETE /api/v1/map/named/{template_id} |2 |1 |2 | +| GET /api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.{format} |1 |1 |1 | + + +#### Professional plans + +|Endpoint |Request |Time period |Burst | +| :--- | ---: | ---: | ---: | +| GET /api/v1/map
POST /api/v1/map |8 |1 |8 | +| GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}
GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format} |2 |1 |2 | +| GET /api/v1/map/static/named/{template_id}/{width}/{height}.{format} |8 |1 |8 | +| GET /api/v1/map/analyses/catalog |1 |1 |1 | +| GET /api/v1/map/{token}/{layer}/widget/{dataviewName}
GET /api/v1/map/{token}/dataview/{dataviewName} |18 |1 |18 | +| GET /{token}/{layer}/widget/{dataviewName}/search
GET /{token}/dataview/{dataviewName}/search |1 |1 |1 | +| GET /api/v1/map/{token}/analysis/node/{nodeId} |1 |1 |1 | +| GET /api/v1/map/{token}/{z}/{x}/{y}@{scale_factor}?x.{format}
GET /api/v1/map/{token}/{z}/{x}/{y}.{format}
GET /api/v1/map/{token}/{layer}/{z}/{x}/{y}.{format} |100
1000 |1
60 |100
500 | +| GET /api/v1/map/{token}/{layer}/attributes/{fid} |4 |1 |4 | +| GET /api/v1/map/named |1 |1 |1 | +| POST /api/v1/map/named |2 |1 |2 | +| GET /api/v1/map/named/{template_id} |12 |1 |12 | +| POST /api/v1/map/named/{template_id}
GET /api/v1/map/named/{template_id}/jsonp |2 |1 |2 | +| PUT /api/v1/map/named/{template_id} |12 |1 |12 | +| DELETE /api/v1/map/named/{template_id} |2 |1 |2 | +| GET /api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.{format} |1 |1 |1 | + + +#### Free plans + +|Endpoint |Request |Time period |Burst | +| :--- | ---: | ---: | ---: | +| GET /api/v1/map
POST /api/v1/map |2 |1 |2 | +| GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}
GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format} |1 |1 |1 | +| GET /api/v1/map/static/named/{template_id}/{width}/{height}.{format} |2 |1 |2 | +| GET /api/v1/map/analyses/catalog |1 |1 |1 | +| GET /api/v1/map/{token}/{layer}/widget/{dataviewName}
GET /api/v1/map/{token}/dataview/{dataviewName} |1 |1 |1 | +| GET /{token}/{layer}/widget/{dataviewName}/search
GET /{token}/dataview/{dataviewName}/search |1 |1 |1 | +| GET /api/v1/map/{token}/analysis/node/{nodeId} |1 |1 |1 | +| GET /api/v1/map/{token}/{z}/{x}/{y}@{scale_factor}?x.{format}
GET /api/v1/map/{token}/{z}/{x}/{y}.{format}
GET /api/v1/map/{token}/{layer}/{z}/{x}/{y}.{format} |30
150 |1
60 |30
75 | +| GET /api/v1/map/{token}/{layer}/attributes/{fid} |1 |1 |1 | +| GET /api/v1/map/named |1 |1 |1 | +| POST /api/v1/map/named |1 |1 |1 | +| GET /api/v1/map/named/{template_id} |4 |1 |4 | +| POST /api/v1/map/named/{template_id}
GET /api/v1/map/named/{template_id}/jsonp |1 |1 |1 | +| PUT /api/v1/map/named/{template_id} |2 |1 |2 | +| DELETE /api/v1/map/named/{template_id} |1 |1 |1 | +| GET /api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.{format} |1 |1 |1 | From 6b521e9985900800b5a7c2b3eaca4476547961eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1igo=20Medina?= Date: Fri, 13 Apr 2018 00:00:50 +0200 Subject: [PATCH 07/84] Rename 04-metrics.md to 05-metrics.md --- docs/support/{04-metrics.md => 05-metrics.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/support/{04-metrics.md => 05-metrics.md} (100%) diff --git a/docs/support/04-metrics.md b/docs/support/05-metrics.md similarity index 100% rename from docs/support/04-metrics.md rename to docs/support/05-metrics.md From 64fd2304a9770098eb62f8da15a8826aea5e66c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1igo=20Medina?= Date: Fri, 13 Apr 2018 00:01:32 +0200 Subject: [PATCH 08/84] Create 05-timeout-limiting.md --- docs/support/05-timeout-limiting.md | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 docs/support/05-timeout-limiting.md diff --git a/docs/support/05-timeout-limiting.md b/docs/support/05-timeout-limiting.md new file mode 100644 index 00000000..e12e6d8f --- /dev/null +++ b/docs/support/05-timeout-limiting.md @@ -0,0 +1,32 @@ +## Timeout limit + +Our APIs work following a request <-> response model. While CARTO is busy getting that action done or retrieving that information, part of our infrastructure is devoted to that process and is therefore unavailable for any other user. Typically this is not a problem, as most requests get serviced quickly enough. However, certain requests can take a long time to process, either by design (e.g., updating a huge table) or by mistake. To prevent this long-running queries from effectively blocking the usage of our platform resources, CARTO will discard requests that cannot be fulfilled in less than a certain amount of time. + +Maps API is affected by this kind of limiting. + +### Per User + +Timeout limit is on a per-user basis (or more accurately described, per user access). + +### How it works + +Every query has a statement timeout. When a request reaches that value, the response returns an error. + +### Response Codes + +When query exceeds the timeout limit, the API will return an HTTP `429 Too Many Requests` error. + +### Tips + +You are able to avoid common issues that trigger timeout limits following these actions: + +- Always use database indexes +- Try to use batch API to insert/update/delete data + +### Timeout Limits Chart + +Below, you can find the values of the timeout limit by user account type. + +|Enterprise plans |Professional plans |Free plans | +| --- | --- | --- | +| 25 seconds | 15 seconds | 5 seconds | From b117e7e2bb174d18b23e0f5a39e2ba63cc454152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1igo=20Medina?= Date: Fri, 13 Apr 2018 00:01:51 +0200 Subject: [PATCH 09/84] Rename 05-timeout-limiting.md to 04-timeout-limiting.md --- docs/support/{05-timeout-limiting.md => 04-timeout-limiting.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/support/{05-timeout-limiting.md => 04-timeout-limiting.md} (100%) diff --git a/docs/support/05-timeout-limiting.md b/docs/support/04-timeout-limiting.md similarity index 100% rename from docs/support/05-timeout-limiting.md rename to docs/support/04-timeout-limiting.md From c8273cec2cee2c8b30892943c181b14042fd059b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1igo=20Medina?= Date: Tue, 17 Apr 2018 14:43:50 +0200 Subject: [PATCH 10/84] Update 02-contribute.md --- docs/support/02-contribute.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/support/02-contribute.md b/docs/support/02-contribute.md index a8bbfa80..6052a1d1 100644 --- a/docs/support/02-contribute.md +++ b/docs/support/02-contribute.md @@ -27,7 +27,7 @@ If you want more detailed information, this [GitHub guide](https://guides.github ## Completing documentation -Maps API documentation is located in ```docs/```. That folder is the content that appears in the [Developer Center](https://carto.com/developer-center/maps-api/). Just follow the instructions described in [contributing code](#contributing-code) and after accepting your pull request, we will make it appear online :). +Maps API documentation is located in ```docs/```. That folder is the content that appears in the [Developer Center](https://carto.com/developers/maps-api/). Just follow the instructions described in [contributing code](#contributing-code) and after accepting your pull request, we will make it appear online :). **Tip:** A convenient, easy way of proposing changes in documentation is by using the GitHub editor directly on the web. You can easily create a branch with your changes and make a PR from there. From 3934e231fe9c3493cbbc58aaa16c2a268984e15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1igo=20Medina=20=28aka=20MacGyver=29?= Date: Tue, 26 Jun 2018 14:52:44 +0200 Subject: [PATCH 11/84] Rename 05-metrics.md to 06-metrics.md --- docs/support/{05-metrics.md => 06-metrics.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/support/{05-metrics.md => 06-metrics.md} (100%) diff --git a/docs/support/05-metrics.md b/docs/support/06-metrics.md similarity index 100% rename from docs/support/05-metrics.md rename to docs/support/06-metrics.md From 6e1f66ad94b7954f871821843a3777e3330a9f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1igo=20Medina=20=28aka=20MacGyver=29?= Date: Tue, 26 Jun 2018 15:08:36 +0200 Subject: [PATCH 12/84] Create 05-quota-limiting.md --- docs/support/05-quota-limiting.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 docs/support/05-quota-limiting.md diff --git a/docs/support/05-quota-limiting.md b/docs/support/05-quota-limiting.md new file mode 100644 index 00000000..6a9143ba --- /dev/null +++ b/docs/support/05-quota-limiting.md @@ -0,0 +1,16 @@ +## Quota limiting + +CARTO platform imposes limits on how much data you can store at CARTO, for every user account and organization. You can learn more about this topic by reading the [fundamentals about limits]({{site.fundamental_docs}}/limits/of the CARTO platform. + +Maps API is affected by this kind of limiting. + +### Quota Limits Chart + +Below, you can find the values of the different quota limits by user account type. + +|Limit |Enterprise plans |Professional plans |Free plans | +| :--- | ---: | ---: | ---: | +| Maximum Static Map image size |4000 X 4000 pixels |4000 X 4000 pixels |4000 X 4000 pixels | +| Maximum number of Named Maps |4096 |4096 |4096 | +| Maximum number of layers |10 |8 |8 | +| Maximum number of layers |16 |8 |8 | From 338ff631539abe6db6706e22971620b25d84e8b1 Mon Sep 17 00:00:00 2001 From: cillas Date: Thu, 16 Aug 2018 14:22:18 +0200 Subject: [PATCH 13/84] Add swagger.yaml --- docs/reference/swagger.yaml | 882 ++++++++++++++++++++++++++++++++++++ 1 file changed, 882 insertions(+) create mode 100644 docs/reference/swagger.yaml diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml new file mode 100644 index 00000000..26848826 --- /dev/null +++ b/docs/reference/swagger.yaml @@ -0,0 +1,882 @@ +openapi: 3.0.0 +info: + title: Maps API + description: > + # Introduction + + The CARTO Maps API allows you to generate maps based on data hosted in your + CARTO account and apply custom SQL and CartoCSS to the data. The API + generates a XYZ-based URL to fetch Web Mercator projected tiles using web + clients such as Leaflet, Google Maps, or OpenLayers. + + # Authorization + + In order to access Maps API you must provide an API key. The CARTO + Authorization guide explains how these keys are sent (TLDR: _HTTP basic + auth_ or _query string param_ with the API key token). Depending on the + permissions granted to the provided API key, the request will be authorized + or not. + version: '1' + contact: + name: Have you found an error? Github issues + url: 'https://github.com/CartoDB/Windshaft-cartodb/issues' +servers: + - url: 'https://{user}.{domain}/api/v1' + description: Production server (uses live data) + variables: + domain: + default: carto.com + description: 'If on premise, change it to your domain' + user: + default: username + description: Your username +tags: + - name: Anonymous Maps + description: Anonymous Maps allow you to instantiate a map given SQL and CartoCSS + externalDocs: + url: 'https://carto.com/developers/maps-api/guides/anonymous-maps/' + - name: Named Maps + description: Run a single SQL statement + externalDocs: + url: 'https://carto.com/developers/maps-api/guides/named-maps/' + - name: Static Maps + description: Create static images of parts of maps + externalDocs: + url: 'https://carto.com/developers/maps-api/guides/static-maps-API/' +paths: + /map: + post: + summary: Create map + description: | + tags: + - Anonymous Maps + operationId: instantiateAnonymousMap + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MapConfig' + example: + 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 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/AnonymousMapResponse' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + security: + - ApiKeyHTTPBasicAuth: [] + - ApiKeyQueryParam: [] + x-code-samples: + - lang: Curl + source: | + curl -X POST -H "Content-Type: application/json" -d '{ \ + "q": "SELECT count(*) FROM cities", \ + "filename": "number_of_cities.json" \ + }' "https://username.carto.com/api/v2/sql" + '/map/{layergroupid}/{z}/{x}/{y}.png': + get: + parameters: + - $ref: '#/components/parameters/layergroupId' + - $ref: '#/components/parameters/z' + - $ref: '#/components/parameters/x' + - $ref: '#/components/parameters/y' + summary: Get tile + description: | + Get a tile + tags: + - Anonymous Maps + operationId: getTile + responses: + '200': + description: Ok + content: + image/png: + schema: + type: string + format: binary + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + security: + - ApiKeyHTTPBasicAuth: [] + - ApiKeyQueryParam: [] + x-code-samples: + - lang: Curl + source: > + curl -X GET \ + + https://username.carto.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/2/3/4.png + '/map/{layergroupid}/{layers_filter}/{z}/{x}/{y}.png': + get: + parameters: + - $ref: '#/components/parameters/layergroupId' + - $ref: '#/components/parameters/layersFilter' + - $ref: '#/components/parameters/z' + - $ref: '#/components/parameters/x' + - $ref: '#/components/parameters/y' + summary: Get tile - Layer filter + description: | + tags: + - Anonymous Maps + operationId: getTileWithLayerFilter + responses: + '200': + description: Ok + content: + image/png: + schema: + type: string + format: binary + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + security: + - ApiKeyHTTPBasicAuth: [] + - ApiKeyQueryParam: [] + x-code-samples: + - lang: Curl + source: > + curl -X GET \ + + https://username.carto.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/2/3/4.png + '/map/{layergroupid}/{layer}/{z}/{x}/{y}.torque.json': + get: + parameters: + - $ref: '#/components/parameters/layergroupId' + - $ref: '#/components/parameters/layerIndex' + - $ref: '#/components/parameters/z' + - $ref: '#/components/parameters/x' + - $ref: '#/components/parameters/y' + summary: Get Torque layer + description: | + If the MapConfig had a Torque layer it could be possible to request it + tags: + - Anonymous Maps + operationId: getTorqueLayer + responses: + '200': + description: Ok + content: + application/json: + schema: + type: object + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + security: + - ApiKeyHTTPBasicAuth: [] + - ApiKeyQueryParam: [] + x-code-samples: + - lang: Curl + source: > + curl -X GET \ + + https://username.carto.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/2/3/4.png + + '/map/static/center/{layergroupid}/{z}/{lat}/{lng}/{width}/{height}.{format}': + get: + parameters: + - $ref: '#/components/parameters/layergroupId' + - $ref: '#/components/parameters/z' + - $ref: '#/components/parameters/lat' + - $ref: '#/components/parameters/lng' + - $ref: '#/components/parameters/width' + - $ref: '#/components/parameters/height' + - $ref: '#/components/parameters/format' + summary: Zoom + center + description: | + Zoom + center + tags: + - Static Maps + operationId: getStaticZoomCenter + responses: + '200': + description: Ok + content: + image/png: + schema: + type: string + format: binary + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + security: + - ApiKeyHTTPBasicAuth: [] + - ApiKeyQueryParam: [] + x-code-samples: + - lang: Curl + source: > + curl -X GET \ + + https://username.carto.com/api/v1/map/static/center/c01a54877c62831bb51720263f91fb33/4/20/40/500/500.png + + '/map/static/bbox/{layergroupid}/{west},{south},{east},{north}/{width}/{height}.{format}': + get: + parameters: + - $ref: '#/components/parameters/layergroupId' + - $ref: '#/components/parameters/west' + - $ref: '#/components/parameters/south' + - $ref: '#/components/parameters/east' + - $ref: '#/components/parameters/north' + - $ref: '#/components/parameters/width' + - $ref: '#/components/parameters/height' + - $ref: '#/components/parameters/format' + summary: Bounding Box + description: | + Bounding Box in WGS 84 (EPSG:4326), comma separated values + tags: + - Static Maps + operationId: getStaticBoundingBox + responses: + '200': + description: Ok + content: + image/png: + schema: + type: string + format: binary + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + security: + - ApiKeyHTTPBasicAuth: [] + - ApiKeyQueryParam: [] + x-code-samples: + - lang: Curl + source: > + curl -X GET \ + + https://username.carto.com/api/map/static/bbox/c01a54877c62831bb51720263f91fb33/0/0/30/30/500/500.png + '/map/static/named/{name}/{width}/{height}.{format}': + get: + parameters: + - $ref: '#/components/parameters/name' + - $ref: '#/components/parameters/width' + - $ref: '#/components/parameters/height' + - $ref: '#/components/parameters/format' + summary: Named map + description: | + Named map + tags: + - Static Maps + operationId: getStaticNamedMap + responses: + '200': + description: Ok + content: + image/png: + schema: + type: string + format: binary + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + security: + - ApiKeyHTTPBasicAuth: [] + - ApiKeyQueryParam: [] + x-code-samples: + - lang: Curl + source: > + curl -X GET \ + + https://username.carto.com/api/map/static/named/mynamedmap/500/500.png + +components: + schemas: + MapConfig: + type: object + properties: + version: + type: string + description: Spec version to use for validation. + default: 1.0.0 + extent: + type: string + description: | + The default map extent for the map projection. + **Note:** Currently, only webmercator is supported. + srid: + type: string + description: The spatial reference identifier for the map. + default: 3857 + maxzoom: + type: string + description: >- + The maximum zoom level for your map. A request beyond the defined + maxzoom returns a 404 error. + default: undefined (infinite) + minzoom: + type: string + description: >- + The minimum zoom level for your map. A request beyond the defined + minzoom returns a 404 error. + default: 0 + layers: + type: array + items: + $ref: '#/components/schemas/Layer' + required: + - layers + Layer: + type: object + properties: + type: + $ref: '#/components/schemas/LayerType' + options: + oneOf: + - $ref: '#/components/schemas/LayerOptionsMapnik' + - $ref: '#/components/schemas/LayerOptionsTorque' + - $ref: '#/components/schemas/LayerOptionsHTTP' + - $ref: '#/components/schemas/LayerOptionsPlain' + - $ref: '#/components/schemas/LayerOptionsNamedMap' + description: Sets different options for each layer type. + required: + - type + - options + LayerOptionsMapnik: + type: object + title: Layer options Mapnik + description: > + If you are using Mapnik as a layer resource, the following + configurations are required in your MapConfig file. + properties: + sql: + type: string + description: > + The SQL request to the user database that will fetch the rendered + data. + + + **Tip:** The SQL request should include the following Mapnik layer + configurations: + * ```geom_column``` + * ```interactivity``` + * ```attributes``` + + + **Note:** The SQL request may contain substitutions tokens, such as + ```!bbox!```, ```!pixel_width!``` and ```!pixel_height!```. It is + suggested to define the layergroup ```minzoom``` and ```extent``` + variables to prevent errors. + cartocss: + $ref: '#/components/schemas/CartoCSS' + cartocss_version: + $ref: '#/components/schemas/CartoCSSVersion' + geom_column: + type: string + description: > + The name of the column containing the geometry. + + + *You **must** specify this value as part of the Mapnik layer + SQLconfiguration. + default: the_geom_webmercator + geom_type: + type: string + enum: + - geometry + - raster + description: > + Defines the type of column as either _geometry_ or _raster_. + + + **Note:** ```geom_type``` is not compatible with the Mapnik layer + interactivity option. + default: geometry + raster_band: + type: string + description: > + Defines the raster band (this option is only applicable when the + ```geom_type=raster```. + + + **Note:** If the default, or no value is specified, raster bands are + interpreted as either: + * grayscale (for single bands) + * RGB (for 3 bands) + * RGBA (for 4 bands). + default: 0 + srid: + $ref: '#/components/schemas/Srid' + affected_tables: + $ref: '#/components/schemas/Affected_tables' + interactivity: + type: string + description: > + A string of values that contains the fields rendered inside + grid.json. All the parameters should be exposed as a result of + executing the Mapnik layer SQL query. + + + **Note:** interactivity is not compatible with the Mapnik layer + ```geom_type``` option. For example, you cannot create a layergroup + instance with a raster layer by defining the ```geom_type=raster```. + + *You **must** specify this value as part of the Mapnik layer SQL + configuration. + attributes: + description: > + The id and column values returned by the Mapnik attributes service. + (This option is disabled if no configuration is defined). + + *You **must** specify this value as part of the Mapnik layer SQL + configuration. + type: array + items: + type: object + properties: + id: + type: string + description: The key value used to fetch columns. + columns: + type: string + description: >- + A string of values (columns) returned by the Mapnik attribute + service. + required: + - id + - columns + required: + - sql + - cartocss + - cartocss_version + LayerOptionsTorque: + type: object + title: Layer options Torque + description: > + If you are using Torque as a layer resource, the following + configurations are required in your MapConfig file. For more details + about Torque layers in general, see the Torque API documentation. + properties: + sql: + type: string + description: > + The SQL request to the user database that will fetch the rendered + data. + + + **Tip:** The SQL request should include the following Mapnik layer + configurations: + * geom_column + * interactivity + * attributes + cartocss: + $ref: '#/components/schemas/CartoCSS' + cartocss_version: + $ref: '#/components/schemas/CartoCSSVersion' + step: + type: integer + description: >- + The number of animation steps to render when requesting a torque.png + tile. + default: 0 + geom_column: + type: string + description: > + The name of the column containing the geometry. + + + *You **must** specify this value as part of the Torque layer + SQLconfiguration. + default: the_geom_webmercator + srid: + $ref: '#/components/schemas/Srid' + affected_tables: + $ref: '#/components/schemas/Affected_tables' + attributes: + description: > + The id and column values returned by the Torque attributes service. + (This option is disabled if no configuration is defined). + + *You **must** specify this value as part of the Torque layer SQL + configuration. + type: array + items: + type: object + properties: + id: + type: string + description: The key value used to fetch columns. + columns: + type: string + description: >- + A string of values (columns) returned by the Torque attribute + service. + required: + - id + - columns + required: + - sql + - cartocss + - cartocss_version + LayerOptionsHTTP: + title: Layer options HTTP + type: object + properties: + urlTemplate: + type: string + description: > + URL from where the tile data is retrieved. _URLs must be included in + the configuration whitelist to be valid._ + + + **Note:** It includes + + * ```{z}``` as the zoom level + + * ```{x} ```and ```{y}``` as the tile coordinates + + * Optionally, the subdomain ```{s}``` may be included as part of the + ```urlTemplate``` configuration. Otherwise, you can define the + ```subdomains``` separately, as shown below. + subdomains: + type: string + description: > + A string of values used to retrieve tiles from different + subdomains. The default value is [```a```, ```b```, ```c```] when + ```{s}``` is defined in the urlTemplate configuration. Otherwise, + the default value is ```[ ]```. + + + **Note:** The subdomains value will consistently replace the + ```{s}``` value defined in the ```urlTemplate```. + tms: + type: boolean + description: > + Specifies whether the tile is using Tile Map Service format + + + **Note:** If the value is ```true```, the TMS inverses the Y axis + numbering for tiles. + default: true + tms2: + type: boolean + description: > + Specifies whether the tile is using Tile Map Service format + + + **Note:** If the value is ```true```, the TMS inverses the Y axis + numbering for tiles. + default: false + required: + - urlTemplate + LayerOptionsPlain: + title: Layer options Plain + type: object + properties: + color: + type: string + description: > + Numbers that define the valid colors to include. Valid colors: + + - A string value that includes CSS colors (i.e. ```blue```) or a hex color string (i.e. ```#0000ff```) + + - An integer array of r,g,b values (i.e. ```[255,0,0]```) + + - An integer array of r,g,b,a values (i.e. ```[255,0,0,128]```) + + + + If **only** the ```color``` value is used for a plain layer, this + value is Required. + + + If **both** ```color``` and ```imageUrl``` are defined, only the + color value is used for the plain layer configuration. + default: null + imageUrl: + type: string + description: > + URL from where the image is retrieved + + * If **only** the ```imageUrl``` value is used for a plain layer, + this value is Required. + + * If ```color``` is defined, this ```imageUrl``` value is ignored. + default: null + LayerOptionsNamedMap: + type: object + properties: + name: + type: string + description: 'A string value, the name for the Named Map to use.' + config: + type: object + description: >- + An object, the replacement values for the Named Map’s template + placeholders. + auth_tokens: + type: array + items: + type: string + description: token + description: >- + Strings array, the authorized tokens in case the Named Map has auth + method set to ```token```. + required: + - name + CartoCSS: + type: string + title: cartocss + description: > + Specifies the CartoCSS style to render the tiles. + + + **Note:** The CartoCSS specification is dependent on the layer type. For + details, see mapnik-reference.json. + CartoCSSVersion: + type: string + title: cartocss version + description: > + A string value, specifying the CartoCSS style version of the CartoCSS + attribute. + + + **Note:** The CartoCSS version is specific to the layer type. + Srid: + type: string + description: The spatial reference identifier for the geometry column. + default: 3857 + Affected_tables: + type: string + title: Affected Tables + description: > + A string of values containing the tables that the Mapnik layer SQL + configuration is using. This value is used if there is a problem + guessing what the affected tables are from the SQL configuration (i.e. + when using PL/SQL functions). + LayerType: + title: Layer type + type: string + enum: + - mapnik + - cartodb + - torque + - http + - plain + - named + description: | + A string value that defines the layer type: + * **mapnik** - rasterized tiles + * **cartodb** - an alias for mapnik (for backward compatibility) + * **torque** - render vector tiles in torque format + * **http** - load tiles over HTTP + * **plain** - color or background image url + * **named** - use a Named Map as a layer + AnonymousMapResponse: + type: object + properties: + layergroupid: + type: string + updated_at: + type: string + format: date-time + metadata: + type: object + properties: + layers: + type: array + items: + type: object + properties: + type: + $ref: '#/components/schemas/LayerType' + meta: + type: object + cdn_url: + type: object + properties: + http: + type: string + https: + type: string + securitySchemes: + ApiKeyHTTPBasicAuth: + type: http + scheme: basic + ApiKeyQueryParam: + type: apiKey + in: header + name: api_key + parameters: + layergroupId: + in: path + name: layergroupid + required: true + schema: + type: string + description: The layergroup ID. + z: + in: path + name: z + required: true + schema: + type: integer + minimum: 0 + description: Zoom level. + x: + in: path + name: x + required: true + schema: + type: integer + description: X coordinate. + 'y': + in: path + name: 'y' + required: true + schema: + type: integer + description: Y coordinate. + lng: + in: path + name: lng + required: true + schema: + type: number + format: float + description: The longitude for the center of the map. + lat: + in: path + name: lat + required: true + schema: + type: number + format: float + description: The latitude for the center of the map. + width: + in: path + name: width + required: true + schema: + type: integer + description: Width in pixels for the output image. + height: + in: path + name: height + required: true + schema: + type: integer + description: Height in pixels for the output image. + format: + in: path + name: format + required: true + schema: + type: string + enum: + - png + - jpg + description: Output image format + west: + in: path + name: west + required: true + schema: + type: number + format: float + description: LowerCorner longitude, in decimal degrees (aka most western) in WGS 84 (EPSG:4326) + south: + in: path + name: south + required: true + schema: + type: number + format: float + description: LowerCorner latitude, in decimal degrees (aka most southern) in WGS 84 (EPSG:4326) + east: + in: path + name: east + required: true + schema: + type: number + format: float + description: UpperCorner longitude, in decimal degrees (aka most eastern) in WGS 84 (EPSG:4326) + north: + in: path + name: north + required: true + schema: + type: number + format: float + description: UpperCorner latitude, in decimal degrees (aka most northern) in WGS 84 (EPSG:4326) + name: + in: path + name: name + required: true + schema: + type: string + description: The named map name + + layersFilter: + in: path + name: layers_filter + required: true + schema: + oneOf: + - type: string + title: all + - type: string + title: list of indexes + description: | + Layers to be rendered together. + + Supports 2 format options: + * a comma separated list of layer indexes (0-based). Examples: + + * **0,1,3** - will filter and blend layers with indexes 0, 1 and 3 + * **2** - only one layer + + * **all** will blend all layers in the layergroup + layerIndex: + in: path + name: layer + required: true + schema: + type: number + title: layer index + minimum: 0 + description: 0 based layer index + responses: + NotFound: + description: The specified resource was not found + Unauthorized: + description: Unauthorized. No authentication provided. + Forbidden: + description: Forbidden. The API key does not authorize this request. + BadInput: + description: Request's parameters error From 2764eb9669aa1544d79f254ff37709496cc5c1cc Mon Sep 17 00:00:00 2001 From: cillas Date: Thu, 16 Aug 2018 15:00:49 +0200 Subject: [PATCH 14/84] Delete old reference markdown --- docs/reference/01-routes.md | 75 ------------------------------------- 1 file changed, 75 deletions(-) delete mode 100644 docs/reference/01-routes.md diff --git a/docs/reference/01-routes.md b/docs/reference/01-routes.md deleted file mode 100644 index 06cc173c..00000000 --- a/docs/reference/01-routes.md +++ /dev/null @@ -1,75 +0,0 @@ -This document list all routes available in Windshaft-cartodb Maps API server. - -## Routes list - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:z/:x/:y@:scale_factor?x.:format {:user(f),:token(f),:z(f),:x(f),:y(f),:scale_factor(t),:format(f)} (1)` -
Notes: Mapnik retina tiles [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:z/:x/:y.:format {:user(f),:token(f),:z(f),:x(f),:y(f),:format(f)} (1)` -
Notes: Mapnik tiles [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/:z/:x/:y.(:format) {:user(f),:token(f),:layer(f),:z(f),:x(f),:y(f),:format(f)} (1)` -
Notes: Per :layer rendering based on :format [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/attributes/:fid {:user(f),:token(f),:layer(f),:fid(f)} (1)` -
Notes: Endpoint for info windows data, alternative for sql api when tables are private [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/center/:token/:z/:lat/:lng/:width/:height.:format {:user(f),:token(f),:z(f),:lat(f),:lng(f),:width(f),:height(f),:format(f)} (1)` -
Notes: Static Maps API [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format {:user(f),:token(f),:west(f),:south(f),:east(f),:north(f),:width(f),:height(f),:format(f)} (1)` -
Notes: Static Maps API [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/widget/:widgetName {:user(f),:token(f),:layer(f),:widgetName(f)} (1)` -
Notes: By :widgetName per :layer widget [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/widget/:widgetName/search {:user(f),:token(f),:layer(f),:widgetName(f)} (1)` -
Notes: By :widgetName per :layer widget search [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)` -
Notes: Map instantiation [0] - -1. `POST (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)` -
Notes: Map instantiation [0] - -1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/jsonp {:user(f),:template_id(f)} (1)` -
Notes: Named maps JSONP instantiation [1] - -1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` -
Notes: Instantiate named map [1] - -1. `OPTIONS (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)` -
Notes: CORS [0] - -1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/:layer/:z/:x/:y.(:format) {:user(f),:template_id(f),:layer(f),:z(f),:x(f),:y(f),:0(f),:format(f)} (1)` -
Notes: Per :layer fixed URL named map tiles [1] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/named/:template_id/:width/:height.:format {:user(f),:template_id(f),:width(f),:height(f),:format(f)} (1)` -
Notes: Static map for named maps [1] - -1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)` -
Notes: Create named map (w/ API KEY) [1] - -1. `PUT (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` -
Notes: Update a named map (w/ API KEY) [1] - -1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` -
Notes: Named map retrieval (w/ API KEY) [1] - -1. `DELETE (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` -
Notes: Delete named map (w/ API KEY) [1] - -1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)` -
Notes: List named maps (w/ API KEY) [1] - -1. `OPTIONS (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` -
Notes: CORS [1] - -1. `GET /health {} (1)` -
Notes: Health check - -1. `GET / {} (1)` -
Notes: Welcome message - -1. `GET /version {} (1)` -
Notes: Return relevant module versions: mapnik, grainstore, etc From 346189cf4cb52400b4479e55b3233b861890c302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 23 Aug 2018 16:50:47 +0200 Subject: [PATCH 15/84] Draft --- docs/reference/swagger.yaml | 221 +++++++++++++++++++++++++++++++++--- 1 file changed, 205 insertions(+), 16 deletions(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index 26848826..11f41b77 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -36,11 +36,11 @@ tags: externalDocs: url: 'https://carto.com/developers/maps-api/guides/anonymous-maps/' - name: Named Maps - description: Run a single SQL statement + description: Instantiate a map from private data, and users without an API Key can view your Named Map. externalDocs: url: 'https://carto.com/developers/maps-api/guides/named-maps/' - name: Static Maps - description: Create static images of parts of maps + description: 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. externalDocs: url: 'https://carto.com/developers/maps-api/guides/static-maps-API/' paths: @@ -88,7 +88,7 @@ paths: curl -X POST -H "Content-Type: application/json" -d '{ \ "q": "SELECT count(*) FROM cities", \ "filename": "number_of_cities.json" \ - }' "https://username.carto.com/api/v2/sql" + }' "https://username.carto.com/api/v2/sql" '/map/{layergroupid}/{z}/{x}/{y}.png': get: parameters: @@ -197,7 +197,113 @@ paths: curl -X GET \ https://username.carto.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/2/3/4.png - + 'map/named': + post: + summary: Upload template + description: | + tags: + - Named Maps + security: + - ApiKeyHTTPBasicAuth: [] + - ApiKeyQueryParam: [] + operationId: instantiateAnonymousMap + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Template' + example: + 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.7.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 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/NamedMapResponse' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + x-code-samples: + - lang: Curl + source: | + curl -X POST -H "Content-Type: application/json" -d '{ \ + "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.7.0", \ + "layers": [ \ + { \ + "type": "cartodb", \ + "options": { \ + "cartocss_version": "2.3.0", \ + "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 \ + }, \ + "preview_layers": { \ + "0": true, \ + "layer1": false \ + } \ + } \ + }' "https://{username}.carto.com/api/v1/map/named?api_key={api_key}" '/map/static/center/{layergroupid}/{z}/{lat}/{lng}/{width}/{height}.{format}': get: parameters: @@ -237,7 +343,7 @@ paths: curl -X GET \ https://username.carto.com/api/v1/map/static/center/c01a54877c62831bb51720263f91fb33/4/20/40/500/500.png - + '/map/static/bbox/{layergroupid}/{west},{south},{east},{north}/{width}/{height}.{format}': get: parameters: @@ -313,7 +419,7 @@ paths: source: > curl -X GET \ - https://username.carto.com/api/map/static/named/mynamedmap/500/500.png + https://username.carto.com/api/map/static/named/mynamedmap/500/500.png components: schemas: @@ -382,7 +488,7 @@ components: **Tip:** The SQL request should include the following Mapnik layer - configurations: + configurations: * ```geom_column``` * ```interactivity``` * ```attributes``` @@ -425,7 +531,7 @@ components: **Note:** If the default, or no value is specified, raster bands are - interpreted as either: + interpreted as either: * grayscale (for single bands) * RGB (for 3 bands) * RGBA (for 4 bands). @@ -490,7 +596,7 @@ components: **Tip:** The SQL request should include the following Mapnik layer - configurations: + configurations: * geom_column * interactivity * attributes @@ -551,7 +657,7 @@ components: type: string description: > URL from where the tile data is retrieved. _URLs must be included in - the configuration whitelist to be valid._ + the configuration whitelist to be valid._ **Note:** It includes @@ -609,8 +715,8 @@ components: - An integer array of r,g,b,a values (i.e. ```[255,0,0,128]```) - - + + If **only** the ```color``` value is used for a plain layer, this value is Required. @@ -724,6 +830,89 @@ components: type: string https: type: string + Template: + type: object + properties: + version: + type: string + description: Spec version to use for validation. + default: 0.0.1 + name: + type: string + description: There can only be one template with the same name for any user. Valid names start with a letter or a number, and only contain letters, numbers, dashes (-), or underscores (_) + auth: + type: object + properties: + method: + type: string + description: token or open + default: open + valid_tokens: + type: array + description: when `"method"` is set to `"token"`, the values listed here allow you to instantiate the Named Map. See this [example](http://docs.carto.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map. + placeholders: + type: object + description: > + Variables that can be placed in layergroup's definition (SQL or CartoCSS of any layer). Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergroup configurations](http://docs.carto.com/carto-engine/maps-api/mapconfig/#layergroup-configurations). + + Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced inside the Named Maps API. + + ```javascript + <%= my_color %> + ```` + + The set of supported placeholders for a template need to be explicitly defined with a specific type, and default value, for each placeholder. + + 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 loosely. + example: <%= my_color %> + layergroup: + type: object + description: The layergroup configurations, as specified in the template. See [MapConfig File Format](http://docs.carto.com/carto-engine/maps-api/mapconfig/) for more information + view: + type: object + description: Extra keys to specify the view area for the map. It can be used to have a static preview of a Named Map without having to instantiate it. It is possible to specify it with `center` + `zoom` or with a bounding box `bbox`. Center+zoom takes precedence over bounding box. Also it is possible to choose which layers are visible or not with `preview_layers` indicating its visibility by layer index or id (visible by default). + properties: + zoom: + type: number + description: The zoom level to use + center: + type: object + properties: + lng: + type: number + description: The longitude to use for the center + lat: + type: number + description: The latitude to use for the center + bounds: + type: object + properties: + 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) + required: + - version + - name + - auth + - placeholders + - layergroup + NamedMapResponse: + type: object + properties: + template_id: + type: string securitySchemes: ApiKeyHTTPBasicAuth: type: http @@ -817,7 +1006,7 @@ components: schema: type: number format: float - description: LowerCorner latitude, in decimal degrees (aka most southern) in WGS 84 (EPSG:4326) + description: LowerCorner latitude, in decimal degrees (aka most southern) in WGS 84 (EPSG:4326) east: in: path name: east @@ -825,7 +1014,7 @@ components: schema: type: number format: float - description: UpperCorner longitude, in decimal degrees (aka most eastern) in WGS 84 (EPSG:4326) + description: UpperCorner longitude, in decimal degrees (aka most eastern) in WGS 84 (EPSG:4326) north: in: path name: north @@ -833,7 +1022,7 @@ components: schema: type: number format: float - description: UpperCorner latitude, in decimal degrees (aka most northern) in WGS 84 (EPSG:4326) + description: UpperCorner latitude, in decimal degrees (aka most northern) in WGS 84 (EPSG:4326) name: in: path name: name @@ -856,7 +1045,7 @@ components: Layers to be rendered together. Supports 2 format options: - * a comma separated list of layer indexes (0-based). Examples: + * a comma separated list of layer indexes (0-based). Examples: * **0,1,3** - will filter and blend layers with indexes 0, 1 and 3 * **2** - only one layer From e3f6d4e9fd8c7ef03e588c989a19f13197f70466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 23 Aug 2018 17:19:45 +0200 Subject: [PATCH 16/84] Fix swagger errors --- docs/reference/swagger.yaml | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index 11f41b77..6cef6f84 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -197,7 +197,7 @@ paths: curl -X GET \ https://username.carto.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/2/3/4.png - 'map/named': + '/map/named': post: summary: Upload template description: | @@ -848,8 +848,8 @@ components: description: token or open default: open valid_tokens: - type: array - description: when `"method"` is set to `"token"`, the values listed here allow you to instantiate the Named Map. See this [example](http://docs.carto.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map. + type: string + description: when method is set to token, the values listed here allow you to instantiate the Named Map. See this [example](http://docs.carto.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map. placeholders: type: object description: > @@ -897,11 +897,20 @@ components: description: The latitude to use for the center bounds: type: object + description: View area for the map. It can be used to have a static preview with bounding box `bbox properties: - 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) + west: + type: number + description: LowerCorner longitude for the bounding box, in decimal degrees (aka most western) + south: + type: number + description: LowerCorner latitude for the bounding box, in decimal degrees (aka most southern) + east: + type: number + description: UpperCorner longitude for the bounding box, in decimal degrees (aka most eastern) + north: + type: number + description: UpperCorner latitude for the bounding box, in decimal degrees (aka most northern) required: - version - name From 409170f661aafc0ce5cde3be53a189aedf3a14e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 23 Aug 2018 18:24:17 +0200 Subject: [PATCH 17/84] Create TemplatePlaceholders --- docs/reference/swagger.yaml | 51 +++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index 6cef6f84..c8d17c71 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -851,34 +851,9 @@ components: type: string description: when method is set to token, the values listed here allow you to instantiate the Named Map. See this [example](http://docs.carto.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map. placeholders: - type: object - description: > - Variables that can be placed in layergroup's definition (SQL or CartoCSS of any layer). Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergroup configurations](http://docs.carto.com/carto-engine/maps-api/mapconfig/#layergroup-configurations). - - Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced inside the Named Maps API. - - ```javascript - <%= my_color %> - ```` - - The set of supported placeholders for a template need to be explicitly defined with a specific type, and default value, for each placeholder. - - 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 loosely. - example: <%= my_color %> + $ref: '#/components/schemas/TemplatePlaceholders' layergroup: - type: object - description: The layergroup configurations, as specified in the template. See [MapConfig File Format](http://docs.carto.com/carto-engine/maps-api/mapconfig/) for more information + $ref: '#/components/schemas/MapConfig' view: type: object description: Extra keys to specify the view area for the map. It can be used to have a static preview of a Named Map without having to instantiate it. It is possible to specify it with `center` + `zoom` or with a bounding box `bbox`. Center+zoom takes precedence over bounding box. Also it is possible to choose which layers are visible or not with `preview_layers` indicating its visibility by layer index or id (visible by default). @@ -917,6 +892,28 @@ components: - auth - placeholders - layergroup + TemplatePlaceholders: + type: object + description: > + Variables that can be placed in layergroup's definition (SQL or CartoCSS of any layer). Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergroup configurations](http://docs.carto.com/carto-engine/maps-api/mapconfig/#layergroup-configurations). Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced inside the Named Maps API. + + **Example**:```<%= my_color %>``` + + The set of supported placeholders for a template need to be explicitly defined with a specific type, and default value, for each placeholder. 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 loosely. + properties: + type: + type: string + enum: [sql_literal, sql_ident, number, css_color] + 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 + default: + type: 'string or number or hex-value' + required: + - type + - default NamedMapResponse: type: object properties: From 49d5f560a78ddf962bdcc8d9db9e3587ebb6a136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 23 Aug 2018 19:07:13 +0200 Subject: [PATCH 18/84] Extract components --- docs/reference/swagger.yaml | 89 ++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 32 deletions(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index c8d17c71..e4200c05 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -855,37 +855,7 @@ components: layergroup: $ref: '#/components/schemas/MapConfig' view: - type: object - description: Extra keys to specify the view area for the map. It can be used to have a static preview of a Named Map without having to instantiate it. It is possible to specify it with `center` + `zoom` or with a bounding box `bbox`. Center+zoom takes precedence over bounding box. Also it is possible to choose which layers are visible or not with `preview_layers` indicating its visibility by layer index or id (visible by default). - properties: - zoom: - type: number - description: The zoom level to use - center: - type: object - properties: - lng: - type: number - description: The longitude to use for the center - lat: - type: number - description: The latitude to use for the center - bounds: - type: object - description: View area for the map. It can be used to have a static preview with bounding box `bbox - properties: - west: - type: number - description: LowerCorner longitude for the bounding box, in decimal degrees (aka most western) - south: - type: number - description: LowerCorner latitude for the bounding box, in decimal degrees (aka most southern) - east: - type: number - description: UpperCorner longitude for the bounding box, in decimal degrees (aka most eastern) - north: - type: number - description: UpperCorner latitude for the bounding box, in decimal degrees (aka most northern) + $ref: '#/components/schemas/TemplateView' required: - version - name @@ -910,10 +880,65 @@ components: number: can only contain numerical representation css_color: can only contain color names or hex-values default: - type: 'string or number or hex-value' + type: string required: - type - default + TemplateView: + type: object + description: Extra keys to specify the view area for the map. It can be used to have a static preview of a Named Map without having to instantiate it. It is possible to specify it with `center` + `zoom` or with a bounding box `bbox`. Center+zoom takes precedence over bounding box. Also it is possible to choose which layers are visible or not with `preview_layers` indicating its visibility by layer index or id (visible by default). + properties: + zoom: + $ref: '#/components/schemas/TemplateViewZoom' + center: + $ref: '#/components/schemas/TemplateViewZoom' + bounds: + $ref: '#/components/schemas/TemplateViewBounds' + preview_layers: + $ref: '#/components/schemas/TemplateViewPreviewLayers' + TemplateViewZoom: + type: number + description: The zoom level to use + example: 4 + TemplateViewCenter: + type: object + properties: + lng: + type: number + description: The longitude to use for the center + lat: + type: number + description: The latitude to use for the center + example: + lng: 0 + lat: 0 + TemplateViewBounds: + type: object + description: View area for the map. It can be used to have a static preview with bounding box `bbox + properties: + west: + type: number + description: LowerCorner longitude for the bounding box, in decimal degrees (aka most western) + south: + type: number + description: LowerCorner latitude for the bounding box, in decimal degrees (aka most southern) + east: + type: number + description: UpperCorner longitude for the bounding box, in decimal degrees (aka most eastern) + north: + type: number + description: UpperCorner latitude for the bounding box, in decimal degrees (aka most northern) + example: + west: -45 + south: -45 + east: 45 + north: 45 + TemplateViewPreviewLayers: + type: object + description: Indicates which layers are visible or not by layer index or id (visible by default). + example: + 0: true + layer1: false NamedMapResponse: type: object properties: From cd75581ccb6cd4b2d160c99ea82fe94f2bb19beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 24 Aug 2018 10:42:08 +0200 Subject: [PATCH 19/84] Add list template endpoint --- docs/reference/swagger.yaml | 49 +++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index e4200c05..57a27909 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -201,11 +201,9 @@ paths: post: summary: Upload template description: | + Upload template tags: - Named Maps - security: - - ApiKeyHTTPBasicAuth: [] - - ApiKeyQueryParam: [] operationId: instantiateAnonymousMap requestBody: required: true @@ -250,6 +248,9 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' + security: + - ApiKeyQueryParam: [] + - ApiKeyHTTPBasicAuth: [] x-code-samples: - lang: Curl source: | @@ -304,6 +305,32 @@ paths: } \ } \ }' "https://{username}.carto.com/api/v1/map/named?api_key={api_key}" + get: + summary: List user's templates + description: | + List user's templates + tags: + - Named Maps + responses: + '200': + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/NamedMapResponseList' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + security: + - ApiKeyQueryParam: [] + - ApiKeyHTTPBasicAuth: [] + x-code-samples: + - lang: Curl + source: | + curl -X GET 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}' '/map/static/center/{layergroupid}/{z}/{lat}/{lng}/{width}/{height}.{format}': get: parameters: @@ -891,11 +918,15 @@ components: zoom: $ref: '#/components/schemas/TemplateViewZoom' center: - $ref: '#/components/schemas/TemplateViewZoom' + $ref: '#/components/schemas/TemplateViewCenter' bounds: $ref: '#/components/schemas/TemplateViewBounds' preview_layers: $ref: '#/components/schemas/TemplateViewPreviewLayers' + required: + - zoom + - center + - bounds TemplateViewZoom: type: number description: The zoom level to use @@ -944,13 +975,21 @@ components: properties: template_id: type: string + NamedMapResponseList: + type: object + properties: + template_ids: + type: array + items: + type: string + description: template name securitySchemes: ApiKeyHTTPBasicAuth: type: http scheme: basic ApiKeyQueryParam: type: apiKey - in: header + in: query name: api_key parameters: layergroupId: From 91002933e38086662a3f4b59aaed56c69021078c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 24 Aug 2018 11:42:46 +0200 Subject: [PATCH 20/84] Add more endpoints --- docs/reference/swagger.yaml | 137 ++++++++++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 4 deletions(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index 57a27909..b228cdb9 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -317,9 +317,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/NamedMapResponseList' + $ref: '#/components/schemas/NamedMapResponseList' '401': $ref: '#/components/responses/Unauthorized' '403': @@ -331,6 +329,131 @@ paths: - lang: Curl source: | curl -X GET 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}' + '/map/named/{template_name}': + get: + summary: Get template definition + description: Get the definition of a requested template + tags: + - Named Maps + parameters: + - $ref: '#/components/parameters/templateName' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Template' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + security: + - ApiKeyQueryParam: [] + - ApiKeyHTTPBasicAuth: [] + x-code-samples: + - lang: Curl + source: | + curl -X GET 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}' + put: + summary: Update template definition + description: Update the definition of the template + tags: + - Named Maps + parameters: + - $ref: '#/components/parameters/templateName' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/NamedMapResponse' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + security: + - ApiKeyQueryParam: [] + - ApiKeyHTTPBasicAuth: [] + x-code-samples: + - lang: Curl + source: | + curl -X PUT \ + -H 'Content-Type: application/json' \ + -d '{ \ + "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.7.0", \ + "layers": [ \ + { \ + "type": "cartodb", \ + "options": { \ + "cartocss_version": "2.3.0", \ + "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 \ + }, \ + "preview_layers": { \ + "0": true, \ + "layer1": false \ + } \ + } \ + }' \ + 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}' + delete: + summary: Delete template + description: Deletes the specified template map from the server, and disables any previously initialized versions of the map. + tags: + - Named Maps + parameters: + - $ref: '#/components/parameters/templateName' + responses: + '204': + description: No Content + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + security: + - ApiKeyQueryParam: [] + - ApiKeyHTTPBasicAuth: [] + x-code-samples: + - lang: Curl + source: | + curl -X DELETE 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}' '/map/static/center/{layergroupid}/{z}/{lat}/{lng}/{width}/{height}.{format}': get: parameters: @@ -1100,7 +1223,6 @@ components: schema: type: string description: The named map name - layersFilter: in: path name: layers_filter @@ -1130,6 +1252,13 @@ components: title: layer index minimum: 0 description: 0 based layer index + templateName: + in: path + name: template_name + required: true + schema: + type: string + description: Name of the requested template responses: NotFound: description: The specified resource was not found From 380dab14612810a3a0c40f57789e189f0d81f2a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 24 Aug 2018 12:46:00 +0200 Subject: [PATCH 21/84] Add title to components --- docs/reference/swagger.yaml | 71 +++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index b228cdb9..e58b8367 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -454,6 +454,56 @@ paths: - lang: Curl source: | curl -X DELETE 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}' + post: + summary: Instantiate a Named Map + description: Instantiating a Named Map allows you to fetch the map tiles. You can use the Maps API to instantiate, or use the CARTO.js createLayer() function. The result is an Anonymous Map + tags: + - Named Maps + parameters: + - in: query + name: auth_token + description: > + `"token"` or `"open"` ("open" is the default if not specified. Use "token" to password-protect your map) + schema: + type: string + operationId: instantiateNamedMap + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TemplateParams' + example: + color: '#ff0000' + cartodb_id: 3 + responses: + '200': + description: Ok. You can then use the layergroupid for fetching tiles and grids as you would normally + content: + application/json: + schema: + $ref: '#/components/schemas/TemplateInstantiationResponse' + example: + layergroupid: 'docs@fd2861af@c01a54877c62831bb51720263f91fb33:123456788' + last_updated: '2013-11-14T11:20:15.000Z' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + security: + - ApiKeyQueryParam: [] + - ApiKeyHTTPBasicAuth: [] + x-code-samples: + - lang: Curl + source: | + curl -X POST \ + -H 'Content-Type: application/json' \ + -d '{ \ + "color": "#ff0000", \ + "cartodb_id": 3 \ + }' \ + 'https://{username}.carto.com/api/v1/map/named/{template_name}?auth_token={auth_token}' + '/map/named/{template_name}/jsonp': '/map/static/center/{layergroupid}/{z}/{lat}/{lng}/{width}/{height}.{format}': get: parameters: @@ -982,6 +1032,7 @@ components: type: string Template: type: object + title: Template properties: version: type: string @@ -1012,8 +1063,12 @@ components: - auth - placeholders - layergroup + TemplateParams: + type: object + title: Template Parameters TemplatePlaceholders: type: object + title: Template Placeholders description: > Variables that can be placed in layergroup's definition (SQL or CartoCSS of any layer). Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergroup configurations](http://docs.carto.com/carto-engine/maps-api/mapconfig/#layergroup-configurations). Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced inside the Named Maps API. @@ -1036,6 +1091,7 @@ components: - default TemplateView: type: object + title: Template View description: Extra keys to specify the view area for the map. It can be used to have a static preview of a Named Map without having to instantiate it. It is possible to specify it with `center` + `zoom` or with a bounding box `bbox`. Center+zoom takes precedence over bounding box. Also it is possible to choose which layers are visible or not with `preview_layers` indicating its visibility by layer index or id (visible by default). properties: zoom: @@ -1052,10 +1108,12 @@ components: - bounds TemplateViewZoom: type: number + title: Template View Zoom description: The zoom level to use example: 4 TemplateViewCenter: type: object + title: Template View Center properties: lng: type: number @@ -1068,6 +1126,7 @@ components: lat: 0 TemplateViewBounds: type: object + title: Template View Bounds description: View area for the map. It can be used to have a static preview with bounding box `bbox properties: west: @@ -1089,23 +1148,35 @@ components: north: 45 TemplateViewPreviewLayers: type: object + title: Template View Preview Layers description: Indicates which layers are visible or not by layer index or id (visible by default). example: 0: true layer1: false NamedMapResponse: type: object + title: Named Map Response properties: template_id: type: string NamedMapResponseList: type: object + title: Named Map Response List properties: template_ids: type: array items: type: string description: template name + TemplateInstantiationResponse: + type: object + title: Template Instantiation Response + poperties: + layergroupid: + type: string + last_updated: + type: string + format: date-time securitySchemes: ApiKeyHTTPBasicAuth: type: http From 7c13561a4f7373504efc3bd7e0d5ff9ea94b107b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 24 Aug 2018 13:28:53 +0200 Subject: [PATCH 22/84] Add jsonp named map instantiation endpoint --- docs/reference/swagger.yaml | 57 +++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index e58b8367..b2ee06d9 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -74,7 +74,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AnonymousMapResponse' + $ref: '#/components/schemas/MapResponse' '401': $ref: '#/components/responses/Unauthorized' '403': @@ -482,17 +482,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TemplateInstantiationResponse' - example: - layergroupid: 'docs@fd2861af@c01a54877c62831bb51720263f91fb33:123456788' - last_updated: '2013-11-14T11:20:15.000Z' - '401': - $ref: '#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' - security: - - ApiKeyQueryParam: [] - - ApiKeyHTTPBasicAuth: [] + $ref: '#/components/schemas/MapResponse' x-code-samples: - lang: Curl source: | @@ -504,6 +494,38 @@ paths: }' \ 'https://{username}.carto.com/api/v1/map/named/{template_name}?auth_token={auth_token}' '/map/named/{template_name}/jsonp': + get: + summary: Instantiate a Named Map using JSONP + description: Instantiating a Named Map allows you to fetch the map tiles. The result is an Anonymous Map + tags: + - Named Maps + parameters: + - $ref: '#/components/parameters/templateName' + - in: query + name: auth_token + description: > + `"token"` or `"open"` ("open" is the default if not specified. Use "token" to password-protect your map) + schema: + type: string + - in: query + name: config + description: > + Encoded JSON with the params (variables) needed for the Named Map + schema: + type: string + - in: query + name: callback + description: > + JSON callback name + schema: + type: string + responses: + '200': + description: Ok. You can then use the layergroupid for fetching tiles and grids as you would normally + content: + application/json: + schema: + $ref: '#/components/schemas/MapResponse' '/map/static/center/{layergroupid}/{z}/{lat}/{lng}/{width}/{height}.{format}': get: parameters: @@ -1003,7 +1025,7 @@ components: * **http** - load tiles over HTTP * **plain** - color or background image url * **named** - use a Named Map as a layer - AnonymousMapResponse: + MapResponse: type: object properties: layergroupid: @@ -1168,15 +1190,6 @@ components: items: type: string description: template name - TemplateInstantiationResponse: - type: object - title: Template Instantiation Response - poperties: - layergroupid: - type: string - last_updated: - type: string - format: date-time securitySchemes: ApiKeyHTTPBasicAuth: type: http From 44c4b29ea23edda3f84262d0717c0ed65034d026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 24 Aug 2018 15:11:08 +0200 Subject: [PATCH 23/84] Adding curl example --- docs/reference/swagger.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index b2ee06d9..1eacaf5d 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -460,6 +460,7 @@ paths: tags: - Named Maps parameters: + - $ref: '#/components/parameters/templateName' - in: query name: auth_token description: > @@ -526,6 +527,11 @@ paths: application/json: schema: $ref: '#/components/schemas/MapResponse' + x-code-samples: + - lang: Curl + source: | + curl -X GET \ + 'https://{username}.carto.com/api/v1/map/named/{template_name}/jsonp?auth_token={auth_token}&callback=callback&config={"color": "#ff0000", "cartodb_id": 3}' '/map/static/center/{layergroupid}/{z}/{lat}/{lng}/{width}/{height}.{format}': get: parameters: From 158f2159b77f929190197da316f352b7c74dcfdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 24 Aug 2018 17:37:46 +0200 Subject: [PATCH 24/84] Add layer query params --- docs/reference/swagger.yaml | 40 +++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index 1eacaf5d..b57be805 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -40,7 +40,8 @@ tags: externalDocs: url: 'https://carto.com/developers/maps-api/guides/named-maps/' - name: Static Maps - description: 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. + description: > + 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. Begin by instantiating either a Named or Anonymous Map using the layergroupid token to generate static images. externalDocs: url: 'https://carto.com/developers/maps-api/guides/static-maps-API/' paths: @@ -542,9 +543,10 @@ paths: - $ref: '#/components/parameters/width' - $ref: '#/components/parameters/height' - $ref: '#/components/parameters/format' + - $ref: '#/components/parameters/layersQueryParam' summary: Zoom + center description: | - Zoom + center + Get static image by defining both the zoom level and geographic center (longitude & latitude) tags: - Static Maps operationId: getStaticZoomCenter @@ -569,9 +571,7 @@ paths: - lang: Curl source: > curl -X GET \ - - https://username.carto.com/api/v1/map/static/center/c01a54877c62831bb51720263f91fb33/4/20/40/500/500.png - + https://{username}.carto.com/api/v1/map/static/center/{layergroupid}/{z}/{lat}/{lng}/{width}/{height}.{format}?layer=all '/map/static/bbox/{layergroupid}/{west},{south},{east},{north}/{width}/{height}.{format}': get: parameters: @@ -583,9 +583,10 @@ paths: - $ref: '#/components/parameters/width' - $ref: '#/components/parameters/height' - $ref: '#/components/parameters/format' + - $ref: '#/components/parameters/layersQueryParam' summary: Bounding Box description: | - Bounding Box in WGS 84 (EPSG:4326), comma separated values + Get static image by defining bounding box in WGS 84 (EPSG:4326), comma separated values tags: - Static Maps operationId: getStaticBoundingBox @@ -611,7 +612,7 @@ paths: source: > curl -X GET \ - https://username.carto.com/api/map/static/bbox/c01a54877c62831bb51720263f91fb33/0/0/30/30/500/500.png + https://{username}.carto.com/api/map/static/bbox/{layergroupid}/{west},{south},{east},{north}/{width}/{height}.{format}?layer=all '/map/static/named/{name}/{width}/{height}.{format}': get: parameters: @@ -621,7 +622,7 @@ paths: - $ref: '#/components/parameters/format' summary: Named map description: | - Named map + Get the static image of a Named Map by defining width, height and, format. It will use the default vaules defined in the template tags: - Static Maps operationId: getStaticNamedMap @@ -646,9 +647,7 @@ paths: - lang: Curl source: > curl -X GET \ - - https://username.carto.com/api/map/static/named/mynamedmap/500/500.png - + https://{username}.carto.com/api/map/static/named/{name}/{width}/{height}.{format} components: schemas: MapConfig: @@ -1333,6 +1332,25 @@ components: * **2** - only one layer * **all** will blend all layers in the layergroup + layersQueryParam: + in: query + name: layer + schema: + oneOf: + - type: string + title: all + - type: string + title: list of indexes + description: | + Layers to be rendered together. + + Supports 2 format options: + * a comma separated list of layer indexes (0-based). Examples: + + * **0,1,3** - will filter and blend layers with indexes 0, 1 and 3 + * **2** - only one layer + + * **all** will blend all layers in the layergroup (**default value**) layerIndex: in: path name: layer From 7822f59fd4c02ae4bca119df8630736e03dc5d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 24 Aug 2018 17:40:04 +0200 Subject: [PATCH 25/84] Typo --- docs/reference/swagger.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index b57be805..3ed99417 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -611,7 +611,6 @@ paths: - lang: Curl source: > curl -X GET \ - https://{username}.carto.com/api/map/static/bbox/{layergroupid}/{west},{south},{east},{north}/{width}/{height}.{format}?layer=all '/map/static/named/{name}/{width}/{height}.{format}': get: From 02dc61b7c74c89fe9bb7d69104f0a0b8c0474d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 24 Aug 2018 18:04:16 +0200 Subject: [PATCH 26/84] Add Bad Request --- docs/reference/swagger.yaml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index 3ed99417..b4a5d604 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -245,6 +245,8 @@ paths: application/json: schema: $ref: '#/components/schemas/NamedMapResponse' + '400': + $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': @@ -319,6 +321,8 @@ paths: application/json: schema: $ref: '#/components/schemas/NamedMapResponseList' + '400': + $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': @@ -345,10 +349,14 @@ paths: application/json: schema: $ref: '#/components/schemas/Template' + '400': + $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' security: - ApiKeyQueryParam: [] - ApiKeyHTTPBasicAuth: [] @@ -370,10 +378,14 @@ paths: application/json: schema: $ref: '#/components/schemas/NamedMapResponse' + '400': + $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' security: - ApiKeyQueryParam: [] - ApiKeyHTTPBasicAuth: [] @@ -444,10 +456,14 @@ paths: responses: '204': description: No Content + '400': + $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' security: - ApiKeyQueryParam: [] - ApiKeyHTTPBasicAuth: [] @@ -485,6 +501,10 @@ paths: application/json: schema: $ref: '#/components/schemas/MapResponse' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' x-code-samples: - lang: Curl source: | @@ -528,6 +548,10 @@ paths: application/json: schema: $ref: '#/components/schemas/MapResponse' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' x-code-samples: - lang: Curl source: | @@ -558,6 +582,8 @@ paths: schema: type: string format: binary + '400': + $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': @@ -598,6 +624,8 @@ paths: schema: type: string format: binary + '400': + $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': @@ -633,6 +661,8 @@ paths: schema: type: string format: binary + '400': + $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': @@ -1367,6 +1397,8 @@ components: type: string description: Name of the requested template responses: + BadRequest: + description: The server could not understand the request due to invalid syntax. NotFound: description: The specified resource was not found Unauthorized: From 9de9c57dc2542f14a2b966ead96acd71509fceeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 27 Aug 2018 11:55:52 +0200 Subject: [PATCH 27/84] Remove meaningless sentence --- docs/reference/swagger.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index b4a5d604..523fd4ce 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -473,7 +473,7 @@ paths: curl -X DELETE 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}' post: summary: Instantiate a Named Map - description: Instantiating a Named Map allows you to fetch the map tiles. You can use the Maps API to instantiate, or use the CARTO.js createLayer() function. The result is an Anonymous Map + description: Instantiating a Named Map allows you to fetch the map tiles. The result is an Anonymous Map tags: - Named Maps parameters: From 5b30c390fd20da203a05385d1b7741d4caf43256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 27 Aug 2018 17:25:52 +0200 Subject: [PATCH 28/84] Add 500 server internal error --- docs/reference/swagger.yaml | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index 523fd4ce..c763ea85 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -80,6 +80,8 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' + '500': + $ref: '#/components/responses/InternalServerError' security: - ApiKeyHTTPBasicAuth: [] - ApiKeyQueryParam: [] @@ -117,6 +119,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' security: - ApiKeyHTTPBasicAuth: [] - ApiKeyQueryParam: [] @@ -153,6 +157,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' security: - ApiKeyHTTPBasicAuth: [] - ApiKeyQueryParam: [] @@ -189,6 +195,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' security: - ApiKeyHTTPBasicAuth: [] - ApiKeyQueryParam: [] @@ -251,6 +259,8 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' + '500': + $ref: '#/components/responses/InternalServerError' security: - ApiKeyQueryParam: [] - ApiKeyHTTPBasicAuth: [] @@ -327,6 +337,8 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' + '500': + $ref: '#/components/responses/InternalServerError' security: - ApiKeyQueryParam: [] - ApiKeyHTTPBasicAuth: [] @@ -357,6 +369,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' security: - ApiKeyQueryParam: [] - ApiKeyHTTPBasicAuth: [] @@ -386,6 +400,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' security: - ApiKeyQueryParam: [] - ApiKeyHTTPBasicAuth: [] @@ -464,6 +480,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' security: - ApiKeyQueryParam: [] - ApiKeyHTTPBasicAuth: [] @@ -505,6 +523,8 @@ paths: $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' x-code-samples: - lang: Curl source: | @@ -552,6 +572,8 @@ paths: $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' x-code-samples: - lang: Curl source: | @@ -590,6 +612,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' security: - ApiKeyHTTPBasicAuth: [] - ApiKeyQueryParam: [] @@ -632,6 +656,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' security: - ApiKeyHTTPBasicAuth: [] - ApiKeyQueryParam: [] @@ -669,6 +695,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' security: - ApiKeyHTTPBasicAuth: [] - ApiKeyQueryParam: [] @@ -1397,8 +1425,10 @@ components: type: string description: Name of the requested template responses: + InternalServerError: + description: Server encountered an unexpected condition that prevented it from fulfilling the request. BadRequest: - description: The server could not understand the request due to invalid syntax. + description: The server could not understand the request due to invalid syntax or unexpected condition. NotFound: description: The specified resource was not found Unauthorized: From 2ec0b4674ca42023e0159105f6025c79f8a1aa1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 27 Aug 2018 17:49:38 +0200 Subject: [PATCH 29/84] Add 429 too many request error --- docs/reference/swagger.yaml | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index c763ea85..85c06048 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -80,6 +80,8 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' security: @@ -119,6 +121,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' security: @@ -157,6 +161,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' security: @@ -195,6 +201,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' security: @@ -259,6 +267,8 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' security: @@ -337,6 +347,8 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' security: @@ -369,6 +381,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' security: @@ -400,6 +414,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' security: @@ -480,6 +496,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' security: @@ -523,6 +541,8 @@ paths: $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' x-code-samples: @@ -572,6 +592,8 @@ paths: $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' x-code-samples: @@ -612,6 +634,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' security: @@ -656,6 +680,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' security: @@ -695,6 +721,8 @@ paths: $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequest' '500': $ref: '#/components/responses/InternalServerError' security: @@ -1430,10 +1458,12 @@ components: BadRequest: description: The server could not understand the request due to invalid syntax or unexpected condition. NotFound: - description: The specified resource was not found + description: The specified resource was not found. Unauthorized: description: Unauthorized. No authentication provided. Forbidden: description: Forbidden. The API key does not authorize this request. BadInput: - description: Request's parameters error + description: Request's parameters error. + TooManyRequest: + description: The user has sent too many requests in a given amount of time ("rate limiting" or "database timeout"). From 94cd3d008c39971469515313e30731967b7d41dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 28 Aug 2018 09:37:16 +0200 Subject: [PATCH 30/84] Add placeholder default value --- docs/reference/swagger.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index 85c06048..a9c898f4 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -1197,10 +1197,15 @@ components: number: can only contain numerical representation css_color: can only contain color names or hex-values default: - type: string + $ref: '#/components/schemas/PlaceholderDefaultValue' required: - type - default + PlaceholderDefaultValue: + anyOf: + - type: string + - type: number + - type: boolean TemplateView: type: object title: Template View From 810ad46446a3483fe93b8beef659d8cb04a2429a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 28 Aug 2018 09:38:59 +0200 Subject: [PATCH 31/84] Simplify schemas --- docs/reference/swagger.yaml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index a9c898f4..ccb84146 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -1197,15 +1197,13 @@ components: number: can only contain numerical representation css_color: can only contain color names or hex-values default: - $ref: '#/components/schemas/PlaceholderDefaultValue' + anyOf: + - type: string + - type: number + - type: boolean required: - type - default - PlaceholderDefaultValue: - anyOf: - - type: string - - type: number - - type: boolean TemplateView: type: object title: Template View From a47c5b5568b8045b09563f13296133482ae45f1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Mart=C3=ADn?= Date: Fri, 11 Jan 2019 13:53:11 +0100 Subject: [PATCH 32/84] rate limits new values --- docs/support/03-rate-limiting.md | 79 +++++++++++++++----------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/docs/support/03-rate-limiting.md b/docs/support/03-rate-limiting.md index 82d66823..12dd4475 100644 --- a/docs/support/03-rate-limiting.md +++ b/docs/support/03-rate-limiting.md @@ -16,17 +16,17 @@ Rate limit is on a per-user basis (or more accurately described, per user access We are using the [generic cell rate algorithm](https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm), a [leaky bucket](https://en.wikipedia.org/wiki/Leaky_bucket) algorithm type. The main keys to keep in mind about this algorithm and our implementation are: -- We allow a request every a certain time period +- We allow a request every a certain time period ``` If an endpoint has a limit of 5 requests per second, you will have a request available every 200ms and when you spend all the available requests, you will need to wait 200ms to have another available request, instead of 1 second ``` -- Most of the endpoints are limited per second +- Most of the endpoints are limited per second ``` If an endpoint has a limit of 5 requests per second, after a second without requests, you will have at least 5 available requests ``` -- Most of the endpoints allow an initial burst equal to the number of requests per second +- Most of the endpoints allow an initial burst equal to the number of requests per second ``` -If an endpoint has a limit of 5 requests per second, initially you will have 5 available requests +If an endpoint has a limit of 5 requests per second, initially you will have 5 available requests ``` ### Caches @@ -44,7 +44,7 @@ When an application exceeds the rate limit for a given API endpoint, the API wil Use the HTTP headers in order to understand where the application is at for a given rate limit, on the method that was just utilized. Note that the HTTP headers are contextual. That is, they indicate the rate limit for the user context. If you have multiple apps (maps) accessing to their resources with the same user, HTTP headers are related to that user. -- **Carto-Rate-Limit-Limit**: total allowed requests +- **Carto-Rate-Limit-Limit**: total allowed requests - **Carto-Rate-Limit-Remaining**: remaining requests - **Retry-After**: seconds until next available request (returns `-1` if the current request is allowed) - **Carto-Rate-Limit-Reset**: seconds until the limit will reset to its maximum capacity @@ -63,43 +63,41 @@ Below, you can find the values of the rate limit by user account type and endpoi |Endpoint |Request |Time period |Burst | | :--- | ---: | ---: | ---: | | GET /api/v1/map
POST /api/v1/map |10 |1 |10 | -| GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}
GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format} |2 |1 |2 | -| GET /api/v1/map/static/named/{template_id}/{width}/{height}.{format} |10 |1 |10 | -| GET /api/v1/map/analyses/catalog |1 |1 |1 | -| GET /api/v1/map/{token}/{layer}/widget/{dataviewName}
GET /api/v1/map/{token}/dataview/{dataviewName} |20 |1 |20 | -| GET /{token}/{layer}/widget/{dataviewName}/search
GET /{token}/dataview/{dataviewName}/search |1 |1 |1 | -| GET /api/v1/map/{token}/analysis/node/{nodeId} |1 |1 |1 | -| GET /api/v1/map/{token}/{z}/{x}/{y}@{scale_factor}?x.{format}
GET /api/v1/map/{token}/{z}/{x}/{y}.{format}
GET /api/v1/map/{token}/{layer}/{z}/{x}/{y}.{format} |120
1200 |1
60 |120
600 | -| GET /api/v1/map/{token}/{layer}/attributes/{fid} |4 |1 |4 | -| GET /api/v1/map/named |1 |1 |1 | -| POST /api/v1/map/named |2 |1 |2 | -| GET /api/v1/map/named/{template_id} |15 |1 |15 | -| POST /api/v1/map/named/{template_id}
GET /api/v1/map/named/{template_id}/jsonp |2 |1 |2 | -| PUT /api/v1/map/named/{template_id} |15 |1 |15 | -| DELETE /api/v1/map/named/{template_id} |2 |1 |2 | -| GET /api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.{format} |1 |1 |1 | +| GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}
GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format} |3 |1 |3 | +| GET /api/v1/map/static/named/{template_id}/{width}/{height}.{format} |3 |1 |3 | +| GET /api/v1/map/{token}/{layer}/widget/{dataviewName}
GET /api/v1/map/{token}/dataview/{dataviewName} |25 |1 |25 | +| GET /{token}/{layer}/widget/{dataviewName}/search
GET /{token}/dataview/{dataviewName}/search |3 |1 |3 | +| GET /api/v1/map/{token}/analysis/node/{nodeId} |3 |1 |3 | +| GET /api/v1/map/{token}/{z}/{x}/{y}@{scale_factor}?x.{format}
GET /api/v1/map/{token}/{z}/{x}/{y}.{format}
GET /api/v1/map/{token}/{layer}/{z}/{x}/{y}.{format} |120
1500 |1
60 |120
750 | +| GET /api/v1/map/{token}/{layer}/attributes/{fid} |10 |1 |10 | +| GET /api/v1/map/named |3 |1 |3 | +| POST /api/v1/map/named |3 |1 |3 | +| GET /api/v1/map/named/{template_id} |10 |1 |10 | +| POST /api/v1/map/named/{template_id}
GET /api/v1/map/named/{template_id}/jsonp |10 |1 |10 | +| PUT /api/v1/map/named/{template_id} |10 |1 |10 | +| DELETE /api/v1/map/named/{template_id} |3 |1 |3 | +| GET /api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.{format} |25 |1 |25 | #### Professional plans |Endpoint |Request |Time period |Burst | | :--- | ---: | ---: | ---: | -| GET /api/v1/map
POST /api/v1/map |8 |1 |8 | -| GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}
GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format} |2 |1 |2 | -| GET /api/v1/map/static/named/{template_id}/{width}/{height}.{format} |8 |1 |8 | -| GET /api/v1/map/analyses/catalog |1 |1 |1 | -| GET /api/v1/map/{token}/{layer}/widget/{dataviewName}
GET /api/v1/map/{token}/dataview/{dataviewName} |18 |1 |18 | +| GET /api/v1/map
POST /api/v1/map |5 |1 |5 | +| GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}
GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format} |1 |1 |1 | +| GET /api/v1/map/static/named/{template_id}/{width}/{height}.{format} |1 |1 |1 | +| GET /api/v1/map/{token}/{layer}/widget/{dataviewName}
GET /api/v1/map/{token}/dataview/{dataviewName} |15 |1 |15 | | GET /{token}/{layer}/widget/{dataviewName}/search
GET /{token}/dataview/{dataviewName}/search |1 |1 |1 | | GET /api/v1/map/{token}/analysis/node/{nodeId} |1 |1 |1 | -| GET /api/v1/map/{token}/{z}/{x}/{y}@{scale_factor}?x.{format}
GET /api/v1/map/{token}/{z}/{x}/{y}.{format}
GET /api/v1/map/{token}/{layer}/{z}/{x}/{y}.{format} |100
1000 |1
60 |100
500 | -| GET /api/v1/map/{token}/{layer}/attributes/{fid} |4 |1 |4 | +| GET /api/v1/map/{token}/{z}/{x}/{y}@{scale_factor}?x.{format}
GET /api/v1/map/{token}/{z}/{x}/{y}.{format}
GET /api/v1/map/{token}/{layer}/{z}/{x}/{y}.{format} |40
600 |1
60 |40
300 | +| GET /api/v1/map/{token}/{layer}/attributes/{fid} |5 |1 |5 | | GET /api/v1/map/named |1 |1 |1 | -| POST /api/v1/map/named |2 |1 |2 | -| GET /api/v1/map/named/{template_id} |12 |1 |12 | -| POST /api/v1/map/named/{template_id}
GET /api/v1/map/named/{template_id}/jsonp |2 |1 |2 | -| PUT /api/v1/map/named/{template_id} |12 |1 |12 | -| DELETE /api/v1/map/named/{template_id} |2 |1 |2 | -| GET /api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.{format} |1 |1 |1 | +| POST /api/v1/map/named |1 |1 |1 | +| GET /api/v1/map/named/{template_id} |5 |1 |5 | +| POST /api/v1/map/named/{template_id}
GET /api/v1/map/named/{template_id}/jsonp |5 |1 |5 | +| PUT /api/v1/map/named/{template_id} |5 |1 |5 | +| DELETE /api/v1/map/named/{template_id} |1 |1 |1 | +| GET /api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.{format} |10 |1 |10 | #### Free plans @@ -108,17 +106,16 @@ Below, you can find the values of the rate limit by user account type and endpoi | :--- | ---: | ---: | ---: | | GET /api/v1/map
POST /api/v1/map |2 |1 |2 | | GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}
GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format} |1 |1 |1 | -| GET /api/v1/map/static/named/{template_id}/{width}/{height}.{format} |2 |1 |2 | -| GET /api/v1/map/analyses/catalog |1 |1 |1 | -| GET /api/v1/map/{token}/{layer}/widget/{dataviewName}
GET /api/v1/map/{token}/dataview/{dataviewName} |1 |1 |1 | +| GET /api/v1/map/static/named/{template_id}/{width}/{height}.{format} |1 |1 |1 | +| GET /api/v1/map/{token}/{layer}/widget/{dataviewName}
GET /api/v1/map/{token}/dataview/{dataviewName} |10 |1 |10 | | GET /{token}/{layer}/widget/{dataviewName}/search
GET /{token}/dataview/{dataviewName}/search |1 |1 |1 | | GET /api/v1/map/{token}/analysis/node/{nodeId} |1 |1 |1 | -| GET /api/v1/map/{token}/{z}/{x}/{y}@{scale_factor}?x.{format}
GET /api/v1/map/{token}/{z}/{x}/{y}.{format}
GET /api/v1/map/{token}/{layer}/{z}/{x}/{y}.{format} |30
150 |1
60 |30
75 | -| GET /api/v1/map/{token}/{layer}/attributes/{fid} |1 |1 |1 | +| GET /api/v1/map/{token}/{z}/{x}/{y}@{scale_factor}?x.{format}
GET /api/v1/map/{token}/{z}/{x}/{y}.{format}
GET /api/v1/map/{token}/{layer}/{z}/{x}/{y}.{format} |20
600 |1
60 |20
300 | +| GET /api/v1/map/{token}/{layer}/attributes/{fid} |2 |1 |2 | | GET /api/v1/map/named |1 |1 |1 | | POST /api/v1/map/named |1 |1 |1 | -| GET /api/v1/map/named/{template_id} |4 |1 |4 | -| POST /api/v1/map/named/{template_id}
GET /api/v1/map/named/{template_id}/jsonp |1 |1 |1 | +| GET /api/v1/map/named/{template_id} |2 |1 |2 | +| POST /api/v1/map/named/{template_id}
GET /api/v1/map/named/{template_id}/jsonp |2 |1 |2 | | PUT /api/v1/map/named/{template_id} |2 |1 |2 | | DELETE /api/v1/map/named/{template_id} |1 |1 |1 | -| GET /api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.{format} |1 |1 |1 | +| GET /api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.{format} |10 |1 |10 | From 85e19bf16c43b96f925103080899c638b25ebbf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 21 Feb 2019 17:34:29 +0100 Subject: [PATCH 33/84] Drop suppor for Node.js 6, npm 3, yarn and redis 3 --- .travis.yml | 84 +- HOWTO_RELEASE | 2 +- INSTALL.md | 43 +- NEWS.md | 10 +- README.md | 22 +- carto-package.json | 5 +- package-lock.json | 2 +- package.json | 9 +- run_tests_docker.sh | 19 +- scripts/install.sh | 2 +- yarn.lock | 2720 ------------------------------------------- 11 files changed, 38 insertions(+), 2880 deletions(-) delete mode 100644 yarn.lock diff --git a/.travis.yml b/.travis.yml index ceba1f2c..fc228792 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,86 +5,4 @@ jobs: - docker language: generic before_install: docker pull carto/nodejs-xenial-pg101:latest - script: npm run docker-test -- 6.9.2 - - sudo: required - services: - - docker - language: generic - before_install: docker pull carto/nodejs-xenial-pg101:latest - script: npm run docker-test -- 10.15.1 - - dist: precise - addons: - postgresql: "9.5" - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - pkg-config - - libcairo2-dev - - libjpeg8-dev - - libgif-dev - - libpango1.0-dev - - g++-4.9 - - wget - - before_install: - # Add custom PPAs from cartodb - - sudo add-apt-repository -y ppa:cartodb/postgresql-9.5 - - sudo add-apt-repository -y ppa:cartodb/gis - - sudo add-apt-repository -y ppa:cartodb/gis-testing - - - sudo apt-get update - - # Force instalation of libgeos-3.5.0 (presumably needed because of existing version of postgis) - - sudo apt-get -y install libgeos-3.5.0=3.5.0-1cdb2 - - # Install postgres db and build deps - - sudo /etc/init.d/postgresql stop # stop travis default instance - - sudo apt-get -y remove --purge postgresql-9.1 - - sudo apt-get -y remove --purge postgresql-9.2 - - sudo apt-get -y remove --purge postgresql-9.3 - - sudo apt-get -y remove --purge postgresql-9.4 - - sudo apt-get -y remove --purge postgresql-9.5 - - sudo apt-get -y remove --purge postgresql-9.6 - - sudo rm -rf /var/lib/postgresql/ - - sudo rm -rf /var/log/postgresql/ - - sudo rm -rf /etc/postgresql/ - - sudo apt-get -y remove --purge postgis-2.2 - - sudo apt-get -y autoremove - - sudo apt-get -y install postgresql-9.5=9.5.2-3cdb3 - - sudo apt-get -y install postgresql-server-dev-9.5=9.5.2-3cdb3 - - sudo apt-get -y install postgresql-plpython-9.5=9.5.2-3cdb3 - - sudo apt-get -y install postgresql-9.5-postgis-scripts=2.2.2.0-cdb2 - - sudo apt-get -y install postgresql-9.5-postgis-2.2=2.2.2.0-cdb2 - - # configure it to accept local connections from postgres - - echo -e "# TYPE DATABASE USER ADDRESS METHOD \nlocal all postgres trust\nlocal all all trust\nhost all all 127.0.0.1/32 trust" \ - | sudo tee /etc/postgresql/9.5/main/pg_hba.conf - - sudo /etc/init.d/postgresql restart 9.5 - - - createdb template_postgis - - createuser publicuser - - psql -c "CREATE EXTENSION postgis" template_postgis - - - psql -c "select version();" template_postgis - - psql -c "select postgis_version();" template_postgis - - # install yarn 0.27.5 - - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 0.27.5 - - export PATH="$HOME/.yarn/bin:$PATH" - - # instal redis 4 - - wget http://download.redis.io/releases/redis-4.0.8.tar.gz - - tar xvzf redis-4.0.8.tar.gz - - cd redis-4.0.8 - - make - - sudo make install - - cd .. - - rm redis-4.0.8.tar.gz - - env: - - NPROCS=1 JOBS=1 PGUSER=postgres CXX=g++-4.9 - - language: node_js - node_js: - - "6" + script: npm run docker-test -- 10.15.1 # Node.js version diff --git a/HOWTO_RELEASE b/HOWTO_RELEASE index 440af045..0a398d01 100644 --- a/HOWTO_RELEASE +++ b/HOWTO_RELEASE @@ -2,7 +2,7 @@ 2. Ensure proper version in package.json and package-lock.json 3. Ensure NEWS section exists for the new version, review it, add release date 4. If there are modified dependencies in package.json, update them with `npm upgrade {{package_name}}@{{version}}` -5. Commit package.json, package-lock.json (or yarn.lock), NEWS +5. Commit package.json, package-lock.json, NEWS 6. git tag -a Major.Minor.Patch # use NEWS section as content 7. Stub NEWS/package for next version diff --git a/INSTALL.md b/INSTALL.md index 1b796a5b..4b9e6fbe 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -4,32 +4,21 @@ Make sure that you have the requirements needed. These are - Core - - Node >= 10.14.2 or 6.9.2 - - npm >= 6.4.1 or yarn >= 0.27.5 - - PostgreSQL >8.3.x, PostGIS >1.5.x - - Redis >2.4.0 (http://www.redis.io) - - Mapnik >3.x. See [Installing Mapnik](https://github.com/CartoDB/Windshaft#installing-mapnik). + - Node >= 10 + - npm >= 6 + - gcc == 4.9 + - PostgreSQL >= 9.5 + - PostGIS >= 2.2 + - CartoDB Postgres Extension == 0.24.1 + - Redis >= 4 + - Mapnik == 3.0.15.9. 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`) +- For cache control - Varnish (http://www.varnish-cache.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 ## +## PostGIS setup A `template_postgis` database is expected. One can be set up with @@ -38,21 +27,15 @@ createdb --owner postgres --template template0 template_postgis psql -d template_postgis -c 'CREATE EXTENSION postgis;' ``` -## Build/install ## +## Build/install To fetch and build all node-based dependencies, run: -- Node.js >= 10.14.2: ```shell -npm ci +npm install ``` -- Node.js 6.9.2: -```shell -yarn -``` - -Note that the ```npm``` (or ```yarn```) step will populate the node_modules/ +Note that the ```npm``` 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 diff --git a/NEWS.md b/NEWS.md index c358eabd..034f956f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,9 +1,13 @@ # Changelog -**Deprecation warning**: Next major release will drop support for `Node.js 6 LTS`, `npm 3.x` and `yarn`. You'll be able to use the latest ES features as soon as we release 7.0.0. In the meantime, as a developer, you should keep compatibility with Node.js 6 LTS and keep updated both `package-lock.json` and `yarn.lock` files. +## 7.0.0 +Released 2018-mm-dd -## 6.6.0 -Released 2019-mm-dd +Breaking changes: +- Drop support for Postgres 9.5 +- Drop support for Node.js 6 +- Drop support for npm 3 +- Stop supporting `yarn.lock` Announcements: - Update docs: compatible Node.js and npm versions diff --git a/README.md b/README.md index 661db31d..79a17cbd 100644 --- a/README.md +++ b/README.md @@ -31,21 +31,11 @@ Upgrading Checkout your commit/branch. If you need to reinstall dependencies (you can check [NEWS](NEWS.md)) do the following: -- Node.js >= 10.14.2: ```sh $ rm -rf node_modules $ npm install ``` -- Node.js 6.9.2: -``` -$ rm -rf node_modules -$ yarn -``` - -Run ---- - ``` node app.js ``` @@ -79,20 +69,12 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). ### Developing with a custom windshaft version If you plan or want to use a custom / not released yet version of windshaft (or any other dependency) the best option is -to use `yarn link`. You can read more about it at [yarn-link: Symlink a package folder](https://yarnpkg.com/en/docs/cli/link). +to use `npm link`. You can read more about it at [npm-link: Symlink a package folder](https://docs.npmjs.com/cli/link.html). **Quick start**: -- Node.js >= 10.14.2: ```shell -~/windshaft-directory $ npm ci +~/windshaft-directory $ npm install ~/windshaft-directory $ npm link ~/windshaft-cartodb-directory $ npm link windshaft ``` - -- Node.js 6.9.2: -```shell -~/windshaft-directory $ yarn -~/windshaft-directory $ yarn link -~/windshaft-cartodb-directory $ yarn link windshaft -``` diff --git a/carto-package.json b/carto-package.json index b89a78ae..0aa99fc6 100644 --- a/carto-package.json +++ b/carto-package.json @@ -2,13 +2,12 @@ "name": "carto_windshaft", "current_version": { "requires": { - "node": ">=6.9.2 <11.0.0", - "yarn": ">=0.27.5 <1.0.0", + "node": ">=10.15.1", "mapnik": "==3.0.15.9", "crankshaft": "~0.8.1" }, "works_with": { - "redis": ">=3.0.0", + "redis": ">=4.0.0", "postgresql": ">=9.5.0", "postgis": ">=2.2.0.0", "carto_postgresql_ext": ">=0.19.0" diff --git a/package-lock.json b/package-lock.json index fc4803a9..5d4ae011 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "windshaft-cartodb", - "version": "6.6.0", + "version": "7.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index af34abff..403a127e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "windshaft-cartodb", - "version": "6.6.0", + "version": "7.0.0", "description": "A map tile server for CartoDB", "keywords": [ "cartodb" @@ -66,13 +66,12 @@ "lint": "jshint lib test app.js", "preinstall": "make pre-install", "test": "make test-all", - "update-internal-deps": "rm -rf node_modules && rm -f yarn.lock && yarn", + "update-internal-deps": "rm -rf node_modules && npm install", "docker-test": "./docker-test.sh", "docker-bash": "./docker-bash.sh" }, "engines": { - "node": "^6.9.2 || ^10.12.0", - "npm": "^3.10.9 || ^6.4.1", - "yarn": "0.27.5" + "node": "^10.15.1", + "npm": "^6.4.1" } } diff --git a/run_tests_docker.sh b/run_tests_docker.sh index 7aa8c60b..ee347b30 100644 --- a/run_tests_docker.sh +++ b/run_tests_docker.sh @@ -7,19 +7,12 @@ source /src/nodejs-install.sh echo "Node.js version: " node -v -# install dependencies -if [ "$NODEJS_VERSION" = "6.9.2" ]; -then - npm install -g yarn@0.27.5 - echo "yarn version on install:" - yarn --version - yarn -else - echo "npm version:" - npm -v - npm ci - npm ls -fi +echo "npm version: " +npm -v + +echo "Clean install: " +npm ci +npm ls # run tests npm test diff --git a/scripts/install.sh b/scripts/install.sh index 6f6e1fb1..f596aa12 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -4,4 +4,4 @@ if [[ "$OSTYPE" == "darwin"* ]]; then export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/opt/X11/lib/pkgconfig fi -yarn +npm install diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index ca422332..00000000 --- a/yarn.lock +++ /dev/null @@ -1,2720 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@carto/fqdn-sync@0.2.2": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@carto/fqdn-sync/-/fqdn-sync-0.2.2.tgz#cd7c645ed66690a09249b554d254a0f53963b2dc" - -"@carto/mapnik@3.6.2-carto.11": - version "3.6.2-carto.11" - resolved "https://registry.yarnpkg.com/@carto/mapnik/-/mapnik-3.6.2-carto.11.tgz#f2f0bc4d0051080169267c5c729b90c6bc934661" - dependencies: - mapnik-vector-tile cartodb/mapnik-vector-tile#v1.6.1-carto.2 - nan "2.10.0" - node-pre-gyp "0.10.0" - -"@carto/tilelive-bridge@github:cartodb/tilelive-bridge#2.5.1-cdb11": - version "2.5.1-cdb11" - resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/e35ae36a6e2d555a6b312440f7e1904c5ad03664" - dependencies: - "@carto/mapnik" "3.6.2-carto.11" - "@mapbox/sphericalmercator" "~1.0.1" - mapnik-pool "~0.1.3" - -"@mapbox/sphericalmercator@~1.0.1": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@mapbox/sphericalmercator/-/sphericalmercator-1.0.5.tgz#70237b9774095ed1cfdbcea7a8fd1fc82b2691f2" - -"abaculus@github:cartodb/abaculus#2.0.3-cdb13": - version "2.0.3-cdb13" - resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/3283c523d8bd4bdec78d90166d9c23ff09865ca8" - dependencies: - "@carto/mapnik" "3.6.2-carto.11" - d3-queue "^2.0.2" - sphericalmercator "1.0.5" - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - -abbrev@1.0.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" - -accepts@~1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" - dependencies: - mime-types "~2.1.18" - negotiator "0.6.1" - -ajv@^5.1.0: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - -ajv@^6.5.5: - version "6.9.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1" - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - dependencies: - sprintf-js "~1.0.2" - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - -async@1.x, async@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - -async@^2.5.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" - dependencies: - lodash "^4.17.10" - -async@~0.2.0: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - -aws4@^1.6.0, aws4@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - -basic-auth@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba" - dependencies: - safe-buffer "5.1.1" - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - dependencies: - tweetnacl "^0.14.3" - -bindings@^1.2.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.4.0.tgz#909efa49f2ebe07ecd3cb136778f665052040127" - dependencies: - file-uri-to-path "1.0.0" - -body-parser@1.18.2: - version "1.18.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" - dependencies: - bytes "3.0.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.1" - http-errors "~1.6.2" - iconv-lite "0.4.19" - on-finished "~2.3.0" - qs "6.5.1" - raw-body "2.3.2" - type-is "~1.6.15" - -body-parser@1.18.3: - version "1.18.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" - dependencies: - bytes "3.0.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "~1.6.3" - iconv-lite "0.4.23" - on-finished "~2.3.0" - qs "6.5.2" - raw-body "2.3.3" - type-is "~1.6.16" - -boom@4.x.x: - version "4.3.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" - dependencies: - hoek "4.x.x" - -boom@5.x.x: - version "5.2.0" - resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" - dependencies: - hoek "4.x.x" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - -buffer-writer@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08" - -bunyan@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.1.tgz#68c6a4a502d5620bc9f72d6736810c1b1898097f" - optionalDependencies: - dtrace-provider "~0.6" - moment "^2.10.6" - mv "~2" - safe-json-stringify "~1" - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - -camelcase@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" - -camelcase@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - -camshaft@0.63.4: - version "0.63.4" - resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.63.4.tgz#1bedde8d0088da493f39175b7848639e37d0b961" - dependencies: - async "^1.5.2" - bunyan "1.8.1" - cartodb-psql "0.13.1" - debug "^3.1.0" - dot "^1.0.3" - request "2.85.0" - -"canvas@github:cartodb/node-canvas#1.6.2-cdb3": - version "1.6.2-cdb3" - resolved "https://codeload.github.com/cartodb/node-canvas/tar.gz/45bd3c7cc5e6f99ba75b0d417b6ac6384b43083d" - dependencies: - nan "^2.4.0" - -carto@0.16.3: - version "0.16.3" - resolved "https://registry.yarnpkg.com/carto/-/carto-0.16.3.tgz#f42df84557ff28a9036af716660b1e721cedc8c0" - dependencies: - chroma-js "~1.1.1" - husl "^6.0.1" - js-yaml "^3.4.6" - lodash "^4.5.1" - mapnik-reference "~8.5.3" - semver "^5.1.0" - yargs "^4.2.0" - -"carto@github:cartodb/carto#0.15.1-cdb5", "carto@github:cartodb/carto#master": - version "0.15.1-cdb5" - resolved "https://codeload.github.com/cartodb/carto/tar.gz/85881d99dd7fcf2c4e16478b04db67108d27a50c" - dependencies: - mapnik-reference "~6.0.2" - optimist "~0.6.0" - underscore "1.8.3" - -cartocolor@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cartocolor/-/cartocolor-4.0.0.tgz#841a3222d8b5b22718d9d545b1e5b972cb26eb36" - dependencies: - colorbrewer "1.0.0" - -cartodb-psql@0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/cartodb-psql/-/cartodb-psql-0.13.1.tgz#74abfe71c9a5e0ad3bf287b66b90d13772f25544" - dependencies: - debug "^3.1.0" - pg "github:cartodb/node-postgres#6.4.2-cdb2" - underscore "~1.6.0" - -cartodb-query-tables@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cartodb-query-tables/-/cartodb-query-tables-0.4.0.tgz#c323d99358565fc74bf6eddd9a9ad6f91151d2b2" - -cartodb-redis@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cartodb-redis/-/cartodb-redis-2.1.0.tgz#a38fac99f90bfee16b21b1e698182d6127952f29" - dependencies: - dot "~1.0.2" - redis-mpool "0.7.0" - underscore "~1.6.0" - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - -chai@^4.1.2: - version "4.2.0" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" - dependencies: - assertion-error "^1.1.0" - check-error "^1.0.2" - deep-eql "^3.0.1" - get-func-name "^2.0.0" - pathval "^1.1.0" - type-detect "^4.0.5" - -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -check-error@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" - -chownr@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" - -chroma-js@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-1.1.1.tgz#9bb9434959336ece75700aaadfeedc71806d8c05" - -cli@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cli/-/cli-1.0.1.tgz#22817534f24bfa4950c34d532d48ecbc621b8c14" - dependencies: - exit "0.1.2" - glob "^7.1.1" - -cliui@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" - -cliui@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" - dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi "^2.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - -colorbrewer@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/colorbrewer/-/colorbrewer-1.0.0.tgz#4f97333b969ba7612382be4bc3394b341fb4c8a2" - -combined-stream@^1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" - dependencies: - delayed-stream "~1.0.0" - -commander@2.15.1: - version "2.15.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" - -commander@~2.17.1: - version "2.17.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - -console-browserify@1.1.x: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" - dependencies: - date-now "^0.1.4" - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - -content-disposition@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - -cookie@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - -cryptiles@3.x.x: - version "3.1.4" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.4.tgz#769a68c95612b56faadfcebf57ac86479cbe8322" - dependencies: - boom "5.x.x" - -d3-queue@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/d3-queue/-/d3-queue-2.0.3.tgz#07fbda3acae5358a9c5299aaf880adf0953ed2c2" - -d3@3.5.17: - version "3.5.17" - resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8" - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - dependencies: - assert-plus "^1.0.0" - -date-now@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" - -debug@2.6.9, debug@^2.1.2, debug@^2.2.0: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - dependencies: - ms "2.0.0" - -debug@3.1.0, debug@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - dependencies: - ms "2.0.0" - -debug@^3.1.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - dependencies: - ms "^2.1.1" - -decamelize@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - -deep-eql@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" - dependencies: - type-detect "^4.0.0" - -deep-equal@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - -depd@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" - -depd@~1.1.1, depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - -diff@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - -dom-serializer@0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" - dependencies: - domelementtype "~1.1.1" - entities "~1.1.1" - -domelementtype@1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - -domelementtype@~1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" - -domhandler@2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" - dependencies: - domelementtype "1" - -domutils@1.5: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - dependencies: - dom-serializer "0" - domelementtype "1" - -dot@1.1.2, dot@^1.0.3: - version "1.1.2" - resolved "https://registry.yarnpkg.com/dot/-/dot-1.1.2.tgz#c7377019fc4e550798928b2b9afeb66abfa1f2f9" - -dot@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/dot/-/dot-1.0.3.tgz#f8750bfb6b03c7664eb0e6cb1eb4c66419af9427" - -double-ended-queue@^2.1.0-0: - version "2.1.0-0" - resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" - -dtrace-provider@~0.6: - version "0.6.0" - resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.6.0.tgz#0b078d5517937d873101452d9146737557b75e51" - dependencies: - nan "^2.0.8" - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - -entities@1.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" - -entities@~1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - -error-ex@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - dependencies: - is-arrayish "^0.2.1" - -es6-promise@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.1.2.tgz#795e25ceb47f7babb263d151afbedd92d18e6a07" - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - -escodegen@1.8.x: - version "1.8.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" - dependencies: - esprima "^2.7.1" - estraverse "^1.9.1" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.2.0" - -esprima@2.7.x, esprima@^2.7.1: - version "2.7.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - -estraverse@^1.9.1: - version "1.9.3" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" - -esutils@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -exit@0.1.2, exit@0.1.x: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - -express@4.16.3: - version "4.16.3" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" - dependencies: - accepts "~1.3.5" - array-flatten "1.1.1" - body-parser "1.18.2" - content-disposition "0.5.2" - content-type "~1.0.4" - cookie "0.3.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.1.1" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.2" - path-to-regexp "0.1.7" - proxy-addr "~2.0.3" - qs "6.5.1" - range-parser "~1.2.0" - safe-buffer "5.1.1" - send "0.16.2" - serve-static "1.13.2" - setprototypeof "1.1.0" - statuses "~1.4.0" - type-is "~1.6.16" - utils-merge "1.0.1" - vary "~1.1.2" - -extend@~3.0.1, extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - -fast-deep-equal@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" - -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - -fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - -fast-levenshtein@~2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - -fastly-purge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fastly-purge/-/fastly-purge-1.0.1.tgz#3bdfe9ea1d0fbf2a65712f2f5fe2eca63fcb5960" - dependencies: - request "^2.55.0" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - -finalhandler@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.2" - statuses "~1.4.0" - unpipe "~1.0.0" - -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - -find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - dependencies: - locate-path "^2.0.0" - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - -form-data@~2.3.1, form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - -fs-minipass@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" - dependencies: - minipass "^2.2.1" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gc-stats@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/gc-stats/-/gc-stats-1.2.1.tgz#f3e1bf7e28a385780db22a81681b064932e358da" - dependencies: - nan "^2.10.0" - node-pre-gyp "^0.11.0" - -gdal@~0.9.2: - version "0.9.8" - resolved "https://registry.yarnpkg.com/gdal/-/gdal-0.9.8.tgz#d482fbcdca47d388325903c9aa342b09fb21319b" - dependencies: - nan "~2.10.0" - node-pre-gyp "^0.11.0" - -generic-pool@2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff" - -generic-pool@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.5.0.tgz#acac4fd743a175ff20574f380910036464cb61f7" - -generic-pool@~2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.1.1.tgz#af04dc2c325cfcb975023fa52bfce9617a7435fd" - -generic-pool@~2.2.0, generic-pool@~2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.2.2.tgz#7a89f491d575b42f9f069a0e8e2c6dbaa3c241be" - -generic-pool@~2.4.1: - version "2.4.6" - resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.6.tgz#f1b55e572167dba2fe75d5aa91ebb1e9f72642d7" - -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - -get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - dependencies: - assert-plus "^1.0.0" - -glob@7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^5.0.15: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^6.0.1: - version "6.0.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.1, glob@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -graceful-fs@^4.1.2: - version "4.1.15" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" - -grainstore@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/grainstore/-/grainstore-1.10.0.tgz#ed1d49cbef218dc95209e31a3c81c170570e5625" - dependencies: - carto "0.16.3" - debug "~3.1.0" - generic-pool "~2.2.0" - millstone "github:cartodb/millstone#v0.6.17-carto.2" - postcss "~5.2.8" - postcss-scss "0.4.0" - postcss-strip-inline-comments "0.1.5" - semver "~5.0.3" - underscore "~1.6.0" - -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - -handlebars@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.0.tgz#0d6a6f34ff1f63cecec8423aa4169827bf787c3a" - dependencies: - async "^2.5.0" - optimist "^0.6.1" - source-map "^0.6.1" - optionalDependencies: - uglify-js "^3.1.4" - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - -har-validator@~5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" - dependencies: - ajv "^5.1.0" - har-schema "^2.0.0" - -har-validator@~5.1.0: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - dependencies: - ajv "^6.5.5" - har-schema "^2.0.0" - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - dependencies: - ansi-regex "^2.0.0" - -has-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - -hawk@~6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" - dependencies: - boom "4.x.x" - cryptiles "3.x.x" - hoek "4.x.x" - sntp "2.x.x" - -he@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" - -hiredis@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/hiredis/-/hiredis-0.5.0.tgz#db03a98becd7003d13c260043aceecfacdf59b87" - dependencies: - bindings "^1.2.1" - nan "^2.3.4" - -hoek@4.x.x: - version "4.2.1" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" - -hosted-git-info@^2.1.4: - version "2.7.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" - -htmlparser2@3.8.x: - version "3.8.3" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068" - dependencies: - domelementtype "1" - domhandler "2.3" - domutils "1.5" - entities "1.0" - readable-stream "1.1" - -http-errors@1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" - dependencies: - depd "1.1.1" - inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" - -http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -husl@^6.0.1: - version "6.0.6" - resolved "https://registry.yarnpkg.com/husl/-/husl-6.0.6.tgz#f71b3e45d2000d6406432a9cc17a4b7e0c5b800d" - -iconv-lite@0.4.19: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" - -iconv-lite@0.4.23: - version "0.4.23" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - dependencies: - minimatch "^3.0.4" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.3, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - -ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - -ipaddr.js@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - -istanbul@0.4.5: - version "0.4.5" - resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" - dependencies: - abbrev "1.0.x" - async "1.x" - escodegen "1.8.x" - esprima "2.7.x" - glob "^5.0.15" - handlebars "^4.0.1" - js-yaml "3.x" - mkdirp "0.5.x" - nopt "3.x" - once "1.x" - resolve "1.1.x" - supports-color "^3.1.0" - which "^1.1.1" - wordwrap "^1.0.0" - -js-base64@^2.1.9: - version "2.5.1" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" - -js-string-escape@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" - -js-yaml@3.x, js-yaml@^3.4.6: - version "3.12.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600" - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - -jshint@2.9.7: - version "2.9.7" - resolved "https://registry.yarnpkg.com/jshint/-/jshint-2.9.7.tgz#038a3fa5c328fa3ab03ddfd85df88d3d87bedcbd" - dependencies: - cli "~1.0.0" - console-browserify "1.1.x" - exit "0.1.x" - htmlparser2 "3.8.x" - lodash "~4.17.10" - minimatch "~3.0.2" - shelljs "0.3.x" - strip-json-comments "1.0.x" - -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - dependencies: - invert-kv "^1.0.0" - -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -load-json-file@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - -lodash.assign@^4.0.3, lodash.assign@^4.0.6: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" - -lodash@^4.17.10, lodash@^4.17.5, lodash@^4.5.1, lodash@~4.17.10: - version "4.17.11" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" - -"log4js@github:cartodb/log4js-node#cdb": - version "0.6.25" - resolved "https://codeload.github.com/cartodb/log4js-node/tar.gz/145d5f91e35e7fb14a6278cbf7a711ced6603727" - dependencies: - async "~0.2.0" - readable-stream "~1.0.2" - semver "~4.3.3" - underscore "1.8.2" - -lru-cache@4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lru-cache@^4.0.1: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lzma@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/lzma/-/lzma-2.3.2.tgz#3783b24858b9c0e747a0df3cbf1fb5fcaa92c441" - -mapnik-pool@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/mapnik-pool/-/mapnik-pool-0.1.3.tgz#9bdad1cb34d87a19bc78ecb4cbfbaa203b2f3f2b" - dependencies: - generic-pool "~2.2.1" - xtend "~4.0.0" - -mapnik-reference@~6.0.2: - version "6.0.5" - resolved "https://registry.yarnpkg.com/mapnik-reference/-/mapnik-reference-6.0.5.tgz#d52319e1df88e5a07a811e84def2a1a423b6f05f" - -mapnik-reference@~8.5.3: - version "8.5.6" - resolved "https://registry.yarnpkg.com/mapnik-reference/-/mapnik-reference-8.5.6.tgz#5e8079821409322d6c958ce1823c67883ebb7655" - dependencies: - semver "^5.1.0" - -"mapnik-vector-tile@github:cartodb/mapnik-vector-tile#v1.6.1-carto.2": - version "1.6.1-carto.2" - resolved "https://codeload.github.com/cartodb/mapnik-vector-tile/tar.gz/e7ca5471f9e5de81243e6035e70444321fc0a82f" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - -mem@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" - dependencies: - mimic-fn "^1.0.0" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - -"millstone@github:cartodb/millstone#v0.6.17-carto.2": - version "0.6.17-carto.2" - resolved "https://codeload.github.com/cartodb/millstone/tar.gz/f201885250d2d7ea6c870a1e1060ca6bcfdef186" - dependencies: - generic-pool "~2.4.1" - mime "~2.3.1" - mkdirp "~0.5.0" - optimist "0.6.1" - request "2.x" - sqlite3 "~4.0.1" - srs "^1.2.0" - step "~1.0.0" - underscore "~1.9.1" - zipfile "~0.5.11" - -mime-db@~1.37.0: - version "1.37.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" - -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.19: - version "2.1.21" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" - dependencies: - mime-db "~1.37.0" - -mime@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" - -mime@2.3.1, mime@~2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369" - -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - dependencies: - brace-expansion "^1.1.7" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - -minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - -minimist@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.0.tgz#4dffe525dae2b864c66c2e23c6271d7afdecefce" - -minipass@^2.2.1, minipass@^2.3.4: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - dependencies: - minipass "^2.2.1" - -mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - dependencies: - minimist "0.0.8" - -mocha@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" - dependencies: - browser-stdout "1.3.1" - commander "2.15.1" - debug "3.1.0" - diff "3.5.0" - escape-string-regexp "1.0.5" - glob "7.1.2" - growl "1.10.5" - he "1.1.1" - minimatch "3.0.4" - mkdirp "0.5.1" - supports-color "5.4.0" - -moment@2.22.1: - version "2.22.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad" - -moment@^2.10.6: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - -ms@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - -mv@~2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" - dependencies: - mkdirp "~0.5.1" - ncp "~2.0.0" - rimraf "~2.4.0" - -nan@2.10.0, nan@~2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" - -nan@^2.0.8, nan@^2.10.0, nan@^2.3.4, nan@^2.4.0: - version "2.12.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" - -ncp@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" - -needle@^2.2.0, needle@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" - dependencies: - debug "^2.1.2" - iconv-lite "^0.4.4" - sax "^1.2.4" - -negotiator@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" - -nock@9.2.6: - version "9.2.6" - resolved "https://registry.yarnpkg.com/nock/-/nock-9.2.6.tgz#496ddb2c32e6d0848cda1b3ea51fbe054d4454d3" - dependencies: - chai "^4.1.2" - debug "^3.1.0" - deep-equal "^1.0.0" - json-stringify-safe "^5.0.1" - lodash "^4.17.5" - mkdirp "^0.5.0" - propagate "^1.0.0" - qs "^6.5.1" - semver "^5.5.0" - -node-pre-gyp@0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46" - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.0" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.1.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - -node-pre-gyp@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - -node-pre-gyp@~0.10.2: - version "0.10.3" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - -node-statsd@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/node-statsd/-/node-statsd-0.1.1.tgz#27a59348763d0af7a037ac2a031fef3f051013d3" - -nopt@3.x: - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" - dependencies: - abbrev "1" - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-package-data@^2.3.2: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -npm-bundled@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" - -npm-packlist@^1.1.6: - version "1.3.0" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.3.0.tgz#7f01e8e44408341379ca98cfd756e7b29bd2626c" - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - dependencies: - path-key "^2.0.0" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - -oauth-sign@~0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - -object-assign@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" - -object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - -object-keys@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - dependencies: - ee-first "1.1.1" - -on-headers@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" - -once@1.x, once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - dependencies: - wrappy "1" - -optimist@0.6.1, optimist@^0.6.1, optimist@~0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - -optionator@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.4" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - wordwrap "~1.0.0" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - dependencies: - lcid "^1.0.0" - -os-locale@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" - dependencies: - execa "^0.7.0" - lcid "^1.0.0" - mem "^1.1.0" - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - dependencies: - p-try "^1.0.0" - -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - dependencies: - p-limit "^1.1.0" - -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - -packet-reader@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-0.3.1.tgz#cd62e60af8d7fea8a705ec4ff990871c46871f27" - -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - dependencies: - error-ex "^1.2.0" - -parseurl@~1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - dependencies: - pinkie-promise "^2.0.0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - -path-key@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - -path-type@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -pathval@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - -pg-connection-string@0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" - -pg-int8@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" - -pg-pool@1.*: - version "1.8.0" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-1.8.0.tgz#f7ec73824c37a03f076f51bfdf70e340147c4f37" - dependencies: - generic-pool "2.4.3" - object-assign "4.1.0" - -pg-types@1.*: - version "1.13.0" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.13.0.tgz#75f490b8a8abf75f1386ef5ec4455ecf6b345c63" - dependencies: - pg-int8 "1.0.1" - postgres-array "~1.0.0" - postgres-bytea "~1.0.0" - postgres-date "~1.0.0" - postgres-interval "^1.1.0" - -"pg@github:cartodb/node-postgres#6.4.2-cdb2": - version "6.4.2-cdb2" - resolved "https://codeload.github.com/cartodb/node-postgres/tar.gz/5417d7b29b7272ca2e71bb396899ab3f177a9ae6" - dependencies: - buffer-writer "1.0.1" - js-string-escape "1.0.1" - packet-reader "0.3.1" - pg-connection-string "0.1.3" - pg-pool "1.*" - pg-types "1.*" - pgpass "1.*" - semver "4.3.2" - -pgpass@1.*: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.2.tgz#2a7bb41b6065b67907e91da1b07c1847c877b306" - dependencies: - split "^1.0.0" - -pify@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - -postcss-scss@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-0.4.0.tgz#087c052c529b9270d9580bd1248a0f93d3b40d57" - dependencies: - postcss "^5.2.5" - -postcss-strip-inline-comments@0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/postcss-strip-inline-comments/-/postcss-strip-inline-comments-0.1.5.tgz#7ff6bcdc14e633ed4cdfa020bae3eddad4f84b90" - dependencies: - postcss "^5.0.18" - -postcss-value-parser@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" - -postcss@5.0.19: - version "5.0.19" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.0.19.tgz#b6342a01dc75b8cab7e968afda96aefc67f888af" - dependencies: - js-base64 "^2.1.9" - source-map "^0.5.1" - supports-color "^3.1.2" - -postcss@^5.0.18, postcss@^5.2.5, postcss@~5.2.8: - version "5.2.18" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" - dependencies: - chalk "^1.1.3" - js-base64 "^2.1.9" - source-map "^0.5.6" - supports-color "^3.2.3" - -postgres-array@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-1.0.3.tgz#c561fc3b266b21451fc6555384f4986d78ec80f5" - -postgres-bytea@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" - -postgres-date@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.3.tgz#e2d89702efdb258ff9d9cee0fe91bd06975257a8" - -postgres-interval@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.1.2.tgz#bf71ff902635f21cb241a013fc421d81d1db15a9" - dependencies: - xtend "^4.0.0" - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - -process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - -progress-stream@~0.5.x: - version "0.5.0" - resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-0.5.0.tgz#cc4759167a6ff4f05876179384f0b6ae0d1d7587" - dependencies: - single-line-log "~0.3.1" - speedometer "~0.1.2" - through2 "~0.2.3" - -propagate@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/propagate/-/propagate-1.0.0.tgz#00c2daeedda20e87e3782b344adba1cddd6ad709" - -proxy-addr@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.8.0" - -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - -psl@^1.1.24: - version "1.1.31" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - -qs@6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - -qs@6.5.2, qs@~6.5.1, qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - -qs@^6.5.1: - version "6.6.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.6.0.tgz#a99c0f69a8d26bf7ef012f871cdabb0aee4424c2" - -queue-async@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/queue-async/-/queue-async-1.1.0.tgz#d5f88e32fd01fee4f6d946d21d17abd16ac65f35" - -queue-async@~1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/queue-async/-/queue-async-1.0.7.tgz#22ae0a1dac4a92f5bcd4634f993c682a2a810945" - -range-parser@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - -raw-body@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" - dependencies: - bytes "3.0.0" - http-errors "1.6.2" - iconv-lite "0.4.19" - unpipe "1.0.0" - -raw-body@2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" - dependencies: - bytes "3.0.0" - http-errors "1.6.3" - iconv-lite "0.4.23" - unpipe "1.0.0" - -rc@^1.1.7, rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -read-pkg-up@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" - dependencies: - find-up "^1.0.0" - read-pkg "^1.0.0" - -read-pkg@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" - dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" - -readable-stream@1.1: - version "1.1.13" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@^2.0.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@~1.0.2: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@~1.1.9: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -redis-commands@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.4.0.tgz#52f9cf99153efcce56a8f86af986bd04e988602f" - -redis-mpool@0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/redis-mpool/-/redis-mpool-0.7.0.tgz#03e658ccef7259943b1d9e573bfa29e97fb11120" - dependencies: - generic-pool "~2.1.1" - hiredis "~0.5.0" - redis "^2.8.0" - underscore "~1.6.0" - -redis-parser@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" - -redis@2.8.0, redis@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02" - dependencies: - double-ended-queue "^2.1.0-0" - redis-commands "^1.2.0" - redis-parser "^2.6.0" - -request@2.85.0: - version "2.85.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - -request@2.87.0: - version "2.87.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - tough-cookie "~2.3.3" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - -request@2.x, request@^2.55.0, request@^2.87.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - -resolve@1.1.x: - version "1.1.7" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - -resolve@^1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" - dependencies: - path-parse "^1.0.6" - -rimraf@^2.6.1: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - dependencies: - glob "^7.1.3" - -rimraf@~2.4.0: - version "2.4.5" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da" - dependencies: - glob "^6.0.1" - -safe-buffer@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - -safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - -safe-json-stringify@~1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - -"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@^5.5.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - -semver@4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" - -semver@5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" - -semver@~4.3.3: - version "4.3.6" - resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" - -semver@~5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" - -send@0.16.2: - version "0.16.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.6.2" - mime "1.4.1" - ms "2.0.0" - on-finished "~2.3.0" - range-parser "~1.2.0" - statuses "~1.4.0" - -serve-static@1.13.2: - version "1.13.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.2" - send "0.16.2" - -set-blocking@^2.0.0, set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - -setprototypeof@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - -shelljs@0.3.x: - version "0.3.0" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1" - -signal-exit@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - -simple-statistics@^0.9.0: - version "0.9.2" - resolved "https://registry.yarnpkg.com/simple-statistics/-/simple-statistics-0.9.2.tgz#3e35cb10308fc76963aa4ee7252e6a6ea10d28e4" - -single-line-log@~0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-0.3.1.tgz#a7ad6507f218ce5dfe16c4bf2d659246419e7a06" - -sntp@2.x.x: - version "2.1.0" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" - dependencies: - hoek "4.x.x" - -source-map@^0.5.1, source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - -source-map@^0.6.1, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - -source-map@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" - dependencies: - amdefine ">=0.0.4" - -spdx-correct@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - -spdx-expression-parse@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e" - -speedometer@~0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" - -sphericalmercator@1.0.5, sphericalmercator@~1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.5.tgz#ddc5a049e360e000d0fad9fc22c4071882584980" - -split@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - dependencies: - through "2" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - -sqlite3@~4.0.1: - version "4.0.6" - resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.0.6.tgz#e587b583b5acc6cb38d4437dedb2572359c080ad" - dependencies: - nan "~2.10.0" - node-pre-gyp "^0.11.0" - request "^2.87.0" - -srs@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/srs/-/srs-1.2.0.tgz#ecb447854df76ceb12b4529032be23e9e18eecdc" - dependencies: - gdal "~0.9.2" - -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - -statuses@~1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" - -step-profiler@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/step-profiler/-/step-profiler-0.3.0.tgz#841368ce44f2330c862edd5ec60e7fbec148f0b3" - dependencies: - debug "^2.2.0" - -step@1.0.0, step@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/step/-/step-1.0.0.tgz#b300e9d2ae9057d4d78633aae2303813a94bdff2" - -strftime@0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/strftime/-/strftime-0.10.0.tgz#b3f0fa419295202a5a289f6d6be9f4909a617193" - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - dependencies: - safe-buffer "~5.1.0" - -stringstream@~0.0.5: - version "0.0.6" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - dependencies: - ansi-regex "^3.0.0" - -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - dependencies: - is-utf8 "^0.2.0" - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - -strip-json-comments@1.0.x: - version "1.0.4" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - -supports-color@5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" - dependencies: - has-flag "^3.0.0" - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - -supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" - dependencies: - has-flag "^1.0.0" - -tar@^4: - version "4.4.8" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.3.4" - minizlib "^1.1.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.2" - -through2@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f" - dependencies: - readable-stream "~1.1.9" - xtend "~2.1.1" - -through@2: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - -"tilelive-mapnik@github:cartodb/tilelive-mapnik#0.6.18-cdb18": - version "0.6.18-cdb17" - resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/a89d4147b3288804b679a018271893dda18373c2" - dependencies: - "@carto/mapnik" "3.6.2-carto.11" - generic-pool "3.5.0" - mime "2.3.1" - -tilelive@5.12.3: - version "5.12.3" - resolved "https://registry.yarnpkg.com/tilelive/-/tilelive-5.12.3.tgz#9c8c770e1194aa8d353d9a1d03147404307cdebd" - dependencies: - minimist "~0.2.0" - progress-stream "~0.5.x" - queue-async "~1.0.7" - sphericalmercator "~1.0.1" - -torque.js@2.17.1: - version "2.17.1" - resolved "https://registry.yarnpkg.com/torque.js/-/torque.js-2.17.1.tgz#838118b19b055ffb7c8e3bbe08ac62fce2835237" - dependencies: - carto cartodb/carto#master - d3 "3.5.17" - turbo-carto "^0.21.1" - turf-jenks "~1.0.1" - -tough-cookie@~2.3.3: - version "2.3.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" - dependencies: - punycode "^1.4.1" - -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - dependencies: - safe-buffer "^5.0.1" - -turbo-carto@0.21.0: - version "0.21.0" - resolved "https://registry.yarnpkg.com/turbo-carto/-/turbo-carto-0.21.0.tgz#85da4b9f869c485cefc8fa0394fcddda0c639022" - dependencies: - cartocolor "4.0.0" - colorbrewer "1.0.0" - debug "^3.1.0" - es6-promise "3.1.2" - postcss "5.0.19" - postcss-value-parser "3.3.0" - -turbo-carto@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/turbo-carto/-/turbo-carto-0.21.1.tgz#d104c16a7d0a46de01ccbda31258f66cf1cb5edc" - dependencies: - cartocolor "4.0.0" - colorbrewer "1.0.0" - debug "^3.1.0" - es6-promise "3.1.2" - postcss "5.0.19" - postcss-value-parser "3.3.0" - -turf-jenks@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/turf-jenks/-/turf-jenks-1.0.1.tgz#5c72aea13a716eff2f0023a72cfa549a00202d7f" - dependencies: - simple-statistics "^0.9.0" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - dependencies: - prelude-ls "~1.1.2" - -type-detect@^4.0.0, type-detect@^4.0.5: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - -type-is@~1.6.15, type-is@~1.6.16: - version "1.6.16" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" - dependencies: - media-typer "0.3.0" - mime-types "~2.1.18" - -uglify-js@^3.1.4: - version "3.4.9" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" - dependencies: - commander "~2.17.1" - source-map "~0.6.1" - -underscore@1.6.0, underscore@~1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8" - -underscore@1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.2.tgz#64df2eb590899de950782f3735190ba42ebf311d" - -underscore@1.8.3: - version "1.8.3" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" - -underscore@~1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - dependencies: - punycode "^2.1.0" - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - -uuid@^3.1.0, uuid@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -which-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - -which@^1.1.1, which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - dependencies: - string-width "^1.0.2 || 2" - -window-size@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" - -windshaft@4.13.1: - version "4.13.1" - resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.13.1.tgz#14387b7c088adddefd52aaf82d905f3547b364d9" - dependencies: - "@carto/mapnik" "3.6.2-carto.11" - "@carto/tilelive-bridge" cartodb/tilelive-bridge#2.5.1-cdb11 - abaculus "github:cartodb/abaculus#2.0.3-cdb13" - canvas "github:cartodb/node-canvas#1.6.2-cdb3" - carto "github:cartodb/carto#0.15.1-cdb5" - cartodb-psql "0.13.1" - debug "3.1.0" - dot "1.1.2" - grainstore "1.10.0" - queue-async "1.1.0" - redis-mpool "0.7.0" - request "2.87.0" - semver "5.5.0" - sphericalmercator "1.0.5" - tilelive "5.12.3" - tilelive-mapnik "github:cartodb/tilelive-mapnik#0.6.18-cdb18" - torque.js "2.17.1" - underscore "1.6.0" - -wordwrap@^1.0.0, wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - -xtend@^4.0.0, xtend@~4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" - -xtend@~2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b" - dependencies: - object-keys "~0.4.0" - -y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - -yallist@^3.0.0, yallist@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - -yargs-parser@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4" - dependencies: - camelcase "^3.0.0" - lodash.assign "^4.0.6" - -yargs-parser@^9.0.2: - version "9.0.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" - dependencies: - camelcase "^4.1.0" - -yargs@11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" - dependencies: - cliui "^4.0.0" - decamelize "^1.1.1" - find-up "^2.1.0" - get-caller-file "^1.0.1" - os-locale "^2.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1" - yargs-parser "^9.0.2" - -yargs@^4.2.0: - version "4.8.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" - dependencies: - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - lodash.assign "^4.0.3" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.1" - which-module "^1.0.0" - window-size "^0.2.0" - y18n "^3.2.1" - yargs-parser "^2.4.1" - -zipfile@~0.5.11: - version "0.5.12" - resolved "https://registry.yarnpkg.com/zipfile/-/zipfile-0.5.12.tgz#4488ff93dfb8d9089033ba34d8b2cf05358b08b2" - dependencies: - nan "~2.10.0" - node-pre-gyp "~0.10.2" From 241bb511ea774725306011cd06aa1ecf12744001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 21 Feb 2019 17:55:58 +0100 Subject: [PATCH 34/84] Drop support for Redis 3, Postgres 9.5 and PostGIS 2.2 --- INSTALL.md | 12 ++++++------ NEWS.md | 4 +++- carto-package.json | 8 ++++---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 4b9e6fbe..3bf31f7e 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -4,12 +4,12 @@ Make sure that you have the requirements needed. These are - Core - - Node >= 10 - - npm >= 6 - - gcc == 4.9 - - PostgreSQL >= 9.5 - - PostGIS >= 2.2 - - CartoDB Postgres Extension == 0.24.1 + - Node 10.x + - npm 6.x + - gcc 4.9 + - PostgreSQL >= 10.0 + - PostGIS >= 2.4 + - CartoDB Postgres Extension >= 0.24.1 - Redis >= 4 - Mapnik == 3.0.15.9. See [Installing Mapnik](https://github.com/CartoDB/Windshaft#installing-mapnik). - Windshaft: check [Windshaft dependencies and installation notes](https://github.com/CartoDB/Windshaft#dependencies) diff --git a/NEWS.md b/NEWS.md index 034f956f..b9630cd3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,10 +4,12 @@ Released 2018-mm-dd Breaking changes: -- Drop support for Postgres 9.5 - Drop support for Node.js 6 - Drop support for npm 3 - Stop supporting `yarn.lock` +- Drop support for Postgres 9.5 +- Drop support for PosGIS 2.2 +- Drop support for Redis 3 Announcements: - Update docs: compatible Node.js and npm versions diff --git a/carto-package.json b/carto-package.json index 0aa99fc6..1ce636bf 100644 --- a/carto-package.json +++ b/carto-package.json @@ -2,15 +2,15 @@ "name": "carto_windshaft", "current_version": { "requires": { - "node": ">=10.15.1", + "node": "^10.15.1", "mapnik": "==3.0.15.9", "crankshaft": "~0.8.1" }, "works_with": { "redis": ">=4.0.0", - "postgresql": ">=9.5.0", - "postgis": ">=2.2.0.0", - "carto_postgresql_ext": ">=0.19.0" + "postgresql": ">=10.0.0", + "postgis": ">=2.4.4.5", + "carto_postgresql_ext": ">=0.24.1" } } } From 5455d2997f200678356ef106561c00664f5b4aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 21 Feb 2019 18:11:27 +0100 Subject: [PATCH 35/84] Remove items in requirements list --- INSTALL.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 3bf31f7e..451cf9da 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,7 +1,7 @@ # Installing Windshaft-CartoDB # ## Requirements ## -Make sure that you have the requirements needed. These are +Make sure that you have the requirements needed. These are: - Core - Node 10.x @@ -11,8 +11,6 @@ Make sure that you have the requirements needed. These are - PostGIS >= 2.4 - CartoDB Postgres Extension >= 0.24.1 - Redis >= 4 - - Mapnik == 3.0.15.9. 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 From 227a271bead9a00a9c846638c921556dfc480e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 21 Feb 2019 18:21:37 +0100 Subject: [PATCH 36/84] Typo --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 451cf9da..234daf85 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -9,7 +9,7 @@ Make sure that you have the requirements needed. These are: - gcc 4.9 - PostgreSQL >= 10.0 - PostGIS >= 2.4 - - CartoDB Postgres Extension >= 0.24.1 + - CARTO Postgres Extension >= 0.24.1 - Redis >= 4 - libcairo2-dev, libpango1.0-dev, libjpeg8-dev and libgif-dev for server side canvas support From 30f4b58ced11414cef5e4d826ad5844a8841dd09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 21 Feb 2019 18:26:56 +0100 Subject: [PATCH 37/84] Add npm as dependency --- carto-package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/carto-package.json b/carto-package.json index 1ce636bf..e65b0f8f 100644 --- a/carto-package.json +++ b/carto-package.json @@ -3,6 +3,7 @@ "current_version": { "requires": { "node": "^10.15.1", + "npm": "^6.4.1", "mapnik": "==3.0.15.9", "crankshaft": "~0.8.1" }, From 582947accd92a97526a3cf77bb19f33a4a903536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 21 Feb 2019 18:45:19 +0100 Subject: [PATCH 38/84] Update release date --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index b9630cd3..a6889830 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,7 @@ # Changelog ## 7.0.0 -Released 2018-mm-dd +Released 2019-mm-dd Breaking changes: - Drop support for Node.js 6 From 47576358a21be127d510eee88a95142c3eb3c27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 22 Feb 2019 07:52:39 +0100 Subject: [PATCH 39/84] Remove hack of stripping PARALLEL labels for PG releases before 9.6 --- test/support/prepare_db.sh | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/support/prepare_db.sh b/test/support/prepare_db.sh index ed07c7d7..5834a8ad 100755 --- a/test/support/prepare_db.sh +++ b/test/support/prepare_db.sh @@ -110,13 +110,6 @@ if test x"$PREPARE_PGSQL" = xyes; then ALL_SQL_SCRIPTS="${REMOTE_SQL_SCRIPTS} ${LOCAL_SQL_SCRIPTS}" for i in ${ALL_SQL_SCRIPTS} do - # Strip PARALLEL labels for PostgreSQL releases before 9.6 - if [ $PG_PARALLEL -eq 0 ]; then - TMPFILE=$(mktemp /tmp/$(basename $0).XXXXXXXX) - sed -e 's/PARALLEL \= [A-Z]*,/''/g' \ - -e 's/PARALLEL [A-Z]*/''/g' sql/$i.sql > $TMPFILE - mv $TMPFILE sql/$i.sql - fi cat sql/${i}.sql | sed -e 's/cartodb\./public./g' -e "s/''cartodb''/''public''/g" | sed "s/:PUBLICUSER/${PUBLICUSER}/" | From 561bdb39384da9cedf3738e188676eae29ee28d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 22 Feb 2019 07:53:18 +0100 Subject: [PATCH 40/84] Improve MD formatting --- INSTALL.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 234daf85..5b0c3580 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,20 +1,21 @@ -# Installing Windshaft-CartoDB # +# Installing Windshaft-CartoDB + +## Requirements -## Requirements ## Make sure that you have the requirements needed. These are: -- Core - - Node 10.x - - npm 6.x - - gcc 4.9 - - PostgreSQL >= 10.0 - - PostGIS >= 2.4 - - CARTO Postgres Extension >= 0.24.1 - - Redis >= 4 - - libcairo2-dev, libpango1.0-dev, libjpeg8-dev and libgif-dev for server side canvas support +- Node 10.x +- npm 6.x +- gcc 4.9 +- PostgreSQL >= 10.0 +- PostGIS >= 2.4 +- CARTO Postgres Extension >= 0.24.1 +- Redis >= 4 +- libcairo2-dev, libpango1.0-dev, libjpeg8-dev and libgif-dev for server side canvas support -- For cache control - - Varnish (http://www.varnish-cache.org) +### Optional + +- Varnish (http://www.varnish-cache.org) ## PostGIS setup From 5f43db2e363881f59c680588e43eb3517f8ffde4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 22 Feb 2019 08:31:22 +0100 Subject: [PATCH 41/84] Remove POSTGIS_VERSION env variable and run test using mvt renderer always --- test/acceptance/aggregation.js | 17 +- test/acceptance/buffer-size-format.js | 6 +- test/acceptance/date-wrapping.spec.js | 3 +- test/acceptance/layergroup-metadata.js | 4 +- test/acceptance/mvt-regressions.js | 17 +- test/acceptance/mvt.js | 482 +++++++++--------- test/acceptance/rate-limit.test.js | 6 +- .../stats/mapnik_stats_layergroup.js | 17 +- test/acceptance/tilejson.js | 5 +- .../acceptance/user-database-timeout-limit.js | 5 +- test/acceptance/vector-layergroup.js | 17 +- test/support/prepare_db.sh | 14 - 12 files changed, 283 insertions(+), 310 deletions(-) diff --git a/test/acceptance/aggregation.js b/test/acceptance/aggregation.js index d9924eee..bd03aa41 100644 --- a/test/acceptance/aggregation.js +++ b/test/acceptance/aggregation.js @@ -6,17 +6,16 @@ const assert = require('../support/assert'); const TestClient = require('../support/test-client'); const serverOptions = require('../../lib/cartodb/server_options'); -const suites = [{ - desc: 'mvt (mapnik)', - usePostGIS: false -}]; - -if (process.env.POSTGIS_VERSION >= '20400') { - suites.push({ +const suites = [ + { + desc: 'mvt (mapnik)', + usePostGIS: false + }, + { desc: 'mvt (postgis)', usePostGIS: true - }); -} + } +]; // Generate points with values and times. // The point location is spanned over a given length, by default it is 0 so diff --git a/test/acceptance/buffer-size-format.js b/test/acceptance/buffer-size-format.js index c8d36740..e3daf626 100644 --- a/test/acceptance/buffer-size-format.js +++ b/test/acceptance/buffer-size-format.js @@ -171,8 +171,7 @@ describe('buffer size per format', function () { }); }); - const describe_pg = process.env.POSTGIS_VERSION >= '20400' ? describe : describe.skip; - describe_pg('using postgis mvt renderer', function() { + describe('using postgis mvt renderer', function() { before(function () { serverOptions.renderer.mvt.usePostGIS = true; }); @@ -505,8 +504,7 @@ describe('buffer size per format for named maps w/o placeholders', function () { }); }); - const describe_pg = process.env.POSTGIS_VERSION >= '20400' ? describe : describe.skip; - describe_pg('using postgis mvt renderer', function() { + describe('using postgis mvt renderer', function() { before(function () { serverOptions.renderer.mvt.usePostGIS = true; }); diff --git a/test/acceptance/date-wrapping.spec.js b/test/acceptance/date-wrapping.spec.js index 4247ff5e..f26caf1e 100644 --- a/test/acceptance/date-wrapping.spec.js +++ b/test/acceptance/date-wrapping.spec.js @@ -7,8 +7,7 @@ const mapConfigFactory = require('../fixtures/test_mapconfigFactory'); const serverOptions = require('../../lib/cartodb/server_options'); const usePgMvtRenderer = serverOptions.renderer.mvt.usePostGIS; -const postgisVersion = process.env.POSTGIS_VERSION; -const describe_mvt = postgisVersion >= '20400' || !usePgMvtRenderer ? describe : describe.skip; +const describe_mvt = !usePgMvtRenderer ? describe : describe.skip; describe_mvt('date-wrapping', () => { let testClient; diff --git a/test/acceptance/layergroup-metadata.js b/test/acceptance/layergroup-metadata.js index 235a30ed..50e30895 100644 --- a/test/acceptance/layergroup-metadata.js +++ b/test/acceptance/layergroup-metadata.js @@ -7,12 +7,10 @@ const TestClient = require('../support/test-client'); const serverOptions = require('../../lib/cartodb/server_options'); describe('layergroup metadata', function () { - - const usePgMvtRenderer = process.env.POSTGIS_VERSION >= '20400'; const originalUsePostGIS = serverOptions.renderer.mvt.usePostGIS; before(function () { - serverOptions.renderer.mvt.usePostGIS = usePgMvtRenderer; + serverOptions.renderer.mvt.usePostGIS = true; }); after(function () { diff --git a/test/acceptance/mvt-regressions.js b/test/acceptance/mvt-regressions.js index 0d1c5023..c660e23d 100644 --- a/test/acceptance/mvt-regressions.js +++ b/test/acceptance/mvt-regressions.js @@ -6,17 +6,16 @@ const assert = require('../support/assert'); const TestClient = require('../support/test-client'); const serverOptions = require('../../lib/cartodb/server_options'); -const suites = [{ - desc: 'mapnik', - usePostGIS: false -}]; - -if (process.env.POSTGIS_VERSION >= '20400') { - suites.push({ +const suites = [ + { + desc: 'mapnik', + usePostGIS: false + }, + { desc: 'postgis', usePostGIS: true - }); -} + } +]; describe('mvt regressions', function () { diff --git a/test/acceptance/mvt.js b/test/acceptance/mvt.js index 004a699b..bb446680 100644 --- a/test/acceptance/mvt.js +++ b/test/acceptance/mvt.js @@ -22,273 +22,271 @@ function createMapConfig(sql = TestClient.SQL.ONE_POINT) { } describe('mvt (mapnik)', mvt(false)); -if (process.env.POSTGIS_VERSION >= '20400') { - describe('mvt (postgis)', mvt(true)); -} +describe('mvt (postgis)', mvt(true)); function mvt(usePostGIS) { -return function () { - const originalUsePostGIS = serverOptions.renderer.mvt.usePostGIS; - before(function () { - serverOptions.renderer.mvt.usePostGIS = usePostGIS; - }); - after(function (){ - serverOptions.renderer.mvt.usePostGIS = originalUsePostGIS; - }); + return function () { + const originalUsePostGIS = serverOptions.renderer.mvt.usePostGIS; + before(function () { + serverOptions.renderer.mvt.usePostGIS = usePostGIS; + }); + after(function (){ + serverOptions.renderer.mvt.usePostGIS = originalUsePostGIS; + }); - describe('named map tile', function () { - it('should get default named vector tile', function (done) { - const apikeyToken = 1234; - const templateName = `mvt-template-${usePostGIS ? 'postgis' : 'mapnik'}`; - const template = { - version: '0.0.1', - name: templateName, - placeholders: { - buffersize: { - type: 'number', - default: 0 + describe('named map tile', function () { + it('should get default named vector tile', function (done) { + const apikeyToken = 1234; + const templateName = `mvt-template-${usePostGIS ? 'postgis' : 'mapnik'}`; + const template = { + version: '0.0.1', + name: templateName, + placeholders: { + buffersize: { + type: 'number', + default: 0 + } + }, + layergroup: { + version: '1.7.0', + layers: [{ + type: 'cartodb', + options: { + sql: 'select * from populated_places_simple_reduced limit 10', + cartocss: TestClient.CARTOCSS.POINTS, + cartocss_version: '2.3.0', + } + }] + } + }; + + const testClient = new TestClient(template, apikeyToken); + testClient.keysToDelete['map_tpl|localhost'] = 0; + + testClient.getNamedTile(templateName, 0, 0, 0, 'mvt', {}, (err, res, tile) => { + if (err) { + return done(err); + } + + const tileJSON = tile.toJSON(); + + assert.equal(tileJSON[0].features.length, 10); + + testClient.drain(done); + }); + }); + }); + + describe('analysis-layers-dataviews-mvt', function () { + + function createMapConfig(layers, dataviews, analysis) { + return { + version: '1.5.0', + layers: layers, + dataviews: dataviews || {}, + analyses: analysis || [] + }; + } + + var CARTOCSS = [ + "#points {", + " marker-fill-opacity: 1.0;", + " marker-line-color: #FFF;", + " marker-line-width: 0.5;", + " marker-line-opacity: 1.0;", + " marker-placement: point;", + " marker-type: ellipse;", + " marker-width: 8;", + " marker-fill: red;", + " marker-allow-overlap: true;", + "}" + ].join('\n'); + + var mapConfig = createMapConfig( + [ + { + "type": "cartodb", + "options": { + "source": { + "id": "2570e105-7b37-40d2-bdf4-1af889598745" + }, + "cartocss": CARTOCSS, + "cartocss_version": "2.3.0" + } + } + ], + { + pop_max_histogram: { + source: { + id: '2570e105-7b37-40d2-bdf4-1af889598745' + }, + type: 'histogram', + options: { + column: 'pop_max' + } } }, - layergroup: { - version: '1.7.0', - layers: [{ - type: 'cartodb', - options: { - sql: 'select * from populated_places_simple_reduced limit 10', - cartocss: TestClient.CARTOCSS.POINTS, - cartocss_version: '2.3.0', + [ + { + "id": "2570e105-7b37-40d2-bdf4-1af889598745", + "type": "source", + "params": { + "query": "select * from populated_places_simple_reduced" } - }] - } - }; - - const testClient = new TestClient(template, apikeyToken); - testClient.keysToDelete['map_tpl|localhost'] = 0; - - testClient.getNamedTile(templateName, 0, 0, 0, 'mvt', {}, (err, res, tile) => { - if (err) { - return done(err); - } - - const tileJSON = tile.toJSON(); - - assert.equal(tileJSON[0].features.length, 10); - - testClient.drain(done); - }); - }); - }); - - describe('analysis-layers-dataviews-mvt', function () { - - function createMapConfig(layers, dataviews, analysis) { - return { - version: '1.5.0', - layers: layers, - dataviews: dataviews || {}, - analyses: analysis || [] - }; - } - - var CARTOCSS = [ - "#points {", - " marker-fill-opacity: 1.0;", - " marker-line-color: #FFF;", - " marker-line-width: 0.5;", - " marker-line-opacity: 1.0;", - " marker-placement: point;", - " marker-type: ellipse;", - " marker-width: 8;", - " marker-fill: red;", - " marker-allow-overlap: true;", - "}" - ].join('\n'); - - var mapConfig = createMapConfig( - [ - { - "type": "cartodb", - "options": { - "source": { - "id": "2570e105-7b37-40d2-bdf4-1af889598745" - }, - "cartocss": CARTOCSS, - "cartocss_version": "2.3.0" } - } - ], + ] + ); + + it('should get pop_max column from dataview', function (done) { + var testClient = new TestClient(mapConfig); + + testClient.getTile(0, 0, 0, { format: 'mvt', layers: 0 }, function (err, res, MVT) { + var geojsonTile = JSON.parse(MVT.toGeoJSONSync(0)); + assert.ok(!err, err); + + assert.ok(Array.isArray(geojsonTile.features)); + assert.ok(geojsonTile.features.length > 0); + var feature = geojsonTile.features[0]; + assert.ok(feature.properties.hasOwnProperty('pop_max'), 'Missing pop_max property'); + + testClient.drain(done); + }); + }); + + }); + + + const testCases = [ { - pop_max_histogram: { - source: { - id: '2570e105-7b37-40d2-bdf4-1af889598745' - }, - type: 'histogram', - options: { - column: 'pop_max' + desc: 'should get empty mvt with code 204 (no content)', + coords: { z: 0, x: 0, y: 0 }, + format: 'mvt', + response: { + status: 204, + headers: { + 'Content-Type': undefined } - } + }, + mapConfig: createMapConfig(TestClient.SQL.EMPTY) }, - [ - { - "id": "2570e105-7b37-40d2-bdf4-1af889598745", - "type": "source", - "params": { - "query": "select * from populated_places_simple_reduced" + { + desc: 'should get mvt tile with code 200 (ok)', + coords: { z: 0, x: 0, y: 0 }, + format: 'mvt', + response: { + status: 200, + headers: { + 'Content-Type': 'application/x-protobuf' } - } - ] - ); + }, + mapConfig: createMapConfig() + } + ]; - it('should get pop_max column from dataview', function (done) { - var testClient = new TestClient(mapConfig); + testCases.forEach(function (test) { + it(test.desc, done => { + var testClient = new TestClient(test.mapConfig); + const { z, x, y } = test.coords; + const { format, response } = test; - testClient.getTile(0, 0, 0, { format: 'mvt', layers: 0 }, function (err, res, MVT) { - var geojsonTile = JSON.parse(MVT.toGeoJSONSync(0)); - assert.ok(!err, err); - - assert.ok(Array.isArray(geojsonTile.features)); - assert.ok(geojsonTile.features.length > 0); - var feature = geojsonTile.features[0]; - assert.ok(feature.properties.hasOwnProperty('pop_max'), 'Missing pop_max property'); - - testClient.drain(done); + testClient.getTile(z, x, y, { format, response }, err => { + assert.ifError(err); + testClient.drain(done); + }); }); }); - }); + describe('overviews', function () { + function createMapConfig(layers, dataviews, analysis) { + return { + version: '1.8.0', + layers: layers, + dataviews: dataviews || {}, + analyses: analysis || [] + }; + } - - const testCases = [ - { - desc: 'should get empty mvt with code 204 (no content)', - coords: { z: 0, x: 0, y: 0 }, - format: 'mvt', - response: { - status: 204, - headers: { - 'Content-Type': undefined - } - }, - mapConfig: createMapConfig(TestClient.SQL.EMPTY) - }, - { - desc: 'should get mvt tile with code 200 (ok)', - coords: { z: 0, x: 0, y: 0 }, - format: 'mvt', - response: { - status: 200, - headers: { - 'Content-Type': 'application/x-protobuf' - } - }, - mapConfig: createMapConfig() - } - ]; - - testCases.forEach(function (test) { - it(test.desc, done => { - var testClient = new TestClient(test.mapConfig); - const { z, x, y } = test.coords; - const { format, response } = test; - - testClient.getTile(z, x, y, { format, response }, err => { - assert.ifError(err); - testClient.drain(done); - }); - }); - }); - - describe('overviews', function () { - function createMapConfig(layers, dataviews, analysis) { - return { - version: '1.8.0', - layers: layers, - dataviews: dataviews || {}, - analyses: analysis || [] - }; - } - - it('should use overviews to fetch mvt data', function (done) { - const mapConfig = createMapConfig( - [ - { - "type": "cartodb", - "options": { - "sql": 'SELECT * FROM test_table_overviews', - "cartocss": TestClient.CARTOCSS.POINTS, - "cartocss_version": "2.3.0" + it('should use overviews to fetch mvt data', function (done) { + const mapConfig = createMapConfig( + [ + { + "type": "cartodb", + "options": { + "sql": 'SELECT * FROM test_table_overviews', + "cartocss": TestClient.CARTOCSS.POINTS, + "cartocss_version": "2.3.0" + } } - } - ] - ); + ] + ); - const testClient = new TestClient(mapConfig); - const [ z, x, y ] = [ 0, 0, 0 ]; - const options = { format: 'mvt' }; + const testClient = new TestClient(mapConfig); + const [ z, x, y ] = [ 0, 0, 0 ]; + const options = { format: 'mvt' }; - testClient.getTile(z, x, y, options, function (err, res, mvt) { - assert.ifError(err); + testClient.getTile(z, x, y, options, function (err, res, mvt) { + assert.ifError(err); - const geojsonTile = JSON.parse(mvt.toGeoJSONSync(0)); + const geojsonTile = JSON.parse(mvt.toGeoJSONSync(0)); - assert.ok(Array.isArray(geojsonTile.features)); - assert.ok(geojsonTile.features.length > 0); + assert.ok(Array.isArray(geojsonTile.features)); + assert.ok(geojsonTile.features.length > 0); - const feature = geojsonTile.features[0]; + const feature = geojsonTile.features[0]; - assert.ok(feature.properties.hasOwnProperty('_feature_count'), 'Missing _feature_count property'); - assert.equal(feature.properties.cartodb_id, 1); - assert.equal(feature.properties.name, 'Hawai'); - assert.equal(feature.properties._feature_count, 5); // original table has _feature_count = 1 - assert.equal(feature.properties.value, 3); // original table has value = 1.0 + assert.ok(feature.properties.hasOwnProperty('_feature_count'), 'Missing _feature_count property'); + assert.equal(feature.properties.cartodb_id, 1); + assert.equal(feature.properties.name, 'Hawai'); + assert.equal(feature.properties._feature_count, 5); // original table has _feature_count = 1 + assert.equal(feature.properties.value, 3); // original table has value = 1.0 - testClient.drain(done); + testClient.drain(done); + }); + }); + + it('first layer should use overviews, second layer shouldn\'t', function (done) { + const mapConfig = createMapConfig( + [ + { + "type": "cartodb", + "options": { + "sql": 'SELECT * FROM test_table_overviews', + "cartocss": TestClient.CARTOCSS.POINTS, + "cartocss_version": "2.3.0" + } + }, + { + "type": "cartodb", + "options": { + "sql": 'SELECT * FROM test_table', + "cartocss": TestClient.CARTOCSS.POINTS, + "cartocss_version": "2.3.0" + } + } + ] + ); + + const testClient = new TestClient(mapConfig); + const [ z, x, y ] = [ 0, 0, 0 ]; + const options = { format: 'mvt' }; + + testClient.getTile(z, x, y, options, function (err, res, mvt) { + assert.ifError(err); + + const tileWithOverviews = JSON.parse(mvt.toGeoJSONSync(0)); + const tileWithoutOverviews = JSON.parse(mvt.toGeoJSONSync(1)); + + assert.ok(Array.isArray(tileWithOverviews.features)); + assert.equal(tileWithOverviews.features.length, 1); + assert.equal(tileWithOverviews.features[0].properties._feature_count, 5); + + assert.ok(Array.isArray(tileWithoutOverviews.features)); + assert.equal(tileWithoutOverviews.features.length, 5); + assert.equal(tileWithoutOverviews.features[0].properties._feature_count, undefined); + + testClient.drain(done); + }); }); }); - - it('first layer should use overviews, second layer shouldn\'t', function (done) { - const mapConfig = createMapConfig( - [ - { - "type": "cartodb", - "options": { - "sql": 'SELECT * FROM test_table_overviews', - "cartocss": TestClient.CARTOCSS.POINTS, - "cartocss_version": "2.3.0" - } - }, - { - "type": "cartodb", - "options": { - "sql": 'SELECT * FROM test_table', - "cartocss": TestClient.CARTOCSS.POINTS, - "cartocss_version": "2.3.0" - } - } - ] - ); - - const testClient = new TestClient(mapConfig); - const [ z, x, y ] = [ 0, 0, 0 ]; - const options = { format: 'mvt' }; - - testClient.getTile(z, x, y, options, function (err, res, mvt) { - assert.ifError(err); - - const tileWithOverviews = JSON.parse(mvt.toGeoJSONSync(0)); - const tileWithoutOverviews = JSON.parse(mvt.toGeoJSONSync(1)); - - assert.ok(Array.isArray(tileWithOverviews.features)); - assert.equal(tileWithOverviews.features.length, 1); - assert.equal(tileWithOverviews.features[0].properties._feature_count, 5); - - assert.ok(Array.isArray(tileWithoutOverviews.features)); - assert.equal(tileWithoutOverviews.features.length, 5); - assert.equal(tileWithoutOverviews.features[0].properties._feature_count, undefined); - - testClient.drain(done); - }); - }); - }); -}; + }; } diff --git a/test/acceptance/rate-limit.test.js b/test/acceptance/rate-limit.test.js index 9fc6b641..f5b6f456 100644 --- a/test/acceptance/rate-limit.test.js +++ b/test/acceptance/rate-limit.test.js @@ -276,11 +276,11 @@ describe('rate limit middleware', function () { }); }); -const describe_pg = process.env.POSTGIS_VERSION >= '20400' ? describe : describe.skip; + const originalUsePostGIS = serverOptions.renderer.mvt.usePostGIS; -describe('rate limit and vector tiles (mapnik)', () => { rateLimitAndVectorTilesTest(false); }); -describe_pg('rate limit and vector tiles (postgis)', () => { rateLimitAndVectorTilesTest(true); }); +describe('rate limit and vector tiles (mapnik)', () => rateLimitAndVectorTilesTest(false)); +describe('rate limit and vector tiles (postgis)', () => rateLimitAndVectorTilesTest(true)); function rateLimitAndVectorTilesTest(usePostGIS) { diff --git a/test/acceptance/stats/mapnik_stats_layergroup.js b/test/acceptance/stats/mapnik_stats_layergroup.js index 8f73e5a2..dde4f059 100644 --- a/test/acceptance/stats/mapnik_stats_layergroup.js +++ b/test/acceptance/stats/mapnik_stats_layergroup.js @@ -6,17 +6,16 @@ var assert = require('../../support/assert'); var TestClient = require('../../support/test-client'); const serverOptions = require('../../../lib/cartodb/server_options'); -const suites = [{ - desc: 'mvt (mapnik)', - usePostGIS: false -}]; - -if (process.env.POSTGIS_VERSION >= '20400') { - suites.push({ +const suites = [ + { + desc: 'mvt (mapnik)', + usePostGIS: false + }, + { desc: 'mvt (postgis)', usePostGIS: true - }); -} + } +]; suites.forEach(({desc, usePostGIS}) => { describe(`[${desc}] Create mapnik layergroup`, function() { diff --git a/test/acceptance/tilejson.js b/test/acceptance/tilejson.js index d71865e9..19ef158f 100644 --- a/test/acceptance/tilejson.js +++ b/test/acceptance/tilejson.js @@ -6,11 +6,10 @@ const assert = require('../support/assert'); const TestClient = require('../support/test-client'); const serverOptions = require('../../lib/cartodb/server_options'); -const describe_pg = process.env.POSTGIS_VERSION >= '20400' ? describe : describe.skip; const originalUsePostGIS = serverOptions.renderer.mvt.usePostGIS; -describe('tilejson via mapnik renderer', () => { tileJsonSuite(false); }); -describe_pg('tilejson via postgis renderer', () => { tileJsonSuite(true); }); +describe('tilejson via mapnik renderer', () => tileJsonSuite(false)); +describe('tilejson via postgis renderer', () => tileJsonSuite(true)); function tileJsonSuite(usePostGIS) { diff --git a/test/acceptance/user-database-timeout-limit.js b/test/acceptance/user-database-timeout-limit.js index 3448ebfc..629d3778 100644 --- a/test/acceptance/user-database-timeout-limit.js +++ b/test/acceptance/user-database-timeout-limit.js @@ -394,9 +394,8 @@ describe('user database timeout limit', function () { }); }); - const describe_pg = process.env.POSTGIS_VERSION >= '20400' ? describe : describe.skip; - describe('fetching vector tiles via mapnik renderer', () => { testFetchingVectorTiles(false); }); - describe_pg('fetching vector tiles via postgis renderer', () => { testFetchingVectorTiles(true); }); + describe('fetching vector tiles via mapnik renderer', () => testFetchingVectorTiles(false)); + describe('fetching vector tiles via postgis renderer', () => testFetchingVectorTiles(true)); function testFetchingVectorTiles(usePostGIS) { const originalUsePostGIS = serverOptions.renderer.mvt.usePostGIS; diff --git a/test/acceptance/vector-layergroup.js b/test/acceptance/vector-layergroup.js index d12e8e07..d23403db 100644 --- a/test/acceptance/vector-layergroup.js +++ b/test/acceptance/vector-layergroup.js @@ -69,17 +69,16 @@ const INVALID_FORMAT_ERROR = { ] }; -const suites = [{ - desc: 'mvt (mapnik)', - usePostGIS: false -}]; - -if (process.env.POSTGIS_VERSION >= '20400') { - suites.push({ +const suites = [ + { + desc: 'mvt (mapnik)', + usePostGIS: false + }, + { desc: 'mvt (postgis)', usePostGIS: true - }); -} + } +]; suites.forEach((suite) => { const { desc, usePostGIS } = suite; diff --git a/test/support/prepare_db.sh b/test/support/prepare_db.sh index 5834a8ad..5989b498 100755 --- a/test/support/prepare_db.sh +++ b/test/support/prepare_db.sh @@ -73,20 +73,6 @@ echo "PUBLICPASS: ${PUBLICPASS}" echo "TESTUSER: ${TESTUSER}" echo "TESTPASS: ${TESTPASS}" -# Sets the env variable POSTGIS_VERSION as Major * 10000 + Minor * 100 + Patch -# For example, for 2.4.5 ~> 20405 -auto_postgis_version() { - local POSTGIS_STR=$(psql -c "Select default_version from pg_available_extensions WHERE name = 'postgis';" -t); - local pg_version=$(echo $POSTGIS_STR | awk -F '.' '{print $1 * 10000 + $2 * 100 + $3}') - - echo $pg_version -} - -if [ -z "$POSTGIS_VERSION" ]; then - export POSTGIS_VERSION=$(auto_postgis_version) - echo "POSTGIS_VERSION: ${POSTGIS_VERSION}" -fi - if test x"$PREPARE_PGSQL" = xyes; then echo "preparing postgres..." From 6fee16fe5e097d64bea0cb3c9b8c8e0d2dc3232d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 22 Feb 2019 10:49:22 +0100 Subject: [PATCH 42/84] Update comments and config --- NEWS.md | 1 + config/environments/development.js.example | 7 +++---- config/environments/production.js.example | 7 +++---- config/environments/staging.js.example | 7 +++---- config/environments/test.js.example | 7 +++---- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/NEWS.md b/NEWS.md index a6889830..f7f738bb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,6 +12,7 @@ Breaking changes: - Drop support for Redis 3 Announcements: +- In configuration, set `clipByBox2d` to true by default - Update docs: compatible Node.js and npm versions - Report fine-grained Garbage Collector stats - Adding Authorization to Access-Control-Allow-Headers (https://github.com/CartoDB/CartoDB-SQL-API/issues/534) diff --git a/config/environments/development.js.example b/config/environments/development.js.example index 0cddec6c..ed0aaf58 100644 --- a/config/environments/development.js.example +++ b/config/environments/development.js.example @@ -127,9 +127,8 @@ var config = { cache_ttl: 60000, statsInterval: 5000, // milliseconds between each report to statsd about number of renderers and mapnik pool status mvt: { - //If enabled, MVTs will be generated with PostGIS directly, instead of using Mapnik, - //PostGIS 2.4 is required for this to work - //If disabled it will use Mapnik MVT generation + //If enabled, MVTs will be generated with PostGIS directly + //If disabled, MVTs will be generated with Mapnik MVT usePostGIS: true }, mapnik: { @@ -186,7 +185,7 @@ var config = { // SQL queries will be wrapped with ST_ClipByBox2D // Returning the portion of a geometry falling within a rectangle // It will only work if snapToGrid is enabled - clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5 + clipByBox2d: true, postgis: { // Parameters to pass to datasource plugin of mapnik diff --git a/config/environments/production.js.example b/config/environments/production.js.example index f73a095c..bc34db81 100644 --- a/config/environments/production.js.example +++ b/config/environments/production.js.example @@ -127,9 +127,8 @@ var config = { cache_ttl: 60000, statsInterval: 5000, // milliseconds between each report to statsd about number of renderers and mapnik pool status mvt: { - //If enabled, MVTs will be generated with PostGIS directly, instead of using Mapnik, - //PostGIS 2.4 is required for this to work - //If disabled it will use Mapnik MVT generation + //If enabled, MVTs will be generated with PostGIS directly + //If disabled, MVTs will be generated with Mapnik MVT usePostGIS: true }, mapnik: { @@ -186,7 +185,7 @@ var config = { // SQL queries will be wrapped with ST_ClipByBox2D // Returning the portion of a geometry falling within a rectangle // It will only work if snapToGrid is enabled - clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5 + clipByBox2d: true, postgis: { // Parameters to pass to datasource plugin of mapnik diff --git a/config/environments/staging.js.example b/config/environments/staging.js.example index 9bef903d..41f8e281 100644 --- a/config/environments/staging.js.example +++ b/config/environments/staging.js.example @@ -127,9 +127,8 @@ var config = { cache_ttl: 60000, statsInterval: 5000, // milliseconds between each report to statsd about number of renderers and mapnik pool status mvt: { - //If enabled, MVTs will be generated with PostGIS directly, instead of using Mapnik, - //PostGIS 2.4 is required for this to work - //If disabled it will use Mapnik MVT generation + //If enabled, MVTs will be generated with PostGIS directly + //If disabled, MVTs will be generated with Mapnik MVT usePostGIS: true }, mapnik: { @@ -186,7 +185,7 @@ var config = { // SQL queries will be wrapped with ST_ClipByBox2D // Returning the portion of a geometry falling within a rectangle // It will only work if snapToGrid is enabled - clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5 + clipByBox2d: true, postgis: { // Parameters to pass to datasource plugin of mapnik diff --git a/config/environments/test.js.example b/config/environments/test.js.example index 7f4e4149..05c5e94c 100644 --- a/config/environments/test.js.example +++ b/config/environments/test.js.example @@ -127,9 +127,8 @@ var config = { cache_ttl: 60000, statsInterval: 5000, // milliseconds between each report to statsd about number of renderers and mapnik pool status mvt: { - //If enabled, MVTs will be generated with PostGIS directly, instead of using Mapnik, - //PostGIS 2.4 is required for this to work - //If disabled it will use Mapnik MVT generation + //If enabled, MVTs will be generated with PostGIS directly + //If disabled, MVTs will be generated with Mapnik MVT usePostGIS: true }, mapnik: { @@ -186,7 +185,7 @@ var config = { // SQL queries will be wrapped with ST_ClipByBox2D // Returning the portion of a geometry falling within a rectangle // It will only work if snapToGrid is enabled - clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5 + clipByBox2d: true, postgis: { // Parameters to pass to datasource plugin of mapnik From fff54f021c07ba67a71dc3bd2356dae28cc04c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 22 Feb 2019 12:11:14 +0100 Subject: [PATCH 43/84] Improve doc --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 5b0c3580..f5102f3b 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -6,12 +6,12 @@ Make sure that you have the requirements needed. These are: - Node 10.x - npm 6.x -- gcc 4.9 - PostgreSQL >= 10.0 - PostGIS >= 2.4 - CARTO Postgres Extension >= 0.24.1 - Redis >= 4 - libcairo2-dev, libpango1.0-dev, libjpeg8-dev and libgif-dev for server side canvas support +- C++11 (to build internal dependencies if needed) ### Optional From ca7f0ad4a6a53b2aa7e2e4b78d2a291d3ad9fca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 22 Feb 2019 12:52:10 +0100 Subject: [PATCH 44/84] Release 7.0.0 --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index f7f738bb..09fb5155 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,7 @@ # Changelog ## 7.0.0 -Released 2019-mm-dd +Released 2019-02-22 Breaking changes: - Drop support for Node.js 6 @@ -23,7 +23,7 @@ Announcements: - jshint@2.9.7 - mocha@5.2.0 - Be able to customize max waiting workers parameter -- Handle 'max waitingClients count exceeded' error as "429, You are over platfor's limits" +- Handle 'max waitingClients count exceeded' error as "429, You are over platform's limits" ## 6.5.1 Released 2018-12-26 From afd81a7814041eb1193b35ce380fdcd89913c548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 22 Feb 2019 13:09:00 +0100 Subject: [PATCH 45/84] Stubs next version --- NEWS.md | 4 ++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 09fb5155..1e592442 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # Changelog +## 7.0.1 +Released 2019-mm-dd + + ## 7.0.0 Released 2019-02-22 diff --git a/package-lock.json b/package-lock.json index 5d4ae011..fb770817 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "windshaft-cartodb", - "version": "7.0.0", + "version": "7.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 403a127e..f2ce3a36 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "windshaft-cartodb", - "version": "7.0.0", + "version": "7.0.1", "description": "A map tile server for CartoDB", "keywords": [ "cartodb" From f82e4031802742419bdd071eb09a4219b476b6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 25 Feb 2019 19:40:18 +0100 Subject: [PATCH 46/84] Implement query to get features of a cluster for an aggregated map from @jgoizueta's cluster query --- .../models/aggregation/aggregation-query.js | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib/cartodb/models/aggregation/aggregation-query.js b/lib/cartodb/models/aggregation/aggregation-query.js index 1bddf02d..f74df93a 100644 --- a/lib/cartodb/models/aggregation/aggregation-query.js +++ b/lib/cartodb/models/aggregation/aggregation-query.js @@ -448,3 +448,31 @@ const aggregationQueryTemplates = { module.exports.SUPPORTED_PLACEMENTS = Object.keys(aggregationQueryTemplates); module.exports.GEOMETRY_COLUMN = 'the_geom_webmercator'; + +const clusterFeaturesQuery = ctx => ` + WITH + _cdb_params AS ( + SELECT + ${gridResolution(ctx)} AS res + ), + _cell AS ( + SELECT + ST_MakeEnvelope( + Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res)*_cdb_params.res, + Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)*_cdb_params.res, + Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res + 1)*_cdb_params.res, + Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res + 1)*_cdb_params.res, + 3857 + ) AS bbox + FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params + WHERE _cdb_query.cartodb_id = ${ctx.id} + ) + SELECT _cdb_query.* FROM _cell, (${ctx.sourceQuery}) _cdb_query + WHERE ST_Intersects(_cdb_query.the_geom_webmercator, _cell.bbox) +`; + +module.exports.featuresQuery = (id, options) => clusterFeaturesQuery({ + id, + sourceQuery: options.query, + res: 256/options.resolution +}); From c3df075d91973929650172bffb3ba9845cf5040c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 26 Feb 2019 19:19:44 +0100 Subject: [PATCH 47/84] Draft --- lib/cartodb/api/api-router.js | 6 +- ...lustered-features-layergroup-controller.js | 91 ++++++++++ lib/cartodb/api/map/map-router.js | 15 +- lib/cartodb/backends/cluster.js | 168 ++++++++++++++++++ test/acceptance/cluster.js | 49 +++++ test/support/test-client.js | 100 +++++++++++ 6 files changed, 427 insertions(+), 2 deletions(-) create mode 100644 lib/cartodb/api/map/clustered-features-layergroup-controller.js create mode 100644 lib/cartodb/backends/cluster.js create mode 100644 test/acceptance/cluster.js diff --git a/lib/cartodb/api/api-router.js b/lib/cartodb/api/api-router.js index fafc90b3..af82a3b8 100644 --- a/lib/cartodb/api/api-router.js +++ b/lib/cartodb/api/api-router.js @@ -21,6 +21,8 @@ const OverviewsMetadataBackend = require('../backends/overviews-metadata'); const FilterStatsApi = require('../backends/filter-stats'); const TablesExtentBackend = require('../backends/tables-extent'); +const ClusterBackend = require('../backends/cluster'); + const LayergroupAffectedTablesCache = require('../cache/layergroup_affected_tables'); const SurrogateKeysCache = require('../cache/surrogate_keys_cache'); const VarnishHttpCacheBackend = require('../cache/backend/varnish_http'); @@ -110,6 +112,7 @@ module.exports = class ApiRouter { const analysisBackend = new AnalysisBackend(metadataBackend, serverOptions.analysis); const dataviewBackend = new DataviewBackend(analysisBackend); const statsBackend = new StatsBackend(); + const clusterBackend = new ClusterBackend(); const userLimitsBackend = new UserLimitsBackend(metadataBackend, { limits: { @@ -179,7 +182,8 @@ module.exports = class ApiRouter { statsBackend, layergroupMetadata, namedMapProviderCache, - tablesExtentBackend + tablesExtentBackend, + clusterBackend }; this.mapRouter = new MapRouter({ collaborators }); diff --git a/lib/cartodb/api/map/clustered-features-layergroup-controller.js b/lib/cartodb/api/map/clustered-features-layergroup-controller.js new file mode 100644 index 00000000..2d83fd4d --- /dev/null +++ b/lib/cartodb/api/map/clustered-features-layergroup-controller.js @@ -0,0 +1,91 @@ +'use strict'; + +const layergroupToken = require('../middlewares/layergroup-token'); +const cleanUpQueryParams = require('../middlewares/clean-up-query-params'); +const credentials = require('../middlewares/credentials'); +const dbConnSetup = require('../middlewares/db-conn-setup'); +const authorize = require('../middlewares/authorize'); +const rateLimit = require('../middlewares/rate-limit'); +const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit; +const createMapStoreMapConfigProvider = require('../middlewares/map-store-map-config-provider'); +const cacheControlHeader = require('../middlewares/cache-control-header'); +const cacheChannelHeader = require('../middlewares/cache-channel-header'); +const surrogateKeyHeader = require('../middlewares/surrogate-key-header'); +const lastModifiedHeader = require('../middlewares/last-modified-header'); + +module.exports = class AggregatedFeaturesLayergroupController { + constructor ( + clusterBackend, + pgConnection, + mapStore, + userLimitsBackend, + layergroupAffectedTablesCache, + authBackend, + surrogateKeysCache + ) { + this.clusterBackend = clusterBackend; + this.pgConnection = pgConnection; + this.mapStore = mapStore; + this.userLimitsBackend = userLimitsBackend; + this.layergroupAffectedTablesCache = layergroupAffectedTablesCache; + this.authBackend = authBackend; + this.surrogateKeysCache = surrogateKeysCache; + } + + register (mapRouter) { + mapRouter.get('/:token/:layer/cluster/:clusterId', this.middlewares()); + } + + middlewares () { + return [ + layergroupToken(), + credentials(), + authorize(this.authBackend), + dbConnSetup(this.pgConnection), + rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.ATTRIBUTES), + cleanUpQueryParams(), + createMapStoreMapConfigProvider( + this.mapStore, + this.userLimitsBackend, + this.pgConnection, + this.layergroupAffectedTablesCache + ), + getClusteredFeatures(this.clusterBackend), + cacheControlHeader(), + cacheChannelHeader(), + surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }), + lastModifiedHeader() + ]; + } +}; + +function getClusteredFeatures (clusterBackend) { + return function getFeatureAttributesMiddleware (req, res, next) { + req.profiler.start('windshaft.maplayer_cluster_features'); + + const { mapConfigProvider } = res.locals; + const { token } = res.locals; + const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals; + const { layer, clusterId } = req.params; + + const params = { + token, + dbuser, dbname, dbpassword, dbhost, dbport, + layer, clusterId + }; + + clusterBackend.getClusterFeatures(mapConfigProvider, params, (err, features, stats = {}) => { + req.profiler.add(stats); + + if (err) { + err.label = 'GET CLUSTERED FEATURES'; + return next(err); + } + + res.statusCode = 200; + res.body = features; + + next(); + }); + }; +} diff --git a/lib/cartodb/api/map/map-router.js b/lib/cartodb/api/map/map-router.js index dac55670..8fb7b8b2 100644 --- a/lib/cartodb/api/map/map-router.js +++ b/lib/cartodb/api/map/map-router.js @@ -10,6 +10,7 @@ const TileLayergroupController = require('./tile-layergroup-controller'); const AnonymousMapController = require('./anonymous-map-controller'); const PreviewTemplateController = require('./preview-template-controller'); const AnalysesCatalogController = require('./analyses-catalog-controller'); +const ClusteredFeaturesLayergroupController = require('./clustered-features-layergroup-controller'); module.exports = class MapRouter { constructor ({ collaborators }) { @@ -32,7 +33,8 @@ module.exports = class MapRouter { statsBackend, layergroupMetadata, namedMapProviderCache, - tablesExtentBackend + tablesExtentBackend, + clusterBackend } = collaborators; this.analysisLayergroupController = new AnalysisLayergroupController( @@ -112,6 +114,16 @@ module.exports = class MapRouter { authBackend, userLimitsBackend ); + + this.clusteredFeaturesLayergroupController = new ClusteredFeaturesLayergroupController( + clusterBackend, + pgConnection, + mapStore, + userLimitsBackend, + layergroupAffectedTablesCache, + authBackend, + surrogateKeysCache + ); } register (apiRouter, mapPaths) { @@ -125,6 +137,7 @@ module.exports = class MapRouter { this.anonymousMapController.register(mapRouter); this.previewTemplateController.register(mapRouter); this.analysesController.register(mapRouter); + this.clusteredFeaturesLayergroupController.register(mapRouter); mapPaths.forEach(path => apiRouter.use(path, mapRouter)); } diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js new file mode 100644 index 00000000..a76bba29 --- /dev/null +++ b/lib/cartodb/backends/cluster.js @@ -0,0 +1,168 @@ +'use strict'; + +const PSQL = require('cartodb-psql'); +const dbParamsFromReqParams = require('../utils/database-params'); +const debug = require('debug')('backend:cluster'); + +module.exports = class ClusterBackend { + getClusterFeatures (mapConfigProvider, params, callback) { + mapConfigProvider.getMapConfig((err, mapConfig) => { + if (err) { + return callback(err); + } + + // if (!mapConfig.isAggregationLayer(params.layer)) { + // const error = new Error(`Map ${params.token} has no aggregation defined for layer ${params.layer}`); + // return callback(error); + // } + + const layer = mapConfig.getLayer(params.layer); + + let pg; + try { + pg = new PSQL(dbParamsFromReqParams(params)); + } catch (error) { + return callback(error); + } + + const query = layer.options.sql; + const resolution = layer.options.aggregation.resolution || 1; + + getColumnsName(pg, query, (err, columns) => { + if (err) { + return callback(err); + } + + const { clusterId } = params; + + getClusterFeatures(pg, clusterId, columns, query, resolution, (err, features) => { + if (err) { + return callback(err); + } + + return callback(null, features); + }); + }); + }); + } +}; + +const SKIP_COLUMNS = { + 'the_geom': true, + 'the_geom_webmercator': true +}; + +function getColumnsName (pg, query, callback) { + const sql = replaceTokens(limitedQuery({ + query: query + })); + + debug(sql); + + pg.query(sql, function (err, resultSet) { + if (err) { + return callback(err); + } + + const fields = resultSet.fields || []; + const columnNames = fields.map(field => field.name) + .filter(columnName => !SKIP_COLUMNS[columnName]); + + return callback(null, columnNames); + }, true); +} + +function getClusterFeatures (pg, clusterId, columns, query, resolution, callback) { + const sql = replaceTokens(clusterFeaturesQuery({ + id: clusterId, + query: query, + res: resolution, + columns: columns + })); + + debug(sql); + + pg.query(sql, (err, data) => { + if (err) { + return callback(err); + } + + return callback(null, data); + } , true); // use read-only transaction +} + +const SUBSTITUTION_TOKENS = { + bbox: /!bbox!/g, + scale_denominator: /!scale_denominator!/g, + pixel_width: /!pixel_width!/g, + pixel_height: /!pixel_height!/g, + var_zoom: /@zoom/g, + var_bbox: /@bbox/g, + var_x: /@x/g, + var_y: /@y/g, +}; + +function replaceTokens(sql, replaceValues) { + if (!sql) { + return sql; + } + + replaceValues = replaceValues || { + bbox: 'ST_MakeEnvelope(0,0,0,0)', + scale_denominator: '0', + pixel_width: '1', + pixel_height: '1', + var_zoom: '0', + var_bbox: '[0,0,0,0]', + var_x: '0', + var_y: '0' + }; + + Object.keys(replaceValues).forEach(function(token) { + if (SUBSTITUTION_TOKENS[token]) { + sql = sql.replace(SUBSTITUTION_TOKENS[token], replaceValues[token]); + } + }); + + return sql; +} + +const limitedQuery = ctx => `SELECT * FROM (${ctx.query}) __cdb_schema LIMIT 0`; +// const nonGeomsQuery = ctx => `SELECT ${ctx.columns.join(', ')} FROM (${ctx.query}) __cdb_non_geoms_query`; +const clusterFeaturesQuery = ctx => ` + WITH + _cdb_params AS ( + SELECT + ${gridResolution(ctx)} AS res + ), + _cell AS ( + SELECT + ST_MakeEnvelope( + Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res)*_cdb_params.res, + Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)*_cdb_params.res, + Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res + 1)*_cdb_params.res, + Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res + 1)*_cdb_params.res, + 3857 + ) AS bbox + FROM (${ctx.query}) _cdb_query, _cdb_params + WHERE _cdb_query.cartodb_id = ${ctx.id} + ) + SELECT ${ctx.columns.join(', ')} FROM ( + SELECT _cdb_query.* FROM _cell, (${ctx.query}) _cdb_query + WHERE ST_Intersects(_cdb_query.the_geom_webmercator, _cell.bbox) + ) __cdb_non_geoms_query +`; + +// SQL expression to compute the aggregation resolution (grid cell size). +// This is defined by the ctx.res parameter, which is the number of grid cells per tile linear dimension +// (i.e. each tile is divided into ctx.res*ctx.res cells). +// We limit the the minimum resolution to avoid division by zero problems. The limit used is +// the pixel size of zoom level 30 (i.e. 1/2*(30+8) of the full earth web-mercator extent), which is about 0.15 mm. +// Computing this using !scale_denominator!, !pixel_width! or !pixel_height! produces +// inaccurate results due to rounding present in those values. +const gridResolution = ctx => { + const minimumResolution = 2*Math.PI*6378137/Math.pow(2,38); + const pixelSize = 'CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!))'; + debug(ctx); + return `GREATEST(${256/ctx.res}*${pixelSize}, ${minimumResolution})::double precision`; +}; diff --git a/test/acceptance/cluster.js b/test/acceptance/cluster.js new file mode 100644 index 00000000..0cb3f1a7 --- /dev/null +++ b/test/acceptance/cluster.js @@ -0,0 +1,49 @@ +'use strict'; + +require('../support/test_helper'); + +// const assert = require('../support/assert'); +const TestClient = require('../support/test-client'); + +const POINTS_SQL_1 = ` + select + x + 4 as cartodb_id, + st_setsrid(st_makepoint(x*10, x*10), 4326) as the_geom, + st_transform(st_setsrid(st_makepoint(x*10, x*10), 4326), 3857) as the_geom_webmercator, + x as value + from generate_series(-3, 3) x +`; + +const defaultLayers = [{ + type: 'cartodb', + options: { + sql: POINTS_SQL_1, + aggregation: true + } +}]; + +function createVectorMapConfig (layers = defaultLayers) { + return { + version: '1.8.0', + layers: layers + }; +} + +describe('cluster', function () { + it.only('should get aggregated features of an aggregated map', function (done) { + const mapConfig = createVectorMapConfig(); + const testClient = new TestClient(mapConfig); + const clusterId = 1; + const layerId = 0; + const params = {}; + + testClient.getClusterFeatures(clusterId, layerId, params, (err, body) => { + if (err) { + return done(err); + } + + console.log('>>>>>>>>>>>>', body.rows); + testClient.drain(done); + }); + }); +}); diff --git a/test/support/test-client.js b/test/support/test-client.js index 77363b01..9eb340cb 100644 --- a/test/support/test-client.js +++ b/test/support/test-client.js @@ -620,6 +620,106 @@ TestClient.prototype.getFeatureAttributes = function(featureId, layerId, params, ); }; +TestClient.prototype.getClusterFeatures = function(clusterId, layerId, params, callback) { + var self = this; + + if (!callback) { + callback = params; + params = {}; + } + + var extraParams = {}; + + if (this.apiKey) { + extraParams.api_key = this.apiKey; + } + + // if (params && params.filters) { + // extraParams.filters = JSON.stringify(params.filters); + // } + + var url = '/api/v1/map'; + if (Object.keys(extraParams).length > 0) { + url += '?' + qs.stringify(extraParams); + } + + var expectedResponse = params.response || { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + step( + function createLayergroup() { + var next = this; + assert.response(self.server, + { + url: url, + method: 'POST', + headers: { + host: 'localhost', + 'Content-Type': 'application/json' + }, + data: JSON.stringify(self.mapConfig) + }, + { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }, + function(res, err) { + if (err) { + return next(err); + } + + var parsedBody = JSON.parse(res.body); + + if (parsedBody.layergroupid) { + self.keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0; + self.keysToDelete['user:localhost:mapviews:global'] = 5; + } + + return next(null, parsedBody.layergroupid); + } + ); + }, + function getCLusterFeatures(err, layergroupId) { + assert.ifError(err); + + var next = this; + + url = '/api/v1/map/' + layergroupId + '/' + layerId + '/cluster/' + clusterId; + + assert.response(self.server, + { + url: url, + method: 'GET', + headers: { + host: 'localhost' + } + }, + expectedResponse, + function(res, err) { + if (err) { + return next(err); + } + + next(null, JSON.parse(res.body)); + } + ); + }, + function finish(err, attributes) { + if (err) { + return callback(err); + } + + return callback(null, attributes); + } + ); +}; + TestClient.prototype.getTile = function(z, x, y, params, callback) { var self = this; From b87298bad90f1cd475c245385156217c694032e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 27 Feb 2019 12:42:54 +0100 Subject: [PATCH 48/84] Fix resolution definition and use zoom as path param --- ...lustered-features-layergroup-controller.js | 6 ++-- lib/cartodb/backends/cluster.js | 36 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/lib/cartodb/api/map/clustered-features-layergroup-controller.js b/lib/cartodb/api/map/clustered-features-layergroup-controller.js index 2d83fd4d..b84f0bdf 100644 --- a/lib/cartodb/api/map/clustered-features-layergroup-controller.js +++ b/lib/cartodb/api/map/clustered-features-layergroup-controller.js @@ -33,7 +33,7 @@ module.exports = class AggregatedFeaturesLayergroupController { } register (mapRouter) { - mapRouter.get('/:token/:layer/cluster/:clusterId', this.middlewares()); + mapRouter.get('/:token/:layer/:z/cluster/:clusterId', this.middlewares()); } middlewares () { @@ -66,12 +66,12 @@ function getClusteredFeatures (clusterBackend) { const { mapConfigProvider } = res.locals; const { token } = res.locals; const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals; - const { layer, clusterId } = req.params; + const { layer, z: zoom, clusterId } = req.params; const params = { token, dbuser, dbname, dbpassword, dbhost, dbport, - layer, clusterId + layer, zoom, clusterId }; clusterBackend.getClusterFeatures(mapConfigProvider, params, (err, features, stats = {}) => { diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index a76bba29..2e470596 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -25,7 +25,7 @@ module.exports = class ClusterBackend { return callback(error); } - const query = layer.options.sql; + const query = layer.options.sql_raw; const resolution = layer.options.aggregation.resolution || 1; getColumnsName(pg, query, (err, columns) => { @@ -33,9 +33,9 @@ module.exports = class ClusterBackend { return callback(err); } - const { clusterId } = params; + const { zoom, clusterId } = params; - getClusterFeatures(pg, clusterId, columns, query, resolution, (err, features) => { + getClusterFeatures(pg, zoom, clusterId, columns, query, resolution, (err, features) => { if (err) { return callback(err); } @@ -57,7 +57,7 @@ function getColumnsName (pg, query, callback) { query: query })); - debug(sql); + debug('> getColumnsName:', sql); pg.query(sql, function (err, resultSet) { if (err) { @@ -72,15 +72,16 @@ function getColumnsName (pg, query, callback) { }, true); } -function getClusterFeatures (pg, clusterId, columns, query, resolution, callback) { +function getClusterFeatures (pg, zoom, clusterId, columns, query, resolution, callback) { const sql = replaceTokens(clusterFeaturesQuery({ + zoom: zoom, id: clusterId, query: query, - res: resolution, + res: 256/resolution, columns: columns })); - debug(sql); + debug('> getClusterFeatures:', sql); pg.query(sql, (err, data) => { if (err) { @@ -108,10 +109,10 @@ function replaceTokens(sql, replaceValues) { } replaceValues = replaceValues || { - bbox: 'ST_MakeEnvelope(0,0,0,0)', - scale_denominator: '0', - pixel_width: '1', - pixel_height: '1', + bbox: 'ST_MakeEnvelope(-20037508.34,-20037508.34,20037508.34,20037508.34,3857)', + scale_denominator: '500000001', + pixel_width: '156412', + pixel_height: '156412', var_zoom: '0', var_bbox: '[0,0,0,0]', var_x: '0', @@ -128,7 +129,6 @@ function replaceTokens(sql, replaceValues) { } const limitedQuery = ctx => `SELECT * FROM (${ctx.query}) __cdb_schema LIMIT 0`; -// const nonGeomsQuery = ctx => `SELECT ${ctx.columns.join(', ')} FROM (${ctx.query}) __cdb_non_geoms_query`; const clusterFeaturesQuery = ctx => ` WITH _cdb_params AS ( @@ -147,9 +147,12 @@ const clusterFeaturesQuery = ctx => ` FROM (${ctx.query}) _cdb_query, _cdb_params WHERE _cdb_query.cartodb_id = ${ctx.id} ) - SELECT ${ctx.columns.join(', ')} FROM ( - SELECT _cdb_query.* FROM _cell, (${ctx.query}) _cdb_query - WHERE ST_Intersects(_cdb_query.the_geom_webmercator, _cell.bbox) + SELECT + ${ctx.columns.join(', ')} + FROM ( + SELECT _cdb_query.* + FROM _cell, (${ctx.query}) _cdb_query + WHERE ST_Intersects(_cdb_query.the_geom_webmercator, _cell.bbox) ) __cdb_non_geoms_query `; @@ -162,7 +165,6 @@ const clusterFeaturesQuery = ctx => ` // inaccurate results due to rounding present in those values. const gridResolution = ctx => { const minimumResolution = 2*Math.PI*6378137/Math.pow(2,38); - const pixelSize = 'CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!))'; - debug(ctx); + const pixelSize = `CDB_XYZ_Resolution(${ctx.zoom})`; return `GREATEST(${256/ctx.res}*${pixelSize}, ${minimumResolution})::double precision`; }; From 561be2e5e80de4ff11c092f60204153b0a818c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 27 Feb 2019 12:43:26 +0100 Subject: [PATCH 49/84] Add tests --- test/acceptance/cluster.js | 116 +++++++++++++++++++++++++++++++----- test/support/test-client.js | 6 +- 2 files changed, 105 insertions(+), 17 deletions(-) diff --git a/test/acceptance/cluster.js b/test/acceptance/cluster.js index 0cb3f1a7..c62e87c8 100644 --- a/test/acceptance/cluster.js +++ b/test/acceptance/cluster.js @@ -2,7 +2,7 @@ require('../support/test_helper'); -// const assert = require('../support/assert'); +const assert = require('../support/assert'); const TestClient = require('../support/test-client'); const POINTS_SQL_1 = ` @@ -18,7 +18,9 @@ const defaultLayers = [{ type: 'cartodb', options: { sql: POINTS_SQL_1, - aggregation: true + aggregation: { + threshold: 1 + } } }]; @@ -30,20 +32,106 @@ function createVectorMapConfig (layers = defaultLayers) { } describe('cluster', function () { - it.only('should get aggregated features of an aggregated map', function (done) { - const mapConfig = createVectorMapConfig(); - const testClient = new TestClient(mapConfig); - const clusterId = 1; - const layerId = 0; - const params = {}; - - testClient.getClusterFeatures(clusterId, layerId, params, (err, body) => { - if (err) { - return done(err); + describe('resolution = 1', function () { + const suite = [ + { + cartodb_id: 1, + expected: [ { cartodb_id: 1, value: -3 } ] + }, + { + cartodb_id: 2, + expected: [ { cartodb_id: 2, value: -2 } ] + }, + { + cartodb_id: 3, + expected: [ { cartodb_id: 3, value: -1 } ] + }, + { + cartodb_id: 4, + expected: [ { cartodb_id: 4, value: 0 } ] + }, + { + cartodb_id: 5, + expected: [ { cartodb_id: 5, value: 1 } ] + }, + { + cartodb_id: 6, + expected: [ { cartodb_id: 6, value: 2 } ] } + ]; - console.log('>>>>>>>>>>>>', body.rows); - testClient.drain(done); + suite.forEach(({ cartodb_id, expected }) => { + it(`should get just one disaggregated feature: cartodb_id = ${cartodb_id}`, function (done) { + const mapConfig = createVectorMapConfig(); + const testClient = new TestClient(mapConfig); + const zoom = 0; + const clusterId = cartodb_id; + const layerId = 0; + const params = {}; + + testClient.getClusterFeatures(zoom, clusterId, layerId, params, (err, body) => { + if (err) { + return done(err); + } + + assert.deepStrictEqual(body.rows, expected); + testClient.drain(done); + }); + }); + }); + }); + + describe('resolution = 50', function () { + const suite = [ + { + cartodb_id: 1, + resolution: 50, + expected: [ + { cartodb_id: 1, value: -3 }, + { cartodb_id: 2, value: -2 }, + { cartodb_id: 3, value: -1 }, + { cartodb_id: 4, value: 0 }, + ] + }, + { + cartodb_id: 5, + resolution: 50, + expected: [ + { cartodb_id: 5, value: 1 }, + { cartodb_id: 6, value: 2 }, + { cartodb_id: 7, value: 3 } + ] + } + ]; + + suite.forEach(({ cartodb_id, resolution, expected }) => { + it(`should get just one disaggregated feature: cartodb_id = ${cartodb_id}`, function (done) { + const mapConfig = createVectorMapConfig([{ + type: 'cartodb', + options: { + sql: POINTS_SQL_1, + aggregation: { + threshold: 1, + resolution: resolution + } + } + }]); + + const testClient = new TestClient(mapConfig); + const zoom = 0; + const clusterId = cartodb_id; + const layerId = 0; + const params = {}; + + testClient.getClusterFeatures(zoom, clusterId, layerId, params, (err, body) => { + if (err) { + return done(err); + } + + assert.deepStrictEqual(body.rows, expected); + testClient.drain(done); + }); + }); }); }); }); diff --git a/test/support/test-client.js b/test/support/test-client.js index 9eb340cb..b1e4a211 100644 --- a/test/support/test-client.js +++ b/test/support/test-client.js @@ -620,7 +620,7 @@ TestClient.prototype.getFeatureAttributes = function(featureId, layerId, params, ); }; -TestClient.prototype.getClusterFeatures = function(clusterId, layerId, params, callback) { +TestClient.prototype.getClusterFeatures = function (zoom, clusterId, layerId, params, callback) { var self = this; if (!callback) { @@ -685,12 +685,12 @@ TestClient.prototype.getClusterFeatures = function(clusterId, layerId, params, c } ); }, - function getCLusterFeatures(err, layergroupId) { + function getCLusterFeatures (err, layergroupId) { assert.ifError(err); var next = this; - url = '/api/v1/map/' + layergroupId + '/' + layerId + '/cluster/' + clusterId; + url = '/api/v1/map/' + layergroupId + '/' + layerId + '/' + zoom + '/cluster/' + clusterId; assert.response(self.server, { From 6531770e48d51c6c1ee0acac67c945466dc3b39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 27 Feb 2019 12:47:20 +0100 Subject: [PATCH 50/84] Remove subtitution tokens --- lib/cartodb/backends/cluster.js | 44 +++------------------------------ 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index 2e470596..3779fdf7 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -53,9 +53,9 @@ const SKIP_COLUMNS = { }; function getColumnsName (pg, query, callback) { - const sql = replaceTokens(limitedQuery({ + const sql = limitedQuery({ query: query - })); + }); debug('> getColumnsName:', sql); @@ -73,13 +73,13 @@ function getColumnsName (pg, query, callback) { } function getClusterFeatures (pg, zoom, clusterId, columns, query, resolution, callback) { - const sql = replaceTokens(clusterFeaturesQuery({ + const sql = clusterFeaturesQuery({ zoom: zoom, id: clusterId, query: query, res: 256/resolution, columns: columns - })); + }); debug('> getClusterFeatures:', sql); @@ -92,42 +92,6 @@ function getClusterFeatures (pg, zoom, clusterId, columns, query, resolution, ca } , true); // use read-only transaction } -const SUBSTITUTION_TOKENS = { - bbox: /!bbox!/g, - scale_denominator: /!scale_denominator!/g, - pixel_width: /!pixel_width!/g, - pixel_height: /!pixel_height!/g, - var_zoom: /@zoom/g, - var_bbox: /@bbox/g, - var_x: /@x/g, - var_y: /@y/g, -}; - -function replaceTokens(sql, replaceValues) { - if (!sql) { - return sql; - } - - replaceValues = replaceValues || { - bbox: 'ST_MakeEnvelope(-20037508.34,-20037508.34,20037508.34,20037508.34,3857)', - scale_denominator: '500000001', - pixel_width: '156412', - pixel_height: '156412', - var_zoom: '0', - var_bbox: '[0,0,0,0]', - var_x: '0', - var_y: '0' - }; - - Object.keys(replaceValues).forEach(function(token) { - if (SUBSTITUTION_TOKENS[token]) { - sql = sql.replace(SUBSTITUTION_TOKENS[token], replaceValues[token]); - } - }); - - return sql; -} - const limitedQuery = ctx => `SELECT * FROM (${ctx.query}) __cdb_schema LIMIT 0`; const clusterFeaturesQuery = ctx => ` WITH From 32938eeab79a23663d9c96c925d7a9b65e768d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 27 Feb 2019 18:54:21 +0100 Subject: [PATCH 51/84] Validated only aggregated layers can be requested by the new endpoint --- ...lustered-features-layergroup-controller.js | 4 +- lib/cartodb/backends/cluster.js | 20 +- test/acceptance/cluster.js | 235 ++++++++++++++---- 3 files changed, 200 insertions(+), 59 deletions(-) diff --git a/lib/cartodb/api/map/clustered-features-layergroup-controller.js b/lib/cartodb/api/map/clustered-features-layergroup-controller.js index b84f0bdf..e83a865d 100644 --- a/lib/cartodb/api/map/clustered-features-layergroup-controller.js +++ b/lib/cartodb/api/map/clustered-features-layergroup-controller.js @@ -64,12 +64,12 @@ function getClusteredFeatures (clusterBackend) { req.profiler.start('windshaft.maplayer_cluster_features'); const { mapConfigProvider } = res.locals; - const { token } = res.locals; + const { user, token } = res.locals; const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals; const { layer, z: zoom, clusterId } = req.params; const params = { - token, + user, token, dbuser, dbname, dbpassword, dbhost, dbport, layer, zoom, clusterId }; diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index 3779fdf7..baa48523 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -3,21 +3,15 @@ const PSQL = require('cartodb-psql'); const dbParamsFromReqParams = require('../utils/database-params'); const debug = require('debug')('backend:cluster'); +const AggregationMapConfig = require('../models/aggregation/aggregation-mapconfig'); module.exports = class ClusterBackend { getClusterFeatures (mapConfigProvider, params, callback) { - mapConfigProvider.getMapConfig((err, mapConfig) => { + mapConfigProvider.getMapConfig((err, _mapConfig) => { if (err) { return callback(err); } - // if (!mapConfig.isAggregationLayer(params.layer)) { - // const error = new Error(`Map ${params.token} has no aggregation defined for layer ${params.layer}`); - // return callback(error); - // } - - const layer = mapConfig.getLayer(params.layer); - let pg; try { pg = new PSQL(dbParamsFromReqParams(params)); @@ -25,6 +19,16 @@ module.exports = class ClusterBackend { return callback(error); } + const { user, token, layer: layerIndex } = params; + const mapConfig = new AggregationMapConfig(user, _mapConfig.obj(), pg); + + if (!mapConfig.isAggregationLayer(layerIndex)) { + const error = new Error(`Map ${token} has no aggregation defined for layer ${layerIndex}`); + debug(error); + return callback(error); + } + + const layer = mapConfig.getLayer(layerIndex); const query = layer.options.sql_raw; const resolution = layer.options.aggregation.resolution || 1; diff --git a/test/acceptance/cluster.js b/test/acceptance/cluster.js index c62e87c8..82ec5ff8 100644 --- a/test/acceptance/cluster.js +++ b/test/acceptance/cluster.js @@ -32,58 +32,133 @@ function createVectorMapConfig (layers = defaultLayers) { } describe('cluster', function () { - describe('resolution = 1', function () { - const suite = [ - { - cartodb_id: 1, - expected: [ { cartodb_id: 1, value: -3 } ] - }, - { - cartodb_id: 2, - expected: [ { cartodb_id: 2, value: -2 } ] - }, - { - cartodb_id: 3, - expected: [ { cartodb_id: 3, value: -1 } ] - }, - { - cartodb_id: 4, - expected: [ { cartodb_id: 4, value: 0 } ] - }, - { - cartodb_id: 5, - expected: [ { cartodb_id: 5, value: 1 } ] - }, - { - cartodb_id: 6, - expected: [ { cartodb_id: 6, value: 2 } ] - } - ]; + describe('map-config w/o aggregation', function () { + it('should return error while fetching disaggregated features', function (done) { + const mapConfig = createVectorMapConfig([{ + type: 'cartodb', + options: { + sql: POINTS_SQL_1, + cartocss: TestClient.CARTOCSS.POINTS, + cartocss_version: '2.3.0' + } + }]); + const testClient = new TestClient(mapConfig); + const zoom = 0; + const cartodb_id = 1; + const layerId = 0; + const params = { + response: { + status: 400 + } + }; - suite.forEach(({ cartodb_id, expected }) => { - it(`should get just one disaggregated feature: cartodb_id = ${cartodb_id}`, function (done) { - const mapConfig = createVectorMapConfig(); - const testClient = new TestClient(mapConfig); - const zoom = 0; - const clusterId = cartodb_id; - const layerId = 0; - const params = {}; + testClient.getClusterFeatures(zoom, cartodb_id, layerId, params, (err, body) => { + if (err) { + return done(err); + } - testClient.getClusterFeatures(zoom, clusterId, layerId, params, (err, body) => { - if (err) { - return done(err); - } - - assert.deepStrictEqual(body.rows, expected); - testClient.drain(done); + assert.deepStrictEqual(body, { + errors:[ 'Map d725a568ab961af8197d311eececb83a has no aggregation defined for layer 0' ], + errors_with_context:[ + { + type: 'unknown', + message: 'Map d725a568ab961af8197d311eececb83a has no aggregation defined for layer 0' + } + ] }); + testClient.drain(done); }); }); }); - describe('resolution = 50', function () { + describe('map-config with aggregation', function () { const suite = [ { + zoom: 0, + cartodb_id: 1, + resolution: 0.5, + expected: [ { cartodb_id: 1, value: -3 } ] + }, + { + zoom: 0, + cartodb_id: 2, + resolution: 0.5, + expected: [ { cartodb_id: 2, value: -2 } ] + }, + { + zoom: 0, + cartodb_id: 3, + resolution: 0.5, + expected: [ { cartodb_id: 3, value: -1 } ] + }, + { + zoom: 0, + cartodb_id: 4, + resolution: 0.5, + expected: [ { cartodb_id: 4, value: 0 } ] + }, + { + zoom: 0, + cartodb_id: 5, + resolution: 0.5, + expected: [ { cartodb_id: 5, value: 1 } ] + }, + { + zoom: 0, + cartodb_id: 6, + resolution: 0.5, + expected: [ { cartodb_id: 6, value: 2 } ] + }, + { + zoom: 0, + cartodb_id: 7, + resolution: 0.5, + expected: [ { cartodb_id: 7, value: 3 } ] + }, + { + zoom: 0, + cartodb_id: 1, + resolution: 1, + expected: [ { cartodb_id: 1, value: -3 } ] + }, + { + zoom: 0, + cartodb_id: 2, + resolution: 1, + expected: [ { cartodb_id: 2, value: -2 } ] + }, + { + zoom: 0, + cartodb_id: 3, + resolution: 1, + expected: [ { cartodb_id: 3, value: -1 } ] + }, + { + zoom: 0, + cartodb_id: 4, + resolution: 1, + expected: [ { cartodb_id: 4, value: 0 } ] + }, + { + zoom: 0, + cartodb_id: 5, + resolution: 1, + expected: [ { cartodb_id: 5, value: 1 } ] + }, + { + zoom: 0, + cartodb_id: 6, + resolution: 1, + expected: [ { cartodb_id: 6, value: 2 } ] + }, + { + zoom: 0, + cartodb_id: 7, + resolution: 1, + expected: [ { cartodb_id: 7, value: 3 } ] + }, + { + zoom: 0, cartodb_id: 1, resolution: 50, expected: [ @@ -94,6 +169,70 @@ describe('cluster', function () { ] }, { + zoom: 0, + cartodb_id: 5, + resolution: 50, + expected: [ + { cartodb_id: 5, value: 1 }, + { cartodb_id: 6, value: 2 }, + { cartodb_id: 7, value: 3 } + ] + }, + { + zoom: 1, + cartodb_id: 1, + resolution: 1, + expected: [ { cartodb_id: 1, value: -3 } ] + }, + { + zoom: 1, + cartodb_id: 2, + resolution: 1, + expected: [ { cartodb_id: 2, value: -2 } ] + }, + { + zoom: 1, + cartodb_id: 3, + resolution: 1, + expected: [ { cartodb_id: 3, value: -1 } ] + }, + { + zoom: 1, + cartodb_id: 4, + resolution: 1, + expected: [ { cartodb_id: 4, value: 0 } ] + }, + { + zoom: 1, + cartodb_id: 5, + resolution: 1, + expected: [ { cartodb_id: 5, value: 1 } ] + }, + { + zoom: 1, + cartodb_id: 6, + resolution: 1, + expected: [ { cartodb_id: 6, value: 2 } ] + }, + { + zoom: 1, + cartodb_id: 7, + resolution: 1, + expected: [ { cartodb_id: 7, value: 3 } ] + }, + { + zoom: 1, + cartodb_id: 1, + resolution: 50, + expected: [ + { cartodb_id: 1, value: -3 }, + { cartodb_id: 2, value: -2 }, + { cartodb_id: 3, value: -1 }, + { cartodb_id: 4, value: 0 }, + ] + }, + { + zoom: 1, cartodb_id: 5, resolution: 50, expected: [ @@ -104,8 +243,9 @@ describe('cluster', function () { } ]; - suite.forEach(({ cartodb_id, resolution, expected }) => { - it(`should get just one disaggregated feature: cartodb_id = ${cartodb_id}`, function (done) { + suite.forEach(({ zoom, cartodb_id, resolution, expected }) => { + const description = `should get features for z: ${zoom} cartodb_id: ${cartodb_id}, res: ${resolution}`; + it(description, function (done) { const mapConfig = createVectorMapConfig([{ type: 'cartodb', options: { @@ -116,14 +256,11 @@ describe('cluster', function () { } } }]); - const testClient = new TestClient(mapConfig); - const zoom = 0; - const clusterId = cartodb_id; const layerId = 0; const params = {}; - testClient.getClusterFeatures(zoom, clusterId, layerId, params, (err, body) => { + testClient.getClusterFeatures(zoom, cartodb_id, layerId, params, (err, body) => { if (err) { return done(err); } From 92e62069d4bda3705373d9234341f78b3a49d7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Thu, 28 Feb 2019 12:13:15 +0100 Subject: [PATCH 52/84] Improve error handling --- lib/cartodb/backends/cluster.js | 13 +++++++-- test/acceptance/cluster.js | 52 +++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index baa48523..2b93f2ee 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -22,13 +22,22 @@ module.exports = class ClusterBackend { const { user, token, layer: layerIndex } = params; const mapConfig = new AggregationMapConfig(user, _mapConfig.obj(), pg); - if (!mapConfig.isAggregationLayer(layerIndex)) { + const layer = mapConfig.getLayer(layerIndex); + + if (layer.options.aggregation === false || !mapConfig.isAggregationLayer(layerIndex)) { const error = new Error(`Map ${token} has no aggregation defined for layer ${layerIndex}`); + error.http_status = 400; + error.type = 'layer'; + error.subtype = 'aggregation'; + error.layer = { + index: layerIndex, + type: layer.type + }; + debug(error); return callback(error); } - const layer = mapConfig.getLayer(layerIndex); const query = layer.options.sql_raw; const resolution = layer.options.aggregation.resolution || 1; diff --git a/test/acceptance/cluster.js b/test/acceptance/cluster.js index 82ec5ff8..a31af1b7 100644 --- a/test/acceptance/cluster.js +++ b/test/acceptance/cluster.js @@ -61,11 +61,59 @@ describe('cluster', function () { errors:[ 'Map d725a568ab961af8197d311eececb83a has no aggregation defined for layer 0' ], errors_with_context:[ { - type: 'unknown', - message: 'Map d725a568ab961af8197d311eececb83a has no aggregation defined for layer 0' + layer: { + index: '0', + type: 'cartodb' + }, + message: 'Map d725a568ab961af8197d311eececb83a has no aggregation defined for layer 0', + subtype: 'aggregation', + type: 'layer' } ] }); + + testClient.drain(done); + }); + }); + + it('with aggregation disabled should return error while fetching disaggregated features', function (done) { + const mapConfig = createVectorMapConfig([{ + type: 'cartodb', + options: { + sql: POINTS_SQL_1, + aggregation: false + } + }]); + const testClient = new TestClient(mapConfig); + const zoom = 0; + const cartodb_id = 1; + const layerId = 0; + const params = { + response: { + status: 400 + } + }; + + testClient.getClusterFeatures(zoom, cartodb_id, layerId, params, (err, body) => { + if (err) { + return done(err); + } + + assert.deepStrictEqual(body, { + errors:[ 'Map 3a09728f8c08444820336ea9983ce92b has no aggregation defined for layer 0' ], + errors_with_context:[ + { + layer: { + index: '0', + type: 'cartodb' + }, + message: 'Map 3a09728f8c08444820336ea9983ce92b has no aggregation defined for layer 0', + subtype: 'aggregation', + type: 'layer' + } + ] + }); + testClient.drain(done); }); }); From 77d5d8ebd414bf33e47224f6bd6cdef6da280e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 1 Mar 2019 11:21:18 +0100 Subject: [PATCH 53/84] Be able to aggregate by a field --- ...lustered-features-layergroup-controller.js | 6 +- lib/cartodb/backends/cluster.js | 34 +++- test/acceptance/cluster.js | 187 ++++++++++++++---- test/support/test-client.js | 7 +- 4 files changed, 181 insertions(+), 53 deletions(-) diff --git a/lib/cartodb/api/map/clustered-features-layergroup-controller.js b/lib/cartodb/api/map/clustered-features-layergroup-controller.js index e83a865d..9779a09a 100644 --- a/lib/cartodb/api/map/clustered-features-layergroup-controller.js +++ b/lib/cartodb/api/map/clustered-features-layergroup-controller.js @@ -43,7 +43,7 @@ module.exports = class AggregatedFeaturesLayergroupController { authorize(this.authBackend), dbConnSetup(this.pgConnection), rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.ATTRIBUTES), - cleanUpQueryParams(), + cleanUpQueryParams([ 'aggregation' ]), createMapStoreMapConfigProvider( this.mapStore, this.userLimitsBackend, @@ -67,11 +67,13 @@ function getClusteredFeatures (clusterBackend) { const { user, token } = res.locals; const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals; const { layer, z: zoom, clusterId } = req.params; + const { aggregation } = req.query; const params = { user, token, dbuser, dbname, dbpassword, dbhost, dbport, - layer, zoom, clusterId + layer, zoom, clusterId, + aggregation }; clusterBackend.getClusterFeatures(mapConfigProvider, params, (err, features, stats = {}) => { diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index 2b93f2ee..cba5b80e 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -38,6 +38,7 @@ module.exports = class ClusterBackend { return callback(error); } + const { aggregation } = params; const query = layer.options.sql_raw; const resolution = layer.options.aggregation.resolution || 1; @@ -48,7 +49,7 @@ module.exports = class ClusterBackend { const { zoom, clusterId } = params; - getClusterFeatures(pg, zoom, clusterId, columns, query, resolution, (err, features) => { + getClusterFeatures(pg, zoom, clusterId, columns, query, resolution, aggregation, (err, features) => { if (err) { return callback(err); } @@ -85,8 +86,8 @@ function getColumnsName (pg, query, callback) { }, true); } -function getClusterFeatures (pg, zoom, clusterId, columns, query, resolution, callback) { - const sql = clusterFeaturesQuery({ +function getClusterFeatures (pg, zoom, clusterId, columns, query, resolution, aggregation, callback) { + let sql = clusterFeaturesQuery({ zoom: zoom, id: clusterId, query: query, @@ -94,6 +95,17 @@ function getClusterFeatures (pg, zoom, clusterId, columns, query, resolution, ca columns: columns }); + if (aggregation !== undefined) { + aggregation = JSON.parse(aggregation); + const { columns = [], expresions = [] } = aggregation; + + sql = aggregationQuery({ + columns, + query: sql, + expresions + }); + } + debug('> getClusterFeatures:', sql); pg.query(sql, (err, data) => { @@ -133,15 +145,17 @@ const clusterFeaturesQuery = ctx => ` ) __cdb_non_geoms_query `; -// SQL expression to compute the aggregation resolution (grid cell size). -// This is defined by the ctx.res parameter, which is the number of grid cells per tile linear dimension -// (i.e. each tile is divided into ctx.res*ctx.res cells). -// We limit the the minimum resolution to avoid division by zero problems. The limit used is -// the pixel size of zoom level 30 (i.e. 1/2*(30+8) of the full earth web-mercator extent), which is about 0.15 mm. -// Computing this using !scale_denominator!, !pixel_width! or !pixel_height! produces -// inaccurate results due to rounding present in those values. const gridResolution = ctx => { const minimumResolution = 2*Math.PI*6378137/Math.pow(2,38); const pixelSize = `CDB_XYZ_Resolution(${ctx.zoom})`; return `GREATEST(${256/ctx.res}*${pixelSize}, ${minimumResolution})::double precision`; }; + +const aggregationQuery = ctx => ` + SELECT + count(1) as _cdb_feature_count + ${ctx.columns.length ? `,${ctx.columns.join(', ')}` : ''} + ${ctx.expresions.length ? `,${ctx.expresions.join(', ')}` : ''} + FROM (${ctx.query}) __cdb_aggregation + ${ctx.columns.length ? `GROUP BY ${ctx.columns.join(', ')}` : ''} +`; diff --git a/test/acceptance/cluster.js b/test/acceptance/cluster.js index a31af1b7..8b6ee5d7 100644 --- a/test/acceptance/cluster.js +++ b/test/acceptance/cluster.js @@ -10,7 +10,11 @@ const POINTS_SQL_1 = ` x + 4 as cartodb_id, st_setsrid(st_makepoint(x*10, x*10), 4326) as the_geom, st_transform(st_setsrid(st_makepoint(x*10, x*10), 4326), 3857) as the_geom_webmercator, - x as value + x as value, + CASE + WHEN x % 2 = 0 THEN 'even' + ELSE 'odd' + END AS type from generate_series(-3, 3) x `; @@ -58,14 +62,14 @@ describe('cluster', function () { } assert.deepStrictEqual(body, { - errors:[ 'Map d725a568ab961af8197d311eececb83a has no aggregation defined for layer 0' ], + errors:[ 'Map c502fc8fc1cb0d5e412db3deabffeee5 has no aggregation defined for layer 0' ], errors_with_context:[ { layer: { index: '0', type: 'cartodb' }, - message: 'Map d725a568ab961af8197d311eececb83a has no aggregation defined for layer 0', + message: 'Map c502fc8fc1cb0d5e412db3deabffeee5 has no aggregation defined for layer 0', subtype: 'aggregation', type: 'layer' } @@ -100,14 +104,14 @@ describe('cluster', function () { } assert.deepStrictEqual(body, { - errors:[ 'Map 3a09728f8c08444820336ea9983ce92b has no aggregation defined for layer 0' ], + errors:[ 'Map 18792467ae296929d04e32dfe7f81a80 has no aggregation defined for layer 0' ], errors_with_context:[ { layer: { index: '0', type: 'cartodb' }, - message: 'Map 3a09728f8c08444820336ea9983ce92b has no aggregation defined for layer 0', + message: 'Map 18792467ae296929d04e32dfe7f81a80 has no aggregation defined for layer 0', subtype: 'aggregation', type: 'layer' } @@ -125,95 +129,95 @@ describe('cluster', function () { zoom: 0, cartodb_id: 1, resolution: 0.5, - expected: [ { cartodb_id: 1, value: -3 } ] + expected: [ { cartodb_id: 1, value: -3, type: 'odd' } ] }, { zoom: 0, cartodb_id: 2, resolution: 0.5, - expected: [ { cartodb_id: 2, value: -2 } ] + expected: [ { cartodb_id: 2, value: -2, type: 'even' } ] }, { zoom: 0, cartodb_id: 3, resolution: 0.5, - expected: [ { cartodb_id: 3, value: -1 } ] + expected: [ { cartodb_id: 3, value: -1, type: 'odd' } ] }, { zoom: 0, cartodb_id: 4, resolution: 0.5, - expected: [ { cartodb_id: 4, value: 0 } ] + expected: [ { cartodb_id: 4, value: 0, type: 'even' } ] }, { zoom: 0, cartodb_id: 5, resolution: 0.5, - expected: [ { cartodb_id: 5, value: 1 } ] + expected: [ { cartodb_id: 5, value: 1, type: 'odd' } ] }, { zoom: 0, cartodb_id: 6, resolution: 0.5, - expected: [ { cartodb_id: 6, value: 2 } ] + expected: [ { cartodb_id: 6, value: 2, type: 'even' } ] }, { zoom: 0, cartodb_id: 7, resolution: 0.5, - expected: [ { cartodb_id: 7, value: 3 } ] + expected: [ { cartodb_id: 7, value: 3, type: 'odd' } ] }, { zoom: 0, cartodb_id: 1, resolution: 1, - expected: [ { cartodb_id: 1, value: -3 } ] + expected: [ { cartodb_id: 1, value: -3, type: 'odd' } ] }, { zoom: 0, cartodb_id: 2, resolution: 1, - expected: [ { cartodb_id: 2, value: -2 } ] + expected: [ { cartodb_id: 2, value: -2, type: 'even' } ] }, { zoom: 0, cartodb_id: 3, resolution: 1, - expected: [ { cartodb_id: 3, value: -1 } ] + expected: [ { cartodb_id: 3, value: -1, type: 'odd' } ] }, { zoom: 0, cartodb_id: 4, resolution: 1, - expected: [ { cartodb_id: 4, value: 0 } ] + expected: [ { cartodb_id: 4, value: 0, type: 'even' } ] }, { zoom: 0, cartodb_id: 5, resolution: 1, - expected: [ { cartodb_id: 5, value: 1 } ] + expected: [ { cartodb_id: 5, value: 1, type: 'odd' } ] }, { zoom: 0, cartodb_id: 6, resolution: 1, - expected: [ { cartodb_id: 6, value: 2 } ] + expected: [ { cartodb_id: 6, value: 2, type: 'even' } ] }, { zoom: 0, cartodb_id: 7, resolution: 1, - expected: [ { cartodb_id: 7, value: 3 } ] + expected: [ { cartodb_id: 7, value: 3, type: 'odd' } ] }, { zoom: 0, cartodb_id: 1, resolution: 50, expected: [ - { cartodb_id: 1, value: -3 }, - { cartodb_id: 2, value: -2 }, - { cartodb_id: 3, value: -1 }, - { cartodb_id: 4, value: 0 }, + { cartodb_id: 1, value: -3, type: 'odd' }, + { cartodb_id: 2, value: -2, type: 'even' }, + { cartodb_id: 3, value: -1, type: 'odd' }, + { cartodb_id: 4, value: 0, type: 'even' }, ] }, { @@ -221,62 +225,62 @@ describe('cluster', function () { cartodb_id: 5, resolution: 50, expected: [ - { cartodb_id: 5, value: 1 }, - { cartodb_id: 6, value: 2 }, - { cartodb_id: 7, value: 3 } + { cartodb_id: 5, value: 1, type: 'odd' }, + { cartodb_id: 6, value: 2, type: 'even' }, + { cartodb_id: 7, value: 3, type: 'odd' } ] }, { zoom: 1, cartodb_id: 1, resolution: 1, - expected: [ { cartodb_id: 1, value: -3 } ] + expected: [ { cartodb_id: 1, value: -3, type: 'odd' } ] }, { zoom: 1, cartodb_id: 2, resolution: 1, - expected: [ { cartodb_id: 2, value: -2 } ] + expected: [ { cartodb_id: 2, value: -2, type: 'even' } ] }, { zoom: 1, cartodb_id: 3, resolution: 1, - expected: [ { cartodb_id: 3, value: -1 } ] + expected: [ { cartodb_id: 3, value: -1, type: 'odd' } ] }, { zoom: 1, cartodb_id: 4, resolution: 1, - expected: [ { cartodb_id: 4, value: 0 } ] + expected: [ { cartodb_id: 4, value: 0, type: 'even' } ] }, { zoom: 1, cartodb_id: 5, resolution: 1, - expected: [ { cartodb_id: 5, value: 1 } ] + expected: [ { cartodb_id: 5, value: 1, type: 'odd' } ] }, { zoom: 1, cartodb_id: 6, resolution: 1, - expected: [ { cartodb_id: 6, value: 2 } ] + expected: [ { cartodb_id: 6, value: 2, type: 'even' } ] }, { zoom: 1, cartodb_id: 7, resolution: 1, - expected: [ { cartodb_id: 7, value: 3 } ] + expected: [ { cartodb_id: 7, value: 3, type: 'odd' } ] }, { zoom: 1, cartodb_id: 1, resolution: 50, expected: [ - { cartodb_id: 1, value: -3 }, - { cartodb_id: 2, value: -2 }, - { cartodb_id: 3, value: -1 }, - { cartodb_id: 4, value: 0 }, + { cartodb_id: 1, value: -3, type: 'odd' }, + { cartodb_id: 2, value: -2, type: 'even'}, + { cartodb_id: 3, value: -1, type: 'odd' }, + { cartodb_id: 4, value: 0, type: 'even' }, ] }, { @@ -284,9 +288,9 @@ describe('cluster', function () { cartodb_id: 5, resolution: 50, expected: [ - { cartodb_id: 5, value: 1 }, - { cartodb_id: 6, value: 2 }, - { cartodb_id: 7, value: 3 } + { cartodb_id: 5, value: 1, type: 'odd' }, + { cartodb_id: 6, value: 2, type: 'even' }, + { cartodb_id: 7, value: 3, type: 'odd' } ] } ]; @@ -319,4 +323,107 @@ describe('cluster', function () { }); }); }); + + describe('map-config w/o aggregation', function () { + const suite = [ + { + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { columns: ['type'] }, + expected: [ { _cdb_feature_count: 1, type: 'odd' } ] + }, + { + zoom: 0, + cartodb_id: 2, + resolution: 1, + aggregation: { columns: ['type'] }, + expected: [ { _cdb_feature_count: 1, type: 'even' } ] + }, + { + zoom: 0, + cartodb_id: 3, + resolution: 1, + aggregation: { columns: ['type'] }, + expected: [ { _cdb_feature_count: 1, type: 'odd' } ] + }, + { + zoom: 0, + cartodb_id: 4, + resolution: 1, + aggregation: { columns: ['type'] }, + expected: [ { _cdb_feature_count: 1, type: 'even' } ] + }, + { + zoom: 0, + cartodb_id: 5, + resolution: 1, + aggregation: { columns: ['type'] }, + expected: [ { _cdb_feature_count: 1, type: 'odd' } ] + }, + { + zoom: 0, + cartodb_id: 6, + resolution: 1, + aggregation: { columns: ['type'] }, + expected: [ { _cdb_feature_count: 1, type: 'even' } ] + }, + { + zoom: 0, + cartodb_id: 7, + resolution: 1, + aggregation: { columns: ['type'] }, + expected: [ { _cdb_feature_count: 1, type: 'odd' } ] + }, + { + zoom: 0, + cartodb_id: 1, + resolution: 50, + aggregation: { columns: ['type'] }, + expected: [ + { _cdb_feature_count: 2, type: 'even' }, + { _cdb_feature_count: 2, type: 'odd' } + ] + }, + { + zoom: 0, + cartodb_id: 5, + resolution: 50, + aggregation: { columns: ['type'] }, + expected: [ + { _cdb_feature_count: 1, type: 'even' }, + { _cdb_feature_count: 2, type: 'odd' } + ] + } + ]; + + + suite.forEach(({ zoom, cartodb_id, resolution, aggregation, expected }) => { + it('should return features aggregated by type', function (done) { + const mapConfig = createVectorMapConfig([{ + type: 'cartodb', + options: { + sql: POINTS_SQL_1, + aggregation: { + threshold: 1, + resolution + } + } + }]); + const testClient = new TestClient(mapConfig); + const layerId = 0; + const params = { aggregation }; + + testClient.getClusterFeatures(zoom, cartodb_id, layerId, params, (err, body) => { + if (err) { + return done(err); + } + + assert.deepStrictEqual(body.rows, expected); + + testClient.drain(done); + }); + }); + }); + }); }); diff --git a/test/support/test-client.js b/test/support/test-client.js index b1e4a211..5d88f6e0 100644 --- a/test/support/test-client.js +++ b/test/support/test-client.js @@ -690,7 +690,12 @@ TestClient.prototype.getClusterFeatures = function (zoom, clusterId, layerId, pa var next = this; - url = '/api/v1/map/' + layergroupId + '/' + layerId + '/' + zoom + '/cluster/' + clusterId; + let queryParams = ''; + if (params.aggregation) { + queryParams = qs.stringify({ aggregation: JSON.stringify(params.aggregation) }); + } + + url = `/api/v1/map/${layergroupId}/${layerId}/${zoom}/cluster/${clusterId}?${queryParams}`; assert.response(self.server, { From 6dadb1bf6fa58f36b39e3b48475a4ff20e6a1808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 1 Mar 2019 15:17:22 +0100 Subject: [PATCH 54/84] Validate aggregation input --- lib/cartodb/backends/cluster.js | 63 ++++++++++++- test/acceptance/cluster.js | 159 +++++++++++++++++++++++++++++++- 2 files changed, 215 insertions(+), 7 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index cba5b80e..671684e4 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -38,7 +38,61 @@ module.exports = class ClusterBackend { return callback(error); } - const { aggregation } = params; + let { aggregation } = params; + + if ( aggregation !== undefined) { + try { + aggregation = JSON.parse(aggregation); + } catch (err) { + const error = new Error(`Invalid aggregation input, should be a a valid JSON`); + error.http_status = 400; + error.type = 'layer'; + error.subtype = 'aggregation'; + error.layer = { + index: layerIndex, + type: layer.type + }; + + return callback(error); + } + + const { columns, expressions } = aggregation; + + if (!Array.isArray(columns) || !columns.length) { + const error = new Error( + `Invalid aggregation input, columns should be and array of column names` + ); + error.http_status = 400; + error.type = 'layer'; + error.subtype = 'aggregation'; + error.layer = { + index: layerIndex, + type: layer.type + }; + + return callback(error); + } + + if (expressions !== undefined) { + if (expressions === null || + Array.isArray(expressions) || + ['string', 'number', 'boolean'].includes(typeof expressions)) { + const error = new Error( + `Invalid aggregation input, expressions should be and object with expressions` + ); + error.http_status = 400; + error.type = 'layer'; + error.subtype = 'aggregation'; + error.layer = { + index: layerIndex, + type: layer.type + }; + + return callback(error); + } + } + } + const query = layer.options.sql_raw; const resolution = layer.options.aggregation.resolution || 1; @@ -96,13 +150,12 @@ function getClusterFeatures (pg, zoom, clusterId, columns, query, resolution, ag }); if (aggregation !== undefined) { - aggregation = JSON.parse(aggregation); - const { columns = [], expresions = [] } = aggregation; + const { columns = [], expressions = [] } = aggregation; sql = aggregationQuery({ columns, query: sql, - expresions + expressions }); } @@ -155,7 +208,7 @@ const aggregationQuery = ctx => ` SELECT count(1) as _cdb_feature_count ${ctx.columns.length ? `,${ctx.columns.join(', ')}` : ''} - ${ctx.expresions.length ? `,${ctx.expresions.join(', ')}` : ''} + ${ctx.expressions.length ? `,${ctx.expressions.join(', ')}` : ''} FROM (${ctx.query}) __cdb_aggregation ${ctx.columns.length ? `GROUP BY ${ctx.columns.join(', ')}` : ''} `; diff --git a/test/acceptance/cluster.js b/test/acceptance/cluster.js index 8b6ee5d7..5e856da2 100644 --- a/test/acceptance/cluster.js +++ b/test/acceptance/cluster.js @@ -324,7 +324,7 @@ describe('cluster', function () { }); }); - describe('map-config w/o aggregation', function () { + describe('with aggregation', function () { const suite = [ { zoom: 0, @@ -397,7 +397,6 @@ describe('cluster', function () { } ]; - suite.forEach(({ zoom, cartodb_id, resolution, aggregation, expected }) => { it('should return features aggregated by type', function (done) { const mapConfig = createVectorMapConfig([{ @@ -426,4 +425,160 @@ describe('cluster', function () { }); }); }); + + describe('invalid aggregation', function () { + const expectedColumnsError = { + errors:[ 'Invalid aggregation input, columns should be and array of column names' ], + errors_with_context:[ + { + layer: { + index: '0', + type: 'cartodb' + }, + message: 'Invalid aggregation input, columns should be and array of column names', + subtype: 'aggregation', + type: 'layer' + } + ] + }; + + const expectedExpressionsError = { + errors:[ 'Invalid aggregation input, expressions should be and object with expressions' ], + errors_with_context:[ + { + layer: { + index: '0', + type: 'cartodb' + }, + message: 'Invalid aggregation input, expressions should be and object with expressions', + subtype: 'aggregation', + type: 'layer' + } + ] + }; + + const suite = [ + { + description: 'empty aggregation object should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: {}, + expected: expectedColumnsError + }, + { + description: 'empty aggregation array should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: [], + expected: expectedColumnsError + }, + { + description: 'aggregation as string should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: 'wadus', + expected: expectedColumnsError + }, + { + description: 'empty columns array should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { columns: [] }, + expected: expectedColumnsError + }, + { + description: 'empty columns object should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { columns: {} }, + expected: expectedColumnsError + }, + { + description: 'columns as string should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { columns: 'wadus' }, + expected: expectedColumnsError + }, + { + description: 'columns as null should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { columns: null }, + expected: expectedColumnsError + }, + { + description: 'empty expressions array should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { columns: [ 'type' ], expressions: [] }, + expected: expectedExpressionsError + }, + { + description: 'empty expressions number should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { columns: [ 'type' ], expressions: 1 }, + expected: expectedExpressionsError + }, + { + description: 'expressions as string should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { columns: [ 'type' ], expressions: 'wadus' }, + expected: expectedExpressionsError + }, + { + description: 'expressions as null should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { columns: [ 'type' ], expressions: null }, + expected: expectedExpressionsError + } + ]; + + suite.forEach(({ description, zoom, cartodb_id, resolution, aggregation, expected }) => { + it(description, function (done) { + const mapConfig = createVectorMapConfig([{ + type: 'cartodb', + options: { + sql: POINTS_SQL_1, + aggregation: { + threshold: 1, + resolution + } + } + }]); + const testClient = new TestClient(mapConfig); + const layerId = 0; + const params = { + response: { + status: 400 + }, + aggregation + }; + + testClient.getClusterFeatures(zoom, cartodb_id, layerId, params, (err, body) => { + if (err) { + return done(err); + } + + assert.deepStrictEqual(body, expected); + + testClient.drain(done); + }); + }); + }); + }); }); From d9fe5bf3882eba12621c496186455b6da435ef70 Mon Sep 17 00:00:00 2001 From: csubira Date: Fri, 1 Mar 2019 15:19:27 +0100 Subject: [PATCH 55/84] Update with latest changes --- docs/guides/02-general-concepts.md | 21 ++++- docs/guides/05-static-maps-API.md | 5 +- docs/guides/06-tile-aggregation.md | 81 ++++++++++++++++++- .../08-MapConfig-aggregation-extension.md | 2 +- 4 files changed, 104 insertions(+), 5 deletions(-) diff --git a/docs/guides/02-general-concepts.md b/docs/guides/02-general-concepts.md index 4eeec096..fc66b90d 100644 --- a/docs/guides/02-general-concepts.md +++ b/docs/guides/02-general-concepts.md @@ -24,4 +24,23 @@ If you use JSONP, the 200 HTTP code is always returned so the JavaScript client ### CORS Support -All the endpoints, which might be accessed using a web browser, add CORS headers and allow OPTIONS method. \ No newline at end of file +All the endpoints, which might be accessed using a web browser, add CORS headers and allow OPTIONS method. + + +### Map Tile Rendering + + Map tiles create the graphical representation of your map in a web browser. The performance rendering of map tiles is dependent on the type of geospatial data model (raster or vector) that you are using. + + - **Raster**: Generates map tiles based on a grid of pixels to represent your data. Each cell is a fixed size and contains values for particular map features. On the server-side, each request queries a dataset to retrieve data for each map tile. The grid size of map tiles can often lead to graphic quality issues. + + - **Vector**: Generates map tiles based on pre-defined coordinates to represent your data, similar to how basemap image tiles are rendered. On the client-side, map tiles represent real-world geometries of a map. Depending on the coordinates, vertices are used to connect the data and display points, lines, or polygons for the map tiles. + + **Note:** By default, CARTO uses vector graphics for map rendering. Please [contact us](mailto:support@carto.com) if you need raster rendering enabled as part of your requirements. + + ### Mapbox Vector Tiles (MVT) + + [Mapbox Vector Tiles (MVT)](https://www.mapbox.com/vector-tiles/specification/) are map tiles that store geographic vector data on the client-side. Browser performance is fast since you can pan and zoom without having to query the server. + + CARTO uses a Web Graphics Library (WebGL) to process MVT files. This is useful since WebGL's are compatible with most web browsers, include support for multiple client-side mapping engines, and do not require additional information from the server; which makes it more efficient for rendering map tiles. + + **Tip:** You can process MVT files with the [`ST_AsMVT` PostGIS function](https://postgis.net/docs/manual-dev/ST_AsMVT.html) with the [Maps API Windshaft renderer](https://github.com/CartoDB/Windshaft/blob/1000x/lib/windshaft/renderers/pg_mvt/renderer.js). \ No newline at end of file diff --git a/docs/guides/05-static-maps-API.md b/docs/guides/05-static-maps-API.md index d36d83b0..c8342a48 100644 --- a/docs/guides/05-static-maps-API.md +++ b/docs/guides/05-static-maps-API.md @@ -11,7 +11,7 @@ Begin by instantiating either a Named or Anonymous Map using the `layergroupid t ##### Definition ```bash -GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format} +GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}{{?}extra_options} ``` ##### Params @@ -57,6 +57,9 @@ Note: you can see this endpoint as ```bash GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format}` ``` +#### Extra options + * Layer: List of layers to be shown in the image (by default `all`), for example `?layer=0,1`. + #### Named Map diff --git a/docs/guides/06-tile-aggregation.md b/docs/guides/06-tile-aggregation.md index ef615b2e..437f63ee 100644 --- a/docs/guides/06-tile-aggregation.md +++ b/docs/guides/06-tile-aggregation.md @@ -10,7 +10,7 @@ Aggregation is available only for point geometries. During aggregation the point When no placement or columns are specified a special default aggregation is performed. -This special mode performs only spatial aggregation (using a grid defined by the requested tile and the resolution, parameter, as all the other cases), and returns a _random_ record from each group (grid cell) with all its columns and an additional `_cdb_features_count` with the number of features in the group. +This special mode performs only spatial aggregation (using a grid defined by the requested tile and the resolution, parameter, as all the other cases), and returns a _random_ record from each group (grid cell) with all its columns and an additional `_cdb_feature_count` with the number of features in the group. Regarding the randomness of the sample: currently we use the row with the minimum `cartodb_id` value in each group. @@ -18,7 +18,7 @@ The rationale behind having this special aggregation with all the original colum #### User defined aggregations -When either a explicit placement or columns are requested we no longer use the special, query; we use one determined by the placement (which will default to "centroid"), and it will have as columns only the aggregated columns specified, in addition to `_cdb_features_count`, which is always present. +When either a explicit placement or columns are requested we no longer use the special, query; we use one determined by the placement (which will default to "centroid"), and it will have as columns only the aggregated columns specified, in addition to `_cdb_feature_count`, which is always present. We might decide in the future to allow sampling column values for any of the different placement modes. @@ -185,3 +185,80 @@ This is the minimum number of (estimated) rows in the dataset (query results) fo ] } ``` + +### `filters` + +Aggregated data can be filtered by imposing filtering conditions on the aggregated columns. + +Each condition is represented by one or more parameters: + +* `{ "equal": V }` selects an specific value of the aggregated column. +* `{ "not_equal": V }` selects values different from the one specified. +* `{ "in": [v1, v2, v3] }` selects any value from a list. +* `{ "not_in": [v1, v2, v3] }` selects any value not in a list. +* `{ "less_than": v }` selects values strictly less than the one given. +* `{ "less_than_or_equal_to": v }` selects values less than or equal to the one given. +* `{ "greater_than": v }` selects values strictly greater than the one given. +* `{ "greater_than_or_equal_to": v }` selects values greater than or equal to the one given. + +One of the *less* conditions can be combined with one of the *greater* conditions to select a range of values, for example: +* `{ "greater_than": v1, "less_than": v2 }` +* `{ "greater_than_or_equal_to": v1, "less_than": v2 }` +* `{ "greater_than": v1, "less_than_or_equal_to": v2 }` +* `{ "greater_than_or_equal_to": v1, "less_than_or_equal_to": v2 }` + +For a given column, multiple conditions can be passed in an array; the conditions will logically ORed (any of the conditions have to be verifid for the value to be selected): + +* `"myvalue": [ { "equal": 10 }, { "less_than": 0 }]` will select values of the column `myvalue` which are equal to 10 **or** less than 0. + +In addition, the filters applied to different columns are logically combined with AND (all the conditions have to be satisfied for an element to be selected); for example with the following `filters` parameter we'll select aggregated records which have a `total_value` > 100 **and** a category equal to "a". + +```json +{ + "total_value": { "greater_than": 100 }, + "category": { "equal": "a" } +} +``` + +Note that the filtered columns have to be defined with the `columns` parameter, except for `_cdb_feature_count`, which is always implicitly defined and can be filtered too. + +#### Example + +```json +{ + "version": "1.7.0", + "extent": [-20037508.5, -20037508.5, 20037508.5, 20037508.5], + "srid": 3857, + "maxzoom": 18, + "minzoom": 3, + "layers": [ + { + "type": "mapnik", + "options": { + "sql": "select * from table", + "cartocss": "#table { marker-width: [total]; marker-fill: ramp(value, (red, green, blue), jenks); }", + "cartocss_version": "2.3.0", + "aggregation": { + "placement": "centroid", + "columns": { + "total_value": { + "aggregate_function": "sum", + "aggregated_column": "value" + }, + "category": { + "aggregate_function": "mode", + "aggregated_column": "category" + } + }, + "filters" : { + "total_value": { "greater_than": 100 }, + "category": { "equal": "a" } + }, + "resolution": 2, + "threshold": 500000 + } + } + } + ] +} +``` \ No newline at end of file diff --git a/docs/guides/08-MapConfig-aggregation-extension.md b/docs/guides/08-MapConfig-aggregation-extension.md index 9dfb4dfa..da751a43 100644 --- a/docs/guides/08-MapConfig-aggregation-extension.md +++ b/docs/guides/08-MapConfig-aggregation-extension.md @@ -31,7 +31,7 @@ The value of this attribute can be `false` to explicitly disable aggregation for // object, defines the columns of the aggregated datasets. Each property corresponds to a columns name and // should contain an object with two properties: "aggregate_function" (one of "sum", "max", "min", "avg", "mode" or "count"), // and "aggregated_column" (the name of a column of the original layer query or "*") - // A column defined as `"_cdb_features_count": {"aggregate_function": "count", aggregated_column: "*"}` + // A column defined as `"_cdb_feature_count": {"aggregate_function": "count", aggregated_column: "*"}` // is always generated in addition to the defined columns. // The column names `cartodb_id`, `the_geom`, `the_geom_webmercator` and `_cdb_feature_count` cannot be used // for aggregated columns, as they correspond to columns always present in the result. From a92a2b729197f7cf85dfdc1983e491d847fb8cc7 Mon Sep 17 00:00:00 2001 From: csubira Date: Fri, 1 Mar 2019 15:25:16 +0100 Subject: [PATCH 56/84] Remove older doc files --- docs/Map-API.md | 20 - docs/MapConfig-Aggregation-extension.md | 62 --- docs/MapConfig-Analyses-extension.md | 93 ---- docs/MapConfig-Dataviews-extension.md | 277 ------------ docs/MapConfig-NamedMaps-extension.md | 56 --- docs/MultiLayer-API.md | 28 -- docs/Routes.md | 114 ----- docs/aggregation.md | 268 ----------- docs/anonymous_maps.md | 396 ----------------- docs/general_concepts.md | 27 -- docs/metrics.md | 42 -- docs/named_maps.md | 568 ------------------------ docs/quickstart.md | 100 ----- docs/static_maps_api.md | 229 ---------- 14 files changed, 2280 deletions(-) delete mode 100644 docs/Map-API.md delete mode 100644 docs/MapConfig-Aggregation-extension.md delete mode 100644 docs/MapConfig-Analyses-extension.md delete mode 100644 docs/MapConfig-Dataviews-extension.md delete mode 100644 docs/MapConfig-NamedMaps-extension.md delete mode 100644 docs/MultiLayer-API.md delete mode 100644 docs/Routes.md delete mode 100644 docs/aggregation.md delete mode 100644 docs/anonymous_maps.md delete mode 100644 docs/general_concepts.md delete mode 100644 docs/metrics.md delete mode 100644 docs/named_maps.md delete mode 100644 docs/quickstart.md delete mode 100644 docs/static_maps_api.md diff --git a/docs/Map-API.md b/docs/Map-API.md deleted file mode 100644 index 456cf379..00000000 --- a/docs/Map-API.md +++ /dev/null @@ -1,20 +0,0 @@ -# Maps API - -The CARTO Maps API allows you to generate maps based on data hosted in your CARTO account and apply custom SQL and CartoCSS to the data. The API generates a XYZ-based URL to fetch Web Mercator projected tiles, using web clients such as [Leaflet](http://leafletjs.com), [Google Maps](https://developers.google.com/maps/), or [OpenLayers](http://openlayers.org/). - -You can create two types of maps with the Maps API: - -- **Anonymous Maps** - You can create maps using your CARTO public data. Any client can change the read-only SQL and CartoCSS parameters that generate the map tiles. These maps can be created from a JavaScript application alone and no authenticated calls are needed. See [this CARTO.js example](/carto-engine/carto-js/getting-started/). - -- **Named Maps** - There are also maps that have access to your private data. These maps require an owner to setup and modify any SQL and CartoCSS parameters and are not modifiable without new setup calls. - -## Documentation - -* [Quickstart](quickstart.md) -* [General Concepts](general_concepts.md) -* [Anonymous Maps](anonymous_maps.md) -* [Named Maps](named_maps.md) -* [Static Maps API](static_maps_api.md) -* [MapConfig File Format]([local file in the docs repo](https://github.com/CartoDB/docs/blob/master/_app/_mapsapi/06-mapconfig.md)) diff --git a/docs/MapConfig-Aggregation-extension.md b/docs/MapConfig-Aggregation-extension.md deleted file mode 100644 index bf2172f2..00000000 --- a/docs/MapConfig-Aggregation-extension.md +++ /dev/null @@ -1,62 +0,0 @@ -# 1. Purpose - -This specification describes an extension for -[MapConfig 1.7.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.7.0.md) version. - - -# 2. Changes over specification - -This extension introduces a new layer options for aggregated data tile generation. - -## 2.1 Aggregation options - -The layer options attribute is extended with a new optional `aggregation` attribute. -The value of this attribute can be `false` to explicitly disable aggregation for the layer. - -```javascript -{ - aggregation: { - - // OPTIONAL - // string, defines the placement of aggregated geometries. Can be one of: - // * "point-sample", the default places geometries at a sample point (one of the aggregated geometries) - // * "point-grid" places geometries at the center of the aggregation grid cells - // * "centroid" places geometriea at the average position of the aggregated points - // See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/aggregation.md#placement for more details - placement: "point-sample", - - // OPTIONAL - // object, defines the columns of the aggregated datasets. Each property corresponds to a columns name and - // should contain an object with two properties: "aggregate_function" (one of "sum", "max", "min", "avg", "mode" or "count"), - // and "aggregated_column" (the name of a column of the original layer query or "*") - // A column defined as `"_cdb_feature_count": {"aggregate_function": "count", aggregated_column: "*"}` - // is always generated in addition to the defined columns. - // The column names `cartodb_id`, `the_geom`, `the_geom_webmercator` and `_cdb_feature_count` cannot be used - // for aggregated columns, as they correspond to columns always present in the result. - // See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/aggregation.md#columns for more details - columns: { - "aggregated_column_1": { - "aggregate_function": "sum", - "aggregated_column": "original_column_1" - } - }, - - // OPTIONAL - // Number, defines the cell-size of the spatial aggregation grid as a pixel resolution power of two (1/4, 1/2,... 2, 4, 16) - // to scale from 256x256 pixels; the default is 1 corresponding to 256x256 cells per tile. - // See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/aggregation.md#resolution for more details - resolution: 1, - - // OPTIONAL - // Number, the minimum number of (estimated) rows in the dataset (query results) for aggregation to be applied. - // See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/aggregation.md#threshold for more details - threshold: 500000 - } -} -``` - -# History - -## 1.0.0 - - - Initial version diff --git a/docs/MapConfig-Analyses-extension.md b/docs/MapConfig-Analyses-extension.md deleted file mode 100644 index 53271eb3..00000000 --- a/docs/MapConfig-Analyses-extension.md +++ /dev/null @@ -1,93 +0,0 @@ -# 1. Purpose - -This specification describes an extension for -[MapConfig 1.4.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.4.0.md) version. - - -# 2. Changes over specification - -This extension targets layers with `sql` option, including layer types: `cartodb`, `mapnik`, and `torque`. - -It extends MapConfig with a new attribute: `analyses`. - -## 2.1 Analyses attribute - -The new analyses attribute must be an array of analyses as per [camshaft](https://github.com/CartoDB/camshaft). Each -analysis must adhere to the [camshaft-reference](https://github.com/CartoDB/camshaft/blob/0.8.0/reference/versions/0.7.0/reference.json) specification. - -Each node can have an id that can be later references to consume the query from MapConfig's layers. - -Basic analyses example: - -```javascript -[ - { - // REQUIRED - // string, `id` free identifier that can be reference from any layer - "id": "HEAD", - // REQUIRED - // string, `type` camshaft's analysis type - "type": "source", - // REQUIRED - // object, `params` will depend on `type`, check camshaft-reference for more information - "params": { - "query": "select * from your_table" - } - } -] -``` - -# 2.2. Integration with layers - -As pointed before an analysis node id can be referenced from layers to consume its output query. - -The layer consuming the output must reference it with the following option: - -``` -{ - "options": { - // REQUIRED - // object, `source` as in the future we might want to have other source options - "source": { - // REQUIRED - // string, `id` the analysis node identifier - "id": "HEAD" - } - } -} -``` - -## 2.3. Complete example - -``` -{ - "version": "1.4.0", - "layers": [ - { - "type": "cartodb", - "options": { - "source": { - "id": "HEAD" - }, - "cartocss": "...", - "cartocss_version": "2.3.0" - } - } - ], - "analyses": [ - { - "id": "HEAD", - "type": "source", - "params": { - "query": "select * from your_table" - } - } - ] -} -``` - -# History - -## 1.0.0 - - - Initial version diff --git a/docs/MapConfig-Dataviews-extension.md b/docs/MapConfig-Dataviews-extension.md deleted file mode 100644 index c2b2b8da..00000000 --- a/docs/MapConfig-Dataviews-extension.md +++ /dev/null @@ -1,277 +0,0 @@ -# 1. Purpose - -This specification describes an extension for -[MapConfig 1.4.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.4.0.md) version. - - -# 2. Changes over specification - -This extension depends on Analyses extension. It extends MapConfig with a new attribute: `dataviews`. - -It makes possible to get tabular data from analysis nodes: aggregated lists, aggregations, and histograms. - -## 2.1. Dataview types - -### Aggregation - -An aggregation is a list with aggregated results by a column and a given aggregation function. - -Definition -``` -{ - // REQUIRED - // string, `type` the aggregation type - “type”: “aggregation”, - // REQUIRED - // object, `options` dataview params - “options”: { - // REQUIRED - // string, `column` column name to aggregate by - “column”: “country”, - // REQUIRED - // string, `aggregation` operation to perform - “aggregation”: “count” - // OPTIONAL - // string, `aggregationColumn` column value to aggregate - // This param is required when `aggregation` is different than "count" - “aggregationColumn”: “population” - } -} -``` - -Expected output -``` -{ - "type": "aggregation", - "categories": [ - { - "category": "foo", - "value": 100 - }, - { - "category": "bar", - "value": 200 - } - ] -} -``` - -### Histograms - -Histograms represent the data distribution for a column. - -Definition -``` -{ - // REQUIRED - // string, `type` the histogram type - “type”: “histogram”, - // REQUIRED - // object, `options` dataview params - “options”: { - // REQUIRED - // string, `column` column name to aggregate by - “column”: “name”, - // OPTIONAL - // number, `bins` how many buckets the histogram should use - “bins”: 10 - } -} -``` - -Expected output -``` -{ - "type": "histogram", - "bins": [{"bin": 0, "start": 2, "end": 2, "min": 2, "max": 2, "freq": 1}, null, null, {"bin": 3, "min": 40, "max": 44, "freq": 2}, null], - "width": 10 -} -``` - -### Formula - -Formulas given a final value representing the whole dataset. - -Definition -``` -{ - // REQUIRED - // string, `type` the formula type - “type”: “formula”, - // REQUIRED - // object, `options` dataview params - “options”: { - // REQUIRED - // string, `column` column name to aggregate by - “column”: “name”, - // REQUIRED - // string, `aggregation` operation to perform - “operation”: “count” - } -} -``` - -Operation must be: “min”, “max”, “count”, “avg”, or “sum”. - -Result -``` -{ - "type": "formula", - "operation": "count", - "result": 1000, - "nulls": 0 -} -``` - - -## 2.2 Dataviews attribute - -The new dataviews attribute must be a dictionary of dataviews. - -An analysis node id can be referenced from dataviews to consume its output query. - - -The layer consuming the output must reference it with the following option: - -``` -{ - // REQUIRED - // object, `source` as in the future we might want to have other source options - "source": { - // REQUIRED - // string, `id` the analysis node identifier - "id": "HEAD" - } -} -``` - -## 2.3. Complete example - -``` -{ - "version": "1.4.0", - "layers": [ - { - "type": "cartodb", - "options": { - "source": { - "id": "HEAD" - }, - "cartocss": "...", - "cartocss_version": "2.3.0" - } - } - ], - "dataviews" { - "basic_histogram": { - "source": { - "id": "HEAD" - }, - "type": "histogram", - "options": { - "column": "pop_max" - } - } - }, - "analyses": [ - { - "id": "HEAD", - "type": "source", - "params": { - "query": "select * from your_table" - } - } - ] -} -``` - -## 3. Filters - -Camshaft's analyses expose a filtering capability and `aggregation` and `histogram` dataviews get them for free with - this extension. Filters are available with the very dataview id, so if you have a "basic_histogram" histogram dataview - you can filter with a range filter with "basic_histogram" name. - - -## 3.1 Filter types - -### Category - -Allows to remove results that are not contained within a set of elements. -Initially this filter can be applied to a `numeric` or `text` columns. - -Params - -``` -{ - “accept”: [“Spain”, “Germany”] - “reject”: [“Japan”] -} -``` - -### Range filter - -Allows to remove results that don’t satisfy numeric min and max values. -Filter is applied to a numeric column. - -Params - -``` -{ - “min”: 0, - “max”: 1000 -} -``` - -## 3.2. How to apply filters - -Filters must be applied at map instantiation time. - -With :mapconfig as a valid MapConfig and with :filters (a valid JSON) as: - -### Anonymous map - -`GET /api/v1/map?config=:mapconfig&filters=:filters` - -`POST /api/v1/map?filters=:filters` -with `BODY=:mapconfig` - -If in the future we need to support a bigger filters param and it doesn’t fit in the query string, - we might solve it by accepting: - -`POST /api/v1/map` -with `BODY={“config”: :mapconfig, “filters”: :filters}` - -### Named map - -Assume :params (a valid JSON) as named maps params, like in: `{“color”: “red”}` - -`GET /api/v1/named/:name/jsonp?config=:params&filters=:filters&callback=cb` - -`POST /api/v1/named/:name?filters=:filters` -with `BODY=:params` - -If, again, in the future we need to support a bigger filters param that doesn’t fit in the query string, - we might solve it by accepting: - -`POST /api/v1/named/:name` -with `BODY={“config”: :params, “filters”: :filters}` - - -## 3.3 Bounding box special filter - -A bounding box filter allows to remove results that don’t satisfy a geospatial range. - -The bounding box special filter is available per dataview and there is no need to create a bounding box definition as -it’s always possible to apply a bbox filter per dataview. - -A dataview can get its result filtered by bounding box by sending a bbox param in the query string, -param must be in the form `west,south,east,north`. - -So applying a bbox filter to a dataview looks like: -GET /api/v1/map/:layergroupid/dataview/:dataview_name?bbox=-90,-45,90,45 - -# History - -## 1.0.0-alpha - - - WIP document diff --git a/docs/MapConfig-NamedMaps-extension.md b/docs/MapConfig-NamedMaps-extension.md deleted file mode 100644 index 2793faec..00000000 --- a/docs/MapConfig-NamedMaps-extension.md +++ /dev/null @@ -1,56 +0,0 @@ -# 1. Purpose - -This specification describes an extension for -[MapConfig 1.3.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.3.0.md) version. - - -# 2. Changes over specification - -This extension introduces a new layer type so it's possible to use a Named Map by its name as a layer. - -## 2.1 Named layers definition - -```javascript -{ - // REQUIRED - // string, `named` is the only supported value - type: "named", - - // REQUIRED - // object, set `named` map layers configuration - options: { - - // REQUIRED - // string, the name for the Named Map to use - name: "world_borders", - - // OPTIONAL - // object, the replacement values for the Named Map's template placeholders - // See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md#instantiate-1 for more details - config: { - "color": "#000" - }, - - // OPTIONAL - // string array, the authorized tokens in case the Named Map has auth method set to `token` - // See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md#named-maps-1 for more details - auth_tokens: [ - "token1", - "token2" - ] - } -} -``` - -## 2.2 Limitations - -1. A Named Map will not allow to have `named` type layers inside their templates layergroup's layers definition. -2. A `named` layer does not allow Named Maps form other accounts, it's only possible to use Named Maps from the very -same user account. - - -# History - -## 1.0.0 - - - Initial version diff --git a/docs/MultiLayer-API.md b/docs/MultiLayer-API.md deleted file mode 100644 index febf709b..00000000 --- a/docs/MultiLayer-API.md +++ /dev/null @@ -1,28 +0,0 @@ -The Windshaft-CartoDB MultiLayer API extends the [Windshaft MultiLayer API](https://github.com/CartoDB/Windshaft/blob/master/doc/Multilayer-API.md) in a few ways. - -## Last modification timestamp embedded in the token - -It encodes a timestamp of 'last modification time' into the map token (token:EPOCH) returned to the client. -It accepts tokens with encoded timestamp from the client considering the token suffix as a cache_buster value. - -Clients don't need to be aware of the extension but rather use the API as they would use the base one. -The only difference will be that the _same_ layergroup configuration may result in different tokens if source data was modified between the mapview requests. - -## Additional attributes in the response object - -Windshaft-CartoDB adds the following attributes in the response object - -- ``last_update`` field with ISO format (2013-11-30T12:23:10). -- ``cdn_url`` object containing CDN url client should use (not mandatory) to access the tiles. It's in the form: - - ```json - { - "http": "http://cdn_url.com/", - "https": "https://secure.cdn_url.com/" - } - ``` - - -## Stats tag - -Windshaft-CartoDB adds support for a ``stat_tag`` element in the multilayer configuration to help [stats](https://github.com/CartoDB/Windshaft-cartodb/wiki/Redis-stats-format) gathering. diff --git a/docs/Routes.md b/docs/Routes.md deleted file mode 100644 index 0ebe732b..00000000 --- a/docs/Routes.md +++ /dev/null @@ -1,114 +0,0 @@ -This document list all routes available in Windshaft-cartodb Maps API server. - -## Routes list - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:z/:x/:y@:scale_factor?x.:format {:user(f),:token(f),:z(f),:x(f),:y(f),:scale_factor(t),:format(f)} (1)` -
Notes: Mapnik retina tiles [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:z/:x/:y.:format {:user(f),:token(f),:z(f),:x(f),:y(f),:format(f)} (1)` -
Notes: Mapnik tiles [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/:z/:x/:y.(:format) {:user(f),:token(f),:layer(f),:z(f),:x(f),:y(f),:format(f)} (1)` -
Notes: Per :layer rendering based on :format [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/attributes/:fid {:user(f),:token(f),:layer(f),:fid(f)} (1)` -
Notes: Endpoint for info windows data, alternative for sql api when tables are private [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/center/:token/:z/:lat/:lng/:width/:height.:format {:user(f),:token(f),:z(f),:lat(f),:lng(f),:width(f),:height(f),:format(f)} (1)` -
Notes: Static Maps API [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format {:user(f),:token(f),:west(f),:south(f),:east(f),:north(f),:width(f),:height(f),:format(f)} (1)` -
Notes: Static Maps API [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/widget/:widgetName {:user(f),:token(f),:layer(f),:widgetName(f)} (1)` -
Notes: By :widgetName per :layer widget [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/widget/:widgetName/search {:user(f),:token(f),:layer(f),:widgetName(f)} (1)` -
Notes: By :widgetName per :layer widget search [0] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)` -
Notes: Map instantiation [0] - -1. `POST (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)` -
Notes: Map instantiation [0] - -1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/jsonp {:user(f),:template_id(f)} (1)` -
Notes: Named maps JSONP instantiation [1] - -1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` -
Notes: Instantiate named map [1] - -1. `OPTIONS (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)` -
Notes: CORS [0] - -1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/:layer/:z/:x/:y.(:format) {:user(f),:template_id(f),:layer(f),:z(f),:x(f),:y(f),:0(f),:format(f)} (1)` -
Notes: Per :layer fixed URL named map tiles [1] - -1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/named/:template_id/:width/:height.:format {:user(f),:template_id(f),:width(f),:height(f),:format(f)} (1)` -
Notes: Static map for named maps [1] - -1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)` -
Notes: Create named map (w/ API KEY) [1] - -1. `PUT (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` -
Notes: Update a named map (w/ API KEY) [1] - -1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` -
Notes: Named map retrieval (w/ API KEY) [1] - -1. `DELETE (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` -
Notes: Delete named map (w/ API KEY) [1] - -1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)` -
Notes: List named maps (w/ API KEY) [1] - -1. `OPTIONS (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)` -
Notes: CORS [1] - -1. `GET /health {} (1)` -
Notes: Health check - -1. `GET / {} (1)` -
Notes: Welcome message - -1. `GET /version {} (1)` -
Notes: Return relevant module versions: mapnik, grainstore, etc - - -## Optional deprecated routes - -- [0] `/tiles/layergroup` is deprecated and `/api/v1/map` should be used but we keep it for now. -- [1] `/tiles/template` is deprecated and `/api/v1/map/named` should be used but we keep it for now. - -## How to generate the list of routes - -Something like the following patch should do the trick - -```javascript -diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js -index 5f62850..bca377d 100644 ---- a/lib/cartodb/server.js -+++ b/lib/cartodb/server.js -@@ -215,6 +215,20 @@ module.exports = function(serverOptions) { - * END Routing - ******************************************************************************************************************/ - -+ var format = require('util').format; -+ var routesNotes = app._router.stack -+ .filter(function(handler) { return !!handler.route; }) -+ .map(function(handler) { -+ return format("\n1. `%s %s {%s} (1)`\n
Notes: [DEPRECATED]? ", -+ Object.keys(handler.route.methods)[0].toUpperCase(), -+ handler.route.path, -+ handler.keys.map(function(k) { -+ return format(':%s(%s)', k.name, k.optional ? 't' : 'f'); -+ }).join(',') -+ ); -+ }); -+ console.log(routesNotes.join('\n')); -+ - return app; - }; - - -``` diff --git a/docs/aggregation.md b/docs/aggregation.md deleted file mode 100644 index 506e7c77..00000000 --- a/docs/aggregation.md +++ /dev/null @@ -1,268 +0,0 @@ -# Tile Aggregation - -To be able to represent a large amount of data (say, hundred of thousands to millions of points) in a tile. This can be useful both for raster tiles (where the aggregation reduces the number of features to be rendered) and vector tiles (the tile contais less features). - -Aggregation is available only for point geometries. During aggregation the points are grouped using a grid; all the points laying in the same cell of the grid are summarized in a single aggregated result point. - - The position of the aggregated point is controlled by the `placement` parameter. - - The aggregated rows always contain at least a column, named `_cdb_feature_count`, which contains the number of the original points that the aggregated point represents. - -### Special default aggregation - -When no placement or columns are specified a special default aggregation is performed. - -This special mode performs only spatial aggregation (using a grid defined by the requested tile and the resolution, parameter, as all the other cases), and returns a _random_ record from each group (grid cell) with all its columns and an additional `_cdb_feature_count` with the number of features in the group. - -Regarding the randomness of the sample: currently we use the row with the minimum `cartodb_id` value in each group. - -The rationale behind having this special aggregation with all the original columns is to provide a mostly transparent way to handle large datasets without having to provide special map configurations for those cases (i.e. preserving the logic used to produce the maps with smaller datasets). [Overviews have been used so far with this intent](https://carto.com/docs/tips-and-tricks/back-end-data-performance/), but they are inflexible. - -### User defined aggregations - -When either a explicit placement or columns are requested we no longer use the special, query; we use one determined by the placement (which will default to "centroid"), and it will have as columns only the aggregated columns specified, in addition to `_cdb_feature_count`, which is always present. - -We might decide in the future to allow sampling column values for any of the different placement modes. - -### Behaviour for raster and vector tiles - -The vector tiles from a vector-only map will be aggregated by default. -However, Raster tiles (or vector tiles from a map which defines CartoCSS styles) will be aggregated only upon request. - -Aggregation that would otherwise occur can be disabled by passing an `aggregation=false` parameter to the map instantiation HTTP call. - -To control how aggregation is performed, an aggregation option can be added to the layer: - -```json -{ - "layers": [ - { - "options": { - "sql": "SELECT * FROM data", - "aggregation": { - "placement": "centroid", - "columns": { - "value": { - "aggregate_function": "sum", - "aggregated_column": "value" - } - } - } - } - } - ] -} -``` - -Even if aggregation is explicitly requested it may not be activated, e.g., if the geometries are not points -or the whole dataset is too small. The map instantiation response contains metadata that informs if any particular -layer will be aggregated when tiles are requested, both for vector (mvt) and raster (png) tiles. - -```json -{ - "layergroupid": "7b97b6e76590fef889b63edd2efb1c79:1513608333045", - "metadata": { - "layers": [ - { - "type": "mapnik", - "id": "layer0", - "meta": { - "stats": { - "estimatedFeatureCount": 6232136 - }, - "aggregation": { - "png": true, - "mvt": true - } - } - } - ] - } -} -``` - -## Aggregation parameters - -The aggregation parameters for a layer are defined inside an `aggregation` option of the layer: - -```json -{ - "layers": [ - { - "options": { - "sql": "SELECT * FROM data", - "aggregation": {"...": "..."} - } - } - ] -} -``` - -### `placement` - -Determines the kind of aggregated geometry generated: - -#### `point-sample` - -This is the default placement. It will place the aggregated point at a random sample of the grouped points, -like the default aggregation does. No other attribute is sampled, though, the point will contain the aggregated attributes determined by the `columns` parameter. - -#### `point-grid` - -Generates points at the center of the aggregation grid cells (squares). - -#### `centroid` - -Generates points with the averaged coordinated of the grouped points (i.e. the points inside each grid cell). - -### `columns` - -The aggregated attributes defined by `columns` are computed by a applying an _aggregate function_ to all the points in each group. -Valid aggregate functions are `sum`, `avg` (average), `min` (minimum), `max` (maximum) and `mode` (the most frequent value in the group). -The values to be aggregated are defined by the _aggregated column_ of the source data. The column keys define the name of the resulting column in the aggregated dataset. - -For example here we define three aggregate attributes named `total`, `max_price` and `price` which are all computed with the same column, `price`, -of the original dataset applying three different aggregate functions. - -```json -{ - "columns": { - "total": { "aggregate_function": "sum", "aggregated_column": "price" }, - "max_price": { "aggregate_function": "max", "aggregated_column": "price" }, - "price": { "aggregate_function": "avg", "aggregated_column": "price" } - } -} -``` - -> Note that you can use the original column names as names of the result, but all the result column names must be unique. In particular, the names `cartodb_id`, `the_geom`, `the_geom_webmercator` and `_cdb_feature_count` cannot be used for aggregated columns, as they correspond to columns always present in the result. - -#### Limitations: -* The iso text format does not admit `starting` or `count` parameters -* Cyclic units (day of the week, etc.) don't admit `count` or `starting` either. - -### `resolution` - -Defines the cell-size of the spatial aggregation grid. This is equivalent to the [CartoCSS `-torque-resolution`](https://carto.com/docs/carto-engine/cartocss/properties-for-torque/#-torque-resolution-float) property of Torque maps. - -The aggregation cells are `resolution`×`resolution` pixels in size, where pixels here are defined to be 1/256 of the (linear) size of a tile. -The default value is 1, so that aggregation coincides with raster pixels. A value of 2 would make each cell to be 4 (2×2) pixels, and a value of -0.5 would yield 4 cells per pixel. In teneral values less than 1 produce sub-pixel precision. - -> Note that is independent of the number of pixels for raster tile or the coordinate resolution (mvt_extent) of vector tiles. - - -### `threshold` - -This is the minimum number of (estimated) rows in the dataset (query results) for aggregation to be applied. If the number of rows estimate is less than the threshold aggregation will be disabled for the layer; the instantiation response will reflect that and tiles will be generated without aggregation. - -### Example - -```json -{ - "version": "1.7.0", - "extent": [-20037508.5, -20037508.5, 20037508.5, 20037508.5], - "srid": 3857, - "maxzoom": 18, - "minzoom": 3, - "layers": [ - { - "type": "mapnik", - "options": { - "sql": "select * from table", - "cartocss": "#table { marker-width: [total]; marker-fill: ramp(value, (red, green, blue), jenks); }", - "cartocss_version": "2.3.0", - "aggregation": { - "placement": "centroid", - "columns": { - "value": { - "aggregate_function": "avg", - "aggregated_column": "value" - }, - "total": { - "aggregate_function": "sum", - "aggregated_column": "value" - } - }, - "resolution": 2, - "threshold": 500000 - } - } - } - ] -} -``` - -### `filters` - -Aggregated data can be filtered by imposing filtering conditions on the aggregated columns. - -Each condition is represented by one or more parameters: - -* `{ "equal": V }` selects an specific value of the aggregated column. -* `{ "not_equal": V }` selects values different from the one specified. -* `{ "in": [v1, v2, v3] }` selects any value from a list. -* `{ "not_in": [v1, v2, v3] }` selects any value not in a list. -* `{ "less_than": v }` selects values strictly less than the one given. -* `{ "less_than_or_equal_to": v }` selects values less than or equal to the one given. -* `{ "greater_than": v }` selects values strictly greater than the one given. -* `{ "greater_than_or_equal_to": v }` selects values greater than or equal to the one given. - -One of the *less* conditions can be combined with one of the *greater* conditions to select a range of values, for example: -* `{ "greater_than": v1, "less_than": v2 }` -* `{ "greater_than_or_equal_to": v1, "less_than": v2 }` -* `{ "greater_than": v1, "less_than_or_equal_to": v2 }` -* `{ "greater_than_or_equal_to": v1, "less_than_or_equal_to": v2 }` - -For a given column, multiple conditions can be passed in an array; the conditions will logically ORed (any of the conditions have to be verifid for the value to be selected): - -* `"myvalue": [ { "equal": 10 }, { "less_than": 0 }]` will select values of the column `myvalue` which are equal to 10 **or** less than 0. - -In addition, the filters applied to different columns are logically combined with AND (all the conditions have to be satisfied for an element to be selected); for example with the following `filters` parameter we'll select aggregated records which have a `total_value` > 100 **and** a category equal to "a". - -```json -{ - "total_value": { "greater_than": 100 }, - "category": { "equal": "a" } -} -``` - -Note that the filtered columns have to be defined with the `columns` parameter, except for `_cdb_feature_count`, which is always implicitly defined and can be filtered too. - -#### Example - -```json -{ - "version": "1.7.0", - "extent": [-20037508.5, -20037508.5, 20037508.5, 20037508.5], - "srid": 3857, - "maxzoom": 18, - "minzoom": 3, - "layers": [ - { - "type": "mapnik", - "options": { - "sql": "select * from table", - "cartocss": "#table { marker-width: [total]; marker-fill: ramp(value, (red, green, blue), jenks); }", - "cartocss_version": "2.3.0", - "aggregation": { - "placement": "centroid", - "columns": { - "total_value": { - "aggregate_function": "sum", - "aggregated_column": "value" - }, - "category": { - "aggregate_function": "mode", - "aggregated_column": "category" - } - }, - "filters" : { - "total_value": { "greater_than": 100 }, - "category": { "equal": "a" } - }, - "resolution": 2, - "threshold": 500000 - } - } - } - ] -} -``` diff --git a/docs/anonymous_maps.md b/docs/anonymous_maps.md deleted file mode 100644 index 521584a0..00000000 --- a/docs/anonymous_maps.md +++ /dev/null @@ -1,396 +0,0 @@ -# Anonymous Maps - -Anonymous Maps allows you to instantiate a map given SQL and CartoCSS. It also allows you to add interaction capabilities using [UTF Grid.](https://github.com/mapbox/utfgrid-spec). -Alternatively, you can get the data for the map (geometry and attributes for each layer) using vector tiles (in which case CartoCSS is not required). - - -## Instantiate - -#### 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.carto.com/carto-engine/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://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png` -updated_at | The ISO date of the last time the data involved in the query was updated. -metadata | Includes information about the layers. -cdn_url | URLs to fetch the data using the best CDN for your zone. - -**Improved response metadata** - -Originally, you needed to concantenate the `layergroupid` with the correct domain and the path for the tiles. -Now, for convenience, the layergroup includes the final URLs in two formats: -1. Leaflet's urlTemplate alike: useful when working with raster tiles or with libraries with an API similar to Leaflet's one. -1. [TileJSON spec](https://github.com/mapbox/tilejson-spec): useful when working with Mapbox GL or any other library that supports TileJSON. - -### Example - -#### Call - -```bash -curl 'https://{username}.carto.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": {} - } - ], - "tilejson": { - "raster": { - "tilejson": "2.2.0", - "tiles": [ - "http://a.cdb.com/c01a54877c62831bb51720263f91fb33/{z}/{x}/{y}.png", - "http://b.cdb.com/c01a54877c62831bb51720263f91fb33/{z}/{x}/{y}.png" - ] - } - }, - "url": { - "raster": { - "urlTemplate": "http://{s}.cdb.com/c01a54877c62831bb51720263f91fb33/{z}/{x}/{y}.png", - "subdomains": ["a", "b"] - } - } - }, - "cdn_url": { - "http": "http://cdb.com", - "https": "https://cdb.com", - "templates": { - "http": { "subdomains": ["a","b"], "url": "http://{s}.cdb.com" }, - "https": { "subdomains": ["a","b"], "url": "https://{s}.example.com" }, - } - } -} -``` - -## Map Tile Rendering - -Map tiles are used to create the graphic representation of your map in a web browser. Tiles can be requested either as pre-rendered *raster* tiles (images) or as *vector* map data to be rendered by the client (browser). - -- **Raster**: If a tile is requested as a raster image format, like PNG, the map will be rendered on the server, using the CartoCSS styles defined in the layers of the map. It is necessary that all the layers of a map define CartoCSS styles in order to obtain raster tiles. Raster tiles are made up of 256x256 pixels; to avoid graphic quality issues tiles should be used unscaled to represent the zoom level (Z) for which they are requested. In order to render tiles, data will be retrieved from the database (in vector format) on the server-side. - -- **Vector**: Tiles can also be requested as MVT (Mapbox Vector Tiles). In this case, only the geospatial vector data, without any styling, is returned. These tiles should be processed in the client-side to render the map. In this case layers do not need to define CartoCSS, as any rendering and styling will be performed on the client side. The vector data of a tile represents real-world geometries by defining the vertices of points, lines or polygons in a tile-specific coordinate system. - -## Retrieve resources from the layergroup - -When you have a layergroup, there are several resources for retrieving layergoup details such as, accessing Mapnik tiles, getting individual layers, accessing defined Attributes, and blending and layer selection. - -### Raster tiles - -These raster tiles are PNG images that represent only the Mapnik layers of a map. See [individual layers](#individual-layers) for details about how to retrieve other layers. - -```bash -https://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png -``` - -### Mapbox Vector Tiles (MVT) - -[Mapbox Vector Tiles (MVT)](https://www.mapbox.com/vector-tiles/specification/) are map tiles that transfer geographic vector data to the client-side. Browser performance is fast since you can pan and zoom without having to query the server. - -CARTO uses Web Graphics Library (WebGL) to process MVT files on the browser. This is useful since WebGL is compatible with most web browsers, include support for multiple client-side mapping engines, and do not require additional information from the server; which makes it more efficient for rendering map tiles. However, you can use any implementation tool for processing MVT files. - -The following examples describe how to fetch MVT tiles with a cURL request. - -#### MVT and Windshaft - -CARTO uses Windshaft as the map tiler library to render multilayer maps with the Maps API. You can use Windshaft to request MVT using the same layer type that is used for requesting raster tiles (Mapnik layer). Simply change the file format `.mvt` in the URL. - - -```bash -https://{username}.cartodb.com/api/v1/map/HASH/:layer/{z}/{x}/{y}.mvt -``` - -The following example instantiates an anonymous map with layer options: - -```bash -{ - user_name: 'mycartodbuser', - sublayers: [{ - sql: "SELECT * FROM table_name"; - cartocss: '#layer { marker-fill: #F0F0F0; }' - }], - maps_api_template: 'https://{user}.cartodb.com' // Optional -} -``` - -**Note**: If no layer type is specified, Mapnik tiles are used by default. To access MVT tiles, specify `https://{username}.cartodb.com/api/v1/map/HASH/{z}/{x}/{y}.mvt` as the `maps_api_template` variable. - -**Tip:** If you are using [Named Maps](https://carto.com/docs/carto-engine/maps-api/named-maps/) to instantiate a layer, indicate the MVT file format and layer in the response: - -```bash -https://{username}.cartodb.com/api/v1/map/named/:templateId/:layer/{z}/{x}/{y}.mvt -``` - -For all layers in a Named Map, you must indicate Mapnik as the layer filter: - -```bash -https://{username}.cartodb.com/api/v1/map/named/:templateId/mapnik/{z}/{x}/{y}.mvt -``` - -#### Layergroup Filter for MVT Tiles - -To filter layers using Windshaft, use the following request where layers are numbered: - -```bash -https://{username}.cartodb.com/api/v1/map/HASH/0,1,2/{z}/{x}/{y}.mvt -``` - -To request all layers, remove the layergroup filter parameter: - -```bash -https://{username}.cartodb.com/api/v1/map/HASH/{z}/{x}/{y}.mvt -``` - -To filter a specific layer: - -```bash -https://{username}.cartodb.com/api/v1/map/HASH/2/{z}/{x}/{y}.mvt -``` - -#### Example 1: MVT Tiles with Windshaft, CARTO.js, and MapboxGL - -1) Import the required libraries: - -```bash - - - -``` - -2) Configure Map Client: - -```bash -mapboxgl.accessToken = '{yourMapboxToken}'; -``` - -3) Create Map Object (Mapbox): - -```bash -var map = new mapboxgl.Map({ -container: 'map', -zoom: 1, -minZoom: 0, -maxZoom: 18, -center: [30, 0] -}); -``` - -4) Define Layer Options (CARTO): - -```bash -var layerOptions = { -user_name: "{username}", -sublayers: [{ -sql: "SELECT * FROM {table_name}", -cartocss: "...", - }] -}; -``` - -5) Request Tiles (from CARTO) and Set to Map Object (Mapbox): - -**Note:** By default, [CARTO core functions](https://carto.com/docs/carto-engine/carto-js/core-api/) retrieve URLs for fully rendered tiles. You must replace the default format (.png) with the MVT format (.mvt). - - -```bash -cartodb.Tiles.getTiles(layerOptions, function(result, err) { -var tiles = result.tiles.map(function(tileUrl) { -return tileUrl -.replace('{s}', 'a') -.replace(/\.png/, '.mvt'); -}); -map.setStyle(simpleStyle(tiles)); -}); -``` - -#### Example 2: MVT Libraries with Windshaft and MapboxGL - -When you are not including CARTO.js to implement MVT tiles, you must use the `map.setStyle` parameter to specify vector map rendering. - -1) Import the required libraries: - -```bash - - -``` - -2) Configure Map Client: - -```bash -mapboxgl.accessToken = '{yourMapboxToken}'; -``` - -3) Create Map Object (Mapbox): - -```bash -var map = new mapboxgl.Map({ -container: 'map', -zoom: 1, -minZoom: 0, -maxZoom: 18, -center: [30, 0] -}); -``` - -4) Set the Style - -```bash -map.setStyle({ - "version": 7, - "glyphs": "...", - "constants": {...}, - "sources": { - "cartodb": { - "type": "vector", - "tiles": [ "http://{username}.cartodb.com/api/v1/map/named/templateId/mapnik/{z}/{x}/{y}.mvt" - ], - "maxzoom": 18 - } - }, - "layers": [{...}] -}); -``` - -**Tip:** If you are using MapboxGL, see the following resource for additional information. - -- [MapboxGL API Reference](https://www.mapbox.com/mapbox-gl-js/api/) -- [MapboxGL Style Specifications](https://www.mapbox.com/mapbox-gl-js/style-spec/) -- [Example of MapboxGL Implementation](https://www.mapbox.com/mapbox-gl-js/examples/) - -### Individual layers - -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://{username}.carto.com/api/v1/map/{layergroupid}/{layer}/{z}/{x}/{y}.grid.json -``` - -In this case, `layer` as 0 returns the UTF grid tiles/attributes for layer 0, the only layer in the example MapConfig. - -If the MapConfig had a Torque layer at index 1 it could be possible to request it with: - -```bash -https://{username}.carto.com/api/v1/map/{layergroupid}/1/{z}/{x}/{y}.torque.json -``` - -### Attributes defined in `attributes` section - -```bash -https://{username}.carto.com/api/v1/map/{layergroupid}/{layer}/attributes/{feature_id} -``` - -Which returns JSON with the attributes defined, such as: - -```javascript -{ "c": 1, "d": 2 } -``` - -### Blending and layer selection - -```bash -https://{username}.carto.com/api/v1/map/{layergroupid}/{layer_filter}/{z}/{x}/{y}.png -``` - -Note: currently format is limited to `png`. - -`layer_filter` can be used to select some layers to be rendered together. `layer_filter` supports two formats: - -- `all` alias - -Using `all` as `layer_filter` will blend all layers in the layergroup - -```bash -https://{username}.carto.com/api/v1/map/{layergroupid}/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://{username}.carto.com/api/v1/map/{layergroupid}/0,3,4/{z}/{x}/{y}.png -``` - -Some notes about filtering: - - - Invalid index values or out of bounds indexes will end in `Invalid layer filtering` errors. - - Ordering is not considered. So right now filtering layers 0,3,4 is the very same thing as filtering 3,4,0. As this may change in the future, **it is recommended** to always select the layers in ascending order so that you will always get consistent behavior. - -## Create JSONP - -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://{username}.carto.com/api/v1/map?callback=callback&config=%7B%22version%22%3A%221.0.1%22%2C%22layers%22%3A%5B%7B%22type%22%3A%22cartodb%22%2C%22options%22%3A%7B%22sql%22%3A%22select+%2A+from+european_countries_e%22%2C%22cartocss%22%3A%22%23european_countries_e%7B+polygon-fill%3A+%23FF6600%3B+%7D%22%2C%22cartocss_version%22%3A%222.3.0%22%2C%22interactivity%22%3A%5B%22cartodb_id%22%5D%7D%7D%5D%7D" -``` - -#### Response - -```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, or sometimes longer. If an Anonymous Map expires and tiles are requested from it, an error will be raised. This could happen if a user leaves a map open and after time, returns to the map and attempts to interact with it in a way that requires new tiles (e.g. zoom). The client will need to go through the steps of creating the map again to fix the problem. diff --git a/docs/general_concepts.md b/docs/general_concepts.md deleted file mode 100644 index ca8c72ec..00000000 --- a/docs/general_concepts.md +++ /dev/null @@ -1,27 +0,0 @@ -# General Concepts - -The following concepts are the same for every endpoint in the API except when it's noted explicitly. - -## Auth - -By default, users do not have access to private tables in CARTO. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a Named Map). - -To execute an authorized request, `api_key=YOURAPIKEY` should be added to the request URL. The param can be also passed as POST param. Using HTTPS is mandatory when you are performing requests that include your `api_key`. - -## Errors - -Errors are reported using standard HTTP codes and extended information encoded in JSON with this format: - -```javascript -{ - "errors": [ - "access forbidden to table TABLE" - ] -} -``` - -If you use JSONP, the 200 HTTP code is always returned so the JavaScript client can receive errors from the JSON object. - -## CORS Support - -All the endpoints, which might be accessed using a web browser, add CORS headers and allow OPTIONS method. diff --git a/docs/metrics.md b/docs/metrics.md deleted file mode 100644 index 0d641ac6..00000000 --- a/docs/metrics.md +++ /dev/null @@ -1,42 +0,0 @@ -Windshaft-cartodb metrics -========================= -See [Windshaft metrics documentation](https://github.com/CartoDB/Windshaft/blob/master/doc/metrics.md) to understand the full picture. - -The next list includes the API endpoints, each endpoint may have several inner timers, some of them are displayed within this list as subitems. Find the description for them in the Inner timers section. -## Timers -- **windshaft-cartodb.flush_cache**: time to flush the tile and sql cache -- **windshaft-cartodb.get_template**: time to retrieve an specific template -- **windshaft-cartodb.delete_template**: time to delete an specific template -- **windshaft-cartodb.get_template_list**: time to retrieve the list of owned templates -- **windshaft-cartodb.instance_template_post**: time to create a template via HTTP POST -- **windshaft-cartodb.instance_template_get**: time to create a template via HTTP GET - + TemplateMaps_instance - + createLayergroup - -There are some endpoints that are not being tracked: -- Adding a template -- Updating a template - -### Inner timers -Again, each inner timer may have several inner timers. - -- **addCacheChannel**: time to add X-Cache-Channel header based on table last modifications -- **LZMA decompress**: time to decompress request params with LZMA -- **TemplateMaps_instance**: time to retrieve a map template instance, see *getTemplate* and *authorizedByCert* -- **affectedTables**: time to check what are the affected tables for adding the cache channel, see *addCacheChannel* -- **authorize**: time to authorize a request, see *authorizedByAPIKey*, *authorizedByCert*, *authorizedBySigner* -- **authorizedByCert**: time to authorize a template instantiation -- **findLastUpdated**: time to retrieve the last update time for a list of tables, see *affectedTables* -- **generateCacheChannel**: time to generate the headers for the cache channel based on the request, see *addCacheChannel* -- **getSignerMapKey**: time to retrieve from redis the authorized user for a template map -- **getTablePrivacy**: time to retrieve from redis the privacy of a table -- **getTemplate**: time to retrieve from redis the template for a map -- **getUserMapKey**: time to retrieve from redis the user key for a map -- **incMapviewCount**: time to incremenent in redis the map views -- **mapStore_load**: time to retrieve from redis a map configuration -- **req2params.setup**: time to prepare the params from a request, see *req2params* in Windshaft documentation -- **setDBAuth**: time to retrieve from redis and set db user and db password from a user -- **setDBConn**: time to retrieve from redis and set db host and db name from a user -- **setDBParams**: time to prepare all db params to be able to connect/query a database, see *setDBAuth* and *setDBConn* -- **tablePrivacy_getUserDBName**: time to retrieve from redis the database for a user - diff --git a/docs/named_maps.md b/docs/named_maps.md deleted file mode 100644 index fdb77df4..00000000 --- a/docs/named_maps.md +++ /dev/null @@ -1,568 +0,0 @@ -# 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 uploading a MapConfig file to CARTO servers, to select data from your CARTO user database by using SQL, and specifying the CartoCSS for your map. - -The response back from the API provides the template_id of your Named Map as the `name` (the identifier of your Named Map), which is the name that you specified in the MapConfig. You can which you can then use to create your Named Map details, or [fetch XYZ tiles](#fetching-xyz-tiles-for-named-maps) directly for Named Maps. - -**Tip:** You can also use a Named Map that you created (which is defined by its `name`), to create a map using CARTO.js. This is achieved by adding the [`namedmap` type](http://docs.carto.com/carto-engine/carto-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map. - -The main differences, compared to Anonymous Maps, is that Named Maps include: - -- **auth token** - This allows you to control who is able to see the map based on an auth token, and create a secure Named Map with password-protection. - -- **template map** - The template map is static and may contain placeholders, enabling you to modify your maps appearance by using variables. Templates maps are persistent with no preset expiration. They can only be created, or deleted, by a CARTO user with a valid API KEY (See [auth argument](#arguments)). - - Uploading a MapConfig creates a Named Map. MapConfigs are uploaded to the server by sending the server a "template".json file, which contain the [MapConfig specifications](http://docs.carto.com/carto-engine/maps-api/mapconfig/). - -**Note:** There is a limit of 4,096 Named Maps allowed per account. If you need to create more Named Maps, it is recommended to use a single Named Map and change the variables using [placeholders](#placeholder-format), instead of uploading multiple [Named Map MapConfigs](http://docs.carto.com/carto-engine/maps-api/mapconfig/#named-map-layer-options). - -## Create - -#### Definition - -```html -POST /api/v1/map/named -``` - -#### Params - -Params | Description ---- | --- -api_key | is required -MapConfig | a [Named Map MapConfig](http://docs.carto.com/carto-engine/maps-api/mapconfig/#named-map-layer-options) is required to create a Named Map - -#### template.json - -The `name` argument defines how to name this "template_name".json. Note that there are some requirements for how to name a Named Map template. See the [`name`](#arguments) argument description for details. - -```javascript -{ - "version": "0.0.1", - "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 - }, - "preview_layers": { - "0": true, - "layer1": false - } - } -} -``` - -#### Arguments - -Params | Description ---- | --- -name | There can only be _one_ template with the same name for any user. Valid names start with a letter or a number, and only contain letters, numbers, dashes (-), or underscores (_). _This is specific to the name of your Named Map that is specified in the `name` property of the template file_. - -auth | ---- | --- -|_ method | `"token"` or `"open"` (`"open"` is the default if no method is specified. Use `"token"` to password-protect your map) -|_ valid_tokens | when `"method"` is set to `"token"`, the values listed here allow you to instantiate the Named Map. See this [example](http://docs.carto.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map. -placeholders | Placeholders are variables that can be placed in your template.json file's SQL or CartoCSS. -layergroup | the layergroup configurations, as specified in the template. See [MapConfig File Format](http://docs.carto.com/carto-engine/maps-api/mapconfig/) for more information. -view (optional) | extra keys to specify the view area for the map. It can be used to have a static preview of a Named Map without having to instantiate it. It is possible to specify it with `center` + `zoom` or with a bounding box `bbox`. Center+zoom takes precedence over bounding box. Also it is possible to choose which layers are visible or not with `preview_layers` indicating its visibility by layer index or id (visible by default). ---- | --- -|_ 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) - - -### Placeholder Format - -Placeholders are variables that can be placed in your template.json file. Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergroup configurations](http://docs.carto.com/carto-engine/maps-api/mapconfig/#layergroup-configurations). - -Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced inside the Named Maps API. - -#### Example - -```javascript -<%= my_color %> -``` - -The set of supported placeholders for a template need to be explicitly defined with a specific type, and default value, for each placeholder. - -### Placeholder Types - -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 loosely. - -#### Call - -This is the call for creating the Named Map. It is sending the template.json file to the service, and the server responds with the template id. - -```bash -curl -X POST \ - -H 'Content-Type: application/json' \ - -d @template.json \ - 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}' -``` - -#### Response - -The response back from the API provides the name of your MapConfig as a template, enabling you to edit the Named Map details by inserting your variables into the template where placeholders are defined, and create custom queries using SQL. - -```javascript -{ - "template_id":"name" -} -``` - -## Instantiate - -Instantiating a Named Map allows you to fetch the map tiles. You can use the Maps API to instantiate, or use the CARTO.js `createLayer()` function. The result is an Anonymous Map. - -#### Definition - -```html -POST /api/v1/map/named/{template_name} -``` - -#### Param - -Param | Description ---- | --- -auth_token | `"token"` or `"open"` (`"open"` is the default if not specified. Use `"token"` to password-protect your map) - -```javascript -// params.json, this is required if the Named Map allows variables (if placeholders were defined in the template.json by the user) -{ - "color": "#ff0000", - "cartodb_id": 3 -} -``` - -The fields you pass as `params.json` depend on the variables allowed by the Named Map. If there are variables missing, it will raise an error (HTTP 400). - -**Note:** It is required that you include a `params.json` file to instantiate a Named Map that contains variables, even if you have no fields to pass and the JSON is empty. (This is specific to when a Named Map allows variables (if placeholders were defined in the template.json by the user). - -#### Example - -You can initialize a template map by passing all of the required parameters in a POST to `/api/v1/map/named/{template_name}`. - -Valid auth token will be needed, if required by the template. - - -#### Call - -```bash -curl -X POST \ - -H 'Content-Type: application/json' \ - -d @params.json \ - 'https://{username}.carto.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 Maps](http://docs.carto.com/carto-engine/maps-api/anonymous-maps/)). - -## Update - -#### Definition - -```bash -PUT /api/v1/map/named/{template_name} -``` - -#### Params - -Param | Description ---- | --- -api_key | is required - -#### Response - -Same as updating a map. - -### Other Information - -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://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}' -``` - -#### 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 - -Deletes the specified template map from the server, and disables any previously initialized versions of the map. - -#### Definition - -```bash -DELETE /api/v1/map/named/{template_name} -``` - -#### Params - -Param | Description ---- | --- -api_key | is required - -### Example - -#### Call - -```bash -curl -X DELETE 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}' -``` - -#### 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://{username}.carto.com/api/v1/map/named?api_key={api_key}' -``` - -#### Response - -```javascript -{ - "template_ids": ["@template_name1","@template_name2"] -} -``` - -#### Error - -```javascript -{ - "errors" : ["Some error string here"] -} -``` - -## Get Template Definition - -This gets the definition of a requested template. - -#### Definition - -```bash -GET /api/v1/map/named/{template_name} -``` - -#### Params - -Param | Description ---- | --- -api_key | is required - -### Example - -#### Call - -```bash -curl -X GET 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}' -``` - -#### Response - -```javascript -{ - "template": {...} // see [template.json](#templatejson) -} -``` - -#### Error - -```javascript -{ - "errors" : ["Some error string here"] -} -``` - -## JSONP for Named Maps - -If using a [JSONP](https://en.wikipedia.org/wiki/JSONP) (for old browsers) request, there is a special endpoint used to initialize and create a Named Map. - -#### Definition - -```bash -GET /api/v1/map/named/{template_name}/jsonp -``` - -#### Params - -Params | Description ---- | --- -auth_token | `"token"` or `"open"` (`"open"` is the default if no method is specified. Use `"token"` to password-protect your map) -params | Encoded JSON with the params (variables) needed for the Named Map -lmza | You can use an LZMA compressed file instead of a params JSON file -callback | JSON callback name - -#### Call - -```bash -curl 'https://{username}.carto.com/api/v1/map/named/{template_name}/jsonp?auth_token={auth_token}&callback=callback&config=template_params_json' -``` - -#### Response - -```javascript -callback({ - "layergroupid":"c01a54877c62831bb51720263f91fb33:0", - "last_updated":"1970-01-01T00:00:00.000Z" - "cdn_url": { - "http": "http://cdb.com", - "https": "https://cdb.com" - } -}) -``` - -This takes the `callback` function (required), `auth_token` if the template needs auth, and `config` which is the variable for the template (in cases where it has variables). - -```javascript -url += "config=" + encodeURIComponent( -JSON.stringify({ color: 'red' }); -``` - -The response is: - -```javascript -callback({ - layergroupid: "dev@744bd0ed9b047f953fae673d56a47b4d:1390844463021.1401", - last_updated: "2014-01-27T17:41:03.021Z" -}) -``` - -## CARTO.js for Named Maps - -You can use a Named Map that you created (which is defined by its `name`), to create a map using CARTO.js. This is achieved by adding the [`namedmap` type](http://docs.carto.com/carto-engine/carto-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map. - -```javascript -{ - user_name: '{username}', // Required - type: 'namedmap', // Required - named_map: { - name: '{name_of_map}', // Required, the 'name' of the Named Map that you have created - // Optional - layers: [{ - layer_name: "sublayer0", // Optional - interactivity: "column1, column2, ..." // Optional - }, - { - layer_name: "sublayer1", - interactivity: "column1, column2, ..." - }, - ... - ], - // Optional - params: { - color: "hex_value", - num: 2 - } - } -} -``` - -**Note:** Instantiating a Named Map over a `createLayer` does not require an API Key and by default, does not include auth tokens. _If_ you defined auth tokens for the Named Map configuration, then you will have to include them. - -[CARTO.js](http://docs.carto.com/carto-engine/carto-js/) has methods for accessing your Named Maps. - -1. [layer.setParams()](http://docs.carto.com/carto-engine/carto-js/api-methods/#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript - - **Note:** The CARTO.js `layer.setParams()` function is not supported when using Named Maps for Torque. Alternatively, you can create a [Torque layer in a Named Map](http://bl.ocks.org/iriberri/de37be6406f9cc7cfe5a) - -2. [layer.setAuthToken()](http://docs.carto.com/carto-engine/carto-js/api-methods/#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer - -### Torque Layer in a Named Map - -If you are creating a Torque layer in a Named Map without using the Torque.js library, you can apply the Torque layer by applying the following code with CARTO.js: - -```javascript - // add cartodb layer with one sublayer - cartodb.createLayer(map, { - user_name: '{username}', - type: 'torque', - order: 1, - options: { - query: "", - table_name: "named_map_tutorial_table", - user_name: "{username}", - tile_style: 'Map { -torque-frame-count:512; -torque-animation-duration:10; -torque-time-attribute:"cartodb_id"; -torque-aggregation-function:"count(cartodb_id)"; -torque-resolution:2; -torque-data-aggregation:linear; } #named_map_tutorial_table_copy{ comp-op: lighter; marker-fill-opacity: 0.9; marker-line-color: #FFF; marker-line-width: 1.5; marker-line-opacity: 1; marker-type: ellipse; marker-width: 6; marker-fill: #FF9900; } #named_map_tutorial_table_copy[frame-offset=1] { marker-width:8; marker-fill-opacity:0.45; } #named_map_tutorial_table_copy[frame-offset=2] { marker-width:10; marker-fill-opacity:0.225; }' - - }, - named_map: { - name: "{namedmap_example}", - layers: [{ - layer_name: "t" - }] - } - }) - .addTo(map) - .done(function(layer) { - - }); -} -``` - -#### Examples of Named Maps created with CARTO.js - -- [Named Map selectors with interaction](http://bl.ocks.org/andy-esch/515a8af1f99d5e690484) - -- [Named Map with interactivity](http://bl.ocks.org/andy-esch/d1a45b8ff5e7bd90cd68) - -- [Toggling sublayers in a Named Map](http://bl.ocks.org/andy-esch/c1a0f4913610eec53cd3) - -## Fetching XYZ Tiles for Named Maps - -Optionally, authenticated users can fetch projected tiles (XYZ tiles or Mapnik Retina tiles) for your Named Map. - -### Fetch XYZ Tiles Directly with a URL - -Authenticated users, with an auth token, can use XYZ-based URLs to fetch tiles directly, and instantiate the Named Map as part of the request to your application. You do not have to do any other steps to initialize your map. - -To call a template_id in a URL: - -`/{template_id}/{layer}/{z}/{x}/{y}.{format}` - -For example, a complete URL might appear as: - -"https://{username}.carto.com/api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.png" - -The placeholders indicate the following: - -- [`template_id`](http://docs.carto.com/carto-engine/maps-api/named-maps/#response) is the response of your Named Map. -- layers can be a number (referring to the # layer of your map), all layers of your map, or a list of layers. - - To show just the basemap layer, enter the number value `0` in the layer placeholder "https://{username}.carto.com/api/v1/map/named/{template_id}/0/{z}/{x}/{y}.png" - - To show the first layer, enter the number value `1` in the layer placeholder "https://{username}.carto.com/api/v1/map/named/{template_id}/1/{z}/{x}/{y}.png" - - To show all layers, enter the value `all` for the layer placeholder "https://{username}.carto.com/api/v1/map/named/{template_id}/all/{z}/{x}/{y}.png" - - To show a [list of layers](http://docs.carto.com/carto-engine/maps-api/anonymous-maps/#blending-and-layer-selection), enter the comma separated layer value as 0,1,2 in the layer placeholder. For example, to show the basemap and the first layer, "https://{username}.carto.com/api/v1/map/named/{template_id}/0,1/{z}/{x}/{y}.png" - - -### Get Mapnik Retina Tiles - -Mapnik Retina tiles are not directly supported for Named Maps, so you cannot use the Named Map template_id. To fetch Mapnik Retina tiles, get the [layergroupid](http://docs.carto.com/carto-engine/maps-api/named-maps/#response-1) to initialize the map. - -Instantiate the map by using your `layergroupid` in the token placeholder: - - `{token}/{z}/{x}/{y}@{scale_factor}?{x}.{format}` diff --git a/docs/quickstart.md b/docs/quickstart.md deleted file mode 100644 index 3a9bf457..00000000 --- a/docs/quickstart.md +++ /dev/null @@ -1,100 +0,0 @@ -# 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://{username}.carto.com/api/v1/map', - data: JSON.stringify(mapconfig), - success: function(data) { - var templateUrl = 'https://{username}.carto.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 CARTO 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 MapConfig needs to be sent to CARTO's Map API using an authenticated call. Here we will use a command line tool called `curl`. For more info about this tool, see [this blog post](http://quickleft.com/blog/command-line-tutorials-curl), or type `man curl` in bash. Using `curl`, and storing the config from above in a file `MapConfig.json`, the call would look like: - -#### Call - -```bash -curl 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}' -H 'Content-Type: application/json' -d @mapconfig.json -``` - -To get the `URL` to fetch the tiles you need to instantiate the map, where `template_id` is the template name from the previous response. - -#### Call - -```bash -curl -X POST 'https://{username}.carto.com/api/v1/map/named/{template_id}' -H 'Content-Type: application/json' -``` - -The response will return JSON with properties for the `layergroupid`, the timestamp (`last_updated`) of the last data modification and some key/value pairs with `metadata` for the `layers`. - -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://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png -``` diff --git a/docs/static_maps_api.md b/docs/static_maps_api.md deleted file mode 100644 index 554f892d..00000000 --- a/docs/static_maps_api.md +++ /dev/null @@ -1,229 +0,0 @@ -# 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}{{?}extra_options} -``` - -#### 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` ---- | --- -|_ 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` ---- | --- -|_ 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}` -``` - -#### Extra options - * Layer: List of layers to be shown in the image (by default `all`), for example `?layer=0,1`. - -### 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 - -format | the format for the image, supported types: `png`, `jpg` ---- | --- -|_ jpg | will have a default quality of 85. - -A Named Maps static image will get its constraints from the [`view` argument of the Create Named Map function](http://docs.carto.com/carto-engine/maps-api/named-maps/#arguments). If `view` is not defined, it will estimate the extent based on the involved tables, otherwise it fallbacks to `"zoom": 1`, `"lng": 0` and `"lat": 0`. - -#### Layers - -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" - } -}, -``` - -**CARTO** - -As described in the [MapConfig File Format](http://docs.carto.com/carto-engine/maps-api/mapconfig/), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility. - -```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 CARTO account. This means that if the data changes, the cached image will also change. When linking dynamically, it is important to take into consideration the state of the data and longevity of the static image to avoid broken images or changes in how the image is displayed. To obtain a static snapshot of the map as it is today and preserve the image long-term regardless of changes in data, the image must be saved and stored locally. - -### Limits - -* While images can encompass an entirety of a map, the limit for pixel range is 8192 x 8192. -* Image resolution is set to 72 DPI -* JPEG quality is 85% -* Timeout limits for generating static maps are the same across CARTO Builder and CARTO Engine. It is important to ensure timely processing of queries. -* If you are publishing your map as a static image with the API, you must manually add [attributions](https://carto.com/attribution) for your static map image. For example, add the following attribution code: - -{% highlight javascript %} -attribution: '© OpenStreetMap contributors, © CARTO -{% endhighlight %} - -## Examples - -After instantiating a map from a CARTO account: - -#### Call - -```bash - GET /api/v1/map/static/center/{layergroupid}/{z}/{x}/{y}/{width}/{height}.png -``` - -#### Response - -

static-api

- -### 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" - } - } - ] -} -``` From 57a229655c816d6c25735c5b2b77fde6ff1427b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 1 Mar 2019 15:45:38 +0100 Subject: [PATCH 57/84] Implement aggregation functions --- lib/cartodb/backends/cluster.js | 12 ++- test/acceptance/cluster.js | 145 +++++++++++++++++++++++++++++++- 2 files changed, 151 insertions(+), 6 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index 671684e4..c9108da9 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -78,7 +78,7 @@ module.exports = class ClusterBackend { Array.isArray(expressions) || ['string', 'number', 'boolean'].includes(typeof expressions)) { const error = new Error( - `Invalid aggregation input, expressions should be and object with expressions` + `Invalid aggregation input, expressions should be and object with valid functions` ); error.http_status = 400; error.type = 'layer'; @@ -150,12 +150,16 @@ function getClusterFeatures (pg, zoom, clusterId, columns, query, resolution, ag }); if (aggregation !== undefined) { - const { columns = [], expressions = [] } = aggregation; + let { columns = [], expressions = [] } = aggregation; + + if (expressions) { + expressions = Object.entries(expressions).map(entries =>`${entries[1].aggregated_function}(${entries[1].aggregated_column}) AS ${entries[0]}`); + } sql = aggregationQuery({ columns, - query: sql, - expressions + expressions, + query: sql }); } diff --git a/test/acceptance/cluster.js b/test/acceptance/cluster.js index 5e856da2..881dbf8e 100644 --- a/test/acceptance/cluster.js +++ b/test/acceptance/cluster.js @@ -394,6 +394,147 @@ describe('cluster', function () { { _cdb_feature_count: 1, type: 'even' }, { _cdb_feature_count: 2, type: 'odd' } ] + }, + { + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregated_function: 'max', + aggregated_column: 'value', + } + } + }, + expected: [ { _cdb_feature_count: 1, type: 'odd', max_value: -3 } ] + }, + { + zoom: 0, + cartodb_id: 2, + resolution: 1, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregated_function: 'max', + aggregated_column: 'value', + } + } + }, + expected: [ { _cdb_feature_count: 1, type: 'even', max_value: -2 } ] + }, + { + zoom: 0, + cartodb_id: 3, + resolution: 1, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregated_function: 'max', + aggregated_column: 'value', + } + } + }, + expected: [ { _cdb_feature_count: 1, type: 'odd', max_value: -1 } ] + }, + { + zoom: 0, + cartodb_id: 4, + resolution: 1, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregated_function: 'max', + aggregated_column: 'value', + } + } + }, + expected: [ { _cdb_feature_count: 1, type: 'even', max_value: 0 } ] + }, + { + zoom: 0, + cartodb_id: 5, + resolution: 1, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregated_function: 'max', + aggregated_column: 'value', + } + } + }, + expected: [ { _cdb_feature_count: 1, type: 'odd', max_value: 1 } ] + }, + { + zoom: 0, + cartodb_id: 6, + resolution: 1, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregated_function: 'max', + aggregated_column: 'value', + } + } + }, + expected: [ { _cdb_feature_count: 1, type: 'even', max_value: 2 } ] + }, + { + zoom: 0, + cartodb_id: 7, + resolution: 1, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregated_function: 'max', + aggregated_column: 'value', + } + } + }, + expected: [ { _cdb_feature_count: 1, type: 'odd', max_value: 3 } ] + }, + { + zoom: 0, + cartodb_id: 1, + resolution: 50, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregated_function: 'max', + aggregated_column: 'value', + } + } + }, + expected: [ + { _cdb_feature_count: 2, type: 'even', max_value: 0 }, + { _cdb_feature_count: 2, type: 'odd', max_value: -1 } + ] + }, + { + zoom: 0, + cartodb_id: 5, + resolution: 50, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregated_function: 'max', + aggregated_column: 'value', + } + } + }, + expected: [ + { _cdb_feature_count: 1, type: 'even', max_value: 2 }, + { _cdb_feature_count: 2, type: 'odd', max_value: 3 } + ] } ]; @@ -443,14 +584,14 @@ describe('cluster', function () { }; const expectedExpressionsError = { - errors:[ 'Invalid aggregation input, expressions should be and object with expressions' ], + errors:[ 'Invalid aggregation input, expressions should be and object with valid functions' ], errors_with_context:[ { layer: { index: '0', type: 'cartodb' }, - message: 'Invalid aggregation input, expressions should be and object with expressions', + message: 'Invalid aggregation input, expressions should be and object with valid functions', subtype: 'aggregation', type: 'layer' } From f9e5d9d0a976771ca448aaae78e182a4b4289d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 1 Mar 2019 17:02:06 +0100 Subject: [PATCH 58/84] Validate aggregation expresions --- lib/cartodb/backends/cluster.js | 33 +++++++- test/acceptance/cluster.js | 142 ++++++++++++++++++++++++++++---- 2 files changed, 160 insertions(+), 15 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index c9108da9..650b42ce 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -90,6 +90,36 @@ module.exports = class ClusterBackend { return callback(error); } + + for (const [columnName, exp] of Object.entries(expressions)) { + const { aggregate_function, aggregated_column } = exp; + + if (typeof aggregated_column !== 'string') { + const error = new Error(`Invalid aggregation input, aggregated column should be an string`); + error.http_status = 400; + error.type = 'layer'; + error.subtype = 'aggregation'; + error.layer = { + index: layerIndex, + type: layer.type + }; + + return callback(error); + } + + if (typeof aggregate_function !== 'string') { + const error = new Error(`Invalid aggregation input, aggregate function should be an string`); + error.http_status = 400; + error.type = 'layer'; + error.subtype = 'aggregation'; + error.layer = { + index: layerIndex, + type: layer.type + }; + + return callback(error); + } + } } } @@ -153,7 +183,8 @@ function getClusterFeatures (pg, zoom, clusterId, columns, query, resolution, ag let { columns = [], expressions = [] } = aggregation; if (expressions) { - expressions = Object.entries(expressions).map(entries =>`${entries[1].aggregated_function}(${entries[1].aggregated_column}) AS ${entries[0]}`); + expressions = Object.entries(expressions) + .map(([columnName, exp]) => `${exp.aggregate_function}(${exp.aggregated_column}) AS ${columnName}`); } sql = aggregationQuery({ diff --git a/test/acceptance/cluster.js b/test/acceptance/cluster.js index 881dbf8e..87ff63f5 100644 --- a/test/acceptance/cluster.js +++ b/test/acceptance/cluster.js @@ -403,7 +403,7 @@ describe('cluster', function () { columns: [ 'type' ], expressions: { max_value: { - aggregated_function: 'max', + aggregate_function: 'max', aggregated_column: 'value', } } @@ -418,7 +418,7 @@ describe('cluster', function () { columns: [ 'type' ], expressions: { max_value: { - aggregated_function: 'max', + aggregate_function: 'max', aggregated_column: 'value', } } @@ -433,7 +433,7 @@ describe('cluster', function () { columns: [ 'type' ], expressions: { max_value: { - aggregated_function: 'max', + aggregate_function: 'max', aggregated_column: 'value', } } @@ -448,7 +448,7 @@ describe('cluster', function () { columns: [ 'type' ], expressions: { max_value: { - aggregated_function: 'max', + aggregate_function: 'max', aggregated_column: 'value', } } @@ -463,7 +463,7 @@ describe('cluster', function () { columns: [ 'type' ], expressions: { max_value: { - aggregated_function: 'max', + aggregate_function: 'max', aggregated_column: 'value', } } @@ -478,7 +478,7 @@ describe('cluster', function () { columns: [ 'type' ], expressions: { max_value: { - aggregated_function: 'max', + aggregate_function: 'max', aggregated_column: 'value', } } @@ -493,7 +493,7 @@ describe('cluster', function () { columns: [ 'type' ], expressions: { max_value: { - aggregated_function: 'max', + aggregate_function: 'max', aggregated_column: 'value', } } @@ -508,7 +508,7 @@ describe('cluster', function () { columns: [ 'type' ], expressions: { max_value: { - aggregated_function: 'max', + aggregate_function: 'max', aggregated_column: 'value', } } @@ -526,7 +526,7 @@ describe('cluster', function () { columns: [ 'type' ], expressions: { max_value: { - aggregated_function: 'max', + aggregate_function: 'max', aggregated_column: 'value', } } @@ -567,7 +567,7 @@ describe('cluster', function () { }); }); - describe('invalid aggregation', function () { + describe.only('invalid aggregation', function () { const expectedColumnsError = { errors:[ 'Invalid aggregation input, columns should be and array of column names' ], errors_with_context:[ @@ -598,6 +598,57 @@ describe('cluster', function () { ] }; + const invalidFunctionExpressionsError = { + errors:[ 'function wadus(integer) does not exist' ], + errors_with_context:[ + { + message: 'function wadus(integer) does not exist', + type: 'unknown' + } + ] + }; + + const invalidColumnExpressionsError = { + errors:[ 'column \"wadus\" does not exist' ], + errors_with_context:[ + { + message: 'column \"wadus\" does not exist', + type: 'unknown' + } + ] + }; + + + const expectedAggregatedColumnError = { + errors:[ 'Invalid aggregation input, aggregated column should be an string' ], + errors_with_context:[ + { + layer: { + index: '0', + type: 'cartodb' + }, + message: 'Invalid aggregation input, aggregated column should be an string', + subtype: 'aggregation', + type: 'layer' + } + ] + }; + + const expectedAggregateFunctionError = { + errors:[ 'Invalid aggregation input, aggregate function should be an string' ], + errors_with_context:[ + { + layer: { + index: '0', + type: 'cartodb' + }, + message: 'Invalid aggregation input, aggregate function should be an string', + subtype: 'aggregation', + type: 'layer' + } + ] + }; + const suite = [ { description: 'empty aggregation object should respond with error', @@ -686,10 +737,75 @@ describe('cluster', function () { resolution: 1, aggregation: { columns: [ 'type' ], expressions: null }, expected: expectedExpressionsError + }, + { + description: 'invalid aggregation function should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregate_function: 'wadus', + aggregated_column: 'value' + } + } + }, + expected: invalidFunctionExpressionsError + }, + { + description: 'invalid aggregation column should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregate_function: 'max', + aggregated_column: 'wadus' + } + } + }, + status: 404, + expected: invalidColumnExpressionsError + }, + { + description: 'aggregated column as non string should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregate_function: 'max', + aggregated_column: 1 + } + } + }, + expected: expectedAggregatedColumnError + }, + { + description: 'aggregate function as non string should respond with error', + zoom: 0, + cartodb_id: 1, + resolution: 1, + aggregation: { + columns: [ 'type' ], + expressions: { + max_value: { + aggregate_function: 1, + aggregated_column: 'value' + } + } + }, + expected: expectedAggregateFunctionError } ]; - suite.forEach(({ description, zoom, cartodb_id, resolution, aggregation, expected }) => { + suite.forEach(({ description, zoom, cartodb_id, resolution, aggregation, expected, status = 400 }) => { it(description, function (done) { const mapConfig = createVectorMapConfig([{ type: 'cartodb', @@ -704,9 +820,7 @@ describe('cluster', function () { const testClient = new TestClient(mapConfig); const layerId = 0; const params = { - response: { - status: 400 - }, + response: { status }, aggregation }; From dcf147cdfbc38ac9efd20c1f95e4457793badd1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 1 Mar 2019 17:05:35 +0100 Subject: [PATCH 59/84] linter --- lib/cartodb/backends/cluster.js | 9 +++++---- test/acceptance/cluster.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index 650b42ce..c90f176d 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -6,6 +6,7 @@ const debug = require('debug')('backend:cluster'); const AggregationMapConfig = require('../models/aggregation/aggregation-mapconfig'); module.exports = class ClusterBackend { + // jshint maxcomplexity: 17 getClusterFeatures (mapConfigProvider, params, callback) { mapConfigProvider.getMapConfig((err, _mapConfig) => { if (err) { @@ -91,9 +92,7 @@ module.exports = class ClusterBackend { return callback(error); } - for (const [columnName, exp] of Object.entries(expressions)) { - const { aggregate_function, aggregated_column } = exp; - + for (const { aggregate_function, aggregated_column } of Object.values(expressions)) { if (typeof aggregated_column !== 'string') { const error = new Error(`Invalid aggregation input, aggregated column should be an string`); error.http_status = 400; @@ -108,7 +107,9 @@ module.exports = class ClusterBackend { } if (typeof aggregate_function !== 'string') { - const error = new Error(`Invalid aggregation input, aggregate function should be an string`); + const error = new Error( + `Invalid aggregation input, aggregate function should be an string` + ); error.http_status = 400; error.type = 'layer'; error.subtype = 'aggregation'; diff --git a/test/acceptance/cluster.js b/test/acceptance/cluster.js index 87ff63f5..a535f6c3 100644 --- a/test/acceptance/cluster.js +++ b/test/acceptance/cluster.js @@ -567,7 +567,7 @@ describe('cluster', function () { }); }); - describe.only('invalid aggregation', function () { + describe('invalid aggregation', function () { const expectedColumnsError = { errors:[ 'Invalid aggregation input, columns should be and array of column names' ], errors_with_context:[ From a412e37a32e71432af76e07d334a6a34021488b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 1 Mar 2019 17:21:55 +0100 Subject: [PATCH 60/84] Improve test descriptions --- test/acceptance/cluster.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/acceptance/cluster.js b/test/acceptance/cluster.js index a535f6c3..632b6e8a 100644 --- a/test/acceptance/cluster.js +++ b/test/acceptance/cluster.js @@ -36,7 +36,7 @@ function createVectorMapConfig (layers = defaultLayers) { } describe('cluster', function () { - describe('map-config w/o aggregation', function () { + describe('w/o aggregation', function () { it('should return error while fetching disaggregated features', function (done) { const mapConfig = createVectorMapConfig([{ type: 'cartodb', @@ -123,7 +123,7 @@ describe('cluster', function () { }); }); - describe('map-config with aggregation', function () { + describe('fetch features within a cluster grid', function () { const suite = [ { zoom: 0, @@ -324,7 +324,7 @@ describe('cluster', function () { }); }); - describe('with aggregation', function () { + describe('valid aggregation input', function () { const suite = [ { zoom: 0, @@ -567,7 +567,7 @@ describe('cluster', function () { }); }); - describe('invalid aggregation', function () { + describe('invalid aggregation input', function () { const expectedColumnsError = { errors:[ 'Invalid aggregation input, columns should be and array of column names' ], errors_with_context:[ From f5fb60aa560afaf92869c2af21498ec0313f368f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 1 Mar 2019 17:34:32 +0100 Subject: [PATCH 61/84] Filter output --- .../api/map/clustered-features-layergroup-controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/cartodb/api/map/clustered-features-layergroup-controller.js b/lib/cartodb/api/map/clustered-features-layergroup-controller.js index 9779a09a..48e56b32 100644 --- a/lib/cartodb/api/map/clustered-features-layergroup-controller.js +++ b/lib/cartodb/api/map/clustered-features-layergroup-controller.js @@ -85,7 +85,8 @@ function getClusteredFeatures (clusterBackend) { } res.statusCode = 200; - res.body = features; + const { rows, fields } = features; + res.body = { rows, fields }; next(); }); From 2a0287b35820c60df8f339f91752af7d9d704e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 1 Mar 2019 17:49:26 +0100 Subject: [PATCH 62/84] Add todos --- lib/cartodb/api/map/clustered-features-layergroup-controller.js | 1 + lib/cartodb/backends/cluster.js | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/cartodb/api/map/clustered-features-layergroup-controller.js b/lib/cartodb/api/map/clustered-features-layergroup-controller.js index 48e56b32..5c17c5b1 100644 --- a/lib/cartodb/api/map/clustered-features-layergroup-controller.js +++ b/lib/cartodb/api/map/clustered-features-layergroup-controller.js @@ -42,6 +42,7 @@ module.exports = class AggregatedFeaturesLayergroupController { credentials(), authorize(this.authBackend), dbConnSetup(this.pgConnection), + // TODO: create its rate limit rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.ATTRIBUTES), cleanUpQueryParams([ 'aggregation' ]), createMapStoreMapConfigProvider( diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index c90f176d..c9776eba 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -6,6 +6,7 @@ const debug = require('debug')('backend:cluster'); const AggregationMapConfig = require('../models/aggregation/aggregation-mapconfig'); module.exports = class ClusterBackend { + // TODO: reduce complexity // jshint maxcomplexity: 17 getClusterFeatures (mapConfigProvider, params, callback) { mapConfigProvider.getMapConfig((err, _mapConfig) => { From 47321aebc45a141f71d06707d1ec9b9ae7a384d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 1 Mar 2019 17:53:15 +0100 Subject: [PATCH 63/84] Updates NEWS and next release version --- NEWS.md | 5 ++++- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 1e592442..605a9751 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,8 +1,11 @@ # Changelog -## 7.0.1 +## 7.1.0 Released 2019-mm-dd +Announcements: +- Experimental support for listing features in a grid when the map uses the dynamic agregation. + ## 7.0.0 Released 2019-02-22 diff --git a/package-lock.json b/package-lock.json index fb770817..94641675 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "windshaft-cartodb", - "version": "7.0.1", + "version": "7.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f2ce3a36..b62b2e6e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "windshaft-cartodb", - "version": "7.0.1", + "version": "7.1.0", "description": "A map tile server for CartoDB", "keywords": [ "cartodb" From e74f734546207416aca2c374e4fbe410f50b1d0e Mon Sep 17 00:00:00 2001 From: csubira Date: Fri, 1 Mar 2019 18:24:11 +0100 Subject: [PATCH 64/84] Revert addition due to conflict when deploying devcenter --- docs/guides/05-static-maps-API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/05-static-maps-API.md b/docs/guides/05-static-maps-API.md index c8342a48..ffa73add 100644 --- a/docs/guides/05-static-maps-API.md +++ b/docs/guides/05-static-maps-API.md @@ -11,7 +11,7 @@ Begin by instantiating either a Named or Anonymous Map using the `layergroupid t ##### Definition ```bash -GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}{{?}extra_options} +GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format} ``` ##### Params From 98121e0e64fce919822b1725c0344c2d4efd137f Mon Sep 17 00:00:00 2001 From: csubira Date: Mon, 4 Mar 2019 12:20:51 +0100 Subject: [PATCH 65/84] Add liquid raw to fix block code --- docs/guides/05-static-maps-API.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/guides/05-static-maps-API.md b/docs/guides/05-static-maps-API.md index ffa73add..bbb6f86c 100644 --- a/docs/guides/05-static-maps-API.md +++ b/docs/guides/05-static-maps-API.md @@ -1,24 +1,26 @@ ## Static Maps API -The Static Maps API can be initiated using both Named and Anonymous Maps using the 'layergroupid' token. The API can be used to create static images of parts of maps and thumbnails for use in web design, graphic design, print, field work, and many other applications that require standard image formats. +The Static Maps API can be initiated using both Named and Anonymous Maps using the `layergroupid` token. The API can be used to create static images of parts of maps and thumbnails for use in web design, graphic design, print, field work, and many other applications that require standard image formats. ### Maps API endpoints -Begin by instantiating either a Named or Anonymous Map using the `layergroupid token` as demonstrated in the Maps API documentation above. The `layergroupid` token calls to the map and allows for parameters in the definition to generate static images. +Begin by instantiating either a Named or Anonymous Map using the `layergroupid` token as demonstrated in the Maps API documentation above. The `layergroupid` token calls to the map and allows for parameters in the definition to generate static images. #### Zoom + center ##### Definition ```bash -GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format} +{% raw %} + GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}{{?}extra_options} +{% endraw %} ``` ##### Params Param | Description --- | --- -token | the layergroupid token from the map instantiation +token | the `layergroupid` token from the map instantiation z | the zoom level of the map lat | the latitude for the center of the map @@ -38,7 +40,7 @@ GET /api/v1/map/static/bbox/{token}/{bbox}/{width}/{height}.{format}` Param | Description --- | --- -token | the layergroupid token from the map instantiation +token | the `layergroupid` token from the map instantiation bbox | the bounding box in WGS 84 (EPSG:4326), comma separated values for: --- | --- @@ -145,7 +147,7 @@ Additionally, static images from Torque maps and other map layers can be used to #### Caching -It is important to note that generated images are cached from the live data referenced with the `layergroupid token` on the specified CARTO account. This means that if the data changes, the cached image will also change. When linking dynamically, it is important to take into consideration the state of the data and longevity of the static image to avoid broken images or changes in how the image is displayed. To obtain a static snapshot of the map as it is today and preserve the image long-term regardless of changes in data, the image must be saved and stored locally. +It is important to note that generated images are cached from the live data referenced with the `layergroupid` token on the specified CARTO account. This means that if the data changes, the cached image will also change. When linking dynamically, it is important to take into consideration the state of the data and longevity of the static image to avoid broken images or changes in how the image is displayed. To obtain a static snapshot of the map as it is today and preserve the image long-term regardless of changes in data, the image must be saved and stored locally. #### Limits From df7b5db47bbe4cbcc2690cda9bb4c9713349df83 Mon Sep 17 00:00:00 2001 From: csubira Date: Mon, 4 Mar 2019 12:26:04 +0100 Subject: [PATCH 66/84] Remove extra line --- docs/guides/05-static-maps-API.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/guides/05-static-maps-API.md b/docs/guides/05-static-maps-API.md index bbb6f86c..1cae7cc2 100644 --- a/docs/guides/05-static-maps-API.md +++ b/docs/guides/05-static-maps-API.md @@ -11,9 +11,7 @@ Begin by instantiating either a Named or Anonymous Map using the `layergroupid` ##### Definition ```bash -{% raw %} - GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}{{?}extra_options} -{% endraw %} +{% raw %}GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}{{?}extra_options}{% endraw %} ``` ##### Params From 51ff565c5f1c3ab5f2f081013b42dcf1bd3f6e51 Mon Sep 17 00:00:00 2001 From: csubira Date: Wed, 6 Mar 2019 14:54:48 +0100 Subject: [PATCH 67/84] Update links to deprecated docs site --- docs/guides/02-general-concepts.md | 2 +- docs/guides/03-anonymous-maps.md | 6 +++--- docs/guides/04-named-maps.md | 28 ++++++++++++------------- docs/guides/05-static-maps-API.md | 4 ++-- docs/guides/06-tile-aggregation.md | 2 +- docs/guides/07-MapConfig-file-format.md | 12 +++++------ docs/reference/swagger.yaml | 2 +- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/guides/02-general-concepts.md b/docs/guides/02-general-concepts.md index fc66b90d..a30b54d7 100644 --- a/docs/guides/02-general-concepts.md +++ b/docs/guides/02-general-concepts.md @@ -4,7 +4,7 @@ The following concepts are the same for every endpoint in the API except when it ### Auth -By default, users do not have access to private tables in CARTO. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a Named Map). +By default, users do not have access to private tables in CARTO. In order to instantiate a map from private table data an API Key is required. Additionally, an API Key is also required to use some of the API endpoints (e.g. to create a Named Map). To execute an authorized request, `api_key=YOURAPIKEY` should be added to the request URL. The param can be also passed as POST param. Using HTTPS is mandatory when you are performing requests that include your `api_key`. diff --git a/docs/guides/03-anonymous-maps.md b/docs/guides/03-anonymous-maps.md index 56d61502..01b20bef 100644 --- a/docs/guides/03-anonymous-maps.md +++ b/docs/guides/03-anonymous-maps.md @@ -29,7 +29,7 @@ POST /api/v1/map } ``` -See [MapConfig File Formats](http://docs.carto.com/carto-engine/maps-api/mapconfig/) for details. +See [MapConfig File Formats]({{site.mapsapi_docs}}/guides/MapConfig-file-format/) for details. ##### Response @@ -149,7 +149,7 @@ The following example instantiates an anonymous map with layer options: **Note**: If no layer type is specified, Mapnik tiles are used by default. To access MVT tiles, specify `https://{username}.cartodb.com/api/v1/map/HASH/{z}/{x}/{y}.mvt` as the `maps_api_template` variable. -**Tip:** If you are using [Named Maps](https://carto.com/docs/carto-engine/maps-api/named-maps/) to instantiate a layer, indicate the MVT file format and layer in the response: +**Tip:** If you are using [Named Maps]({{site.mapasapi_docs}}/guides/named-maps/) to instantiate a layer, indicate the MVT file format and layer in the response: ```bash https://{username}.cartodb.com/api/v1/map/named/:templateId/:layer/{z}/{x}/{y}.mvt @@ -223,7 +223,7 @@ cartocss: "...", 5) Request Tiles (from CARTO) and Set to Map Object (Mapbox): -**Note:** By default, [CARTO core functions](https://carto.com/docs/carto-engine/carto-js/core-api/) retrieve URLs for fully rendered tiles. You must replace the default format (.png) with the MVT format (.mvt). +**Note:** By default, [CARTO core functions]({{site.cartojs_docs}}/v3/guides/core-API-functionality/) retrieve URLs for fully rendered tiles. You must replace the default format (.png) with the MVT format (.mvt). ```bash diff --git a/docs/guides/04-named-maps.md b/docs/guides/04-named-maps.md index fb91adb3..31950b98 100644 --- a/docs/guides/04-named-maps.md +++ b/docs/guides/04-named-maps.md @@ -6,7 +6,7 @@ The Named Map workflow consists of uploading a MapConfig file to CARTO servers, The response back from the API provides the template_id of your Named Map as the `name` (the identifier of your Named Map), which is the name that you specified in the MapConfig. You can which you can then use to create your Named Map details, or [fetch XYZ tiles](#fetching-xyz-tiles-for-named-maps) directly for Named Maps. -**Tip:** You can also use a Named Map that you created (which is defined by its `name`), to create a map using CARTO.js. This is achieved by adding the [`namedmap` type](http://docs.carto.com/carto-engine/carto-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map. +**Tip:** You can also use a Named Map that you created (which is defined by its `name`), to create a map using CARTO.js. This is achieved by adding the [`namedmap` type]({{site.cartojs_docs}}/v3/guides/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map. The main differences, compared to Anonymous Maps, is that Named Maps include: @@ -16,9 +16,9 @@ The main differences, compared to Anonymous Maps, is that Named Maps include: - **template map** The template map is static and may contain placeholders, enabling you to modify your maps appearance by using variables. Templates maps are persistent with no preset expiration. They can only be created, or deleted, by a CARTO user with a valid API KEY (See [auth argument](#arguments)). - Uploading a MapConfig creates a Named Map. MapConfigs are uploaded to the server by sending the server a "template".json file, which contain the [MapConfig specifications](http://docs.carto.com/carto-engine/maps-api/mapconfig/). + Uploading a MapConfig creates a Named Map. MapConfigs are uploaded to the server by sending the server a "template".json file, which contain the [MapConfig specifications]({{site.mapsapi_docs}}/guides/MapConfig-file-format/). -**Note:** There is a limit of 4,096 Named Maps allowed per account. If you need to create more Named Maps, it is recommended to use a single Named Map and change the variables using [placeholders](#placeholder-format), instead of uploading multiple [Named Map MapConfigs](http://docs.carto.com/carto-engine/maps-api/mapconfig/#named-map-layer-options). +**Note:** There is a limit of 4,096 Named Maps allowed per account. If you need to create more Named Maps, it is recommended to use a single Named Map and change the variables using [placeholders](#placeholder-format), instead of uploading multiple [Named Map MapConfigs]({{site.mapsapi_docs}}/guides/MapConfig-file-format/#named-map-layer-options). ### Create @@ -33,7 +33,7 @@ POST /api/v1/map/named Params | Description --- | --- api_key | is required -MapConfig | a [Named Map MapConfig](http://docs.carto.com/carto-engine/maps-api/mapconfig/#named-map-layer-options) is required to create a Named Map +MapConfig | a [Named Map MapConfig]({{site.mapsapi_docs}}/guides/MapConfig-file-format/#named-map-layer-options) is required to create a Named Map ##### template.json @@ -104,7 +104,7 @@ auth | |_ method | `"token"` or `"open"` (`"open"` is the default if no method is specified. Use `"token"` to password-protect your map) |_ valid_tokens | when `"method"` is set to `"token"`, the values listed here allow you to instantiate the Named Map. See this [example](http://docs.carto.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map. placeholders | Placeholders are variables that can be placed in your template.json file's SQL or CartoCSS. -layergroup | the layergroup configurations, as specified in the template. See [MapConfig File Format](http://docs.carto.com/carto-engine/maps-api/mapconfig/) for more information. +layergroup | the layergroup configurations, as specified in the template. See [MapConfig File Format]({{site.mapsapi_docs}}/guides/MapConfig-file-format/) for more information. view (optional) | extra keys to specify the view area for the map. It can be used to have a static preview of a Named Map without having to instantiate it. It is possible to specify it with `center` + `zoom` or with a bounding box `bbox`. Center+zoom takes precedence over bounding box. Also it is possible to choose which layers are visible or not with `preview_layers` indicating its visibility by layer index or id (visible by default). --- | --- |_ zoom | The zoom level to use @@ -124,7 +124,7 @@ view (optional) | extra keys to specify the view area for the map. It can be use #### Placeholder Format -Placeholders are variables that can be placed in your template.json file. Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergroup configurations](http://docs.carto.com/carto-engine/maps-api/mapconfig/#layergroup-configurations). +Placeholders are variables that can be placed in your template.json file. Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergroup configurations]({{site.mapsapi_docs}}/guides/MapConfig-file-format/#layergroup-configurations). Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced inside the Named Maps API. @@ -233,7 +233,7 @@ curl -X POST \ } ``` -You can then use the `layergroupid` for fetching tiles and grids as you would normally (see [Anonymous Maps](http://docs.carto.com/carto-engine/maps-api/anonymous-maps/)). +You can then use the `layergroupid` for fetching tiles and grids as you would normally (see [Anonymous Maps]({{site.mapsapi_docs}}/guides/anonymous-maps/)). ### Update @@ -456,7 +456,7 @@ callback({ ### CARTO.js for Named Maps -You can use a Named Map that you created (which is defined by its `name`), to create a map using CARTO.js. This is achieved by adding the [`namedmap` type](http://docs.carto.com/carto-engine/carto-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map. +You can use a Named Map that you created (which is defined by its `name`), to create a map using CARTO.js. This is achieved by adding the [`namedmap` type]({{site.cartojs_docs}}/v3/guides/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map. ```javascript { @@ -486,13 +486,13 @@ You can use a Named Map that you created (which is defined by its `name`), to cr **Note:** Instantiating a Named Map over a `createLayer` does not require an API Key and by default, does not include auth tokens. _If_ you defined auth tokens for the Named Map configuration, then you will have to include them. -[CARTO.js](http://docs.carto.com/carto-engine/carto-js/) has methods for accessing your Named Maps. +[CARTO.js]({{site.cartojs_docs/v3/}) has methods for accessing your Named Maps. -1. [layer.setParams()](http://docs.carto.com/carto-engine/carto-js/api-methods/#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript +1. [layer.setParams()]({{site.cartojs_docs}}/v3/reference/#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript **Note:** The CARTO.js `layer.setParams()` function is not supported when using Named Maps for Torque. Alternatively, you can create a [Torque layer in a Named Map](http://bl.ocks.org/iriberri/de37be6406f9cc7cfe5a) -2. [layer.setAuthToken()](http://docs.carto.com/carto-engine/carto-js/api-methods/#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer +2. [layer.setAuthToken()](h{{site.cartojs_docs}}/v3/reference/#layersetauthtokenauth_token) allows you to set the auth tokens to create the layer #### Torque Layer in a Named Map @@ -551,17 +551,17 @@ For example, a complete URL might appear as: The placeholders indicate the following: -- [`template_id`](http://docs.carto.com/carto-engine/maps-api/named-maps/#response) is the response of your Named Map. +- [`template_id`]({{site.mapsapi_docs}}/guides/named-maps/#response) is the response of your Named Map. - layers can be a number (referring to the ## layer of your map), all layers of your map, or a list of layers. - To show just the basemap layer, enter the number value `0` in the layer placeholder "https://{username}.carto.com/api/v1/map/named/{template_id}/0/{z}/{x}/{y}.png" - To show the first layer, enter the number value `1` in the layer placeholder "https://{username}.carto.com/api/v1/map/named/{template_id}/1/{z}/{x}/{y}.png" - To show all layers, enter the value `all` for the layer placeholder "https://{username}.carto.com/api/v1/map/named/{template_id}/all/{z}/{x}/{y}.png" - - To show a [list of layers](http://docs.carto.com/carto-engine/maps-api/anonymous-maps/#blending-and-layer-selection), enter the comma separated layer value as 0,1,2 in the layer placeholder. For example, to show the basemap and the first layer, "https://{username}.carto.com/api/v1/map/named/{template_id}/0,1/{z}/{x}/{y}.png" + - To show a [list of layers]({{site.mapsapi_docs}}/guides/anonymous-maps/#blending-and-layer-selection), enter the comma separated layer value as 0,1,2 in the layer placeholder. For example, to show the basemap and the first layer, "https://{username}.carto.com/api/v1/map/named/{template_id}/0,1/{z}/{x}/{y}.png" #### Get Mapnik Retina Tiles -Mapnik Retina tiles are not directly supported for Named Maps, so you cannot use the Named Map template_id. To fetch Mapnik Retina tiles, get the [layergroupid](http://docs.carto.com/carto-engine/maps-api/named-maps/#response-1) to initialize the map. +Mapnik Retina tiles are not directly supported for Named Maps, so you cannot use the Named Map template_id. To fetch Mapnik Retina tiles, get the [layergroupid]({{site.mapsapi_docs}}/guides/named-maps/#response-1) to initialize the map. Instantiate the map by using your `layergroupid` in the token placeholder: diff --git a/docs/guides/05-static-maps-API.md b/docs/guides/05-static-maps-API.md index 1cae7cc2..b0467646 100644 --- a/docs/guides/05-static-maps-API.md +++ b/docs/guides/05-static-maps-API.md @@ -81,7 +81,7 @@ format | the format for the image, supported types: `png`, `jpg` --- | --- |_ jpg | will have a default quality of 85. -A Named Maps static image will get its constraints from the [`view` argument of the Create Named Map function](http://docs.carto.com/carto-engine/maps-api/named-maps/#arguments). If `view` is not defined, it will estimate the extent based on the involved tables, otherwise it fallbacks to `"zoom": 1`, `"lng": 0` and `"lat": 0`. +A Named Maps static image will get its constraints from the [`view` argument of the Create Named Map function]({{site.mapasapi_docs}}/guides/named-maps/). If `view` is not defined, it will estimate the extent based on the involved tables, otherwise it fallbacks to `"zoom": 1`, `"lng": 0` and `"lat": 0`. ##### Layers @@ -127,7 +127,7 @@ By manipulating the `"urlTemplate"` custom basemaps can be used in generating st **CARTO** -As described in the [MapConfig File Format](http://docs.carto.com/carto-engine/maps-api/mapconfig/), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility. +As described in the [MapConfig File Format]({{site.mapsapi_docs}}/guides/MapConfig-file-format/), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility. ```javascript { diff --git a/docs/guides/06-tile-aggregation.md b/docs/guides/06-tile-aggregation.md index 437f63ee..0ff634f3 100644 --- a/docs/guides/06-tile-aggregation.md +++ b/docs/guides/06-tile-aggregation.md @@ -136,7 +136,7 @@ of the original dataset applying three different aggregate functions. #### `resolution` -Defines the cell-size of the spatial aggregation grid. This is equivalent to the [CartoCSS `-torque-resolution`](https://carto.com/docs/carto-engine/cartocss/properties-for-torque/#-torque-resolution-float) property of Torque maps. +Defines the cell-size of the spatial aggregation grid. This is equivalent to the [CartoCSS `-torque-resolution`]({{site.styling_cartocss}}/#-torque-resolution-float) property of Torque maps. The aggregation cells are `resolution`×`resolution` pixels in size, where pixels here are defined to be 1/256 of the (linear) size of a tile. The default value is 1, so that aggregation coincides with raster pixels. A value of 2 would make each cell to be 4 (2×2) pixels, and a value of diff --git a/docs/guides/07-MapConfig-file-format.md b/docs/guides/07-MapConfig-file-format.md index 2f8068a9..94a32985 100644 --- a/docs/guides/07-MapConfig-file-format.md +++ b/docs/guides/07-MapConfig-file-format.md @@ -5,7 +5,7 @@ https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.4.0.md. However ## MapConfig File Format -CARTO uses Windshaft as the map tiler library to render multilayer maps with the [Maps API]({{ site.baseurl }}/carto-engine/maps-api/). The MapConfig file is where these Windshaft layers are stored and applied. You can configure tiles and use the MapConfig document to request different resources for your map. +CARTO uses Windshaft as the map tiler library to render multilayer maps with the [Maps API]({{ site.mapsapi_docs }}/). The MapConfig file is where these Windshaft layers are stored and applied. You can configure tiles and use the MapConfig document to request different resources for your map. This section describes the MapConfig specifications, and required formats, when using the Maps API. @@ -91,14 +91,14 @@ Mapnik Layer Option | Description | Optional or Required? ### Torque Layer Options -If you are using Torque as a layer resource, the following configurations are required in your MapConfig file. For more details about Torque layers in general, see the [Torque API]({{ site.baseurl }}/carto-engine/torque/torqueapi/#torque-api) documentation. +If you are using Torque as a layer resource, the following configurations are required in your MapConfig file. For more details about Torque layers in general, see the [Torque API]({{ site.torque_docs}}/reference/) documentation. Torque Layer Option | Description | Optional or Required? --- | --- `sql` | A string value, the SQL request to the user database that will fetch the rendered data.

**Tip:** The SQL request should include the following Torque layer configurations: `geom_column`, `interactivity`, and `attributes`, as described in this section. | Required `cartocss` | A string value, specifying the CartoCSS style to render the tiles.

**Note:** The CartoCSS specification is dependent on the layer type. For details, see [Torque cartocss-reference.js](https://github.com/CartoDB/torque/blob/master/lib/torque/cartocss_reference.js).| Required `cartocss_version` | A string value, specifying the CartoCSS style version of the CartoCSS attribute.

**Note:** The CartoCSS version is specific to the layer type. | Required -`step` | The number of [animation steps]({{ site.baseurl }}/carto-engine/cartocss/properties-for-torque/#torque-frame-count-number) to render when requesting a torque.png tile. The default value is `0`. | Optional +`step` | The number of [animation steps]({{site.styling_cartocss}}/-#torque-frame-count-number) to render when requesting a torque.png tile. The default value is `0`. | Optional `geom_column` | The name of the column containing the geometry. The default is `the_geom_webmercator`.

*You must specify this value as part of the Torque layer `SQL`configuration. | *Optional `srid` | The spatial reference identifier for the geometry column. The default is `3857`. | Optional `affected_tables` | A string of values containing the tables that the Mapnik layer `SQL` configuration is using. This value is used if there is a problem guessing what the affected tables are from the SQL configuration (i.e. when using PL/SQL functions). | Optional @@ -169,7 +169,7 @@ Plain Layer Option | Description | Optional or Required? ### Named Map Layer Options -You can use a [Named Map]({{ site.baseurl }}/carto-engine/maps-api/named-maps/#named-maps) as a map layer. Note the following limitations before referencing the MapConfig options for a Named Map layer. +You can use a [Named Map]({{site.mapsapi_docs}}/guides/named-maps/) as a map layer. Note the following limitations before referencing the MapConfig options for a Named Map layer. _**Limitations:**_ @@ -201,7 +201,7 @@ Named Layer Option | Description | Optional or Required? ### Aggregation Options -The data used to render tiles, or contained in the tiles (for the case of vector tiles), can be spatially [aggregated](https://carto.com/docs/carto-engine/maps-api/named-maps/) under some circumstances. +The data used to render tiles, or contained in the tiles (for the case of vector tiles), can be spatially [aggregated]({{site.mapsapi_docs}}/guides/named-maps/) under some circumstances. An `aggregation` attribute can be used in the layer `options` to control the aggregation. A value of `false` will disable aggregation for the layer. Otherwise, an object can be passed with the following aggregation parameters: @@ -250,7 +250,7 @@ Parameter|Description|Default value ### MapConfig Requirements -All of these are MapConfig requirements for [Anonymous Maps]({{ site.baseurl }}/carto-engine/maps-api/anonymous-maps/#retrieve-resources-from-the-layergroup). +All of these are MapConfig requirements for [Anonymous Maps]({{site.mapsapi_docs}}/guides/anonymous-maps/#retrieve-resources-from-the-layergroup). - Identified by `{z}/{x}/{y}` path diff --git a/docs/reference/swagger.yaml b/docs/reference/swagger.yaml index ccb84146..416f1d4a 100644 --- a/docs/reference/swagger.yaml +++ b/docs/reference/swagger.yaml @@ -1182,7 +1182,7 @@ components: type: object title: Template Placeholders description: > - Variables that can be placed in layergroup's definition (SQL or CartoCSS of any layer). Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergroup configurations](http://docs.carto.com/carto-engine/maps-api/mapconfig/#layergroup-configurations). Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced inside the Named Maps API. + Variables that can be placed in layergroup's definition (SQL or CartoCSS of any layer). Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergroup configurations](https://carto.com/developers/maps-api/guides/MapConfig-file-format/#layergroup-configurations). Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced inside the Named Maps API. **Example**:```<%= my_color %>``` From a768575feaaa9658e7893a6c365fd3dc26e08949 Mon Sep 17 00:00:00 2001 From: csubira Date: Wed, 6 Mar 2019 14:55:41 +0100 Subject: [PATCH 68/84] Add multilayer api file as internal doc --- docs/internal/multilayer-API.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docs/internal/multilayer-API.md diff --git a/docs/internal/multilayer-API.md b/docs/internal/multilayer-API.md new file mode 100644 index 00000000..a37caf21 --- /dev/null +++ b/docs/internal/multilayer-API.md @@ -0,0 +1,28 @@ +The Windshaft-CartoDB MultiLayer API extends the [Windshaft MultiLayer API](https://github.com/CartoDB/Windshaft/blob/master/doc/internal/multilayer-API.md) in a few ways. + +## Last modification timestamp embedded in the token + +It encodes a timestamp of 'last modification time' into the map token (token:EPOCH) returned to the client. +It accepts tokens with encoded timestamp from the client considering the token suffix as a cache_buster value. + +Clients don't need to be aware of the extension but rather use the API as they would use the base one. +The only difference will be that the _same_ layergroup configuration may result in different tokens if source data was modified between the mapview requests. + +## Additional attributes in the response object + +Windshaft-CartoDB adds the following attributes in the response object + +- ``last_update`` field with ISO format (2013-11-30T12:23:10). +- ``cdn_url`` object containing CDN url client should use (not mandatory) to access the tiles. It's in the form: + + ```json + { + "http": "http://cdn_url.com/", + "https": "https://secure.cdn_url.com/" + } + ``` + + +## Stats tag + +Windshaft-CartoDB adds support for a ``stat_tag`` element in the multilayer configuration to help [stats](https://github.com/CartoDB/Windshaft-cartodb/wiki/Redis-stats-format) gathering. \ No newline at end of file From 2d37d4bf9d9929e872388f88e3f3e56e8c41e70a Mon Sep 17 00:00:00 2001 From: csubira Date: Wed, 6 Mar 2019 15:26:14 +0100 Subject: [PATCH 69/84] Fix typo in named maps guide --- docs/guides/04-named-maps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/04-named-maps.md b/docs/guides/04-named-maps.md index 31950b98..1d5f5095 100644 --- a/docs/guides/04-named-maps.md +++ b/docs/guides/04-named-maps.md @@ -486,7 +486,7 @@ You can use a Named Map that you created (which is defined by its `name`), to cr **Note:** Instantiating a Named Map over a `createLayer` does not require an API Key and by default, does not include auth tokens. _If_ you defined auth tokens for the Named Map configuration, then you will have to include them. -[CARTO.js]({{site.cartojs_docs/v3/}) has methods for accessing your Named Maps. +[CARTO.js]({{site.cartojs_docs}}/v3/) has methods for accessing your Named Maps. 1. [layer.setParams()]({{site.cartojs_docs}}/v3/reference/#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript From f0c82f21d23e874fb21687ec9d015b69525bc65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Mar 2019 17:01:13 +0100 Subject: [PATCH 70/84] Reduce complexity by extracting a complex condition to a function --- lib/cartodb/backends/cluster.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index c9776eba..31a2c7e9 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -7,7 +7,7 @@ const AggregationMapConfig = require('../models/aggregation/aggregation-mapconfi module.exports = class ClusterBackend { // TODO: reduce complexity - // jshint maxcomplexity: 17 + // jshint maxcomplexity: 16 getClusterFeatures (mapConfigProvider, params, callback) { mapConfigProvider.getMapConfig((err, _mapConfig) => { if (err) { @@ -26,7 +26,7 @@ module.exports = class ClusterBackend { const layer = mapConfig.getLayer(layerIndex); - if (layer.options.aggregation === false || !mapConfig.isAggregationLayer(layerIndex)) { + if (!hasAggregationLayer(mapConfig, layerIndex)) { const error = new Error(`Map ${token} has no aggregation defined for layer ${layerIndex}`); error.http_status = 400; error.type = 'layer'; @@ -37,6 +37,7 @@ module.exports = class ClusterBackend { }; debug(error); + return callback(error); } @@ -249,3 +250,14 @@ const aggregationQuery = ctx => ` FROM (${ctx.query}) __cdb_aggregation ${ctx.columns.length ? `GROUP BY ${ctx.columns.join(', ')}` : ''} `; + +// TODO: update when https://github.com/CartoDB/Windshaft-cartodb/pull/1082 is merged +function hasAggregationLayer (mapConfig, layerIndex) { + const layer = mapConfig.getLayer(layerIndex); + + if (typeof layer.options.aggregation === 'boolean') { + return layer.options.aggregation; + } + + return mapConfig.isAggregationLayer(layerIndex); +} From fecedfdc68b19e132522b4d72842a0f7522741be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Mar 2019 17:06:04 +0100 Subject: [PATCH 71/84] Reduce complexity by extracting validation to a function --- lib/cartodb/backends/cluster.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index 31a2c7e9..8ad2343c 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -7,7 +7,7 @@ const AggregationMapConfig = require('../models/aggregation/aggregation-mapconfi module.exports = class ClusterBackend { // TODO: reduce complexity - // jshint maxcomplexity: 16 + // jshint maxcomplexity: 15 getClusterFeatures (mapConfigProvider, params, callback) { mapConfigProvider.getMapConfig((err, _mapConfig) => { if (err) { @@ -61,7 +61,7 @@ module.exports = class ClusterBackend { const { columns, expressions } = aggregation; - if (!Array.isArray(columns) || !columns.length) { + if (!hasColumns(columns)) { const error = new Error( `Invalid aggregation input, columns should be and array of column names` ); @@ -261,3 +261,7 @@ function hasAggregationLayer (mapConfig, layerIndex) { return mapConfig.isAggregationLayer(layerIndex); } + +function hasColumns (columns) { + return Array.isArray(columns) && columns.length; +} From 8051dc5110074d399b31331a4882b33bff2ff0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Mar 2019 17:14:07 +0100 Subject: [PATCH 72/84] Reduce complexity by extracting validation condition to its function --- lib/cartodb/backends/cluster.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index 8ad2343c..e305eb28 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -7,7 +7,7 @@ const AggregationMapConfig = require('../models/aggregation/aggregation-mapconfi module.exports = class ClusterBackend { // TODO: reduce complexity - // jshint maxcomplexity: 15 + // jshint maxcomplexity: 13 getClusterFeatures (mapConfigProvider, params, callback) { mapConfigProvider.getMapConfig((err, _mapConfig) => { if (err) { @@ -77,9 +77,7 @@ module.exports = class ClusterBackend { } if (expressions !== undefined) { - if (expressions === null || - Array.isArray(expressions) || - ['string', 'number', 'boolean'].includes(typeof expressions)) { + if (!isValidExpression(expressions)) { const error = new Error( `Invalid aggregation input, expressions should be and object with valid functions` ); @@ -265,3 +263,9 @@ function hasAggregationLayer (mapConfig, layerIndex) { function hasColumns (columns) { return Array.isArray(columns) && columns.length; } + +function isValidExpression (expressions) { + const invalidTypes = ['string', 'number', 'boolean']; + + return expressions !== null && !Array.isArray(expressions) && !invalidTypes.includes(typeof expressions); +} From 49104a6add53be5b39d9a93ea78267b618f300f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Mar 2019 17:25:29 +0100 Subject: [PATCH 73/84] Reduce complexity by extracting function to validate expressions --- lib/cartodb/backends/cluster.js | 57 ++++++++++++--------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index e305eb28..f858b27a 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -7,7 +7,7 @@ const AggregationMapConfig = require('../models/aggregation/aggregation-mapconfi module.exports = class ClusterBackend { // TODO: reduce complexity - // jshint maxcomplexity: 13 + // jshint maxcomplexity: 10 getClusterFeatures (mapConfigProvider, params, callback) { mapConfigProvider.getMapConfig((err, _mapConfig) => { if (err) { @@ -43,7 +43,7 @@ module.exports = class ClusterBackend { let { aggregation } = params; - if ( aggregation !== undefined) { + if (aggregation !== undefined) { try { aggregation = JSON.parse(aggregation); } catch (err) { @@ -77,10 +77,9 @@ module.exports = class ClusterBackend { } if (expressions !== undefined) { - if (!isValidExpression(expressions)) { - const error = new Error( - `Invalid aggregation input, expressions should be and object with valid functions` - ); + try { + validateExpressions(expressions); + } catch (error) { error.http_status = 400; error.type = 'layer'; error.subtype = 'aggregation'; @@ -91,36 +90,6 @@ module.exports = class ClusterBackend { return callback(error); } - - for (const { aggregate_function, aggregated_column } of Object.values(expressions)) { - if (typeof aggregated_column !== 'string') { - const error = new Error(`Invalid aggregation input, aggregated column should be an string`); - error.http_status = 400; - error.type = 'layer'; - error.subtype = 'aggregation'; - error.layer = { - index: layerIndex, - type: layer.type - }; - - return callback(error); - } - - if (typeof aggregate_function !== 'string') { - const error = new Error( - `Invalid aggregation input, aggregate function should be an string` - ); - error.http_status = 400; - error.type = 'layer'; - error.subtype = 'aggregation'; - error.layer = { - index: layerIndex, - type: layer.type - }; - - return callback(error); - } - } } } @@ -264,6 +233,22 @@ function hasColumns (columns) { return Array.isArray(columns) && columns.length; } +function validateExpressions (expressions) { + if (!isValidExpression(expressions)) { + throw new Error(`Invalid aggregation input, expressions should be and object with valid functions`); + } + + for (const { aggregate_function, aggregated_column } of Object.values(expressions)) { + if (typeof aggregated_column !== 'string') { + throw new Error(`Invalid aggregation input, aggregated column should be an string`); + } + + if (typeof aggregate_function !== 'string') { + throw new Error(`Invalid aggregation input, aggregate function should be an string`); + } + } +} + function isValidExpression (expressions) { const invalidTypes = ['string', 'number', 'boolean']; From 589996b79c679049ca1a7be44ec32271d1c4ea8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Mar 2019 17:53:34 +0100 Subject: [PATCH 74/84] Reduce complexity --- lib/cartodb/backends/cluster.js | 106 +++++++++++++++----------------- 1 file changed, 48 insertions(+), 58 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index f858b27a..113dde65 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -6,8 +6,6 @@ const debug = require('debug')('backend:cluster'); const AggregationMapConfig = require('../models/aggregation/aggregation-mapconfig'); module.exports = class ClusterBackend { - // TODO: reduce complexity - // jshint maxcomplexity: 10 getClusterFeatures (mapConfigProvider, params, callback) { mapConfigProvider.getMapConfig((err, _mapConfig) => { if (err) { @@ -43,54 +41,19 @@ module.exports = class ClusterBackend { let { aggregation } = params; - if (aggregation !== undefined) { - try { - aggregation = JSON.parse(aggregation); - } catch (err) { - const error = new Error(`Invalid aggregation input, should be a a valid JSON`); - error.http_status = 400; - error.type = 'layer'; - error.subtype = 'aggregation'; - error.layer = { - index: layerIndex, - type: layer.type - }; + try { + aggregation = parseAggregation(aggregation); + validateAggregation(aggregation); + } catch (error) { + error.http_status = 400; + error.type = 'layer'; + error.subtype = 'aggregation'; + error.layer = { + index: layerIndex, + type: layer.type + }; - return callback(error); - } - - const { columns, expressions } = aggregation; - - if (!hasColumns(columns)) { - const error = new Error( - `Invalid aggregation input, columns should be and array of column names` - ); - error.http_status = 400; - error.type = 'layer'; - error.subtype = 'aggregation'; - error.layer = { - index: layerIndex, - type: layer.type - }; - - return callback(error); - } - - if (expressions !== undefined) { - try { - validateExpressions(expressions); - } catch (error) { - error.http_status = 400; - error.type = 'layer'; - error.subtype = 'aggregation'; - error.layer = { - index: layerIndex, - type: layer.type - }; - - return callback(error); - } - } + return callback(error); } const query = layer.options.sql_raw; @@ -229,22 +192,49 @@ function hasAggregationLayer (mapConfig, layerIndex) { return mapConfig.isAggregationLayer(layerIndex); } +function parseAggregation (aggregation) { + if (aggregation !== undefined) { + try { + aggregation = JSON.parse(aggregation); + } catch (err) { + throw new Error(`Invalid aggregation input, should be a a valid JSON`); + } + + } + + return aggregation; +} + +function validateAggregation (aggregation) { + if (aggregation !== undefined) { + const { columns, expressions } = aggregation; + + if (!hasColumns(columns)) { + throw new Error(`Invalid aggregation input, columns should be and array of column names`); + } + + validateExpressions(expressions); + } +} + function hasColumns (columns) { return Array.isArray(columns) && columns.length; } function validateExpressions (expressions) { - if (!isValidExpression(expressions)) { - throw new Error(`Invalid aggregation input, expressions should be and object with valid functions`); - } - - for (const { aggregate_function, aggregated_column } of Object.values(expressions)) { - if (typeof aggregated_column !== 'string') { - throw new Error(`Invalid aggregation input, aggregated column should be an string`); + if (expressions !== undefined) { + if (!isValidExpression(expressions)) { + throw new Error(`Invalid aggregation input, expressions should be and object with valid functions`); } - if (typeof aggregate_function !== 'string') { - throw new Error(`Invalid aggregation input, aggregate function should be an string`); + for (const { aggregate_function, aggregated_column } of Object.values(expressions)) { + if (typeof aggregated_column !== 'string') { + throw new Error(`Invalid aggregation input, aggregated column should be an string`); + } + + if (typeof aggregate_function !== 'string') { + throw new Error(`Invalid aggregation input, aggregate function should be an string`); + } } } } From 0aa3b288a03d962370a0221370f9cdb835ab4147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Mar 2019 18:04:47 +0100 Subject: [PATCH 75/84] Improve validation --- lib/cartodb/backends/cluster.js | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index 113dde65..ba998c5d 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -19,29 +19,13 @@ module.exports = class ClusterBackend { return callback(error); } - const { user, token, layer: layerIndex } = params; + const { user, layer: layerIndex } = params; const mapConfig = new AggregationMapConfig(user, _mapConfig.obj(), pg); - const layer = mapConfig.getLayer(layerIndex); - - if (!hasAggregationLayer(mapConfig, layerIndex)) { - const error = new Error(`Map ${token} has no aggregation defined for layer ${layerIndex}`); - error.http_status = 400; - error.type = 'layer'; - error.subtype = 'aggregation'; - error.layer = { - index: layerIndex, - type: layer.type - }; - - debug(error); - - return callback(error); - } - let { aggregation } = params; try { + validateAggregationLayer(mapConfig, layerIndex); aggregation = parseAggregation(aggregation); validateAggregation(aggregation); } catch (error) { @@ -53,6 +37,8 @@ module.exports = class ClusterBackend { type: layer.type }; + debug(error); + return callback(error); } @@ -181,6 +167,12 @@ const aggregationQuery = ctx => ` ${ctx.columns.length ? `GROUP BY ${ctx.columns.join(', ')}` : ''} `; +function validateAggregationLayer (mapConfig, layerIndex) { + if (!hasAggregationLayer(mapConfig, layerIndex)) { + throw new Error(`Map ${mapConfig.id()} has no aggregation defined for layer ${layerIndex}`); + } +} + // TODO: update when https://github.com/CartoDB/Windshaft-cartodb/pull/1082 is merged function hasAggregationLayer (mapConfig, layerIndex) { const layer = mapConfig.getLayer(layerIndex); From d86b01ba330be36c1a91e57319bb5096863ad516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Mar 2019 18:09:41 +0100 Subject: [PATCH 76/84] Add debug statement --- lib/cartodb/backends/cluster.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index ba998c5d..87b3b27d 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -16,6 +16,8 @@ module.exports = class ClusterBackend { try { pg = new PSQL(dbParamsFromReqParams(params)); } catch (error) { + debug(error); + return callback(error); } From 367ca399c8a507695ccdc065ed94bb60b416e13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 11 Mar 2019 18:53:47 +0100 Subject: [PATCH 77/84] Improve readability --- lib/cartodb/backends/cluster.js | 39 +++++++++++++++++---------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/lib/cartodb/backends/cluster.js b/lib/cartodb/backends/cluster.js index 87b3b27d..ab663997 100644 --- a/lib/cartodb/backends/cluster.js +++ b/lib/cartodb/backends/cluster.js @@ -24,6 +24,7 @@ module.exports = class ClusterBackend { const { user, layer: layerIndex } = params; const mapConfig = new AggregationMapConfig(user, _mapConfig.obj(), pg); const layer = mapConfig.getLayer(layerIndex); + let { aggregation } = params; try { @@ -44,35 +45,35 @@ module.exports = class ClusterBackend { return callback(error); } - const query = layer.options.sql_raw; - const resolution = layer.options.aggregation.resolution || 1; + params.aggregation = aggregation; - getColumnsName(pg, query, (err, columns) => { - if (err) { - return callback(err); - } - - const { zoom, clusterId } = params; - - getClusterFeatures(pg, zoom, clusterId, columns, query, resolution, aggregation, (err, features) => { - if (err) { - return callback(err); - } - - return callback(null, features); - }); - }); + getFeatures(pg, layer, params, callback); }); } }; +function getFeatures (pg, layer, params, callback) { + const query = layer.options.sql_raw; + const resolution = layer.options.aggregation.resolution || 1; + + getColumnsName(pg, query, (err, columns) => { + if (err) { + return callback(err); + } + + const { zoom, clusterId, aggregation } = params; + + getClusterFeatures(pg, zoom, clusterId, columns, query, resolution, aggregation, callback); + }); +} + const SKIP_COLUMNS = { 'the_geom': true, 'the_geom_webmercator': true }; function getColumnsName (pg, query, callback) { - const sql = limitedQuery({ + const sql = schemaQuery({ query: query }); @@ -126,7 +127,7 @@ function getClusterFeatures (pg, zoom, clusterId, columns, query, resolution, ag } , true); // use read-only transaction } -const limitedQuery = ctx => `SELECT * FROM (${ctx.query}) __cdb_schema LIMIT 0`; +const schemaQuery = ctx => `SELECT * FROM (${ctx.query}) __cdb_schema LIMIT 0`; const clusterFeaturesQuery = ctx => ` WITH _cdb_params AS ( From 8324d4c4c200aacbff7eb47ca9447a60f836cf27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 12 Mar 2019 10:35:16 +0100 Subject: [PATCH 78/84] Place points out of boundaries to avoid conficlts with PG11 --- test/acceptance/cluster.js | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/test/acceptance/cluster.js b/test/acceptance/cluster.js index 632b6e8a..9a1e9a3a 100644 --- a/test/acceptance/cluster.js +++ b/test/acceptance/cluster.js @@ -9,7 +9,7 @@ const POINTS_SQL_1 = ` select x + 4 as cartodb_id, st_setsrid(st_makepoint(x*10, x*10), 4326) as the_geom, - st_transform(st_setsrid(st_makepoint(x*10, x*10), 4326), 3857) as the_geom_webmercator, + st_transform(st_setsrid(st_makepoint(x*10 + 10, x*10 + 10), 4326), 3857) as the_geom_webmercator, x as value, CASE WHEN x % 2 = 0 THEN 'even' @@ -62,14 +62,14 @@ describe('cluster', function () { } assert.deepStrictEqual(body, { - errors:[ 'Map c502fc8fc1cb0d5e412db3deabffeee5 has no aggregation defined for layer 0' ], + errors:[ 'Map 22771437ab05d49179f535c790a347c3 has no aggregation defined for layer 0' ], errors_with_context:[ { layer: { index: '0', type: 'cartodb' }, - message: 'Map c502fc8fc1cb0d5e412db3deabffeee5 has no aggregation defined for layer 0', + message: 'Map 22771437ab05d49179f535c790a347c3 has no aggregation defined for layer 0', subtype: 'aggregation', type: 'layer' } @@ -104,14 +104,14 @@ describe('cluster', function () { } assert.deepStrictEqual(body, { - errors:[ 'Map 18792467ae296929d04e32dfe7f81a80 has no aggregation defined for layer 0' ], + errors:[ 'Map c7fbde4bbf7cc73692b4f3767d5c6604 has no aggregation defined for layer 0' ], errors_with_context:[ { layer: { index: '0', type: 'cartodb' }, - message: 'Map 18792467ae296929d04e32dfe7f81a80 has no aggregation defined for layer 0', + message: 'Map c7fbde4bbf7cc73692b4f3767d5c6604 has no aggregation defined for layer 0', subtype: 'aggregation', type: 'layer' } @@ -216,8 +216,7 @@ describe('cluster', function () { expected: [ { cartodb_id: 1, value: -3, type: 'odd' }, { cartodb_id: 2, value: -2, type: 'even' }, - { cartodb_id: 3, value: -1, type: 'odd' }, - { cartodb_id: 4, value: 0, type: 'even' }, + { cartodb_id: 3, value: -1, type: 'odd' } ] }, { @@ -225,6 +224,7 @@ describe('cluster', function () { cartodb_id: 5, resolution: 50, expected: [ + { cartodb_id: 4, value: 0, type: 'even' }, { cartodb_id: 5, value: 1, type: 'odd' }, { cartodb_id: 6, value: 2, type: 'even' }, { cartodb_id: 7, value: 3, type: 'odd' } @@ -279,8 +279,7 @@ describe('cluster', function () { expected: [ { cartodb_id: 1, value: -3, type: 'odd' }, { cartodb_id: 2, value: -2, type: 'even'}, - { cartodb_id: 3, value: -1, type: 'odd' }, - { cartodb_id: 4, value: 0, type: 'even' }, + { cartodb_id: 3, value: -1, type: 'odd' } ] }, { @@ -288,9 +287,9 @@ describe('cluster', function () { cartodb_id: 5, resolution: 50, expected: [ + { cartodb_id: 4, value: 0, type: 'even' }, { cartodb_id: 5, value: 1, type: 'odd' }, - { cartodb_id: 6, value: 2, type: 'even' }, - { cartodb_id: 7, value: 3, type: 'odd' } + { cartodb_id: 6, value: 2, type: 'even' } ] } ]; @@ -381,7 +380,7 @@ describe('cluster', function () { resolution: 50, aggregation: { columns: ['type'] }, expected: [ - { _cdb_feature_count: 2, type: 'even' }, + { _cdb_feature_count: 1, type: 'even' }, { _cdb_feature_count: 2, type: 'odd' } ] }, @@ -391,7 +390,7 @@ describe('cluster', function () { resolution: 50, aggregation: { columns: ['type'] }, expected: [ - { _cdb_feature_count: 1, type: 'even' }, + { _cdb_feature_count: 2, type: 'even' }, { _cdb_feature_count: 2, type: 'odd' } ] }, @@ -514,7 +513,7 @@ describe('cluster', function () { } }, expected: [ - { _cdb_feature_count: 2, type: 'even', max_value: 0 }, + { _cdb_feature_count: 1, type: 'even', max_value: -2 }, { _cdb_feature_count: 2, type: 'odd', max_value: -1 } ] }, @@ -532,14 +531,14 @@ describe('cluster', function () { } }, expected: [ - { _cdb_feature_count: 1, type: 'even', max_value: 2 }, + { _cdb_feature_count: 2, type: 'even', max_value: 2 }, { _cdb_feature_count: 2, type: 'odd', max_value: 3 } ] } ]; suite.forEach(({ zoom, cartodb_id, resolution, aggregation, expected }) => { - it('should return features aggregated by type', function (done) { + it(`should aggregate by type; z: ${zoom}, cartodb_id: ${cartodb_id}, res: ${resolution}`, function (done) { const mapConfig = createVectorMapConfig([{ type: 'cartodb', options: { From 6b2ad8826b8628473ca9f9daad27800ad9e2214a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Tue, 12 Mar 2019 15:18:31 +0100 Subject: [PATCH 79/84] Move points to avoid x/y axes --- test/acceptance/cluster.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/test/acceptance/cluster.js b/test/acceptance/cluster.js index 9a1e9a3a..d668745a 100644 --- a/test/acceptance/cluster.js +++ b/test/acceptance/cluster.js @@ -8,8 +8,8 @@ const TestClient = require('../support/test-client'); const POINTS_SQL_1 = ` select x + 4 as cartodb_id, - st_setsrid(st_makepoint(x*10, x*10), 4326) as the_geom, - st_transform(st_setsrid(st_makepoint(x*10 + 10, x*10 + 10), 4326), 3857) as the_geom_webmercator, + st_setsrid(st_makepoint(x*10 + 18, x*10 + 5), 4326) as the_geom, + st_transform(st_setsrid(st_makepoint(x*10 + 18, x*10 + 5), 4326), 3857) as the_geom_webmercator, x as value, CASE WHEN x % 2 = 0 THEN 'even' @@ -62,14 +62,14 @@ describe('cluster', function () { } assert.deepStrictEqual(body, { - errors:[ 'Map 22771437ab05d49179f535c790a347c3 has no aggregation defined for layer 0' ], + errors:[ 'Map f697fb370c6479559ae2f66d684e8227 has no aggregation defined for layer 0' ], errors_with_context:[ { layer: { index: '0', type: 'cartodb' }, - message: 'Map 22771437ab05d49179f535c790a347c3 has no aggregation defined for layer 0', + message: 'Map f697fb370c6479559ae2f66d684e8227 has no aggregation defined for layer 0', subtype: 'aggregation', type: 'layer' } @@ -104,14 +104,14 @@ describe('cluster', function () { } assert.deepStrictEqual(body, { - errors:[ 'Map c7fbde4bbf7cc73692b4f3767d5c6604 has no aggregation defined for layer 0' ], + errors:[ 'Map 7521bcd1029c401289dd651ce91d5d9d has no aggregation defined for layer 0' ], errors_with_context:[ { layer: { index: '0', type: 'cartodb' }, - message: 'Map c7fbde4bbf7cc73692b4f3767d5c6604 has no aggregation defined for layer 0', + message: 'Map 7521bcd1029c401289dd651ce91d5d9d has no aggregation defined for layer 0', subtype: 'aggregation', type: 'layer' } @@ -215,8 +215,7 @@ describe('cluster', function () { resolution: 50, expected: [ { cartodb_id: 1, value: -3, type: 'odd' }, - { cartodb_id: 2, value: -2, type: 'even' }, - { cartodb_id: 3, value: -1, type: 'odd' } + { cartodb_id: 2, value: -2, type: 'even' } ] }, { @@ -278,8 +277,7 @@ describe('cluster', function () { resolution: 50, expected: [ { cartodb_id: 1, value: -3, type: 'odd' }, - { cartodb_id: 2, value: -2, type: 'even'}, - { cartodb_id: 3, value: -1, type: 'odd' } + { cartodb_id: 2, value: -2, type: 'even'} ] }, { @@ -288,8 +286,7 @@ describe('cluster', function () { resolution: 50, expected: [ { cartodb_id: 4, value: 0, type: 'even' }, - { cartodb_id: 5, value: 1, type: 'odd' }, - { cartodb_id: 6, value: 2, type: 'even' } + { cartodb_id: 5, value: 1, type: 'odd' } ] } ]; @@ -381,7 +378,7 @@ describe('cluster', function () { aggregation: { columns: ['type'] }, expected: [ { _cdb_feature_count: 1, type: 'even' }, - { _cdb_feature_count: 2, type: 'odd' } + { _cdb_feature_count: 1, type: 'odd' } ] }, { @@ -514,7 +511,7 @@ describe('cluster', function () { }, expected: [ { _cdb_feature_count: 1, type: 'even', max_value: -2 }, - { _cdb_feature_count: 2, type: 'odd', max_value: -1 } + { _cdb_feature_count: 1, type: 'odd', max_value: -3 } ] }, { From eaa38b76767e89cda7a441bb89bb63a6a649f332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Wed, 13 Mar 2019 18:52:17 +0100 Subject: [PATCH 80/84] Upgrade windshaft to version 4.13.3 --- NEWS.md | 3 ++- package-lock.json | 20 ++++++++++---------- package.json | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/NEWS.md b/NEWS.md index 605a9751..aab947e4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,7 +5,8 @@ Released 2019-mm-dd Announcements: - Experimental support for listing features in a grid when the map uses the dynamic agregation. - +- Update deps: + - windshaft@4.13.3: Upgrade grainstore to version 1.11.0, do not hang when child process is not able to generate a Mapnik XML ## 7.0.0 Released 2019-02-22 diff --git a/package-lock.json b/package-lock.json index 94641675..e8e1115c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1988,9 +1988,9 @@ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "grainstore": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/grainstore/-/grainstore-1.10.0.tgz", - "integrity": "sha512-mHBznPEqsM7g11ycVOrWBO7/1VAE4i/G8rFHTw7O5Ru79cbJdl0hJrnuG2ILBY207VNjSPxBC80uN1Pm1tx0Bg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/grainstore/-/grainstore-1.11.0.tgz", + "integrity": "sha512-JuEnCHX+qseEgD+Ii5V8SI7VlbSMSE/Jzq0UWC+sdZFi8mFcz15Nj8EKRkF28Rxj2NoWg2tFYMiUb9h8LQEOdg==", "requires": { "carto": "0.16.3", "debug": "~3.1.0", @@ -2901,9 +2901,9 @@ "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" }, "npm-packlist": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.3.0.tgz", - "integrity": "sha512-qPBc6CnxEzpOcc4bjoIBJbYdy0D/LFFPUdxvfwor4/w3vxeE0h6TiOVurCEPpQ6trjN77u/ShyfeJGsbAfB3dA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1" @@ -4156,9 +4156,9 @@ "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=" }, "windshaft": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/windshaft/-/windshaft-4.13.1.tgz", - "integrity": "sha512-sjk8D6gOZTp2jb7iuF38FyhPIcAAOUsOQtS+SmTLLIk1AipEK+xQU11IVl9cOvhiaCYjs7dRLscM54qoMqKVxw==", + "version": "4.13.3", + "resolved": "https://registry.npmjs.org/windshaft/-/windshaft-4.13.3.tgz", + "integrity": "sha512-A5a9y1jA4ehtWgSPRUG1gBL/KF1mYytIk1r6OA85d1MsdgEGrcLaBgH1VISIoIGzKsEeriPanhRhagfn2/TL2g==", "requires": { "@carto/mapnik": "3.6.2-carto.11", "@carto/tilelive-bridge": "github:cartodb/tilelive-bridge#e35ae36a6e2d555a6b312440f7e1904c5ad03664", @@ -4168,7 +4168,7 @@ "cartodb-psql": "0.13.1", "debug": "3.1.0", "dot": "1.1.2", - "grainstore": "1.10.0", + "grainstore": "^1.11.0", "queue-async": "1.1.0", "redis-mpool": "0.7.0", "request": "2.87.0", diff --git a/package.json b/package.json index b62b2e6e..d1b62868 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "step-profiler": "0.3.0", "turbo-carto": "0.21.0", "underscore": "1.6.0", - "windshaft": "4.13.1", + "windshaft": "^4.13.3", "yargs": "11.1.0" }, "devDependencies": { From 6241b23d4f5ef9c99aa774b6ccb60a513eafb6d7 Mon Sep 17 00:00:00 2001 From: Raul Marin Date: Mon, 4 Mar 2019 14:09:30 +0100 Subject: [PATCH 81/84] Histogram: Speed up IRQ calculation --- .../dataview/histograms/numeric-histogram.js | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/lib/cartodb/models/dataview/histograms/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js index ddf3577f..51980b84 100644 --- a/lib/cartodb/models/dataview/histograms/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -15,24 +15,15 @@ const irqQueryTpl = ctx => ` SELECT max(${ctx.column}) AS __cdb_max_val, min(${ctx.column}) AS __cdb_min_val, - count(1) AS __cdb_total_rows + count(1) AS __cdb_total_rows, + ${ctx.irq ? ctx.irq : `0`} AS __cdb_iqr FROM __cdb_filtered_source ) `; -/* Query to calculate the number of bins (needs irqQueryTpl before it*/ +/* Query to calculate the number of bins (needs irqQueryTpl before it. + * It uses the Freedman–Diaconis rule to calculate the witdh of the bins */ const binsQueryTpl = ctx => ` - __cdb_iqrange AS ( - SELECT max(quartile_max) - min(quartile_max) AS __cdb_iqr - FROM ( - SELECT quartile, max(_cdb_iqr_column) AS quartile_max from ( - SELECT ${ctx.column} AS _cdb_iqr_column, ntile(4) over (order by ${ctx.column} - ) AS quartile - FROM __cdb_filtered_source) _cdb_quartiles - WHERE quartile = 1 or quartile = 3 - GROUP BY 1 - ) __cdb_iqr - ), __cdb_bins AS ( SELECT CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0 @@ -45,7 +36,7 @@ const binsQueryTpl = ctx => ` ) ) END AS __cdb_bins_number - FROM __cdb_basics, __cdb_iqrange, __cdb_filtered_source + FROM __cdb_basics, __cdb_filtered_source LIMIT 1 ) `; @@ -118,6 +109,7 @@ module.exports = class NumericHistogram extends BaseHistogram { if (ctx.bins <= 0) { ctx.bins = `__cdb_bins.__cdb_bins_number`; + ctx.irq = `percentile_disc(0.75) within group (order by ${ctx.column}) - percentile_disc(0.25) within group (order by ${ctx.column})`; extra_groupby += `, __cdb_bins.__cdb_bins_number`; extra_tables += `, __cdb_bins`; extra_queries = `WITH ${irqQueryTpl(ctx)}, ${binsQueryTpl(ctx)}`; From 730076469e91f090127ce4d18869e15ef0f56a13 Mon Sep 17 00:00:00 2001 From: Raul Marin Date: Mon, 4 Mar 2019 15:52:27 +0100 Subject: [PATCH 82/84] Numeric histogram: Simplify bin calculation --- .../dataview/histograms/numeric-histogram.js | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/lib/cartodb/models/dataview/histograms/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js index 51980b84..26623b01 100644 --- a/lib/cartodb/models/dataview/histograms/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -4,41 +4,37 @@ const BaseHistogram = require('./base-histogram'); const debug = require('debug')('windshaft:dataview:numeric-histogram'); const utils = require('../../../utils/query-utils'); -/** Query to get min and max values from the query */ +/** Query to get min, max, count and (if necessary) bin number of the query */ const irqQueryTpl = ctx => ` - __cdb_filtered_source AS ( - SELECT * - FROM (${ctx.query}) __cdb_filtered_source_query - WHERE ${utils.handleFloatColumn(ctx)} IS NOT NULL - ), - __cdb_basics AS ( + __cdb_basics AS ( + SELECT + *, + CASE + WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0 THEN 1 + ELSE GREATEST( + LEAST( + ${ctx.minBins}, + __cdb_total_rows::int), + LEAST( + ${ctx.maxBins}, + ((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3)))::int) + ) + END AS __cdb_bins_number + FROM + ( SELECT max(${ctx.column}) AS __cdb_max_val, min(${ctx.column}) AS __cdb_min_val, count(1) AS __cdb_total_rows, ${ctx.irq ? ctx.irq : `0`} AS __cdb_iqr - FROM __cdb_filtered_source - ) -`; - -/* Query to calculate the number of bins (needs irqQueryTpl before it. - * It uses the Freedman–Diaconis rule to calculate the witdh of the bins */ -const binsQueryTpl = ctx => ` - __cdb_bins AS ( - SELECT - CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0 - THEN 1 - ELSE GREATEST( - LEAST(${ctx.minBins}, CAST(__cdb_total_rows AS INT)), - LEAST( - CAST(((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3))) AS INT), - ${ctx.maxBins} - ) - ) - END AS __cdb_bins_number - FROM __cdb_basics, __cdb_filtered_source - LIMIT 1 - ) + FROM + ( + SELECT * + FROM (${ctx.query}) __cdb_filtered_source_query + WHERE ${utils.handleFloatColumn(ctx)} IS NOT NULL + ) __cdb_filtered_source + ) __cdb_basics_2 +) `; const BIN_MIN_NUMBER = 6; @@ -108,11 +104,11 @@ module.exports = class NumericHistogram extends BaseHistogram { } if (ctx.bins <= 0) { - ctx.bins = `__cdb_bins.__cdb_bins_number`; - ctx.irq = `percentile_disc(0.75) within group (order by ${ctx.column}) - percentile_disc(0.25) within group (order by ${ctx.column})`; - extra_groupby += `, __cdb_bins.__cdb_bins_number`; - extra_tables += `, __cdb_bins`; - extra_queries = `WITH ${irqQueryTpl(ctx)}, ${binsQueryTpl(ctx)}`; + ctx.bins = `__cdb_basics.__cdb_bins_number`; + ctx.irq = `percentile_disc(0.75) within group (order by ${ctx.column}) + - percentile_disc(0.25) within group (order by ${ctx.column})`; + extra_groupby += `, __cdb_basics.__cdb_bins_number`; + extra_queries = `WITH ${irqQueryTpl(ctx)}`; } return ` From 8db090ae9c35f9b3064c55846ca2b2440bbe0f89 Mon Sep 17 00:00:00 2001 From: Raul Marin Date: Mon, 4 Mar 2019 16:53:55 +0100 Subject: [PATCH 83/84] Numeric histogram: Test when start and end are provided but not bins --- .../dataview/histograms/numeric-histogram.js | 1 + test/acceptance/dataviews/histogram.js | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/cartodb/models/dataview/histograms/numeric-histogram.js b/lib/cartodb/models/dataview/histograms/numeric-histogram.js index 26623b01..492316ba 100644 --- a/lib/cartodb/models/dataview/histograms/numeric-histogram.js +++ b/lib/cartodb/models/dataview/histograms/numeric-histogram.js @@ -108,6 +108,7 @@ module.exports = class NumericHistogram extends BaseHistogram { ctx.irq = `percentile_disc(0.75) within group (order by ${ctx.column}) - percentile_disc(0.25) within group (order by ${ctx.column})`; extra_groupby += `, __cdb_basics.__cdb_bins_number`; + extra_tables = `, __cdb_basics`; extra_queries = `WITH ${irqQueryTpl(ctx)}`; } diff --git a/test/acceptance/dataviews/histogram.js b/test/acceptance/dataviews/histogram.js index aa89d53e..f517a59a 100644 --- a/test/acceptance/dataviews/histogram.js +++ b/test/acceptance/dataviews/histogram.js @@ -90,6 +90,26 @@ describe('histogram-dataview', function() { }); }); + it('should work with min >= start and max <= end, autodetect bins', function(done) { + var params = { + start: 50, + end: 500 + }; + + this.testClient = new TestClient(mapConfig, 1234); + this.testClient.getDataview('pop_max_histogram', params, function(err, dataview) { + assert.ok(!err, err); + + assert.ok(6 === dataview.bins_count, 'Unexpected bin count: ' + dataview.bins_count); + assert.ok(6 === dataview.bins.length, 'Unexpected number of bins: ' + dataview.bins.length); + dataview.bins.forEach(function(bin) { + assert.ok(bin.min >= params.start, 'bin min < start: ' + JSON.stringify(bin)); + assert.ok(bin.max <= params.end, 'bin max > end: ' + JSON.stringify(bin)); + }); + done(); + }); + }); + it('should get bin_width right when max > min in filter', function(done) { var params = { bins: 10, From 59107496b4faa2f54da96f6f9b423246c2379f01 Mon Sep 17 00:00:00 2001 From: Raul Marin Date: Thu, 14 Mar 2019 15:17:42 +0100 Subject: [PATCH 84/84] Update NEWS --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index aab947e4..b3a26e19 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,7 @@ Released 2019-mm-dd Announcements: - Experimental support for listing features in a grid when the map uses the dynamic agregation. +- Numeric histogram performance improvement (#1080) - Update deps: - windshaft@4.13.3: Upgrade grainstore to version 1.11.0, do not hang when child process is not able to generate a Mapnik XML