@deck.gl/carto 8.6.3 → 8.7.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/dist.dev.js +51866 -6683
  2. package/dist/es5/api/index.js +14 -8
  3. package/dist/es5/api/index.js.map +1 -1
  4. package/dist/es5/api/layer-map.js +256 -0
  5. package/dist/es5/api/layer-map.js.map +1 -0
  6. package/dist/es5/api/maps-api-common.js +13 -9
  7. package/dist/es5/api/maps-api-common.js.map +1 -1
  8. package/dist/es5/api/maps-client.js +157 -98
  9. package/dist/es5/api/maps-client.js.map +1 -1
  10. package/dist/es5/api/maps-v3-client.js +549 -190
  11. package/dist/es5/api/maps-v3-client.js.map +1 -1
  12. package/dist/es5/api/parseMap.js +207 -0
  13. package/dist/es5/api/parseMap.js.map +1 -0
  14. package/dist/es5/basemap.js +1 -1
  15. package/dist/es5/basemap.js.map +1 -1
  16. package/dist/es5/config.js +30 -37
  17. package/dist/es5/config.js.map +1 -1
  18. package/dist/es5/index.js +20 -30
  19. package/dist/es5/index.js.map +1 -1
  20. package/dist/es5/layers/carto-dynamic-tile-layer.js +168 -0
  21. package/dist/es5/layers/carto-dynamic-tile-layer.js.map +1 -0
  22. package/dist/es5/layers/carto-layer.js +217 -131
  23. package/dist/es5/layers/carto-layer.js.map +1 -1
  24. package/dist/es5/style/color-bins-style.js +16 -11
  25. package/dist/es5/style/color-bins-style.js.map +1 -1
  26. package/dist/es5/style/color-categories-style.js +43 -14
  27. package/dist/es5/style/color-categories-style.js.map +1 -1
  28. package/dist/es5/style/color-continuous-style.js +16 -11
  29. package/dist/es5/style/color-continuous-style.js.map +1 -1
  30. package/dist/es5/style/palette.js +24 -12
  31. package/dist/es5/style/palette.js.map +1 -1
  32. package/dist/es5/style/utils.js +13 -6
  33. package/dist/es5/style/utils.js.map +1 -1
  34. package/dist/esm/api/index.js +1 -1
  35. package/dist/esm/api/index.js.map +1 -1
  36. package/dist/esm/api/layer-map.js +220 -0
  37. package/dist/esm/api/layer-map.js.map +1 -0
  38. package/dist/esm/api/maps-client.js +1 -1
  39. package/dist/esm/api/maps-client.js.map +1 -1
  40. package/dist/esm/api/maps-v3-client.js +166 -25
  41. package/dist/esm/api/maps-v3-client.js.map +1 -1
  42. package/dist/esm/api/parseMap.js +195 -0
  43. package/dist/esm/api/parseMap.js.map +1 -0
  44. package/dist/esm/config.js +11 -26
  45. package/dist/esm/config.js.map +1 -1
  46. package/dist/esm/index.js +1 -3
  47. package/dist/esm/index.js.map +1 -1
  48. package/dist/esm/layers/carto-dynamic-tile-layer.js +98 -0
  49. package/dist/esm/layers/carto-dynamic-tile-layer.js.map +1 -0
  50. package/dist/esm/layers/carto-layer.js +35 -28
  51. package/dist/esm/layers/carto-layer.js.map +1 -1
  52. package/dist/esm/style/color-bins-style.js +1 -1
  53. package/dist/esm/style/color-bins-style.js.map +1 -1
  54. package/dist/esm/style/color-continuous-style.js +1 -1
  55. package/dist/esm/style/color-continuous-style.js.map +1 -1
  56. package/dist.min.js +278 -1
  57. package/package.json +13 -8
  58. package/src/api/index.js +1 -1
  59. package/src/api/layer-map.js +195 -0
  60. package/src/api/maps-client.js +3 -7
  61. package/src/api/maps-v3-client.js +140 -20
  62. package/src/api/parseMap.js +183 -0
  63. package/src/config.js +8 -39
  64. package/src/index.js +1 -2
  65. package/src/layers/carto-dynamic-tile-layer.js +87 -0
  66. package/src/layers/carto-layer.js +25 -25
  67. package/src/style/color-bins-style.js +2 -4
  68. package/src/style/color-continuous-style.js +2 -4
  69. package/dist/es5/layers/carto-bqtiler-layer.js +0 -32
  70. package/dist/es5/layers/carto-bqtiler-layer.js.map +0 -1
  71. package/dist/es5/layers/carto-sql-layer.js +0 -33
  72. package/dist/es5/layers/carto-sql-layer.js.map +0 -1
  73. package/dist/esm/layers/carto-bqtiler-layer.js +0 -16
  74. package/dist/esm/layers/carto-bqtiler-layer.js.map +0 -1
  75. package/dist/esm/layers/carto-sql-layer.js +0 -17
  76. package/dist/esm/layers/carto-sql-layer.js.map +0 -1
  77. package/src/layers/carto-bqtiler-layer.js +0 -21
  78. package/src/layers/carto-sql-layer.js +0 -22
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@deck.gl/carto",
3
3
  "description": "CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.",
