@deck.gl/carto 8.6.4 → 8.7.0-alpha.11
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.
- package/dist/dist.dev.js +49068 -6661
- package/dist/es5/api/index.js +14 -8
- package/dist/es5/api/index.js.map +1 -1
- package/dist/es5/api/layer-map.js +256 -0
- package/dist/es5/api/layer-map.js.map +1 -0
- package/dist/es5/api/maps-api-common.js +13 -9
- package/dist/es5/api/maps-api-common.js.map +1 -1
- package/dist/es5/api/maps-client.js +157 -98
- package/dist/es5/api/maps-client.js.map +1 -1
- package/dist/es5/api/maps-v3-client.js +549 -190
- package/dist/es5/api/maps-v3-client.js.map +1 -1
- package/dist/es5/api/parseMap.js +207 -0
- package/dist/es5/api/parseMap.js.map +1 -0
- package/dist/es5/basemap.js +1 -1
- package/dist/es5/basemap.js.map +1 -1
- package/dist/es5/config.js +30 -37
- package/dist/es5/config.js.map +1 -1
- package/dist/es5/index.js +20 -30
- package/dist/es5/index.js.map +1 -1
- package/dist/es5/layers/carto-dynamic-tile-layer.js +166 -0
- package/dist/es5/layers/carto-dynamic-tile-layer.js.map +1 -0
- package/dist/es5/layers/carto-layer.js +217 -131
- package/dist/es5/layers/carto-layer.js.map +1 -1
- package/dist/es5/style/color-bins-style.js +16 -11
- package/dist/es5/style/color-bins-style.js.map +1 -1
- package/dist/es5/style/color-categories-style.js +43 -14
- package/dist/es5/style/color-categories-style.js.map +1 -1
- package/dist/es5/style/color-continuous-style.js +16 -11
- package/dist/es5/style/color-continuous-style.js.map +1 -1
- package/dist/es5/style/palette.js +24 -12
- package/dist/es5/style/palette.js.map +1 -1
- package/dist/es5/style/utils.js +13 -6
- package/dist/es5/style/utils.js.map +1 -1
- package/dist/esm/api/index.js +1 -1
- package/dist/esm/api/index.js.map +1 -1
- package/dist/esm/api/layer-map.js +220 -0
- package/dist/esm/api/layer-map.js.map +1 -0
- package/dist/esm/api/maps-client.js +1 -1
- package/dist/esm/api/maps-client.js.map +1 -1
- package/dist/esm/api/maps-v3-client.js +166 -25
- package/dist/esm/api/maps-v3-client.js.map +1 -1
- package/dist/esm/api/parseMap.js +195 -0
- package/dist/esm/api/parseMap.js.map +1 -0
- package/dist/esm/config.js +11 -26
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/index.js +1 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/layers/carto-dynamic-tile-layer.js +97 -0
- package/dist/esm/layers/carto-dynamic-tile-layer.js.map +1 -0
- package/dist/esm/layers/carto-layer.js +35 -28
- package/dist/esm/layers/carto-layer.js.map +1 -1
- package/dist/esm/style/color-bins-style.js +1 -1
- package/dist/esm/style/color-bins-style.js.map +1 -1
- package/dist/esm/style/color-continuous-style.js +1 -1
- package/dist/esm/style/color-continuous-style.js.map +1 -1
- package/dist.min.js +278 -1
- package/package.json +13 -8
- package/src/api/index.js +1 -1
- package/src/api/layer-map.js +195 -0
- package/src/api/maps-client.js +3 -7
- package/src/api/maps-v3-client.js +140 -20
- package/src/api/parseMap.js +183 -0
- package/src/config.js +8 -39
- package/src/index.js +1 -2
- package/src/layers/carto-dynamic-tile-layer.js +86 -0
- package/src/layers/carto-layer.js +25 -25
- package/src/style/color-bins-style.js +2 -4
- package/src/style/color-continuous-style.js +2 -4
- package/dist/es5/layers/carto-bqtiler-layer.js +0 -32
- package/dist/es5/layers/carto-bqtiler-layer.js.map +0 -1
- package/dist/es5/layers/carto-sql-layer.js +0 -33
- package/dist/es5/layers/carto-sql-layer.js.map +0 -1
- package/dist/esm/layers/carto-bqtiler-layer.js +0 -16
- package/dist/esm/layers/carto-bqtiler-layer.js.map +0 -1
- package/dist/esm/layers/carto-sql-layer.js +0 -17
- package/dist/esm/layers/carto-sql-layer.js.map +0 -1
- package/src/layers/carto-bqtiler-layer.js +0 -21
- 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.
|
|
5
|
+
"version": "8.7.0-alpha.11",
|
|
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/
|
|
35
|
-
"@loaders.gl/
|
|
36
|
-
"@loaders.gl/
|
|
37
|
-
"@
|
|
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-
|
|
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.
|
|
50
|
+
"@loaders.gl/core": "^3.1.3"
|
|
46
51
|
},
|
|
47
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "da1a3ab7327e4605280294c0862c4b67ab9aa877"
|
|
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
|
+
}
|
package/src/api/maps-client.js
CHANGED
|
@@ -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
|
|
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',
|
|
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:
|
|
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
|
|
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
|
-
|
|
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({
|
|
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
|
+
}
|