@carto/api-client 0.4.2 → 0.4.3
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/CHANGELOG.md +4 -0
- package/build/api-client.cjs +933 -925
- package/build/api-client.cjs.map +1 -1
- package/build/api-client.modern.js +873 -865
- package/build/api-client.modern.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/sources/boundary-query-source.d.ts +2 -1
- package/build/sources/boundary-table-source.d.ts +2 -1
- package/build/sources/h3-query-source.d.ts +2 -1
- package/build/sources/h3-table-source.d.ts +2 -1
- package/build/sources/h3-tileset-source.d.ts +2 -1
- package/build/sources/index.d.ts +13 -14
- package/build/sources/quadbin-query-source.d.ts +2 -1
- package/build/sources/quadbin-table-source.d.ts +2 -1
- package/build/sources/quadbin-tileset-source.d.ts +2 -1
- package/build/sources/raster-source.d.ts +2 -1
- package/build/sources/types.d.ts +16 -0
- package/build/sources/vector-query-source.d.ts +2 -1
- package/build/sources/vector-table-source.d.ts +2 -1
- package/build/sources/vector-tileset-source.d.ts +2 -1
- package/package.json +1 -1
- package/src/index.ts +1 -36
- package/src/sources/boundary-query-source.ts +4 -2
- package/src/sources/boundary-table-source.ts +4 -2
- package/src/sources/h3-query-source.ts +3 -1
- package/src/sources/h3-table-source.ts +3 -1
- package/src/sources/h3-tileset-source.ts +4 -2
- package/src/sources/index.ts +54 -24
- package/src/sources/quadbin-query-source.ts +4 -1
- package/src/sources/quadbin-table-source.ts +4 -1
- package/src/sources/quadbin-tileset-source.ts +4 -2
- package/src/sources/raster-source.ts +4 -2
- package/src/sources/types.ts +18 -0
- package/src/sources/vector-query-source.ts +9 -1
- package/src/sources/vector-table-source.ts +10 -1
- package/src/sources/vector-tileset-source.ts +4 -2
package/build/api-client.cjs
CHANGED
|
@@ -381,449 +381,603 @@ const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4;
|
|
|
381
381
|
*/
|
|
382
382
|
const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6;
|
|
383
383
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
/** @internalRemarks Source: @carto/react-api */
|
|
390
|
-
|
|
391
|
-
function _catch(body, recover) {
|
|
392
|
-
try {
|
|
393
|
-
var result = body();
|
|
394
|
-
} catch (e) {
|
|
395
|
-
return recover(e);
|
|
396
|
-
}
|
|
397
|
-
if (result && result.then) {
|
|
398
|
-
return result.then(void 0, recover);
|
|
399
|
-
}
|
|
400
|
-
return result;
|
|
384
|
+
// deck.gl
|
|
385
|
+
// SPDX-License-Identifier: MIT
|
|
386
|
+
// Copyright (c) vis.gl contributors
|
|
387
|
+
function joinPath() {
|
|
388
|
+
return [].slice.call(arguments).map(part => part.endsWith('/') ? part.slice(0, -1) : part).join('/');
|
|
401
389
|
}
|
|
402
|
-
|
|
390
|
+
function buildV3Path(apiBaseUrl, version, endpoint) {
|
|
391
|
+
return joinPath(apiBaseUrl, version, endpoint, ...[].slice.call(arguments, 3));
|
|
392
|
+
}
|
|
393
|
+
/** @internal Required by fetchMap(). */
|
|
394
|
+
function buildPublicMapUrl(_ref) {
|
|
403
395
|
let {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
396
|
+
apiBaseUrl,
|
|
397
|
+
cartoMapId
|
|
398
|
+
} = _ref;
|
|
399
|
+
return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId);
|
|
400
|
+
}
|
|
401
|
+
/** @internal Required by fetchMap(). */
|
|
402
|
+
function buildStatsUrl(_ref2) {
|
|
403
|
+
let {
|
|
404
|
+
attribute,
|
|
405
|
+
apiBaseUrl,
|
|
406
|
+
connectionName,
|
|
407
|
+
source,
|
|
408
|
+
type
|
|
407
409
|
} = _ref2;
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
function _temp2(_result) {
|
|
411
|
-
if (_exit) ;
|
|
412
|
-
if (!response.ok) {
|
|
413
|
-
dealWithApiError({
|
|
414
|
-
response,
|
|
415
|
-
data
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
return data;
|
|
419
|
-
}
|
|
420
|
-
let response;
|
|
421
|
-
let data;
|
|
422
|
-
const isPost = opts?.method === 'POST';
|
|
423
|
-
const _temp = _catch(function () {
|
|
424
|
-
return Promise.resolve(fetch(url.toString(), {
|
|
425
|
-
headers: {
|
|
426
|
-
Authorization: `Bearer ${accessToken}`,
|
|
427
|
-
...(isPost && {
|
|
428
|
-
'Content-Type': 'application/json'
|
|
429
|
-
})
|
|
430
|
-
},
|
|
431
|
-
...(isPost && {
|
|
432
|
-
method: opts?.method,
|
|
433
|
-
body: opts?.body
|
|
434
|
-
}),
|
|
435
|
-
signal: opts?.abortController?.signal,
|
|
436
|
-
...opts?.otherOptions
|
|
437
|
-
})).then(function (_fetch) {
|
|
438
|
-
response = _fetch;
|
|
439
|
-
return Promise.resolve(response.json()).then(function (_response$json) {
|
|
440
|
-
data = _response$json;
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
}, function (error) {
|
|
444
|
-
if (error.name === 'AbortError') throw error;
|
|
445
|
-
throw new Error(`Failed request: ${error}`);
|
|
446
|
-
});
|
|
447
|
-
return Promise.resolve(_temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp));
|
|
448
|
-
} catch (e) {
|
|
449
|
-
return Promise.reject(e);
|
|
410
|
+
if (type === 'query') {
|
|
411
|
+
return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, attribute);
|
|
450
412
|
}
|
|
451
|
-
|
|
452
|
-
|
|
413
|
+
// type === 'table'
|
|
414
|
+
return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, source, attribute);
|
|
415
|
+
}
|
|
416
|
+
function buildSourceUrl(_ref3) {
|
|
453
417
|
let {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
case 403:
|
|
467
|
-
throw new Error('Forbidden access to the requested data');
|
|
468
|
-
default:
|
|
469
|
-
const msg = data && data.error && typeof data.error === 'string' ? data.error : JSON.stringify(data?.hint || data.error?.[0]);
|
|
470
|
-
throw new Error(msg);
|
|
471
|
-
}
|
|
418
|
+
apiBaseUrl,
|
|
419
|
+
connectionName,
|
|
420
|
+
endpoint
|
|
421
|
+
} = _ref3;
|
|
422
|
+
return buildV3Path(apiBaseUrl, 'v3', 'maps', connectionName, endpoint);
|
|
423
|
+
}
|
|
424
|
+
function buildQueryUrl(_ref4) {
|
|
425
|
+
let {
|
|
426
|
+
apiBaseUrl,
|
|
427
|
+
connectionName
|
|
428
|
+
} = _ref4;
|
|
429
|
+
return buildV3Path(apiBaseUrl, 'v3', 'sql', connectionName, 'query');
|
|
472
430
|
}
|
|
473
431
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
V3
|
|
478
|
-
} = exports.ApiVersion;
|
|
479
|
-
const REQUEST_GET_MAX_URL_LENGTH = 2048;
|
|
432
|
+
// deck.gl
|
|
433
|
+
// SPDX-License-Identifier: MIT
|
|
434
|
+
// Copyright (c) vis.gl contributors
|
|
480
435
|
/**
|
|
481
|
-
*
|
|
482
|
-
*
|
|
436
|
+
*
|
|
437
|
+
* Custom error for reported errors in CARTO Maps API.
|
|
438
|
+
* Provides useful debugging information in console and context for applications.
|
|
439
|
+
*
|
|
483
440
|
*/
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
accessToken,
|
|
500
|
-
connectionName,
|
|
501
|
-
clientId
|
|
502
|
-
} = source;
|
|
503
|
-
assert(apiBaseUrl, 'executeModel: missing apiBaseUrl');
|
|
504
|
-
assert(accessToken, 'executeModel: missing accessToken');
|
|
505
|
-
assert(apiVersion === V3, 'executeModel: SQL Model API requires CARTO 3+');
|
|
506
|
-
assert(type !== 'tileset', 'executeModel: Tilesets not supported');
|
|
507
|
-
let url = `${apiBaseUrl}/v3/sql/${connectionName}/model/${model}`;
|
|
508
|
-
const {
|
|
509
|
-
data,
|
|
510
|
-
filters,
|
|
511
|
-
filtersLogicalOperator = 'and',
|
|
512
|
-
geoColumn = DEFAULT_GEO_COLUMN
|
|
513
|
-
} = source;
|
|
514
|
-
const queryParameters = source.queryParameters ? JSON.stringify(source.queryParameters) : '';
|
|
515
|
-
const queryParams = {
|
|
516
|
-
type,
|
|
517
|
-
client: clientId,
|
|
518
|
-
source: data,
|
|
519
|
-
params: JSON.stringify(params),
|
|
520
|
-
queryParameters,
|
|
521
|
-
filters: JSON.stringify(filters),
|
|
522
|
-
filtersLogicalOperator
|
|
523
|
-
};
|
|
524
|
-
// Picking Model API requires 'spatialDataColumn'.
|
|
525
|
-
if (model === 'pick') {
|
|
526
|
-
queryParams.spatialDataColumn = geoColumn;
|
|
527
|
-
}
|
|
528
|
-
// API supports multiple filters, we apply it only to geoColumn
|
|
529
|
-
const spatialFilters = source.spatialFilter ? {
|
|
530
|
-
[geoColumn]: source.spatialFilter
|
|
531
|
-
} : undefined;
|
|
532
|
-
if (spatialFilters) {
|
|
533
|
-
queryParams.spatialFilters = JSON.stringify(spatialFilters);
|
|
534
|
-
}
|
|
535
|
-
const urlWithSearchParams = url + '?' + new URLSearchParams(queryParams).toString();
|
|
536
|
-
const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
|
|
537
|
-
if (isGet) {
|
|
538
|
-
url = urlWithSearchParams;
|
|
539
|
-
} else {
|
|
540
|
-
// undo the JSON.stringify, @TODO find a better pattern
|
|
541
|
-
queryParams.params = params;
|
|
542
|
-
queryParams.filters = filters;
|
|
543
|
-
queryParams.queryParameters = source.queryParameters;
|
|
544
|
-
if (spatialFilters) {
|
|
545
|
-
queryParams.spatialFilters = spatialFilters;
|
|
441
|
+
class CartoAPIError extends Error {
|
|
442
|
+
constructor(error, errorContext, response, responseJson) {
|
|
443
|
+
let responseString = 'Failed to connect';
|
|
444
|
+
if (response) {
|
|
445
|
+
responseString = 'Server returned: ';
|
|
446
|
+
if (response.status === 400) {
|
|
447
|
+
responseString += 'Bad request';
|
|
448
|
+
} else if (response.status === 401 || response.status === 403) {
|
|
449
|
+
responseString += 'Unauthorized access';
|
|
450
|
+
} else if (response.status === 404) {
|
|
451
|
+
responseString += 'Not found';
|
|
452
|
+
} else {
|
|
453
|
+
responseString += 'Error';
|
|
454
|
+
}
|
|
455
|
+
responseString += ` (${response.status}):`;
|
|
546
456
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
method: isGet ? 'GET' : 'POST',
|
|
554
|
-
...(!isGet && {
|
|
555
|
-
body: JSON.stringify(queryParams)
|
|
556
|
-
})
|
|
457
|
+
responseString += ` ${error.message || error}`;
|
|
458
|
+
let message = `${errorContext.requestType} API request failed`;
|
|
459
|
+
message += `\n${responseString}`;
|
|
460
|
+
for (const key of Object.keys(errorContext)) {
|
|
461
|
+
if (key === 'requestType') continue;
|
|
462
|
+
message += `\n${formatErrorKey(key)}: ${errorContext[key]}`;
|
|
557
463
|
}
|
|
558
|
-
|
|
464
|
+
message += '\n';
|
|
465
|
+
super(message);
|
|
466
|
+
/** Source error from server */
|
|
467
|
+
this.error = void 0;
|
|
468
|
+
/** Context (API call & parameters) in which error occured */
|
|
469
|
+
this.errorContext = void 0;
|
|
470
|
+
/** Response from server */
|
|
471
|
+
this.response = void 0;
|
|
472
|
+
/** JSON Response from server */
|
|
473
|
+
this.responseJson = void 0;
|
|
474
|
+
this.name = 'CartoAPIError';
|
|
475
|
+
this.response = response;
|
|
476
|
+
this.responseJson = responseJson;
|
|
477
|
+
this.error = error;
|
|
478
|
+
this.errorContext = errorContext;
|
|
479
|
+
}
|
|
559
480
|
}
|
|
560
|
-
|
|
561
481
|
/**
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
* Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
|
|
482
|
+
* Converts camelCase to Camel Case
|
|
565
483
|
*/
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
484
|
+
function formatErrorKey(key) {
|
|
485
|
+
return key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// deck.gl
|
|
489
|
+
const requestWithParameters = function (_ref) {
|
|
490
|
+
let {
|
|
491
|
+
baseUrl,
|
|
492
|
+
parameters = {},
|
|
493
|
+
headers: customHeaders = {},
|
|
494
|
+
errorContext,
|
|
495
|
+
maxLengthURL = DEFAULT_MAX_LENGTH_URL,
|
|
496
|
+
localCache
|
|
497
|
+
} = _ref;
|
|
498
|
+
try {
|
|
499
|
+
// Parameters added to all requests issued with `requestWithParameters()`.
|
|
500
|
+
// These parameters override parameters already in the base URL, but not
|
|
501
|
+
// user-provided parameters.
|
|
502
|
+
parameters = {
|
|
503
|
+
v: V3_MINOR_VERSION,
|
|
504
|
+
client: getClient(),
|
|
505
|
+
...(typeof deck !== 'undefined' && deck.VERSION && {
|
|
506
|
+
deckglVersion: deck.VERSION
|
|
507
|
+
}),
|
|
508
|
+
...parameters
|
|
572
509
|
};
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
510
|
+
baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters));
|
|
511
|
+
const key = createCacheKey(baseUrl, parameters, customHeaders);
|
|
512
|
+
const {
|
|
513
|
+
cache: REQUEST_CACHE,
|
|
514
|
+
canReadCache,
|
|
515
|
+
canStoreInCache
|
|
516
|
+
} = getCacheSettings(localCache);
|
|
517
|
+
if (canReadCache && REQUEST_CACHE.has(key)) {
|
|
518
|
+
return Promise.resolve(REQUEST_CACHE.get(key));
|
|
519
|
+
}
|
|
520
|
+
const url = createURLWithParameters(baseUrl, parameters);
|
|
521
|
+
const headers = {
|
|
522
|
+
...DEFAULT_HEADERS,
|
|
523
|
+
...customHeaders
|
|
585
524
|
};
|
|
525
|
+
/* global fetch */
|
|
526
|
+
const fetchPromise = url.length > maxLengthURL ? fetch(baseUrl, {
|
|
527
|
+
method: 'POST',
|
|
528
|
+
body: JSON.stringify(parameters),
|
|
529
|
+
headers
|
|
530
|
+
}) : fetch(url, {
|
|
531
|
+
headers
|
|
532
|
+
});
|
|
533
|
+
let response;
|
|
534
|
+
let responseJson;
|
|
535
|
+
const jsonPromise = fetchPromise.then(_response => {
|
|
536
|
+
response = _response;
|
|
537
|
+
return response.json();
|
|
538
|
+
}).then(json => {
|
|
539
|
+
responseJson = json;
|
|
540
|
+
if (!response || !response.ok) {
|
|
541
|
+
throw new Error(json.error);
|
|
542
|
+
}
|
|
543
|
+
return json;
|
|
544
|
+
}).catch(error => {
|
|
545
|
+
if (canStoreInCache) {
|
|
546
|
+
REQUEST_CACHE.delete(key);
|
|
547
|
+
}
|
|
548
|
+
throw new CartoAPIError(error, errorContext, response, responseJson);
|
|
549
|
+
});
|
|
550
|
+
if (canStoreInCache) {
|
|
551
|
+
REQUEST_CACHE.set(key, jsonPromise);
|
|
552
|
+
}
|
|
553
|
+
return Promise.resolve(jsonPromise);
|
|
554
|
+
} catch (e) {
|
|
555
|
+
return Promise.reject(e);
|
|
586
556
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
557
|
+
};
|
|
558
|
+
const DEFAULT_HEADERS = {
|
|
559
|
+
Accept: 'application/json',
|
|
560
|
+
'Content-Type': 'application/json'
|
|
561
|
+
};
|
|
562
|
+
const DEFAULT_REQUEST_CACHE = new Map();
|
|
563
|
+
function getCacheSettings(localCache) {
|
|
564
|
+
const canReadCache = localCache?.cacheControl?.includes('no-cache') ? false : true;
|
|
565
|
+
const canStoreInCache = localCache?.cacheControl?.includes('no-store') ? false : true;
|
|
566
|
+
const cache = localCache?.cache || DEFAULT_REQUEST_CACHE;
|
|
567
|
+
return {
|
|
568
|
+
cache,
|
|
569
|
+
canReadCache,
|
|
570
|
+
canStoreInCache
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
function createCacheKey(baseUrl, parameters, headers) {
|
|
574
|
+
const parameterEntries = Object.entries(parameters).sort((_ref2, _ref3) => {
|
|
575
|
+
let [a] = _ref2;
|
|
576
|
+
let [b] = _ref3;
|
|
577
|
+
return a > b ? 1 : -1;
|
|
578
|
+
});
|
|
579
|
+
const headerEntries = Object.entries(headers).sort((_ref4, _ref5) => {
|
|
580
|
+
let [a] = _ref4;
|
|
581
|
+
let [b] = _ref5;
|
|
582
|
+
return a > b ? 1 : -1;
|
|
583
|
+
});
|
|
584
|
+
return JSON.stringify({
|
|
585
|
+
baseUrl,
|
|
586
|
+
parameters: parameterEntries,
|
|
587
|
+
headers: headerEntries
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Appends query string parameters to a URL. Existing URL parameters are kept,
|
|
592
|
+
* unless there is a conflict, in which case the new parameters override
|
|
593
|
+
* those already in the URL.
|
|
594
|
+
*/
|
|
595
|
+
function createURLWithParameters(baseUrlString, parameters) {
|
|
596
|
+
const baseUrl = new URL(baseUrlString);
|
|
597
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
598
|
+
if (isPureObject(value) || Array.isArray(value)) {
|
|
599
|
+
baseUrl.searchParams.set(key, JSON.stringify(value));
|
|
600
|
+
} else {
|
|
601
|
+
baseUrl.searchParams.set(key, value.toString());
|
|
625
602
|
}
|
|
626
603
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
*/
|
|
638
|
-
getFeatures(options) {
|
|
639
|
-
try {
|
|
640
|
-
const _this2 = this;
|
|
641
|
-
const {
|
|
642
|
-
filterOwner,
|
|
643
|
-
spatialFilter,
|
|
644
|
-
abortController,
|
|
645
|
-
...params
|
|
646
|
-
} = options;
|
|
647
|
-
const {
|
|
648
|
-
columns,
|
|
649
|
-
dataType,
|
|
650
|
-
featureIds,
|
|
651
|
-
z,
|
|
652
|
-
limit,
|
|
653
|
-
tileResolution
|
|
654
|
-
} = params;
|
|
655
|
-
return Promise.resolve(executeModel({
|
|
656
|
-
model: 'pick',
|
|
657
|
-
source: {
|
|
658
|
-
..._this2.getModelSource(filterOwner),
|
|
659
|
-
spatialFilter
|
|
660
|
-
},
|
|
661
|
-
params: {
|
|
662
|
-
columns,
|
|
663
|
-
dataType,
|
|
664
|
-
featureIds,
|
|
665
|
-
z,
|
|
666
|
-
limit: limit || 1000,
|
|
667
|
-
tileResolution: tileResolution || DEFAULT_TILE_RESOLUTION
|
|
668
|
-
},
|
|
669
|
-
opts: {
|
|
670
|
-
abortController
|
|
671
|
-
}
|
|
672
|
-
// Avoid `normalizeObjectKeys()`, which changes column names.
|
|
673
|
-
}).then(_ref => {
|
|
674
|
-
let {
|
|
675
|
-
rows
|
|
676
|
-
} = _ref;
|
|
677
|
-
return {
|
|
678
|
-
rows
|
|
679
|
-
};
|
|
680
|
-
}));
|
|
681
|
-
} catch (e) {
|
|
682
|
-
return Promise.reject(e);
|
|
604
|
+
return baseUrl.toString();
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Deletes query string parameters from a URL.
|
|
608
|
+
*/
|
|
609
|
+
function excludeURLParameters(baseUrlString, parameters) {
|
|
610
|
+
const baseUrl = new URL(baseUrlString);
|
|
611
|
+
for (const param of parameters) {
|
|
612
|
+
if (baseUrl.searchParams.has(param)) {
|
|
613
|
+
baseUrl.searchParams.delete(param);
|
|
683
614
|
}
|
|
684
615
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
model: 'formula',
|
|
708
|
-
source: {
|
|
709
|
-
..._this3.getModelSource(filterOwner),
|
|
710
|
-
spatialFilter
|
|
711
|
-
},
|
|
712
|
-
params: {
|
|
713
|
-
column: column ?? '*',
|
|
714
|
-
operation,
|
|
715
|
-
operationExp
|
|
716
|
-
},
|
|
717
|
-
opts: {
|
|
718
|
-
abortController
|
|
719
|
-
}
|
|
720
|
-
}).then(res => normalizeObjectKeys(res.rows[0])));
|
|
721
|
-
} catch (e) {
|
|
722
|
-
return Promise.reject(e);
|
|
616
|
+
return baseUrl.toString();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// deck.gl
|
|
620
|
+
const baseSource = function (endpoint, options, urlParameters) {
|
|
621
|
+
try {
|
|
622
|
+
const {
|
|
623
|
+
accessToken,
|
|
624
|
+
connectionName,
|
|
625
|
+
cache,
|
|
626
|
+
...optionalOptions
|
|
627
|
+
} = options;
|
|
628
|
+
const mergedOptions = {
|
|
629
|
+
...SOURCE_DEFAULTS,
|
|
630
|
+
accessToken,
|
|
631
|
+
connectionName,
|
|
632
|
+
endpoint
|
|
633
|
+
};
|
|
634
|
+
for (const key in optionalOptions) {
|
|
635
|
+
if (optionalOptions[key]) {
|
|
636
|
+
mergedOptions[key] = optionalOptions[key];
|
|
637
|
+
}
|
|
723
638
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
639
|
+
const baseUrl = buildSourceUrl(mergedOptions);
|
|
640
|
+
const {
|
|
641
|
+
clientId,
|
|
642
|
+
maxLengthURL,
|
|
643
|
+
format,
|
|
644
|
+
localCache
|
|
645
|
+
} = mergedOptions;
|
|
646
|
+
const headers = {
|
|
647
|
+
Authorization: `Bearer ${options.accessToken}`,
|
|
648
|
+
...options.headers
|
|
649
|
+
};
|
|
650
|
+
const parameters = {
|
|
651
|
+
client: clientId,
|
|
652
|
+
...urlParameters
|
|
653
|
+
};
|
|
654
|
+
const errorContext = {
|
|
655
|
+
requestType: 'Map instantiation',
|
|
656
|
+
connection: options.connectionName,
|
|
657
|
+
type: endpoint,
|
|
658
|
+
source: JSON.stringify(parameters, undefined, 2)
|
|
659
|
+
};
|
|
660
|
+
return Promise.resolve(requestWithParameters({
|
|
661
|
+
baseUrl,
|
|
662
|
+
parameters,
|
|
663
|
+
headers,
|
|
664
|
+
errorContext,
|
|
665
|
+
maxLengthURL,
|
|
666
|
+
localCache
|
|
667
|
+
})).then(function (mapInstantiation) {
|
|
668
|
+
let _exit;
|
|
669
|
+
function _temp2(_result) {
|
|
670
|
+
return _exit ? _result : Promise.resolve(requestWithParameters({
|
|
671
|
+
baseUrl: dataUrl,
|
|
672
|
+
headers,
|
|
673
|
+
errorContext,
|
|
674
|
+
maxLengthURL,
|
|
675
|
+
localCache
|
|
676
|
+
}));
|
|
677
|
+
}
|
|
678
|
+
const dataUrl = mapInstantiation[format].url[0];
|
|
679
|
+
if (cache) {
|
|
680
|
+
cache.value = parseInt(new URL(dataUrl).searchParams.get('cache') || '', 10);
|
|
681
|
+
}
|
|
682
|
+
errorContext.requestType = 'Map data';
|
|
683
|
+
const _temp = function () {
|
|
684
|
+
if (format === 'tilejson') {
|
|
685
|
+
return Promise.resolve(requestWithParameters({
|
|
686
|
+
baseUrl: dataUrl,
|
|
687
|
+
headers,
|
|
688
|
+
errorContext,
|
|
689
|
+
maxLengthURL,
|
|
690
|
+
localCache
|
|
691
|
+
})).then(function (json) {
|
|
692
|
+
if (accessToken) {
|
|
693
|
+
json.accessToken = accessToken;
|
|
694
|
+
}
|
|
695
|
+
_exit = 1;
|
|
696
|
+
return json;
|
|
771
697
|
});
|
|
772
|
-
return result;
|
|
773
698
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
777
|
-
|
|
699
|
+
}();
|
|
700
|
+
return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
|
|
701
|
+
});
|
|
702
|
+
} catch (e) {
|
|
703
|
+
return Promise.reject(e);
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
const SOURCE_DEFAULTS = {
|
|
707
|
+
apiBaseUrl: DEFAULT_API_BASE_URL,
|
|
708
|
+
clientId: getClient(),
|
|
709
|
+
format: 'tilejson',
|
|
710
|
+
headers: {},
|
|
711
|
+
maxLengthURL: DEFAULT_MAX_LENGTH_URL
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
// deck.gl
|
|
715
|
+
const boundaryQuerySource = function (options) {
|
|
716
|
+
try {
|
|
717
|
+
const {
|
|
718
|
+
columns,
|
|
719
|
+
filters,
|
|
720
|
+
tilesetTableName,
|
|
721
|
+
propertiesSqlQuery,
|
|
722
|
+
queryParameters
|
|
723
|
+
} = options;
|
|
724
|
+
const urlParameters = {
|
|
725
|
+
tilesetTableName,
|
|
726
|
+
propertiesSqlQuery
|
|
727
|
+
};
|
|
728
|
+
if (columns) {
|
|
729
|
+
urlParameters.columns = columns.join(',');
|
|
730
|
+
}
|
|
731
|
+
if (filters) {
|
|
732
|
+
urlParameters.filters = filters;
|
|
733
|
+
}
|
|
734
|
+
if (queryParameters) {
|
|
735
|
+
urlParameters.queryParameters = queryParameters;
|
|
778
736
|
}
|
|
737
|
+
return Promise.resolve(baseSource('boundary', options, urlParameters));
|
|
738
|
+
} catch (e) {
|
|
739
|
+
return Promise.reject(e);
|
|
779
740
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
return Promise.resolve(executeModel({
|
|
801
|
-
model: 'range',
|
|
802
|
-
source: {
|
|
803
|
-
..._this5.getModelSource(filterOwner),
|
|
804
|
-
spatialFilter
|
|
805
|
-
},
|
|
806
|
-
params: {
|
|
807
|
-
column
|
|
808
|
-
},
|
|
809
|
-
opts: {
|
|
810
|
-
abortController
|
|
811
|
-
}
|
|
812
|
-
}).then(res => normalizeObjectKeys(res.rows[0])));
|
|
813
|
-
} catch (e) {
|
|
814
|
-
return Promise.reject(e);
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
// deck.gl
|
|
744
|
+
const boundaryTableSource = function (options) {
|
|
745
|
+
try {
|
|
746
|
+
const {
|
|
747
|
+
filters,
|
|
748
|
+
tilesetTableName,
|
|
749
|
+
columns,
|
|
750
|
+
propertiesTableName
|
|
751
|
+
} = options;
|
|
752
|
+
const urlParameters = {
|
|
753
|
+
tilesetTableName,
|
|
754
|
+
propertiesTableName
|
|
755
|
+
};
|
|
756
|
+
if (columns) {
|
|
757
|
+
urlParameters.columns = columns.join(',');
|
|
758
|
+
}
|
|
759
|
+
if (filters) {
|
|
760
|
+
urlParameters.filters = filters;
|
|
815
761
|
}
|
|
762
|
+
return Promise.resolve(baseSource('boundary', options, urlParameters));
|
|
763
|
+
} catch (e) {
|
|
764
|
+
return Promise.reject(e);
|
|
816
765
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Return more descriptive error from API
|
|
770
|
+
* @internalRemarks Source: @carto/react-api
|
|
771
|
+
*/
|
|
772
|
+
|
|
773
|
+
/** @internalRemarks Source: @carto/react-api */
|
|
774
|
+
|
|
775
|
+
function _catch(body, recover) {
|
|
776
|
+
try {
|
|
777
|
+
var result = body();
|
|
778
|
+
} catch (e) {
|
|
779
|
+
return recover(e);
|
|
780
|
+
}
|
|
781
|
+
if (result && result.then) {
|
|
782
|
+
return result.then(void 0, recover);
|
|
783
|
+
}
|
|
784
|
+
return result;
|
|
785
|
+
}
|
|
786
|
+
const makeCall = function (_ref2) {
|
|
787
|
+
let {
|
|
788
|
+
url,
|
|
789
|
+
accessToken,
|
|
790
|
+
opts
|
|
791
|
+
} = _ref2;
|
|
792
|
+
try {
|
|
793
|
+
let _exit;
|
|
794
|
+
function _temp2(_result) {
|
|
795
|
+
if (_exit) ;
|
|
796
|
+
if (!response.ok) {
|
|
797
|
+
dealWithApiError({
|
|
798
|
+
response,
|
|
799
|
+
data
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
return data;
|
|
803
|
+
}
|
|
804
|
+
let response;
|
|
805
|
+
let data;
|
|
806
|
+
const isPost = opts?.method === 'POST';
|
|
807
|
+
const _temp = _catch(function () {
|
|
808
|
+
return Promise.resolve(fetch(url.toString(), {
|
|
809
|
+
headers: {
|
|
810
|
+
Authorization: `Bearer ${accessToken}`,
|
|
811
|
+
...(isPost && {
|
|
812
|
+
'Content-Type': 'application/json'
|
|
813
|
+
})
|
|
814
|
+
},
|
|
815
|
+
...(isPost && {
|
|
816
|
+
method: opts?.method,
|
|
817
|
+
body: opts?.body
|
|
818
|
+
}),
|
|
819
|
+
signal: opts?.abortController?.signal,
|
|
820
|
+
...opts?.otherOptions
|
|
821
|
+
})).then(function (_fetch) {
|
|
822
|
+
response = _fetch;
|
|
823
|
+
return Promise.resolve(response.json()).then(function (_response$json) {
|
|
824
|
+
data = _response$json;
|
|
825
|
+
});
|
|
826
|
+
});
|
|
827
|
+
}, function (error) {
|
|
828
|
+
if (error.name === 'AbortError') throw error;
|
|
829
|
+
throw new Error(`Failed request: ${error}`);
|
|
830
|
+
});
|
|
831
|
+
return Promise.resolve(_temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp));
|
|
832
|
+
} catch (e) {
|
|
833
|
+
return Promise.reject(e);
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
function dealWithApiError(_ref) {
|
|
837
|
+
let {
|
|
838
|
+
response,
|
|
839
|
+
data
|
|
840
|
+
} = _ref;
|
|
841
|
+
if (data.error === 'Column not found') {
|
|
842
|
+
throw new InvalidColumnError(`${data.error} ${data.column_name}`);
|
|
843
|
+
}
|
|
844
|
+
if (typeof data.error === 'string' && data.error?.includes('Missing columns')) {
|
|
845
|
+
throw new InvalidColumnError(data.error);
|
|
846
|
+
}
|
|
847
|
+
switch (response.status) {
|
|
848
|
+
case 401:
|
|
849
|
+
throw new Error('Unauthorized access. Invalid credentials');
|
|
850
|
+
case 403:
|
|
851
|
+
throw new Error('Forbidden access to the requested data');
|
|
852
|
+
default:
|
|
853
|
+
const msg = data && data.error && typeof data.error === 'string' ? data.error : JSON.stringify(data?.hint || data.error?.[0]);
|
|
854
|
+
throw new Error(msg);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/** @internalRemarks Source: @carto/react-api */
|
|
859
|
+
const AVAILABLE_MODELS = ['category', 'histogram', 'formula', 'pick', 'timeseries', 'range', 'scatterplot', 'table'];
|
|
860
|
+
const {
|
|
861
|
+
V3
|
|
862
|
+
} = exports.ApiVersion;
|
|
863
|
+
const REQUEST_GET_MAX_URL_LENGTH = 2048;
|
|
864
|
+
/**
|
|
865
|
+
* Execute a SQL model request.
|
|
866
|
+
* @internalRemarks Source: @carto/react-api
|
|
867
|
+
*/
|
|
868
|
+
function executeModel(props) {
|
|
869
|
+
assert(props.source, 'executeModel: missing source');
|
|
870
|
+
assert(props.model, 'executeModel: missing model');
|
|
871
|
+
assert(props.params, 'executeModel: missing params');
|
|
872
|
+
assert(AVAILABLE_MODELS.includes(props.model), `executeModel: model provided isn't valid. Available models: ${AVAILABLE_MODELS.join(', ')}`);
|
|
873
|
+
const {
|
|
874
|
+
model,
|
|
875
|
+
source,
|
|
876
|
+
params,
|
|
877
|
+
opts
|
|
878
|
+
} = props;
|
|
879
|
+
const {
|
|
880
|
+
type,
|
|
881
|
+
apiVersion,
|
|
882
|
+
apiBaseUrl,
|
|
883
|
+
accessToken,
|
|
884
|
+
connectionName,
|
|
885
|
+
clientId
|
|
886
|
+
} = source;
|
|
887
|
+
assert(apiBaseUrl, 'executeModel: missing apiBaseUrl');
|
|
888
|
+
assert(accessToken, 'executeModel: missing accessToken');
|
|
889
|
+
assert(apiVersion === V3, 'executeModel: SQL Model API requires CARTO 3+');
|
|
890
|
+
assert(type !== 'tileset', 'executeModel: Tilesets not supported');
|
|
891
|
+
let url = `${apiBaseUrl}/v3/sql/${connectionName}/model/${model}`;
|
|
892
|
+
const {
|
|
893
|
+
data,
|
|
894
|
+
filters,
|
|
895
|
+
filtersLogicalOperator = 'and',
|
|
896
|
+
geoColumn = DEFAULT_GEO_COLUMN
|
|
897
|
+
} = source;
|
|
898
|
+
const queryParameters = source.queryParameters ? JSON.stringify(source.queryParameters) : '';
|
|
899
|
+
const queryParams = {
|
|
900
|
+
type,
|
|
901
|
+
client: clientId,
|
|
902
|
+
source: data,
|
|
903
|
+
params: JSON.stringify(params),
|
|
904
|
+
queryParameters,
|
|
905
|
+
filters: JSON.stringify(filters),
|
|
906
|
+
filtersLogicalOperator
|
|
907
|
+
};
|
|
908
|
+
// Picking Model API requires 'spatialDataColumn'.
|
|
909
|
+
if (model === 'pick') {
|
|
910
|
+
queryParams.spatialDataColumn = geoColumn;
|
|
911
|
+
}
|
|
912
|
+
// API supports multiple filters, we apply it only to geoColumn
|
|
913
|
+
const spatialFilters = source.spatialFilter ? {
|
|
914
|
+
[geoColumn]: source.spatialFilter
|
|
915
|
+
} : undefined;
|
|
916
|
+
if (spatialFilters) {
|
|
917
|
+
queryParams.spatialFilters = JSON.stringify(spatialFilters);
|
|
918
|
+
}
|
|
919
|
+
const urlWithSearchParams = url + '?' + new URLSearchParams(queryParams).toString();
|
|
920
|
+
const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
|
|
921
|
+
if (isGet) {
|
|
922
|
+
url = urlWithSearchParams;
|
|
923
|
+
} else {
|
|
924
|
+
// undo the JSON.stringify, @TODO find a better pattern
|
|
925
|
+
queryParams.params = params;
|
|
926
|
+
queryParams.filters = filters;
|
|
927
|
+
queryParams.queryParameters = source.queryParameters;
|
|
928
|
+
if (spatialFilters) {
|
|
929
|
+
queryParams.spatialFilters = spatialFilters;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return makeCall({
|
|
933
|
+
url,
|
|
934
|
+
accessToken: source.accessToken,
|
|
935
|
+
opts: {
|
|
936
|
+
...opts,
|
|
937
|
+
method: isGet ? 'GET' : 'POST',
|
|
938
|
+
...(!isGet && {
|
|
939
|
+
body: JSON.stringify(queryParams)
|
|
940
|
+
})
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Source for Widget API requests on a data source defined by a SQL query.
|
|
947
|
+
*
|
|
948
|
+
* Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
|
|
949
|
+
*/
|
|
950
|
+
class WidgetBaseSource {
|
|
951
|
+
constructor(props) {
|
|
952
|
+
this.props = void 0;
|
|
953
|
+
this.props = {
|
|
954
|
+
...WidgetBaseSource.defaultProps,
|
|
955
|
+
...props
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
_getModelSource(owner) {
|
|
959
|
+
const props = this.props;
|
|
960
|
+
return {
|
|
961
|
+
apiVersion: props.apiVersion,
|
|
962
|
+
apiBaseUrl: props.apiBaseUrl,
|
|
963
|
+
clientId: props.clientId,
|
|
964
|
+
accessToken: props.accessToken,
|
|
965
|
+
connectionName: props.connectionName,
|
|
966
|
+
filters: getApplicableFilters(owner, props.filters),
|
|
967
|
+
filtersLogicalOperator: props.filtersLogicalOperator,
|
|
968
|
+
geoColumn: props.geoColumn
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
/****************************************************************************
|
|
972
|
+
* CATEGORIES
|
|
973
|
+
*/
|
|
974
|
+
/**
|
|
975
|
+
* Returns a list of labeled datapoints for categorical data. Suitable for
|
|
976
|
+
* charts including grouped bar charts, pie charts, and tree charts.
|
|
977
|
+
*/
|
|
978
|
+
getCategories(options) {
|
|
979
|
+
try {
|
|
980
|
+
const _this = this;
|
|
827
981
|
const {
|
|
828
982
|
filterOwner,
|
|
829
983
|
spatialFilter,
|
|
@@ -831,50 +985,43 @@ class WidgetBaseSource {
|
|
|
831
985
|
...params
|
|
832
986
|
} = options;
|
|
833
987
|
const {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
yAxisJoinOperation
|
|
988
|
+
column,
|
|
989
|
+
operation,
|
|
990
|
+
operationColumn
|
|
838
991
|
} = params;
|
|
839
|
-
// Make sure this is sync with the same constant in cloud-native/maps-api
|
|
840
|
-
const HARD_LIMIT = 500;
|
|
841
992
|
return Promise.resolve(executeModel({
|
|
842
|
-
model: '
|
|
993
|
+
model: 'category',
|
|
843
994
|
source: {
|
|
844
|
-
...
|
|
995
|
+
..._this.getModelSource(filterOwner),
|
|
845
996
|
spatialFilter
|
|
846
997
|
},
|
|
847
998
|
params: {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
yAxisJoinOperation,
|
|
852
|
-
limit: HARD_LIMIT
|
|
999
|
+
column,
|
|
1000
|
+
operation,
|
|
1001
|
+
operationColumn: operationColumn || column
|
|
853
1002
|
},
|
|
854
1003
|
opts: {
|
|
855
1004
|
abortController
|
|
856
1005
|
}
|
|
857
|
-
}).then(res => normalizeObjectKeys(res.rows))
|
|
858
|
-
let {
|
|
859
|
-
x,
|
|
860
|
-
y
|
|
861
|
-
} = _ref3;
|
|
862
|
-
return [x, y];
|
|
863
|
-
})));
|
|
1006
|
+
}).then(res => normalizeObjectKeys(res.rows)));
|
|
864
1007
|
} catch (e) {
|
|
865
1008
|
return Promise.reject(e);
|
|
866
1009
|
}
|
|
867
1010
|
}
|
|
868
1011
|
/****************************************************************************
|
|
869
|
-
*
|
|
1012
|
+
* FEATURES
|
|
870
1013
|
*/
|
|
871
1014
|
/**
|
|
872
|
-
*
|
|
873
|
-
*
|
|
1015
|
+
* Given a list of feature IDs (as found in `_carto_feature_id`) returns all
|
|
1016
|
+
* matching features. In datasets containing features with duplicate geometries,
|
|
1017
|
+
* feature IDs may be duplicated (IDs are a hash of geometry) and so more
|
|
1018
|
+
* results may be returned than IDs in the request.
|
|
1019
|
+
* @internal
|
|
1020
|
+
* @experimental
|
|
874
1021
|
*/
|
|
875
|
-
|
|
1022
|
+
getFeatures(options) {
|
|
876
1023
|
try {
|
|
877
|
-
const
|
|
1024
|
+
const _this2 = this;
|
|
878
1025
|
const {
|
|
879
1026
|
filterOwner,
|
|
880
1027
|
spatialFilter,
|
|
@@ -883,549 +1030,402 @@ class WidgetBaseSource {
|
|
|
883
1030
|
} = options;
|
|
884
1031
|
const {
|
|
885
1032
|
columns,
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
limit
|
|
1033
|
+
dataType,
|
|
1034
|
+
featureIds,
|
|
1035
|
+
z,
|
|
1036
|
+
limit,
|
|
1037
|
+
tileResolution
|
|
890
1038
|
} = params;
|
|
891
1039
|
return Promise.resolve(executeModel({
|
|
892
|
-
model: '
|
|
1040
|
+
model: 'pick',
|
|
893
1041
|
source: {
|
|
894
|
-
...
|
|
1042
|
+
..._this2.getModelSource(filterOwner),
|
|
895
1043
|
spatialFilter
|
|
896
1044
|
},
|
|
897
1045
|
params: {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1046
|
+
columns,
|
|
1047
|
+
dataType,
|
|
1048
|
+
featureIds,
|
|
1049
|
+
z,
|
|
1050
|
+
limit: limit || 1000,
|
|
1051
|
+
tileResolution: tileResolution || DEFAULT_TILE_RESOLUTION
|
|
903
1052
|
},
|
|
904
1053
|
opts: {
|
|
905
1054
|
abortController
|
|
906
1055
|
}
|
|
907
|
-
}).then(res => ({
|
|
908
1056
|
// Avoid `normalizeObjectKeys()`, which changes column names.
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1057
|
+
}).then(_ref => {
|
|
1058
|
+
let {
|
|
1059
|
+
rows
|
|
1060
|
+
} = _ref;
|
|
1061
|
+
return {
|
|
1062
|
+
rows
|
|
1063
|
+
};
|
|
1064
|
+
}));
|
|
912
1065
|
} catch (e) {
|
|
913
1066
|
return Promise.reject(e);
|
|
914
1067
|
}
|
|
915
1068
|
}
|
|
916
1069
|
/****************************************************************************
|
|
917
|
-
*
|
|
1070
|
+
* FORMULA
|
|
918
1071
|
*/
|
|
919
1072
|
/**
|
|
920
|
-
* Returns a
|
|
921
|
-
*
|
|
1073
|
+
* Returns a scalar numerical statistic over all matching data. Suitable
|
|
1074
|
+
* for 'headline' or 'scorecard' figures such as counts and sums.
|
|
922
1075
|
*/
|
|
923
|
-
|
|
1076
|
+
getFormula(options) {
|
|
924
1077
|
try {
|
|
925
|
-
const
|
|
1078
|
+
const _this3 = this;
|
|
926
1079
|
const {
|
|
927
1080
|
filterOwner,
|
|
1081
|
+
spatialFilter,
|
|
928
1082
|
abortController,
|
|
1083
|
+
operationExp,
|
|
1084
|
+
...params
|
|
1085
|
+
} = options;
|
|
1086
|
+
const {
|
|
1087
|
+
column,
|
|
1088
|
+
operation
|
|
1089
|
+
} = params;
|
|
1090
|
+
return Promise.resolve(executeModel({
|
|
1091
|
+
model: 'formula',
|
|
1092
|
+
source: {
|
|
1093
|
+
..._this3.getModelSource(filterOwner),
|
|
1094
|
+
spatialFilter
|
|
1095
|
+
},
|
|
1096
|
+
params: {
|
|
1097
|
+
column: column ?? '*',
|
|
1098
|
+
operation,
|
|
1099
|
+
operationExp
|
|
1100
|
+
},
|
|
1101
|
+
opts: {
|
|
1102
|
+
abortController
|
|
1103
|
+
}
|
|
1104
|
+
}).then(res => normalizeObjectKeys(res.rows[0])));
|
|
1105
|
+
} catch (e) {
|
|
1106
|
+
return Promise.reject(e);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
/****************************************************************************
|
|
1110
|
+
* HISTOGRAM
|
|
1111
|
+
*/
|
|
1112
|
+
/**
|
|
1113
|
+
* Returns a list of labeled datapoints for 'bins' of data defined as ticks
|
|
1114
|
+
* over a numerical range. Suitable for histogram charts.
|
|
1115
|
+
*/
|
|
1116
|
+
getHistogram(options) {
|
|
1117
|
+
try {
|
|
1118
|
+
const _this4 = this;
|
|
1119
|
+
const {
|
|
1120
|
+
filterOwner,
|
|
929
1121
|
spatialFilter,
|
|
1122
|
+
abortController,
|
|
930
1123
|
...params
|
|
931
1124
|
} = options;
|
|
932
1125
|
const {
|
|
933
1126
|
column,
|
|
934
|
-
operationColumn,
|
|
935
|
-
joinOperation,
|
|
936
1127
|
operation,
|
|
937
|
-
|
|
938
|
-
stepMultiplier,
|
|
939
|
-
splitByCategory,
|
|
940
|
-
splitByCategoryLimit,
|
|
941
|
-
splitByCategoryValues
|
|
1128
|
+
ticks
|
|
942
1129
|
} = params;
|
|
943
1130
|
return Promise.resolve(executeModel({
|
|
944
|
-
model: '
|
|
1131
|
+
model: 'histogram',
|
|
945
1132
|
source: {
|
|
946
|
-
...
|
|
1133
|
+
..._this4.getModelSource(filterOwner),
|
|
947
1134
|
spatialFilter
|
|
948
1135
|
},
|
|
949
1136
|
params: {
|
|
950
1137
|
column,
|
|
951
|
-
stepSize,
|
|
952
|
-
stepMultiplier,
|
|
953
|
-
operationColumn: operationColumn || column,
|
|
954
|
-
joinOperation,
|
|
955
1138
|
operation,
|
|
956
|
-
|
|
957
|
-
splitByCategoryLimit,
|
|
958
|
-
splitByCategoryValues
|
|
1139
|
+
ticks
|
|
959
1140
|
},
|
|
960
1141
|
opts: {
|
|
961
1142
|
abortController
|
|
962
1143
|
}
|
|
963
|
-
}).then(res => ({
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1144
|
+
}).then(res => normalizeObjectKeys(res.rows))).then(function (data) {
|
|
1145
|
+
if (data.length) {
|
|
1146
|
+
// Given N ticks the API returns up to N+1 bins, omitting any empty bins. Bins
|
|
1147
|
+
// include 1 bin below the lowest tick, N-1 between ticks, and 1 bin above the highest tick.
|
|
1148
|
+
const result = Array(ticks.length + 1).fill(0);
|
|
1149
|
+
data.forEach(_ref2 => {
|
|
1150
|
+
let {
|
|
1151
|
+
tick,
|
|
1152
|
+
value
|
|
1153
|
+
} = _ref2;
|
|
1154
|
+
return result[tick] = value;
|
|
1155
|
+
});
|
|
1156
|
+
return result;
|
|
1157
|
+
}
|
|
1158
|
+
return [];
|
|
1159
|
+
});
|
|
967
1160
|
} catch (e) {
|
|
968
1161
|
return Promise.reject(e);
|
|
969
1162
|
}
|
|
970
1163
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
type: 'query',
|
|
1008
|
-
data: this.props.sqlQuery,
|
|
1009
|
-
queryParameters: this.props.queryParameters
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
/**
|
|
1015
|
-
* Source for Widget API requests on a data source defined as a table.
|
|
1016
|
-
*
|
|
1017
|
-
* Generally not intended to be constructed directly. Instead, call
|
|
1018
|
-
* {@link vectorTableSource}, {@link h3TableSource}, or {@link quadbinTableSource},
|
|
1019
|
-
* which can be shared with map layers. Sources contain a `widgetSource` property,
|
|
1020
|
-
* for use by widget implementations.
|
|
1021
|
-
*
|
|
1022
|
-
* Example:
|
|
1023
|
-
*
|
|
1024
|
-
* ```javascript
|
|
1025
|
-
* import { vectorTableSource } from '@carto/api-client';
|
|
1026
|
-
*
|
|
1027
|
-
* const data = vectorTableSource({
|
|
1028
|
-
* accessToken: '••••',
|
|
1029
|
-
* connectionName: 'carto_dw',
|
|
1030
|
-
* tableName: 'carto-demo-data.demo_tables.retail_stores'
|
|
1031
|
-
* });
|
|
1032
|
-
*
|
|
1033
|
-
* const { widgetSource } = await data;
|
|
1034
|
-
* ```
|
|
1035
|
-
*/
|
|
1036
|
-
class WidgetTableSource extends WidgetBaseSource {
|
|
1037
|
-
getModelSource(owner) {
|
|
1038
|
-
return {
|
|
1039
|
-
...super._getModelSource(owner),
|
|
1040
|
-
type: 'table',
|
|
1041
|
-
data: this.props.tableName
|
|
1042
|
-
};
|
|
1164
|
+
/****************************************************************************
|
|
1165
|
+
* RANGE
|
|
1166
|
+
*/
|
|
1167
|
+
/**
|
|
1168
|
+
* Returns a range (min and max) for a numerical column of matching rows.
|
|
1169
|
+
* Suitable for displaying certain 'headline' or 'scorecard' statistics,
|
|
1170
|
+
* or rendering a range slider UI for filtering.
|
|
1171
|
+
*/
|
|
1172
|
+
getRange(options) {
|
|
1173
|
+
try {
|
|
1174
|
+
const _this5 = this;
|
|
1175
|
+
const {
|
|
1176
|
+
filterOwner,
|
|
1177
|
+
spatialFilter,
|
|
1178
|
+
abortController,
|
|
1179
|
+
...params
|
|
1180
|
+
} = options;
|
|
1181
|
+
const {
|
|
1182
|
+
column
|
|
1183
|
+
} = params;
|
|
1184
|
+
return Promise.resolve(executeModel({
|
|
1185
|
+
model: 'range',
|
|
1186
|
+
source: {
|
|
1187
|
+
..._this5.getModelSource(filterOwner),
|
|
1188
|
+
spatialFilter
|
|
1189
|
+
},
|
|
1190
|
+
params: {
|
|
1191
|
+
column
|
|
1192
|
+
},
|
|
1193
|
+
opts: {
|
|
1194
|
+
abortController
|
|
1195
|
+
}
|
|
1196
|
+
}).then(res => normalizeObjectKeys(res.rows[0])));
|
|
1197
|
+
} catch (e) {
|
|
1198
|
+
return Promise.reject(e);
|
|
1199
|
+
}
|
|
1043
1200
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
/**
|
|
1096
|
-
* Converts camelCase to Camel Case
|
|
1097
|
-
*/
|
|
1098
|
-
function formatErrorKey(key) {
|
|
1099
|
-
return key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
// deck.gl
|
|
1103
|
-
// SPDX-License-Identifier: MIT
|
|
1104
|
-
// Copyright (c) vis.gl contributors
|
|
1105
|
-
function joinPath() {
|
|
1106
|
-
return [].slice.call(arguments).map(part => part.endsWith('/') ? part.slice(0, -1) : part).join('/');
|
|
1107
|
-
}
|
|
1108
|
-
function buildV3Path(apiBaseUrl, version, endpoint) {
|
|
1109
|
-
return joinPath(apiBaseUrl, version, endpoint, ...[].slice.call(arguments, 3));
|
|
1110
|
-
}
|
|
1111
|
-
/** @internal Required by fetchMap(). */
|
|
1112
|
-
function buildPublicMapUrl(_ref) {
|
|
1113
|
-
let {
|
|
1114
|
-
apiBaseUrl,
|
|
1115
|
-
cartoMapId
|
|
1116
|
-
} = _ref;
|
|
1117
|
-
return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId);
|
|
1118
|
-
}
|
|
1119
|
-
/** @internal Required by fetchMap(). */
|
|
1120
|
-
function buildStatsUrl(_ref2) {
|
|
1121
|
-
let {
|
|
1122
|
-
attribute,
|
|
1123
|
-
apiBaseUrl,
|
|
1124
|
-
connectionName,
|
|
1125
|
-
source,
|
|
1126
|
-
type
|
|
1127
|
-
} = _ref2;
|
|
1128
|
-
if (type === 'query') {
|
|
1129
|
-
return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, attribute);
|
|
1130
|
-
}
|
|
1131
|
-
// type === 'table'
|
|
1132
|
-
return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, source, attribute);
|
|
1133
|
-
}
|
|
1134
|
-
function buildSourceUrl(_ref3) {
|
|
1135
|
-
let {
|
|
1136
|
-
apiBaseUrl,
|
|
1137
|
-
connectionName,
|
|
1138
|
-
endpoint
|
|
1139
|
-
} = _ref3;
|
|
1140
|
-
return buildV3Path(apiBaseUrl, 'v3', 'maps', connectionName, endpoint);
|
|
1141
|
-
}
|
|
1142
|
-
function buildQueryUrl(_ref4) {
|
|
1143
|
-
let {
|
|
1144
|
-
apiBaseUrl,
|
|
1145
|
-
connectionName
|
|
1146
|
-
} = _ref4;
|
|
1147
|
-
return buildV3Path(apiBaseUrl, 'v3', 'sql', connectionName, 'query');
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
// deck.gl
|
|
1151
|
-
const requestWithParameters = function (_ref) {
|
|
1152
|
-
let {
|
|
1153
|
-
baseUrl,
|
|
1154
|
-
parameters = {},
|
|
1155
|
-
headers: customHeaders = {},
|
|
1156
|
-
errorContext,
|
|
1157
|
-
maxLengthURL = DEFAULT_MAX_LENGTH_URL,
|
|
1158
|
-
localCache
|
|
1159
|
-
} = _ref;
|
|
1160
|
-
try {
|
|
1161
|
-
// Parameters added to all requests issued with `requestWithParameters()`.
|
|
1162
|
-
// These parameters override parameters already in the base URL, but not
|
|
1163
|
-
// user-provided parameters.
|
|
1164
|
-
parameters = {
|
|
1165
|
-
v: V3_MINOR_VERSION,
|
|
1166
|
-
client: getClient(),
|
|
1167
|
-
...(typeof deck !== 'undefined' && deck.VERSION && {
|
|
1168
|
-
deckglVersion: deck.VERSION
|
|
1169
|
-
}),
|
|
1170
|
-
...parameters
|
|
1171
|
-
};
|
|
1172
|
-
baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters));
|
|
1173
|
-
const key = createCacheKey(baseUrl, parameters, customHeaders);
|
|
1174
|
-
const {
|
|
1175
|
-
cache: REQUEST_CACHE,
|
|
1176
|
-
canReadCache,
|
|
1177
|
-
canStoreInCache
|
|
1178
|
-
} = getCacheSettings(localCache);
|
|
1179
|
-
if (canReadCache && REQUEST_CACHE.has(key)) {
|
|
1180
|
-
return Promise.resolve(REQUEST_CACHE.get(key));
|
|
1181
|
-
}
|
|
1182
|
-
const url = createURLWithParameters(baseUrl, parameters);
|
|
1183
|
-
const headers = {
|
|
1184
|
-
...DEFAULT_HEADERS,
|
|
1185
|
-
...customHeaders
|
|
1186
|
-
};
|
|
1187
|
-
/* global fetch */
|
|
1188
|
-
const fetchPromise = url.length > maxLengthURL ? fetch(baseUrl, {
|
|
1189
|
-
method: 'POST',
|
|
1190
|
-
body: JSON.stringify(parameters),
|
|
1191
|
-
headers
|
|
1192
|
-
}) : fetch(url, {
|
|
1193
|
-
headers
|
|
1194
|
-
});
|
|
1195
|
-
let response;
|
|
1196
|
-
let responseJson;
|
|
1197
|
-
const jsonPromise = fetchPromise.then(_response => {
|
|
1198
|
-
response = _response;
|
|
1199
|
-
return response.json();
|
|
1200
|
-
}).then(json => {
|
|
1201
|
-
responseJson = json;
|
|
1202
|
-
if (!response || !response.ok) {
|
|
1203
|
-
throw new Error(json.error);
|
|
1204
|
-
}
|
|
1205
|
-
return json;
|
|
1206
|
-
}).catch(error => {
|
|
1207
|
-
if (canStoreInCache) {
|
|
1208
|
-
REQUEST_CACHE.delete(key);
|
|
1209
|
-
}
|
|
1210
|
-
throw new CartoAPIError(error, errorContext, response, responseJson);
|
|
1211
|
-
});
|
|
1212
|
-
if (canStoreInCache) {
|
|
1213
|
-
REQUEST_CACHE.set(key, jsonPromise);
|
|
1214
|
-
}
|
|
1215
|
-
return Promise.resolve(jsonPromise);
|
|
1216
|
-
} catch (e) {
|
|
1217
|
-
return Promise.reject(e);
|
|
1218
|
-
}
|
|
1219
|
-
};
|
|
1220
|
-
const DEFAULT_HEADERS = {
|
|
1221
|
-
Accept: 'application/json',
|
|
1222
|
-
'Content-Type': 'application/json'
|
|
1223
|
-
};
|
|
1224
|
-
const DEFAULT_REQUEST_CACHE = new Map();
|
|
1225
|
-
function getCacheSettings(localCache) {
|
|
1226
|
-
const canReadCache = localCache?.cacheControl?.includes('no-cache') ? false : true;
|
|
1227
|
-
const canStoreInCache = localCache?.cacheControl?.includes('no-store') ? false : true;
|
|
1228
|
-
const cache = localCache?.cache || DEFAULT_REQUEST_CACHE;
|
|
1229
|
-
return {
|
|
1230
|
-
cache,
|
|
1231
|
-
canReadCache,
|
|
1232
|
-
canStoreInCache
|
|
1233
|
-
};
|
|
1234
|
-
}
|
|
1235
|
-
function createCacheKey(baseUrl, parameters, headers) {
|
|
1236
|
-
const parameterEntries = Object.entries(parameters).sort((_ref2, _ref3) => {
|
|
1237
|
-
let [a] = _ref2;
|
|
1238
|
-
let [b] = _ref3;
|
|
1239
|
-
return a > b ? 1 : -1;
|
|
1240
|
-
});
|
|
1241
|
-
const headerEntries = Object.entries(headers).sort((_ref4, _ref5) => {
|
|
1242
|
-
let [a] = _ref4;
|
|
1243
|
-
let [b] = _ref5;
|
|
1244
|
-
return a > b ? 1 : -1;
|
|
1245
|
-
});
|
|
1246
|
-
return JSON.stringify({
|
|
1247
|
-
baseUrl,
|
|
1248
|
-
parameters: parameterEntries,
|
|
1249
|
-
headers: headerEntries
|
|
1250
|
-
});
|
|
1251
|
-
}
|
|
1252
|
-
/**
|
|
1253
|
-
* Appends query string parameters to a URL. Existing URL parameters are kept,
|
|
1254
|
-
* unless there is a conflict, in which case the new parameters override
|
|
1255
|
-
* those already in the URL.
|
|
1256
|
-
*/
|
|
1257
|
-
function createURLWithParameters(baseUrlString, parameters) {
|
|
1258
|
-
const baseUrl = new URL(baseUrlString);
|
|
1259
|
-
for (const [key, value] of Object.entries(parameters)) {
|
|
1260
|
-
if (isPureObject(value) || Array.isArray(value)) {
|
|
1261
|
-
baseUrl.searchParams.set(key, JSON.stringify(value));
|
|
1262
|
-
} else {
|
|
1263
|
-
baseUrl.searchParams.set(key, value.toString());
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
return baseUrl.toString();
|
|
1267
|
-
}
|
|
1268
|
-
/**
|
|
1269
|
-
* Deletes query string parameters from a URL.
|
|
1270
|
-
*/
|
|
1271
|
-
function excludeURLParameters(baseUrlString, parameters) {
|
|
1272
|
-
const baseUrl = new URL(baseUrlString);
|
|
1273
|
-
for (const param of parameters) {
|
|
1274
|
-
if (baseUrl.searchParams.has(param)) {
|
|
1275
|
-
baseUrl.searchParams.delete(param);
|
|
1201
|
+
/****************************************************************************
|
|
1202
|
+
* SCATTER
|
|
1203
|
+
*/
|
|
1204
|
+
/**
|
|
1205
|
+
* Returns a list of bivariate datapoints defined as numerical 'x' and 'y'
|
|
1206
|
+
* values. Suitable for rendering scatter plots.
|
|
1207
|
+
*/
|
|
1208
|
+
getScatter(options) {
|
|
1209
|
+
try {
|
|
1210
|
+
const _this6 = this;
|
|
1211
|
+
const {
|
|
1212
|
+
filterOwner,
|
|
1213
|
+
spatialFilter,
|
|
1214
|
+
abortController,
|
|
1215
|
+
...params
|
|
1216
|
+
} = options;
|
|
1217
|
+
const {
|
|
1218
|
+
xAxisColumn,
|
|
1219
|
+
xAxisJoinOperation,
|
|
1220
|
+
yAxisColumn,
|
|
1221
|
+
yAxisJoinOperation
|
|
1222
|
+
} = params;
|
|
1223
|
+
// Make sure this is sync with the same constant in cloud-native/maps-api
|
|
1224
|
+
const HARD_LIMIT = 500;
|
|
1225
|
+
return Promise.resolve(executeModel({
|
|
1226
|
+
model: 'scatterplot',
|
|
1227
|
+
source: {
|
|
1228
|
+
..._this6.getModelSource(filterOwner),
|
|
1229
|
+
spatialFilter
|
|
1230
|
+
},
|
|
1231
|
+
params: {
|
|
1232
|
+
xAxisColumn,
|
|
1233
|
+
xAxisJoinOperation,
|
|
1234
|
+
yAxisColumn,
|
|
1235
|
+
yAxisJoinOperation,
|
|
1236
|
+
limit: HARD_LIMIT
|
|
1237
|
+
},
|
|
1238
|
+
opts: {
|
|
1239
|
+
abortController
|
|
1240
|
+
}
|
|
1241
|
+
}).then(res => normalizeObjectKeys(res.rows)).then(res => res.map(_ref3 => {
|
|
1242
|
+
let {
|
|
1243
|
+
x,
|
|
1244
|
+
y
|
|
1245
|
+
} = _ref3;
|
|
1246
|
+
return [x, y];
|
|
1247
|
+
})));
|
|
1248
|
+
} catch (e) {
|
|
1249
|
+
return Promise.reject(e);
|
|
1276
1250
|
}
|
|
1277
1251
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1252
|
+
/****************************************************************************
|
|
1253
|
+
* TABLE
|
|
1254
|
+
*/
|
|
1255
|
+
/**
|
|
1256
|
+
* Returns a list of arbitrary data rows, with support for pagination and
|
|
1257
|
+
* sorting. Suitable for displaying tables and lists.
|
|
1258
|
+
*/
|
|
1259
|
+
getTable(options) {
|
|
1260
|
+
try {
|
|
1261
|
+
const _this7 = this;
|
|
1262
|
+
const {
|
|
1263
|
+
filterOwner,
|
|
1264
|
+
spatialFilter,
|
|
1265
|
+
abortController,
|
|
1266
|
+
...params
|
|
1267
|
+
} = options;
|
|
1268
|
+
const {
|
|
1269
|
+
columns,
|
|
1270
|
+
sortBy,
|
|
1271
|
+
sortDirection,
|
|
1272
|
+
offset = 0,
|
|
1273
|
+
limit = 10
|
|
1274
|
+
} = params;
|
|
1275
|
+
return Promise.resolve(executeModel({
|
|
1276
|
+
model: 'table',
|
|
1277
|
+
source: {
|
|
1278
|
+
..._this7.getModelSource(filterOwner),
|
|
1279
|
+
spatialFilter
|
|
1280
|
+
},
|
|
1281
|
+
params: {
|
|
1282
|
+
column: columns,
|
|
1283
|
+
sortBy,
|
|
1284
|
+
sortDirection,
|
|
1285
|
+
limit,
|
|
1286
|
+
offset
|
|
1287
|
+
},
|
|
1288
|
+
opts: {
|
|
1289
|
+
abortController
|
|
1290
|
+
}
|
|
1291
|
+
}).then(res => ({
|
|
1292
|
+
// Avoid `normalizeObjectKeys()`, which changes column names.
|
|
1293
|
+
rows: res.rows ?? res.ROWS,
|
|
1294
|
+
totalCount: res.metadata?.total ?? res.METADATA?.TOTAL
|
|
1295
|
+
})));
|
|
1296
|
+
} catch (e) {
|
|
1297
|
+
return Promise.reject(e);
|
|
1300
1298
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
baseUrl: dataUrl,
|
|
1349
|
-
headers,
|
|
1350
|
-
errorContext,
|
|
1351
|
-
maxLengthURL,
|
|
1352
|
-
localCache
|
|
1353
|
-
})).then(function (json) {
|
|
1354
|
-
if (accessToken) {
|
|
1355
|
-
json.accessToken = accessToken;
|
|
1356
|
-
}
|
|
1357
|
-
_exit = 1;
|
|
1358
|
-
return json;
|
|
1359
|
-
});
|
|
1299
|
+
}
|
|
1300
|
+
/****************************************************************************
|
|
1301
|
+
* TIME SERIES
|
|
1302
|
+
*/
|
|
1303
|
+
/**
|
|
1304
|
+
* Returns a series of labeled numerical values, grouped into equally-sized
|
|
1305
|
+
* time intervals. Suitable for rendering time series charts.
|
|
1306
|
+
*/
|
|
1307
|
+
getTimeSeries(options) {
|
|
1308
|
+
try {
|
|
1309
|
+
const _this8 = this;
|
|
1310
|
+
const {
|
|
1311
|
+
filterOwner,
|
|
1312
|
+
abortController,
|
|
1313
|
+
spatialFilter,
|
|
1314
|
+
...params
|
|
1315
|
+
} = options;
|
|
1316
|
+
const {
|
|
1317
|
+
column,
|
|
1318
|
+
operationColumn,
|
|
1319
|
+
joinOperation,
|
|
1320
|
+
operation,
|
|
1321
|
+
stepSize,
|
|
1322
|
+
stepMultiplier,
|
|
1323
|
+
splitByCategory,
|
|
1324
|
+
splitByCategoryLimit,
|
|
1325
|
+
splitByCategoryValues
|
|
1326
|
+
} = params;
|
|
1327
|
+
return Promise.resolve(executeModel({
|
|
1328
|
+
model: 'timeseries',
|
|
1329
|
+
source: {
|
|
1330
|
+
..._this8.getModelSource(filterOwner),
|
|
1331
|
+
spatialFilter
|
|
1332
|
+
},
|
|
1333
|
+
params: {
|
|
1334
|
+
column,
|
|
1335
|
+
stepSize,
|
|
1336
|
+
stepMultiplier,
|
|
1337
|
+
operationColumn: operationColumn || column,
|
|
1338
|
+
joinOperation,
|
|
1339
|
+
operation,
|
|
1340
|
+
splitByCategory,
|
|
1341
|
+
splitByCategoryLimit,
|
|
1342
|
+
splitByCategoryValues
|
|
1343
|
+
},
|
|
1344
|
+
opts: {
|
|
1345
|
+
abortController
|
|
1360
1346
|
}
|
|
1361
|
-
}(
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1347
|
+
}).then(res => ({
|
|
1348
|
+
rows: normalizeObjectKeys(res.rows),
|
|
1349
|
+
categories: res.metadata?.categories
|
|
1350
|
+
})));
|
|
1351
|
+
} catch (e) {
|
|
1352
|
+
return Promise.reject(e);
|
|
1353
|
+
}
|
|
1366
1354
|
}
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1355
|
+
}
|
|
1356
|
+
WidgetBaseSource.defaultProps = {
|
|
1357
|
+
apiVersion: exports.ApiVersion.V3,
|
|
1369
1358
|
apiBaseUrl: DEFAULT_API_BASE_URL,
|
|
1370
1359
|
clientId: getClient(),
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1360
|
+
filters: {},
|
|
1361
|
+
filtersLogicalOperator: 'and',
|
|
1362
|
+
geoColumn: DEFAULT_GEO_COLUMN
|
|
1374
1363
|
};
|
|
1375
1364
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1365
|
+
/**
|
|
1366
|
+
* Source for Widget API requests on a data source defined by a SQL query.
|
|
1367
|
+
*
|
|
1368
|
+
* Generally not intended to be constructed directly. Instead, call
|
|
1369
|
+
* {@link vectorQuerySource}, {@link h3QuerySource}, or {@link quadbinQuerySource},
|
|
1370
|
+
* which can be shared with map layers. Sources contain a `widgetSource` property,
|
|
1371
|
+
* for use by widget implementations.
|
|
1372
|
+
*
|
|
1373
|
+
* Example:
|
|
1374
|
+
*
|
|
1375
|
+
* ```javascript
|
|
1376
|
+
* import { vectorQuerySource } from '@carto/api-client';
|
|
1377
|
+
*
|
|
1378
|
+
* const data = vectorQuerySource({
|
|
1379
|
+
* accessToken: '••••',
|
|
1380
|
+
* connectionName: 'carto_dw',
|
|
1381
|
+
* sqlQuery: 'SELECT * FROM carto-demo-data.demo_tables.retail_stores'
|
|
1382
|
+
* });
|
|
1383
|
+
*
|
|
1384
|
+
* const { widgetSource } = await data;
|
|
1385
|
+
* ```
|
|
1386
|
+
*/
|
|
1387
|
+
class WidgetQuerySource extends WidgetBaseSource {
|
|
1388
|
+
getModelSource(owner) {
|
|
1389
|
+
return {
|
|
1390
|
+
...super._getModelSource(owner),
|
|
1391
|
+
type: 'query',
|
|
1392
|
+
data: this.props.sqlQuery,
|
|
1393
|
+
queryParameters: this.props.queryParameters
|
|
1389
1394
|
};
|
|
1390
|
-
if (columns) {
|
|
1391
|
-
urlParameters.columns = columns.join(',');
|
|
1392
|
-
}
|
|
1393
|
-
if (filters) {
|
|
1394
|
-
urlParameters.filters = filters;
|
|
1395
|
-
}
|
|
1396
|
-
if (queryParameters) {
|
|
1397
|
-
urlParameters.queryParameters = queryParameters;
|
|
1398
|
-
}
|
|
1399
|
-
return Promise.resolve(baseSource('boundary', options, urlParameters));
|
|
1400
|
-
} catch (e) {
|
|
1401
|
-
return Promise.reject(e);
|
|
1402
1395
|
}
|
|
1403
|
-
}
|
|
1396
|
+
}
|
|
1404
1397
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1398
|
+
/**
|
|
1399
|
+
* Source for Widget API requests on a data source defined as a table.
|
|
1400
|
+
*
|
|
1401
|
+
* Generally not intended to be constructed directly. Instead, call
|
|
1402
|
+
* {@link vectorTableSource}, {@link h3TableSource}, or {@link quadbinTableSource},
|
|
1403
|
+
* which can be shared with map layers. Sources contain a `widgetSource` property,
|
|
1404
|
+
* for use by widget implementations.
|
|
1405
|
+
*
|
|
1406
|
+
* Example:
|
|
1407
|
+
*
|
|
1408
|
+
* ```javascript
|
|
1409
|
+
* import { vectorTableSource } from '@carto/api-client';
|
|
1410
|
+
*
|
|
1411
|
+
* const data = vectorTableSource({
|
|
1412
|
+
* accessToken: '••••',
|
|
1413
|
+
* connectionName: 'carto_dw',
|
|
1414
|
+
* tableName: 'carto-demo-data.demo_tables.retail_stores'
|
|
1415
|
+
* });
|
|
1416
|
+
*
|
|
1417
|
+
* const { widgetSource } = await data;
|
|
1418
|
+
* ```
|
|
1419
|
+
*/
|
|
1420
|
+
class WidgetTableSource extends WidgetBaseSource {
|
|
1421
|
+
getModelSource(owner) {
|
|
1422
|
+
return {
|
|
1423
|
+
...super._getModelSource(owner),
|
|
1424
|
+
type: 'table',
|
|
1425
|
+
data: this.props.tableName
|
|
1417
1426
|
};
|
|
1418
|
-
if (columns) {
|
|
1419
|
-
urlParameters.columns = columns.join(',');
|
|
1420
|
-
}
|
|
1421
|
-
if (filters) {
|
|
1422
|
-
urlParameters.filters = filters;
|
|
1423
|
-
}
|
|
1424
|
-
return Promise.resolve(baseSource('boundary', options, urlParameters));
|
|
1425
|
-
} catch (e) {
|
|
1426
|
-
return Promise.reject(e);
|
|
1427
1427
|
}
|
|
1428
|
-
}
|
|
1428
|
+
}
|
|
1429
1429
|
|
|
1430
1430
|
// deck.gl
|
|
1431
1431
|
const h3QuerySource = function (options) {
|
|
@@ -1617,7 +1617,8 @@ const vectorQuerySource = function (options) {
|
|
|
1617
1617
|
spatialDataColumn = 'geom',
|
|
1618
1618
|
sqlQuery,
|
|
1619
1619
|
tileResolution = DEFAULT_TILE_RESOLUTION,
|
|
1620
|
-
queryParameters
|
|
1620
|
+
queryParameters,
|
|
1621
|
+
aggregationExp
|
|
1621
1622
|
} = options;
|
|
1622
1623
|
const urlParameters = {
|
|
1623
1624
|
spatialDataColumn,
|
|
@@ -1634,6 +1635,9 @@ const vectorQuerySource = function (options) {
|
|
|
1634
1635
|
if (queryParameters) {
|
|
1635
1636
|
urlParameters.queryParameters = queryParameters;
|
|
1636
1637
|
}
|
|
1638
|
+
if (aggregationExp) {
|
|
1639
|
+
urlParameters.aggregationExp = aggregationExp;
|
|
1640
|
+
}
|
|
1637
1641
|
return Promise.resolve(baseSource('query', options, urlParameters).then(result => ({
|
|
1638
1642
|
...result,
|
|
1639
1643
|
widgetSource: new WidgetQuerySource(options)
|
|
@@ -1651,7 +1655,8 @@ const vectorTableSource = function (options) {
|
|
|
1651
1655
|
filters,
|
|
1652
1656
|
spatialDataColumn = 'geom',
|
|
1653
1657
|
tableName,
|
|
1654
|
-
tileResolution = DEFAULT_TILE_RESOLUTION
|
|
1658
|
+
tileResolution = DEFAULT_TILE_RESOLUTION,
|
|
1659
|
+
aggregationExp
|
|
1655
1660
|
} = options;
|
|
1656
1661
|
const urlParameters = {
|
|
1657
1662
|
name: tableName,
|
|
@@ -1665,6 +1670,9 @@ const vectorTableSource = function (options) {
|
|
|
1665
1670
|
if (filters) {
|
|
1666
1671
|
urlParameters.filters = filters;
|
|
1667
1672
|
}
|
|
1673
|
+
if (aggregationExp) {
|
|
1674
|
+
urlParameters.aggregationExp = aggregationExp;
|
|
1675
|
+
}
|
|
1668
1676
|
return Promise.resolve(baseSource('table', options, urlParameters).then(result => ({
|
|
1669
1677
|
...result,
|
|
1670
1678
|
widgetSource: new WidgetTableSource(options)
|