4
4
  "license": "MIT",
5
- "version": "8.6.3",
5
+ "version": "8.7.0-alpha.10",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
@@ -31,18 +31,23 @@
31
31
  "prepublishOnly": "npm run build-bundle && npm run build-bundle -- --env.dev"
32
32
  },
33
33
  "dependencies": {
34
- "@loaders.gl/loader-utils": "^3.0.8",
35
- "@loaders.gl/mvt": "^3.0.8",
36
- "@loaders.gl/tiles": "^3.0.8",
37
- "@math.gl/web-mercator": "^3.5.4",
34
+ "@loaders.gl/gis": "^3.1.3",
35
+ "@loaders.gl/loader-utils": "^3.1.3",
36
+ "@loaders.gl/mvt": "^3.1.3",
37
+ "@loaders.gl/tiles": "^3.1.3",
38
+ "@math.gl/web-mercator": "^3.5.6",
38
39
  "cartocolor": "^4.0.2",
39
- "d3-scale": "^3.2.3"
40
+ "d3-array": "^2.8.0",
41
+ "d3-color": "^2.0.0",
42
+ "d3-format": "^2.0.0",
43
+ "d3-scale": "^3.2.3",
44
+ "moment-timezone": "^0.5.33"
40
45
  },
41
46
  "peerDependencies": {
42
47
  "@deck.gl/core": "^8.0.0",
43
48
  "@deck.gl/geo-layers": "^8.0.0",
44
49
  "@deck.gl/layers": "^8.0.0",
45
- "@loaders.gl/core": "^3.0.0"
50
+ "@loaders.gl/core": "^3.1.3"
46
51
  },
47
- "gitHead": "938ee03697eecbaced24c9df637d53ab42375df1"
52
+ "gitHead": "b6c48fb68b66755743c28776a0642b56a2df086a"
48
53
  }
package/src/api/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export {FORMATS, MAP_TYPES, API_VERSIONS} from './maps-api-common';
2
2
  export {getDataV2, CONNECTIONS} from './maps-client';
