@deck.gl/carto 8.6.0 → 8.7.0-alpha.1
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 +24459 -726
- package/dist/es5/api/index.js +12 -0
- package/dist/es5/api/index.js.map +1 -1
- package/dist/es5/api/layer-map.js +255 -0
- package/dist/es5/api/layer-map.js.map +1 -0
- package/dist/es5/api/maps-api-common.js +10 -1
- package/dist/es5/api/maps-api-common.js.map +1 -1
- package/dist/es5/api/maps-client.js +18 -12
- package/dist/es5/api/maps-client.js.map +1 -1
- package/dist/es5/api/maps-v3-client.js +340 -42
- package/dist/es5/api/maps-v3-client.js.map +1 -1
- package/dist/es5/api/parseMap.js +211 -0
- package/dist/es5/api/parseMap.js.map +1 -0
- package/dist/es5/config.js +4 -2
- package/dist/es5/config.js.map +1 -1
- package/dist/es5/index.js +12 -0
- package/dist/es5/index.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 +219 -0
- package/dist/esm/api/layer-map.js.map +1 -0
- package/dist/esm/api/maps-api-common.js +4 -0
- package/dist/esm/api/maps-api-common.js.map +1 -1
- package/dist/esm/api/maps-client.js +11 -6
- package/dist/esm/api/maps-client.js.map +1 -1
- package/dist/esm/api/maps-v3-client.js +190 -17
- package/dist/esm/api/maps-v3-client.js.map +1 -1
- package/dist/esm/api/parseMap.js +202 -0
- package/dist/esm/api/parseMap.js.map +1 -0
- package/dist/esm/config.js +4 -4
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist.min.js +278 -1
- package/package.json +7 -3
- package/src/api/index.js +1 -1
- package/src/api/layer-map.js +194 -0
- package/src/api/maps-api-common.js +5 -0
- package/src/api/maps-client.js +16 -3
- package/src/api/maps-v3-client.js +145 -12
- package/src/api/parseMap.js +192 -0
- package/src/config.js +4 -4
- package/src/index.js +2 -0
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.1",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
"@loaders.gl/tiles": "^3.0.8",
|
|
37
37
|
"@math.gl/web-mercator": "^3.5.4",
|
|
38
38
|
"cartocolor": "^4.0.2",
|
|
39
|
-
"d3-
|
|
39
|
+
"d3-array": "^2.8.0",
|
|
40
|
+
"d3-color": "^2.0.0",
|
|
41
|
+
"d3-format": "^2.0.0",
|
|
42
|
+
"d3-scale": "^3.2.3",
|
|
43
|
+
"moment-timezone": "^0.5.33"
|
|
40
44
|
},
|
|
41
45
|
"peerDependencies": {
|
|
42
46
|
"@deck.gl/core": "^8.0.0",
|
|
@@ -44,5 +48,5 @@
|
|
|
44
48
|
"@deck.gl/layers": "^8.0.0",
|
|
45
49
|
"@loaders.gl/core": "^3.0.0"
|
|
46
50
|
},
|
|
47
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "dbaa3941a41f3ddceecad4d2d2879515d97d2510"
|
|
48
52
|
}
|
package/src/api/index.js
CHANGED
|
@@ -0,0 +1,194 @@
|
|
|
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
|
+
textLabel: {
|
|
32
|
+
alignment: 'getTextAlignmentBaseline',
|
|
33
|
+
anchor: 'getTextAnchor',
|
|
34
|
+
color: 'getTextColor',
|
|
35
|
+
size: 'getTextSize'
|
|
36
|
+
},
|
|
37
|
+
visConfig: {
|
|
38
|
+
enable3d: 'extruded',
|
|
39
|
+
filled: 'filled',
|
|
40
|
+
opacity: 'opacity',
|
|
41
|
+
strokeColor: 'getLineColor',
|
|
42
|
+
stroked: 'stroked',
|
|
43
|
+
thickness: 'getLineWidth',
|
|
44
|
+
radius: 'getPointRadius',
|
|
45
|
+
wireframe: 'wireframe'
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const RADIUS_DOWNSCALE = 0.2;
|
|
50
|
+
|
|
51
|
+
const defaultProps = {
|
|
52
|
+
lineMiterLimit: 2,
|
|
53
|
+
lineWidthUnits: 'pixels',
|
|
54
|
+
pointRadiusScale: RADIUS_DOWNSCALE,
|
|
55
|
+
pointRadiusUnits: 'pixels',
|
|
56
|
+
rounded: true,
|
|
57
|
+
wrapLongitude: false
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function mergePropMaps(a, b) {
|
|
61
|
+
return {...a, ...b, visConfig: {...a.visConfig, ...b.visConfig}};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const LAYER_MAP = {
|
|
65
|
+
point: {
|
|
66
|
+
Layer: GeoJsonLayer,
|
|
67
|
+
propMap: mergePropMaps(sharedPropMap, {
|
|
68
|
+
visConfig: {outline: 'stroked'}
|
|
69
|
+
}),
|
|
70
|
+
defaultProps
|
|
71
|
+
},
|
|
72
|
+
geojson: {
|
|
73
|
+
Layer: GeoJsonLayer,
|
|
74
|
+
propMap: sharedPropMap,
|
|
75
|
+
defaultProps: {...defaultProps, lineWidthScale: 2}
|
|
76
|
+
},
|
|
77
|
+
hexagonId: {
|
|
78
|
+
Layer: H3HexagonLayer,
|
|
79
|
+
propMap: mergePropMaps(sharedPropMap, {
|
|
80
|
+
visConfig: {coverage: 'coverage', elevationScale: 'elevationScale'}
|
|
81
|
+
}),
|
|
82
|
+
defaultProps: {...defaultProps, getHexagon: d => d.h3}
|
|
83
|
+
},
|
|
84
|
+
mvt: {
|
|
85
|
+
Layer: MVTLayer,
|
|
86
|
+
propMap: sharedPropMap,
|
|
87
|
+
defaultProps: {...defaultProps, pointRadiusScale: 0.3, lineWidthScale: 2}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
function domainFromAttribute(attribute, scaleType) {
|
|
92
|
+
if (scaleType === 'ordinal' || scaleType === 'point') {
|
|
93
|
+
return attribute.categories.map(c => c.category).filter(c => c !== undefined && c !== null);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let {min} = attribute;
|
|
97
|
+
if (scaleType === 'log' && min === 0) {
|
|
98
|
+
min = 1e-5;
|
|
99
|
+
}
|
|
100
|
+
return [min, attribute.max];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function domainFromValues(values, scaleType) {
|
|
104
|
+
if (scaleType === 'ordinal') {
|
|
105
|
+
return [...new Set(values)].sort();
|
|
106
|
+
}
|
|
107
|
+
return extent(values);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function calculateDomain(data, name, scaleType) {
|
|
111
|
+
if (data.tilestats) {
|
|
112
|
+
// Tileset data type
|
|
113
|
+
const {attributes} = data.tilestats.layers[0];
|
|
114
|
+
const attribute = attributes.find(a => a.attribute === name);
|
|
115
|
+
return domainFromAttribute(attribute, scaleType);
|
|
116
|
+
} else if (data.features) {
|
|
117
|
+
// GeoJSON data type
|
|
118
|
+
const values = data.features.map(({properties}) => properties[name]);
|
|
119
|
+
return domainFromValues(values, scaleType);
|
|
120
|
+
} else if (Array.isArray(data) && data[0][name]) {
|
|
121
|
+
// JSON data type
|
|
122
|
+
const values = data.map(properties => properties[name]);
|
|
123
|
+
return domainFromValues(values, scaleType);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return [0, 1];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function normalizeAccessor(accessor, data) {
|
|
130
|
+
if (data.features || data.tilestats) {
|
|
131
|
+
return ({properties}) => {
|
|
132
|
+
return accessor(properties);
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return accessor;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function getColorAccessor({name}, scaleType, {colors}, opacity, data) {
|
|
139
|
+
const scale = SCALE_FUNCS[scaleType]();
|
|
140
|
+
scale.domain(calculateDomain(data, name, scaleType));
|
|
141
|
+
scale.range(colors);
|
|
142
|
+
const alpha = opacity !== undefined ? 255 * Math.pow(opacity, 1 / 2.2) : 255;
|
|
143
|
+
|
|
144
|
+
const accessor = properties => {
|
|
145
|
+
const rgba = rgb(scale(properties[name]));
|
|
146
|
+
return [rgba.r, rgba.g, rgba.b, alpha];
|
|
147
|
+
};
|
|
148
|
+
return normalizeAccessor(accessor, data);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function getSizeAccessor({name}, scaleType, range, data) {
|
|
152
|
+
const scale = SCALE_FUNCS[scaleType]();
|
|
153
|
+
scale.domain(calculateDomain(data, name, scaleType));
|
|
154
|
+
scale.range(range);
|
|
155
|
+
|
|
156
|
+
const accessor = properties => {
|
|
157
|
+
return scale(properties[name]);
|
|
158
|
+
};
|
|
159
|
+
return normalizeAccessor(accessor, data);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const FORMATS = {
|
|
163
|
+
date: s => moment.utc(s).format('MM/DD/YY HH:mm:ssa'),
|
|
164
|
+
integer: d3Format('i'),
|
|
165
|
+
float: d3Format('.5f'),
|
|
166
|
+
timestamp: s => moment.utc(s).format('X'),
|
|
167
|
+
default: String
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export function getTextAccessor({name, type}, data) {
|
|
171
|
+
const format = FORMATS[type] || FORMATS.default;
|
|
172
|
+
const accessor = properties => {
|
|
173
|
+
return format(properties[name]);
|
|
174
|
+
};
|
|
175
|
+
return normalizeAccessor(accessor, data);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function getTextPixelOffsetAccessor({alignment, anchor, size}, radius) {
|
|
179
|
+
const padding = 20;
|
|
180
|
+
const signX = anchor === 'middle' ? 0 : anchor === 'start' ? 1 : -1;
|
|
181
|
+
const signY = alignment === 'center' ? 0 : alignment === 'bottom' ? 1 : -1;
|
|
182
|
+
const sizeOffset = alignment === 'center' ? 0 : size;
|
|
183
|
+
|
|
184
|
+
const calculateOffset = r => {
|
|
185
|
+
r = RADIUS_DOWNSCALE * r;
|
|
186
|
+
return [signX * (r + padding), signY * (r + padding + sizeOffset)];
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return typeof radius === 'function'
|
|
190
|
+
? d => {
|
|
191
|
+
return calculateOffset(radius(d));
|
|
192
|
+
}
|
|
193
|
+
: calculateOffset(radius);
|
|
194
|
+
}
|
|
@@ -7,6 +7,11 @@ export const API_VERSIONS = {
|
|
|
7
7
|
V3: 'v3'
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
export const DEFAULT_MAPS_URL_FORMAT = {
|
|
11
|
+
[API_VERSIONS.V1]: `https://${DEFAULT_USER_COMPONENT_IN_URL}.carto.com/api/v1/map`,
|
|
12
|
+
[API_VERSIONS.V2]: `https://maps-api-v2.${DEFAULT_REGION_COMPONENT_IN_URL}.carto.com/user/${DEFAULT_USER_COMPONENT_IN_URL}`
|
|
13
|
+
};
|
|
14
|
+
|
|
10
15
|
export const MAP_TYPES = {
|
|
11
16
|
QUERY: 'query',
|
|
12
17
|
TABLE: 'table',
|
package/src/api/maps-client.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Maps API Client for Maps API v1 and Maps API v2
|
|
3
3
|
*/
|
|
4
|
-
import {getDefaultCredentials} from '../config';
|
|
4
|
+
import {defaultClassicCredentials, getDefaultCredentials} from '../config';
|
|
5
5
|
import {
|
|
6
6
|
API_VERSIONS,
|
|
7
|
+
DEFAULT_MAPS_URL_FORMAT,
|
|
7
8
|
DEFAULT_REGION_COMPONENT_IN_URL,
|
|
8
9
|
DEFAULT_USER_COMPONENT_IN_URL,
|
|
9
10
|
encodeParameter,
|
|
@@ -22,8 +23,20 @@ const TILE_EXTENT = 4096;
|
|
|
22
23
|
* Obtain a TileJson from Maps API v1 and v2
|
|
23
24
|
*/
|
|
24
25
|
export async function getDataV2({type, source, credentials}) {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
26
|
+
const defaultCredentials = getDefaultCredentials();
|
|
27
|
+
const apiVersion = (credentials && credentials.apiVersion) || defaultCredentials.apiVersion;
|
|
28
|
+
// Only pick up default credentials if they have been defined for
|
|
29
|
+
// correct API version
|
|
30
|
+
const localCreds = {
|
|
31
|
+
...defaultClassicCredentials,
|
|
32
|
+
...(defaultCredentials.apiVersion === apiVersion && defaultCredentials),
|
|
33
|
+
...credentials
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (!localCreds.mapsUrl) {
|
|
37
|
+
localCreds.mapsUrl = DEFAULT_MAPS_URL_FORMAT[apiVersion];
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
let url;
|
|
28
41
|
|
|
29
42
|
const connection = type === 'tileset' ? CONNECTIONS.BIGQUERY : CONNECTIONS.CARTO;
|
|
@@ -3,6 +3,7 @@
|
|
|
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;
|
|
@@ -68,9 +69,11 @@ function dealWithError({response, error}) {
|
|
|
68
69
|
/**
|
|
69
70
|
* Build a URL with all required parameters
|
|
70
71
|
*/
|
|
71
|
-
function getParameters({type, source, geoColumn, columns}) {
|
|
72
|
-
const
|
|
73
|
-
|
|
72
|
+
function getParameters({type, source, geoColumn, columns, schema}) {
|
|
73
|
+
const parameters = [encodeParameter('client', 'deck-gl-carto')];
|
|
74
|
+
if (schema) {
|
|
75
|
+
parameters.push(encodeParameter('schema', true));
|
|
76
|
+
}
|
|
74
77
|
|
|
75
78
|
const sourceName = type === MAP_TYPES.QUERY ? 'q' : 'name';
|
|
76
79
|
parameters.push(encodeParameter(sourceName, source));
|
|
@@ -93,10 +96,11 @@ export async function mapInstantiation({
|
|
|
93
96
|
connection,
|
|
94
97
|
credentials,
|
|
95
98
|
geoColumn,
|
|
96
|
-
columns
|
|
99
|
+
columns,
|
|
100
|
+
schema
|
|
97
101
|
}) {
|
|
98
102
|
const baseUrl = `${credentials.mapsUrl}/${connection}/${type}`;
|
|
99
|
-
const url = `${baseUrl}?${getParameters({type, source, geoColumn, columns})}`;
|
|
103
|
+
const url = `${baseUrl}?${getParameters({type, source, geoColumn, columns, schema})}`;
|
|
100
104
|
const {accessToken} = credentials;
|
|
101
105
|
|
|
102
106
|
const format = 'json';
|
|
@@ -123,9 +127,7 @@ function getUrlFromMetadata(metadata, format) {
|
|
|
123
127
|
return null;
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
|
|
127
|
-
const localCreds = {...getDefaultCredentials(), ...credentials};
|
|
128
|
-
|
|
130
|
+
function checkFetchLayerDataParameters({type, source, connection, localCreds}) {
|
|
129
131
|
log.assert(connection, 'Must define connection');
|
|
130
132
|
log.assert(type, 'Must define a type');
|
|
131
133
|
log.assert(source, 'Must define a source');
|
|
@@ -133,7 +135,58 @@ export async function getData({type, source, connection, credentials, geoColumn,
|
|
|
133
135
|
log.assert(localCreds.apiVersion === API_VERSIONS.V3, 'Method only available for v3');
|
|
134
136
|
log.assert(localCreds.apiBaseUrl, 'Must define apiBaseUrl');
|
|
135
137
|
log.assert(localCreds.accessToken, 'Must define an accessToken');
|
|
136
|
-
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function fetchLayerData({
|
|
141
|
+
type,
|
|
142
|
+
source,
|
|
143
|
+
connection,
|
|
144
|
+
credentials,
|
|
145
|
+
geoColumn,
|
|
146
|
+
columns,
|
|
147
|
+
format,
|
|
148
|
+
schema
|
|
149
|
+
}) {
|
|
150
|
+
// Internally we split data fetching into two parts to allow us to
|
|
151
|
+
// conditionally fetch the actual data, depending on the metadata state
|
|
152
|
+
const {url, accessToken, mapFormat, metadata} = await _fetchDataUrl({
|
|
153
|
+
type,
|
|
154
|
+
source,
|
|
155
|
+
connection,
|
|
156
|
+
credentials,
|
|
157
|
+
geoColumn,
|
|
158
|
+
columns,
|
|
159
|
+
format,
|
|
160
|
+
schema
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const data = await request({url, format: mapFormat, accessToken});
|
|
164
|
+
const result = {data, format: mapFormat};
|
|
165
|
+
if (schema) {
|
|
166
|
+
result.schema = metadata.schema;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function _fetchDataUrl({
|
|
173
|
+
type,
|
|
174
|
+
source,
|
|
175
|
+
connection,
|
|
176
|
+
credentials,
|
|
177
|
+
geoColumn,
|
|
178
|
+
columns,
|
|
179
|
+
format,
|
|
180
|
+
schema
|
|
181
|
+
}) {
|
|
182
|
+
const defaultCredentials = getDefaultCredentials();
|
|
183
|
+
// Only pick up default credentials if they have been defined for
|
|
184
|
+
// correct API version
|
|
185
|
+
const localCreds = {
|
|
186
|
+
...(defaultCredentials.apiVersion === API_VERSIONS.V3 && defaultCredentials),
|
|
187
|
+
...credentials
|
|
188
|
+
};
|
|
189
|
+
checkFetchLayerDataParameters({type, source, connection, localCreds});
|
|
137
190
|
|
|
138
191
|
if (!localCreds.mapsUrl) {
|
|
139
192
|
localCreds.mapsUrl = buildMapsUrlFromBase(localCreds.apiBaseUrl);
|
|
@@ -145,7 +198,8 @@ export async function getData({type, source, connection, credentials, geoColumn,
|
|
|
145
198
|
connection,
|
|
146
199
|
credentials: localCreds,
|
|
147
200
|
geoColumn,
|
|
148
|
-
columns
|
|
201
|
+
columns,
|
|
202
|
+
schema
|
|
149
203
|
});
|
|
150
204
|
let url;
|
|
151
205
|
let mapFormat;
|
|
@@ -156,7 +210,7 @@ export async function getData({type, source, connection, credentials, geoColumn,
|
|
|
156
210
|
log.assert(url, `Format ${format} not available`);
|
|
157
211
|
} else {
|
|
158
212
|
// guess map format
|
|
159
|
-
const prioritizedFormats = [FORMATS.GEOJSON, FORMATS.NDJSON, FORMATS.TILEJSON];
|
|
213
|
+
const prioritizedFormats = [FORMATS.GEOJSON, FORMATS.JSON, FORMATS.NDJSON, FORMATS.TILEJSON];
|
|
160
214
|
for (const f of prioritizedFormats) {
|
|
161
215
|
url = getUrlFromMetadata(metadata, f);
|
|
162
216
|
if (url) {
|
|
@@ -167,6 +221,85 @@ export async function getData({type, source, connection, credentials, geoColumn,
|
|
|
167
221
|
}
|
|
168
222
|
|
|
169
223
|
const {accessToken} = localCreds;
|
|
224
|
+
return {url, accessToken, mapFormat, metadata};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export async function getData({type, source, connection, credentials, geoColumn, columns, format}) {
|
|
228
|
+
log.deprecated('getData', 'fetchLayerData')();
|
|
229
|
+
const layerData = await fetchLayerData({
|
|
230
|
+
type,
|
|
231
|
+
source,
|
|
232
|
+
connection,
|
|
233
|
+
credentials,
|
|
234
|
+
geoColumn,
|
|
235
|
+
columns,
|
|
236
|
+
format,
|
|
237
|
+
schema: false
|
|
238
|
+
});
|
|
239
|
+
return layerData.data;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* global setInterval, URLSearchParams */
|
|
243
|
+
async function _fetchMapDataset(dataset, accessToken) {
|
|
244
|
+
// First fetch metadata
|
|
245
|
+
const {connectionName: connection, source, type} = dataset;
|
|
246
|
+
const {url, mapFormat} = await _fetchDataUrl({
|
|
247
|
+
credentials: {accessToken},
|
|
248
|
+
connection,
|
|
249
|
+
source,
|
|
250
|
+
type
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Extract the last time the data changed
|
|
254
|
+
const cache = parseInt(new URLSearchParams(url).get('cache'), 10);
|
|
255
|
+
if (cache && dataset.cache === cache) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
dataset.cache = cache;
|
|
259
|
+
|
|
260
|
+
// Only fetch if the data has changed
|
|
261
|
+
const data = await request({url, format: mapFormat, accessToken});
|
|
262
|
+
dataset.data = data;
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function fillInMapDatasets({datasets, publicToken}) {
|
|
267
|
+
const promises = datasets.map(dataset => _fetchMapDataset(dataset, publicToken));
|
|
268
|
+
return await Promise.all(promises);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function fetchMap({mapId, credentials, autoRefresh, onNewData}) {
|
|
272
|
+
const localCreds = {...getDefaultCredentials(), ...credentials};
|
|
273
|
+
|
|
274
|
+
log.assert(mapId, 'Must define map id');
|
|
275
|
+
|
|
276
|
+
log.assert(localCreds.apiVersion === API_VERSIONS.V3, 'Method only available for v3');
|
|
277
|
+
log.assert(localCreds.apiBaseUrl, 'Must define apiBaseUrl');
|
|
278
|
+
if (!localCreds.mapsUrl) {
|
|
279
|
+
localCreds.mapsUrl = buildMapsUrlFromBase(localCreds.apiBaseUrl);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (autoRefresh || onNewData) {
|
|
283
|
+
log.assert(onNewData, 'Must define `onNewData` when using autoRefresh');
|
|
284
|
+
log.assert(typeof onNewData === 'function', '`onNewData` must be a function');
|
|
285
|
+
log.assert(autoRefresh > 0, '`autoRefresh` must be a positive number');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const url = `${localCreds.mapsUrl}/public/${mapId}`;
|
|
289
|
+
const map = await request({url});
|
|
290
|
+
|
|
291
|
+
// Periodically check if the data has changed. Note that this
|
|
292
|
+
// will not update when a map is published.
|
|
293
|
+
if (autoRefresh) {
|
|
294
|
+
setInterval(async () => {
|
|
295
|
+
const changed = await fillInMapDatasets(map);
|
|
296
|
+
if (changed.some(v => v === true)) {
|
|
297
|
+
onNewData(parseMap(map));
|
|
298
|
+
}
|
|
299
|
+
}, autoRefresh * 1000);
|
|
300
|
+
}
|
|
170
301
|
|
|
171
|
-
|
|
302
|
+
// Mutates map.datasets so that dataset.data contains data
|
|
303
|
+
await fillInMapDatasets(map);
|
|
304
|
+
return parseMap(map);
|
|
172
305
|
}
|
|
@@ -0,0 +1,192 @@
|
|
|
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
|
+
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.filter(t => t.field).map(t => {
|
|
57
|
+
return {
|
|
58
|
+
id: `id-label-${t.field.name}`,
|
|
59
|
+
config: {
|
|
60
|
+
textLabel: t,
|
|
61
|
+
...configRest,
|
|
62
|
+
visConfig: {...configRest.visConfig, opacity: 1}
|
|
63
|
+
},
|
|
64
|
+
...rest
|
|
65
|
+
};
|
|
66
|
+
})
|
|
67
|
+
];
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createBlendingProps(layerBlending) {
|
|
73
|
+
if (layerBlending === 'additive') {
|
|
74
|
+
return {
|
|
75
|
+
parameters: {
|
|
76
|
+
blendFunc: [GL.SRC_ALPHA, GL.DST_ALPHA],
|
|
77
|
+
blendEquation: GL.FUNC_ADD
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
} else if (layerBlending === 'subtractive') {
|
|
81
|
+
return {
|
|
82
|
+
parameters: {
|
|
83
|
+
blendFunc: [GL.ONE, GL.ONE_MINUS_DST_COLOR, GL.SRC_ALPHA, GL.DST_ALPHA],
|
|
84
|
+
blendEquation: [GL.FUNC_SUBTRACT, GL.FUNC_ADD]
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function createInteractionProps(interactionConfig) {
|
|
93
|
+
const pickable = interactionConfig && interactionConfig.tooltip.enabled;
|
|
94
|
+
return {
|
|
95
|
+
autoHighlight: pickable,
|
|
96
|
+
pickable
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function mapProps(source, target, mapping) {
|
|
101
|
+
if (Array.isArray(source)) {
|
|
102
|
+
source = source[0];
|
|
103
|
+
}
|
|
104
|
+
if (!source) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
for (const sourceKey in mapping) {
|
|
108
|
+
const sourceValue = source[sourceKey];
|
|
109
|
+
const targetKey = mapping[sourceKey];
|
|
110
|
+
if (sourceValue === undefined) {
|
|
111
|
+
// eslint-disable-next-line no-continue
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (typeof targetKey === 'string') {
|
|
115
|
+
target[targetKey] = sourceValue;
|
|
116
|
+
} else if (typeof targetKey === 'object') {
|
|
117
|
+
// Nested definition, recurse down one level
|
|
118
|
+
mapProps(sourceValue, target, targetKey);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function createStyleProps(config, mapping) {
|
|
124
|
+
const result = {};
|
|
125
|
+
mapProps(config, result, mapping);
|
|
126
|
+
result.highlightColor = config.visConfig.enable3d ? [255, 255, 255, 60] : [252, 242, 26, 255];
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* eslint-disable complexity */
|
|
131
|
+
function createChannelProps(visualChannels, type, config, data) {
|
|
132
|
+
const {
|
|
133
|
+
colorField,
|
|
134
|
+
colorScale,
|
|
135
|
+
sizeField,
|
|
136
|
+
sizeScale,
|
|
137
|
+
strokeColorField,
|
|
138
|
+
strokeColorScale
|
|
139
|
+
} = visualChannels;
|
|
140
|
+
let {heightField, heightScale} = visualChannels;
|
|
141
|
+
if (type === 'hexagonId') {
|
|
142
|
+
heightField = sizeField;
|
|
143
|
+
heightScale = sizeScale;
|
|
144
|
+
}
|
|
145
|
+
const {textLabel, visConfig} = config;
|
|
146
|
+
const result = {};
|
|
147
|
+
const textLabelField = textLabel && textLabel.field;
|
|
148
|
+
if (colorField) {
|
|
149
|
+
result.getFillColor = getColorAccessor(
|
|
150
|
+
colorField,
|
|
151
|
+
colorScale,
|
|
152
|
+
visConfig.colorRange,
|
|
153
|
+
1, // Rely on layer opacity
|
|
154
|
+
data
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (strokeColorField) {
|
|
158
|
+
const opacity =
|
|
159
|
+
visConfig.strokeOpacity !== undefined ? visConfig.strokeOpacity / visConfig.opacity : 1;
|
|
160
|
+
result.getLineColor = getColorAccessor(
|
|
161
|
+
strokeColorField,
|
|
162
|
+
strokeColorScale,
|
|
163
|
+
visConfig.colorRange,
|
|
164
|
+
opacity,
|
|
165
|
+
data
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (heightField) {
|
|
169
|
+
result.getElevation = getSizeAccessor(
|
|
170
|
+
heightField,
|
|
171
|
+
heightScale,
|
|
172
|
+
visConfig.heightRange || visConfig.sizeRange,
|
|
173
|
+
data
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (sizeField) {
|
|
177
|
+
result.getPointRadius = getSizeAccessor(
|
|
178
|
+
sizeField,
|
|
179
|
+
sizeScale,
|
|
180
|
+
visConfig.radiusRange || visConfig.sizeRange,
|
|
181
|
+
data
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
if (textLabelField) {
|
|
185
|
+
result.getText = getTextAccessor(textLabelField, data);
|
|
186
|
+
result.pointType = 'text';
|
|
187
|
+
const radius = result.getPointRadius || visConfig.radius;
|
|
188
|
+
result.getTextPixelOffset = getTextPixelOffsetAccessor(textLabel, radius);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result;
|
|
192
|
+
}
|
package/src/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {API_VERSIONS} from './api/maps-api-common';
|
|
1
|
+
import {API_VERSIONS, DEFAULT_MAPS_URL_FORMAT} from './api/maps-api-common';
|
|
2
2
|
|
|
3
|
-
const defaultClassicCredentials = {
|
|
3
|
+
export const defaultClassicCredentials = {
|
|
4
4
|
username: 'public',
|
|
5
5
|
apiKey: 'default_public',
|
|
6
6
|
region: 'us',
|
|
@@ -22,7 +22,7 @@ export function setDefaultCredentials(opts) {
|
|
|
22
22
|
|
|
23
23
|
switch (apiVersion) {
|
|
24
24
|
case API_VERSIONS.V1:
|
|
25
|
-
opts.mapsUrl = opts.mapsUrl ||
|
|
25
|
+
opts.mapsUrl = opts.mapsUrl || DEFAULT_MAPS_URL_FORMAT[apiVersion];
|
|
26
26
|
credentials = {
|
|
27
27
|
apiVersion,
|
|
28
28
|
...defaultClassicCredentials,
|
|
@@ -31,7 +31,7 @@ export function setDefaultCredentials(opts) {
|
|
|
31
31
|
break;
|
|
32
32
|
|
|
33
33
|
case API_VERSIONS.V2:
|
|
34
|
-
opts.mapsUrl = opts.mapsUrl ||
|
|
34
|
+
opts.mapsUrl = opts.mapsUrl || DEFAULT_MAPS_URL_FORMAT[apiVersion];
|
|
35
35
|
credentials = {
|
|
36
36
|
apiVersion,
|
|
37
37
|
...defaultClassicCredentials,
|