3
- export {fetchLayerData, getData, mapInstantiation} from './maps-v3-client';
3
+ export {fetchLayerData, fetchMap, getData, mapInstantiation} from './maps-v3-client';
@@ -0,0 +1,195 @@
1
+ import {extent} from 'd3-array';
2
+ import {rgb} from 'd3-color';
3
+ import {
4
+ scaleLinear,
5
+ scaleOrdinal,
6
+ scaleLog,
7
+ scalePoint,
8
+ scaleQuantile,
9
+ scaleQuantize,
10
+ scaleSqrt
11
+ } from 'd3-scale';
12
+ import {format as d3Format} from 'd3-format';
13
+ import moment from 'moment-timezone';
14
+ import {H3HexagonLayer, MVTLayer} from '@deck.gl/geo-layers';
15
+ import {GeoJsonLayer} from '@deck.gl/layers';
16
+
17
+ const SCALE_FUNCS = {
18
+ linear: scaleLinear,
19
+ ordinal: scaleOrdinal,
20
+ log: scaleLog,
21
+ point: scalePoint,
22
+ quantile: scaleQuantile,
23
+ quantize: scaleQuantize,
24
+ sqrt: scaleSqrt
25
+ };
26
+
27
+ // Kepler -> Deck.gl
28
+ const sharedPropMap = {
29
+ color: 'getFillColor',
30
+ isVisible: 'visible',
31
+ label: 'cartoLabel',
32
+ textLabel: {
33
+ alignment: 'getTextAlignmentBaseline',
34
+ anchor: 'getTextAnchor',
35
+ color: 'getTextColor',
36
+ size: 'getTextSize'
37
+ },
38
+ visConfig: {
39
+ enable3d: 'extruded',
40
+ filled: 'filled',
41
+ opacity: 'opacity',
42
+ strokeColor: 'getLineColor',
43
+ stroked: 'stroked',
44
+ thickness: 'getLineWidth',
45
+ radius: 'getPointRadius',
46
+ wireframe: 'wireframe'
47
+ }
48
+ };
49
+
50
+ const RADIUS_DOWNSCALE = 0.2;
51
+
52
+ const defaultProps = {
53
+ lineMiterLimit: 2,
54
+ lineWidthUnits: 'pixels',
55
+ pointRadiusScale: RADIUS_DOWNSCALE,
56
+ pointRadiusUnits: 'pixels',
57
+ rounded: true,
58
+ wrapLongitude: false
59
+ };
60
+
61
+ function mergePropMaps(a, b) {
62
+ return {...a, ...b, visConfig: {...a.visConfig, ...b.visConfig}};
63
+ }
64
+
65
+ export const LAYER_MAP = {
66
+ point: {
67
+ Layer: GeoJsonLayer,
68
+ propMap: mergePropMaps(sharedPropMap, {
69
+ visConfig: {outline: 'stroked'}
70
+ }),
71
+ defaultProps
72
+ },
73
+ geojson: {
74
+ Layer: GeoJsonLayer,
75
+ propMap: sharedPropMap,
76
+ defaultProps: {...defaultProps, lineWidthScale: 2}
77
+ },
78
+ hexagonId: {
79
+ Layer: H3HexagonLayer,
80
+ propMap: mergePropMaps(sharedPropMap, {
81
+ visConfig: {coverage: 'coverage', elevationScale: 'elevationScale'}
82
+ }),
83
+ defaultProps: {...defaultProps, getHexagon: d => d.h3}
84
+ },
85
+ mvt: {
86
+ Layer: MVTLayer,
87
+ propMap: sharedPropMap,
88
+ defaultProps: {...defaultProps, pointRadiusScale: 0.3, lineWidthScale: 2}
89
+ }
90
+ };
91
+
92
+ function domainFromAttribute(attribute, scaleType) {
93
+ if (scaleType === 'ordinal' || scaleType === 'point') {
94
+ return attribute.categories.map(c => c.category).filter(c => c !== undefined && c !== null);
95
+ }
96
+
97
+ let {min} = attribute;
98
+ if (scaleType === 'log' && min === 0) {
99
+ min = 1e-5;
100
+ }
101
+ return [min, attribute.max];
102
+ }
103
+
104
+ function domainFromValues(values, scaleType) {
105
+ if (scaleType === 'ordinal') {
106
+ return [...new Set(values)].sort();
107
+ }
108
+ return extent(values);
109
+ }
110
+
111
+ function calculateDomain(data, name, scaleType) {
112
+ if (data.tilestats) {
113
+ // Tileset data type
114
+ const {attributes} = data.tilestats.layers[0];
115
+ const attribute = attributes.find(a => a.attribute === name);
116
+ return domainFromAttribute(attribute, scaleType);
117
+ } else if (data.features) {
118
+ // GeoJSON data type
119
+ const values = data.features.map(({properties}) => properties[name]);
120
+ return domainFromValues(values, scaleType);
121
+ } else if (Array.isArray(data) && data[0][name] !== undefined) {
122
+ // JSON data type
123
+ const values = data.map(properties => properties[name]);
124
+ return domainFromValues(values, scaleType);
125
+ }
126
+
127
+ return [0, 1];
128
+ }
129
+
130
+ function normalizeAccessor(accessor, data) {
131
+ if (data.features || data.tilestats) {
132
+ return ({properties}) => {
133
+ return accessor(properties);
134
+ };
135
+ }
136
+ return accessor;
137
+ }
138
+
139
+ export function getColorAccessor({name}, scaleType, {colors}, opacity, data) {
140
+ const scale = SCALE_FUNCS[scaleType]();
141
+ scale.domain(calculateDomain(data, name, scaleType));
142
+ scale.range(colors);
143
+ const alpha = opacity !== undefined ? Math.round(255 * Math.pow(opacity, 1 / 2.2)) : 255;
144
+
145
+ const accessor = properties => {
146
+ const rgba = rgb(scale(properties[name]));
147
+ return [rgba.r, rgba.g, rgba.b, alpha];
148
+ };
149
+ return normalizeAccessor(accessor, data);
150
+ }
151
+
152
+ export function getSizeAccessor({name}, scaleType, range, data) {
153
+ const scale = SCALE_FUNCS[scaleType]();
154
+ scale.domain(calculateDomain(data, name, scaleType));
155
+ scale.range(range);
156
+
157
+ const accessor = properties => {
158
+ return scale(properties[name]);
159
+ };
160
+ return normalizeAccessor(accessor, data);
161
+ }
162
+
163
+ const FORMATS = {
164
+ date: s => moment.utc(s).format('MM/DD/YY HH:mm:ssa'),
165
+ integer: d3Format('i'),
166
+ float: d3Format('.5f'),
167
+ timestamp: s => moment.utc(s).format('X'),
168
+ default: String
169
+ };
170
+
171
+ export function getTextAccessor({name, type}, data) {
172
+ const format = FORMATS[type] || FORMATS.default;
173
+ const accessor = properties => {
174
+ return format(properties[name]);
175
+ };
176
+ return normalizeAccessor(accessor, data);
177
+ }
178
+
179
+ export function getTextPixelOffsetAccessor({alignment, anchor, size}, radius) {
180
+ const padding = 20;
181
+ const signX = anchor === 'middle' ? 0 : anchor === 'start' ? 1 : -1;
182
+ const signY = alignment === 'center' ? 0 : alignment === 'bottom' ? 1 : -1;
183
+ const sizeOffset = alignment === 'center' ? 0 : size;
184
+
185
+ const calculateOffset = r => {
186
+ r = RADIUS_DOWNSCALE * r;
187
+ return [signX * (r + padding), signY * (r + padding + sizeOffset)];
188
+ };
189
+
190
+ return typeof radius === 'function'
191
+ ? d => {
192
+ return calculateOffset(radius(d));
193
+ }
194
+ : calculateOffset(radius);
195
+ }
@@ -56,7 +56,7 @@ export async function getDataV2({type, source, credentials}) {
56
56
 
57
57
  default:
58
58
  throw new Error(
59
- `Invalid maps API version. It shoud be ${API_VERSIONS.V1} or ${API_VERSIONS.V2}`
59
+ `Invalid maps API version. It should be ${API_VERSIONS.V1} or ${API_VERSIONS.V2}`
60
60
  );
61
61
  }
62
62
  }
@@ -95,15 +95,11 @@ function dealWithError({response, json, credentials}) {
95
95
  switch (response.status) {
96
96
  case 401:
97
97
  throw new Error(
98
- `Unauthorized access to Maps API: invalid combination of user ('${
99
- credentials.username
100
- }') and apiKey ('${credentials.apiKey}')`
98
+ `Unauthorized access to Maps API: invalid combination of user ('${credentials.username}') and apiKey ('${credentials.apiKey}')`
101
99
  );
102
100
  case 403:
103
101
  throw new Error(
104
- `Unauthorized access to dataset: the provided apiKey('${
105
- credentials.apiKey
106
- }') doesn't provide access to the requested data`
102
+ `Unauthorized access to dataset: the provided apiKey('${credentials.apiKey}') doesn't provide access to the requested data`
107
103
  );
108
104
 
109
105
  default:
@@ -3,9 +3,11 @@
3
3
  */
4
4
  import {getDefaultCredentials, buildMapsUrlFromBase} from '../config';
5
5
  import {API_VERSIONS, encodeParameter, FORMATS, MAP_TYPES} from './maps-api-common';
6
+ import {parseMap} from './parseMap';
6
7
  import {log} from '@deck.gl/core';
7
8
 
8
9
  const MAX_GET_LENGTH = 2048;
10
+ const DEFAULT_CLIENT = 'deck-gl-carto';
9
11
 
10
12
  /**
11
13
  * Request against Maps API
@@ -68,8 +70,8 @@ function dealWithError({response, error}) {
68
70
  /**
69
71
  * Build a URL with all required parameters
70
72
  */
71
- function getParameters({type, source, geoColumn, columns, schema}) {
72
- const parameters = [encodeParameter('client', 'deck-gl-carto')];
73
+ function getParameters({type, source, geoColumn, columns, schema, clientId}) {
74
+ const parameters = [encodeParameter('client', clientId || DEFAULT_CLIENT)];
73
75
  if (schema) {
74
76
  parameters.push(encodeParameter('schema', true));
75
77
  }
@@ -96,10 +98,11 @@ export async function mapInstantiation({
96
98
  credentials,
97
99
  geoColumn,
98
100
  columns,
99
- schema
101
+ schema,
102
+ clientId
100
103
  }) {
101
104
  const baseUrl = `${credentials.mapsUrl}/${connection}/${type}`;
102
- const url = `${baseUrl}?${getParameters({type, source, geoColumn, columns, schema})}`;
105
+ const url = `${baseUrl}?${getParameters({type, source, geoColumn, columns, schema, clientId})}`;
103
106
  const {accessToken} = credentials;
104
107
 
105
108
  const format = 'json';
@@ -108,7 +111,7 @@ export async function mapInstantiation({
108
111
  // need to be a POST request
109
112
  const body = JSON.stringify({
110
113
  q: source,
111
- client: 'deck-gl-carto'
114
+ client: clientId || DEFAULT_CLIENT
112
115
  });
113
116
  return await request({method: 'POST', url: baseUrl, format, accessToken, body});
114
117
  }
@@ -126,7 +129,7 @@ function getUrlFromMetadata(metadata, format) {
126
129
  return null;
127
130
  }
128
131
 
129
- function checkGetLayerDataParameters({type, source, connection, localCreds}) {
132
+ function checkFetchLayerDataParameters({type, source, connection, localCreds}) {
130
133
  log.assert(connection, 'Must define connection');
131
134
  log.assert(type, 'Must define a type');
132
135
  log.assert(source, 'Must define a source');
@@ -144,7 +147,42 @@ export async function fetchLayerData({
144
147
  geoColumn,
145
148
  columns,
146
149
  format,
147
- schema
150
+ schema,
151
+ clientId
152
+ }) {
153
+ // Internally we split data fetching into two parts to allow us to
154
+ // conditionally fetch the actual data, depending on the metadata state
155
+ const {url, accessToken, mapFormat, metadata} = await _fetchDataUrl({
156
+ type,
157
+ source,
158
+ connection,
159
+ credentials,
160
+ geoColumn,
161
+ columns,
162
+ format,
163
+ schema,
164
+ clientId
165
+ });
166
+
167
+ const data = await request({url, format: mapFormat, accessToken});
168
+ const result = {data, format: mapFormat};
169
+ if (schema) {
170
+ result.schema = metadata.schema;
171
+ }
172
+
173
+ return result;
174
+ }
175
+
176
+ async function _fetchDataUrl({
177
+ type,
178
+ source,
179
+ connection,
180
+ credentials,
181
+ geoColumn,
182
+ columns,
183
+ format,
184
+ schema,
185
+ clientId
148
186
  }) {
149
187
  const defaultCredentials = getDefaultCredentials();
150
188
  // Only pick up default credentials if they have been defined for
@@ -153,7 +191,7 @@ export async function fetchLayerData({
153
191
  ...(defaultCredentials.apiVersion === API_VERSIONS.V3 && defaultCredentials),
154
192
  ...credentials
155
193
  };
156
- checkGetLayerDataParameters({type, source, connection, localCreds});
194
+ checkFetchLayerDataParameters({type, source, connection, localCreds});
157
195
 
158
196
  if (!localCreds.mapsUrl) {
159
197
  localCreds.mapsUrl = buildMapsUrlFromBase(localCreds.apiBaseUrl);
@@ -166,7 +204,8 @@ export async function fetchLayerData({
166
204
  credentials: localCreds,
167
205
  geoColumn,
168
206
  columns,
169
- schema
207
+ schema,
208
+ clientId
170
209
  });
171
210
  let url;
172
211
  let mapFormat;
@@ -177,7 +216,7 @@ export async function fetchLayerData({
177
216
  log.assert(url, `Format ${format} not available`);
178
217
  } else {
179
218
  // guess map format
180
- const prioritizedFormats = [FORMATS.GEOJSON, FORMATS.NDJSON, FORMATS.TILEJSON];
219
+ const prioritizedFormats = [FORMATS.GEOJSON, FORMATS.JSON, FORMATS.NDJSON, FORMATS.TILEJSON];
181
220
  for (const f of prioritizedFormats) {
182
221
  url = getUrlFromMetadata(metadata, f);
183
222
  if (url) {
@@ -188,17 +227,20 @@ export async function fetchLayerData({
188
227
  }
189
228
 
190
229
  const {accessToken} = localCreds;
191
-
192
- const data = await request({url, format: mapFormat, accessToken});
193
- const result = {data, format: mapFormat};
194
- if (schema) {
195
- result.schema = metadata.schema;
196
- }
197
-
198
- return result;
230
+ return {url, accessToken, mapFormat, metadata};
199
231
  }
200
232
 
201
- export async function getData({type, source, connection, credentials, geoColumn, columns, format}) {
233
+ export async function getData({
234
+ type,
235
+ source,
236
+ connection,
237
+ credentials,
238
+ geoColumn,
239
+ columns,
240
+ format,
241
+ clientId
242
+ }) {
243
+ log.deprecated('getData', 'fetchLayerData')();
202
244
  const layerData = await fetchLayerData({
203
245
  type,
204
246
  source,
@@ -207,7 +249,85 @@ export async function getData({type, source, connection, credentials, geoColumn,
207
249
  geoColumn,
208
250
  columns,
209
251
  format,
210
- schema: false
252
+ schema: false,
253
+ clientId
211
254
  });
212
255
  return layerData.data;
213
256
  }
257
+
258
+ /* global clearInterval, setInterval, URLSearchParams */
259
+ async function _fetchMapDataset(dataset, accessToken, credentials, clientId) {
260
+ // First fetch metadata
261
+ const {connectionName: connection, source, type} = dataset;
262
+ const {url, mapFormat} = await _fetchDataUrl({
263
+ credentials: {...credentials, accessToken},
264
+ connection,
265
+ source,
266
+ type,
267
+ clientId
268
+ });
269
+
270
+ // Extract the last time the data changed
271
+ const cache = parseInt(new URLSearchParams(url).get('cache'), 10);
272
+ if (cache && dataset.cache === cache) {
273
+ return false;
274
+ }
275
+ dataset.cache = cache;
276
+
277
+ // Only fetch if the data has changed
278
+ const data = await request({url, format: mapFormat, accessToken});
279
+ dataset.data = data;
280
+ return true;
281
+ }
282
+
283
+ async function fillInMapDatasets({datasets, token}, clientId, credentials) {
284
+ const promises = datasets.map(dataset => _fetchMapDataset(dataset, token, credentials, clientId));
285
+ return await Promise.all(promises);
286
+ }
287
+
288
+ export async function fetchMap({cartoMapId, clientId, credentials, autoRefresh, onNewData}) {
289
+ const defaultCredentials = getDefaultCredentials();
290
+ const localCreds = {
291
+ ...(defaultCredentials.apiVersion === API_VERSIONS.V3 && defaultCredentials),
292
+ ...credentials
293
+ };
294
+
295
+ log.assert(cartoMapId, `Must define CARTO map id: fetchMap({cartoMapId: 'XXXX-XXXX-XXXX'})`);
296
+
297
+ log.assert(localCreds.apiVersion === API_VERSIONS.V3, 'Method only available for v3');
298
+ log.assert(localCreds.apiBaseUrl, 'Must define apiBaseUrl');
299
+ if (!localCreds.mapsUrl) {
300
+ localCreds.mapsUrl = buildMapsUrlFromBase(localCreds.apiBaseUrl);
301
+ }
302
+
303
+ if (autoRefresh || onNewData) {
304
+ log.assert(onNewData, 'Must define `onNewData` when using autoRefresh');
305
+ log.assert(typeof onNewData === 'function', '`onNewData` must be a function');
306
+ log.assert(autoRefresh > 0, '`autoRefresh` must be a positive number');
307
+ }
308
+
309
+ const url = `${localCreds.mapsUrl}/public/${cartoMapId}`;
310
+ const map = await request({url});
311
+
312
+ // Periodically check if the data has changed. Note that this
313
+ // will not update when a map is published.
314
+ let stopAutoRefresh;
315
+ if (autoRefresh) {
316
+ const intervalId = setInterval(async () => {
317
+ const changed = await fillInMapDatasets(map, clientId, credentials);
318
+ if (changed.some(v => v === true)) {
319
+ onNewData(parseMap(map));
320
+ }
321
+ }, autoRefresh * 1000);
322
+ stopAutoRefresh = () => {
323
+ clearInterval(intervalId);
324
+ };
325
+ }
326
+
327
+ // Mutates map.datasets so that dataset.data contains data
328
+ await fillInMapDatasets(map, clientId, credentials);
329
+ return {
330
+ ...parseMap(map),
331
+ ...{stopAutoRefresh}
332
+ };
333
+ }
@@ -0,0 +1,183 @@
1
+ import GL from '@luma.gl/constants';
2
+ import {
3
+ LAYER_MAP,
4
+ getColorAccessor,
5
+ getSizeAccessor,
6
+ getTextAccessor,
7
+ getTextPixelOffsetAccessor
8
+ } from './layer-map';
9
+ import {_flatten as flatten, log} from '@deck.gl/core';
10
+
11
+ export function parseMap(json) {
12
+ const {keplerMapConfig, datasets} = json;
13
+ log.assert(keplerMapConfig.version === 'v1', 'Only support Kepler v1');
14
+ const {mapState, mapStyle} = keplerMapConfig.config;
15
+ const {layers, layerBlending, interactionConfig} = keplerMapConfig.config.visState;
16
+
17
+ return {
18
+ id: json.id,
19
+ title: json.title,
20
+ description: json.description,
21
+ createdAt: json.createdAt,
22
+ updatedAt: json.updatedAt,
23
+ initialViewState: mapState,
24
+ mapStyle,
25
+ layers: extractTextLayers(layers.reverse()).map(({id, type, config, visualChannels}) => {
26
+ log.assert(type in LAYER_MAP, `Unsupported layer type: ${type}`);
27
+ const {Layer, propMap, defaultProps} = LAYER_MAP[type];
28
+
29
+ const {dataId} = config;
30
+ const dataset = datasets.find(d => d.id === dataId);
31
+ log.assert(dataset, `No dataset matching dataId: ${dataId}`);
32
+ const {data} = dataset;
33
+ log.assert(data, `No data loaded for dataId: ${dataId}`);
34
+ return new Layer({
35
+ id,
36
+ data,
37
+ ...defaultProps,
38
+ ...createBlendingProps(layerBlending),
39
+ ...(!config.textLabel && createInteractionProps(interactionConfig)),
40
+ ...createStyleProps(config, propMap),
41
+ ...createChannelProps(visualChannels, type, config, data) // Must come after style
42
+ });
43
+ })
44
+ };
45
+ }
46
+
47
+ function extractTextLayers(layers) {
48
+ return flatten(
49
+ layers.map(({id, config, ...rest}) => {
50
+ const {textLabel, ...configRest} = config;
51
+ return [
52
+ // Original layer without textLabel
53
+ {id, config: configRest, ...rest},
54
+
55
+ // One layer per valid text label, with full opacity
56
+ ...textLabel
57
+ .filter(t => t.field)
58
+ .map(t => {
59
+ return {
60
+ id: `${id}-label-${t.field.name}`,
61
+ config: {
62
+ textLabel: t,
63
+ ...configRest,
64
+ label: `${config.label}-label-${t.field.name}`,
65
+ visConfig: {...configRest.visConfig, opacity: 1}
66
+ },
67
+ ...rest
68
+ };
69
+ })
70
+ ];
71
+ })
72
+ );
73
+ }
74
+
75
+ function createBlendingProps(layerBlending) {
76
+ if (layerBlending === 'additive') {
77
+ return {
78
+ parameters: {
79
+ blendFunc: [GL.SRC_ALPHA, GL.DST_ALPHA],
80
+ blendEquation: GL.FUNC_ADD
81
+ }
82
+ };
83
+ } else if (layerBlending === 'subtractive') {
84
+ return {
85
+ parameters: {
86
+ blendFunc: [GL.ONE, GL.ONE_MINUS_DST_COLOR, GL.SRC_ALPHA, GL.DST_ALPHA],
87
+ blendEquation: [GL.FUNC_SUBTRACT, GL.FUNC_ADD]
88
+ }
89
+ };
90
+ }
91
+
92
+ return {};
93
+ }
94
+
95
+ function createInteractionProps(interactionConfig) {
96
+ const pickable = interactionConfig && interactionConfig.tooltip.enabled;
97
+ return {
98
+ autoHighlight: pickable,
99
+ pickable
100
+ };
101
+ }
102
+
103
+ function mapProps(source, target, mapping) {
104
+ for (const sourceKey in mapping) {
105
+ const sourceValue = source[sourceKey];
106
+ const targetKey = mapping[sourceKey];
107
+ if (sourceValue === undefined) {
108
+ // eslint-disable-next-line no-continue
109
+ continue;
110
+ }
111
+ if (typeof targetKey === 'string') {
112
+ target[targetKey] = sourceValue;
113
+ } else if (typeof targetKey === 'object') {
114
+ // Nested definition, recurse down one level
115
+ mapProps(sourceValue, target, targetKey);
116
+ }
117
+ }
118
+ }
119
+
120
+ function createStyleProps(config, mapping) {
121
+ const result = {};
122
+ mapProps(config, result, mapping);
123
+ result.highlightColor = config.visConfig.enable3d ? [255, 255, 255, 60] : [252, 242, 26, 255];
124
+ return result;
125
+ }
126
+
127
+ /* eslint-disable complexity */
128
+ function createChannelProps(visualChannels, type, config, data) {
129
+ const {colorField, colorScale, sizeField, sizeScale, strokeColorField, strokeColorScale} =
130
+ visualChannels;
131
+ let {heightField, heightScale} = visualChannels;
132
+ if (type === 'hexagonId') {
133
+ heightField = sizeField;
134
+ heightScale = sizeScale;
135
+ }
136
+ const {textLabel, visConfig} = config;
137
+ const result = {};
138
+ const textLabelField = textLabel && textLabel.field;
139
+ if (colorField) {
140
+ result.getFillColor = getColorAccessor(
141
+ colorField,
142
+ colorScale,
143
+ visConfig.colorRange,
144
+ 1, // Rely on layer opacity
145
+ data
146
+ );
147
+ }
148
+ if (strokeColorField) {
149
+ const opacity =
150
+ visConfig.strokeOpacity !== undefined ? visConfig.strokeOpacity / visConfig.opacity : 1;
151
+ result.getLineColor = getColorAccessor(
152
+ strokeColorField,
153
+ strokeColorScale,
154
+ visConfig.colorRange,
155
+ opacity,
156
+ data
157
+ );
158
+ }
159
+ if (heightField) {
160
+ result.getElevation = getSizeAccessor(
161
+ heightField,
162
+ heightScale,
163
+ visConfig.heightRange || visConfig.sizeRange,
164
+ data
165
+ );
166
+ }
167
+ if (sizeField) {
168
+ result.getPointRadius = getSizeAccessor(
169
+ sizeField,
170
+ sizeScale,
171
+ visConfig.radiusRange || visConfig.sizeRange,
172
+ data
173
+ );
174
+ }
175
+ if (textLabelField) {
176
+ result.getText = getTextAccessor(textLabelField, data);
177
+ result.pointType = 'text';
178
+ const radius = result.getPointRadius || visConfig.radius;
179
+ result.getTextPixelOffset = getTextPixelOffsetAccessor(textLabel, radius);
180
+ }
181
+
182
+ return result;
183
+ }