@carto/api-client 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/build/api/query.d.ts +1 -1
  3. package/build/api-client.cjs +1136 -984
  4. package/build/api-client.cjs.map +1 -1
  5. package/build/api-client.modern.js +1014 -869
  6. package/build/api-client.modern.js.map +1 -1
  7. package/build/index.d.ts +1 -1
  8. package/build/models/model.d.ts +7 -1
  9. package/build/sources/boundary-query-source.d.ts +2 -1
  10. package/build/sources/boundary-table-source.d.ts +2 -1
  11. package/build/sources/h3-query-source.d.ts +2 -1
  12. package/build/sources/h3-table-source.d.ts +2 -1
  13. package/build/sources/h3-tileset-source.d.ts +2 -1
  14. package/build/sources/index.d.ts +13 -14
  15. package/build/sources/quadbin-query-source.d.ts +2 -1
  16. package/build/sources/quadbin-table-source.d.ts +2 -1
  17. package/build/sources/quadbin-tileset-source.d.ts +2 -1
  18. package/build/sources/raster-source.d.ts +2 -1
  19. package/build/sources/types.d.ts +48 -37
  20. package/build/sources/vector-query-source.d.ts +2 -1
  21. package/build/sources/vector-table-source.d.ts +2 -1
  22. package/build/sources/vector-tileset-source.d.ts +2 -1
  23. package/build/spatial-index.d.ts +8 -0
  24. package/build/utils.d.ts +1 -1
  25. package/build/widget-sources/types.d.ts +9 -1
  26. package/build/widget-sources/widget-base-source.d.ts +3 -3
  27. package/package.json +1 -1
  28. package/src/api/query.ts +1 -2
  29. package/src/index.ts +1 -36
  30. package/src/models/model.ts +47 -24
  31. package/src/sources/boundary-query-source.ts +4 -2
  32. package/src/sources/boundary-table-source.ts +4 -2
  33. package/src/sources/h3-query-source.ts +10 -2
  34. package/src/sources/h3-table-source.ts +9 -2
  35. package/src/sources/h3-tileset-source.ts +4 -2
  36. package/src/sources/index.ts +54 -24
  37. package/src/sources/quadbin-query-source.ts +10 -2
  38. package/src/sources/quadbin-table-source.ts +10 -2
  39. package/src/sources/quadbin-tileset-source.ts +4 -2
  40. package/src/sources/raster-source.ts +4 -2
  41. package/src/sources/types.ts +54 -40
  42. package/src/sources/vector-query-source.ts +13 -2
  43. package/src/sources/vector-table-source.ts +14 -2
  44. package/src/sources/vector-tileset-source.ts +4 -2
  45. package/src/spatial-index.ts +111 -0
  46. package/src/utils.ts +1 -1
  47. package/src/widget-sources/types.ts +10 -1
  48. package/src/widget-sources/widget-base-source.ts +183 -23
@@ -381,1051 +381,1169 @@ const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4;
381
381
  */
382
382
  const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6;
383
383
 
384
- /**
385
- * Return more descriptive error from API
386
- * @internalRemarks Source: @carto/react-api
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
- const makeCall = function (_ref2) {
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
- url,
405
- accessToken,
406
- opts
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
- try {
409
- let _exit;
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
- function dealWithApiError(_ref) {
413
+ // type === 'table'
414
+ return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, source, attribute);
415
+ }
416
+ function buildSourceUrl(_ref3) {
453
417
  let {
454
- response,
455
- data
456
- } = _ref;
457
- if (data.error === 'Column not found') {
458
- throw new InvalidColumnError(`${data.error} ${data.column_name}`);
459
- }
460
- if (typeof data.error === 'string' && data.error?.includes('Missing columns')) {
461
- throw new InvalidColumnError(data.error);
462
- }
463
- switch (response.status) {
464
- case 401:
465
- throw new Error('Unauthorized access. Invalid credentials');
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
- /** @internalRemarks Source: @carto/react-api */
475
- const AVAILABLE_MODELS = ['category', 'histogram', 'formula', 'pick', 'timeseries', 'range', 'scatterplot', 'table'];
476
- const {
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
- * Execute a SQL model request.
482
- * @internalRemarks Source: @carto/react-api
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
- function executeModel(props) {
485
- assert(props.source, 'executeModel: missing source');
486
- assert(props.model, 'executeModel: missing model');
487
- assert(props.params, 'executeModel: missing params');
488
- assert(AVAILABLE_MODELS.includes(props.model), `executeModel: model provided isn't valid. Available models: ${AVAILABLE_MODELS.join(', ')}`);
489
- const {
490
- model,
491
- source,
492
- params,
493
- opts
494
- } = props;
495
- const {
496
- type,
497
- apiVersion,
498
- apiBaseUrl,
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
- return makeCall({
549
- url,
550
- accessToken: source.accessToken,
551
- opts: {
552
- ...opts,
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
- * Source for Widget API requests on a data source defined by a SQL query.
563
- *
564
- * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
482
+ * Converts camelCase to Camel Case
565
483
  */
566
- class WidgetBaseSource {
567
- constructor(props) {
568
- this.props = void 0;
569
- this.props = {
570
- ...WidgetBaseSource.defaultProps,
571
- ...props
572
- };
573
- }
574
- _getModelSource(owner) {
575
- const props = this.props;
576
- return {
577
- apiVersion: props.apiVersion,
578
- apiBaseUrl: props.apiBaseUrl,
579
- clientId: props.clientId,
580
- accessToken: props.accessToken,
581
- connectionName: props.connectionName,
582
- filters: getApplicableFilters(owner, props.filters),
583
- filtersLogicalOperator: props.filtersLogicalOperator,
584
- geoColumn: props.geoColumn
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
585
509
  };
586
- }
587
- /****************************************************************************
588
- * CATEGORIES
589
- */
590
- /**
591
- * Returns a list of labeled datapoints for categorical data. Suitable for
592
- * charts including grouped bar charts, pie charts, and tree charts.
593
- */
594
- getCategories(options) {
595
- try {
596
- const _this = this;
597
- const {
598
- filterOwner,
599
- spatialFilter,
600
- abortController,
601
- ...params
602
- } = options;
603
- const {
604
- column,
605
- operation,
606
- operationColumn
607
- } = params;
608
- return Promise.resolve(executeModel({
609
- model: 'category',
610
- source: {
611
- ..._this.getModelSource(filterOwner),
612
- spatialFilter
613
- },
614
- params: {
615
- column,
616
- operation,
617
- operationColumn: operationColumn || column
618
- },
619
- opts: {
620
- abortController
621
- }
622
- }).then(res => normalizeObjectKeys(res.rows)));
623
- } catch (e) {
624
- return Promise.reject(e);
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
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);
625
552
  }
553
+ return Promise.resolve(jsonPromise);
554
+ } catch (e) {
555
+ return Promise.reject(e);
626
556
  }
627
- /****************************************************************************
628
- * FEATURES
629
- */
630
- /**
631
- * Given a list of feature IDs (as found in `_carto_feature_id`) returns all
632
- * matching features. In datasets containing features with duplicate geometries,
633
- * feature IDs may be duplicated (IDs are a hash of geometry) and so more
634
- * results may be returned than IDs in the request.
635
- * @internal
636
- * @experimental
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);
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());
683
602
  }
684
603
  }
685
- /****************************************************************************
686
- * FORMULA
687
- */
688
- /**
689
- * Returns a scalar numerical statistic over all matching data. Suitable
690
- * for 'headline' or 'scorecard' figures such as counts and sums.
691
- */
692
- getFormula(options) {
693
- try {
694
- const _this3 = this;
695
- const {
696
- filterOwner,
697
- spatialFilter,
698
- abortController,
699
- operationExp,
700
- ...params
701
- } = options;
702
- const {
703
- column,
704
- operation
705
- } = params;
706
- return Promise.resolve(executeModel({
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);
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);
723
614
  }
724
615
  }
725
- /****************************************************************************
726
- * HISTOGRAM
727
- */
728
- /**
729
- * Returns a list of labeled datapoints for 'bins' of data defined as ticks
730
- * over a numerical range. Suitable for histogram charts.
731
- */
732
- getHistogram(options) {
733
- try {
734
- const _this4 = this;
735
- const {
736
- filterOwner,
737
- spatialFilter,
738
- abortController,
739
- ...params
740
- } = options;
741
- const {
742
- column,
743
- operation,
744
- ticks
745
- } = params;
746
- return Promise.resolve(executeModel({
747
- model: 'histogram',
748
- source: {
749
- ..._this4.getModelSource(filterOwner),
750
- spatialFilter
751
- },
752
- params: {
753
- column,
754
- operation,
755
- ticks
756
- },
757
- opts: {
758
- abortController
759
- }
760
- }).then(res => normalizeObjectKeys(res.rows))).then(function (data) {
761
- if (data.length) {
762
- // Given N ticks the API returns up to N+1 bins, omitting any empty bins. Bins
763
- // include 1 bin below the lowest tick, N-1 between ticks, and 1 bin above the highest tick.
764
- const result = Array(ticks.length + 1).fill(0);
765
- data.forEach(_ref2 => {
766
- let {
767
- tick,
768
- value
769
- } = _ref2;
770
- return result[tick] = value;
771
- });
772
- return result;
773
- }
774
- return [];
775
- });
776
- } catch (e) {
777
- return Promise.reject(e);
778
- }
779
- }
780
- /****************************************************************************
781
- * RANGE
782
- */
783
- /**
784
- * Returns a range (min and max) for a numerical column of matching rows.
785
- * Suitable for displaying certain 'headline' or 'scorecard' statistics,
786
- * or rendering a range slider UI for filtering.
787
- */
788
- getRange(options) {
789
- try {
790
- const _this5 = this;
791
- const {
792
- filterOwner,
793
- spatialFilter,
794
- abortController,
795
- ...params
796
- } = options;
797
- const {
798
- column
799
- } = params;
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);
815
- }
816
- }
817
- /****************************************************************************
818
- * SCATTER
819
- */
820
- /**
821
- * Returns a list of bivariate datapoints defined as numerical 'x' and 'y'
822
- * values. Suitable for rendering scatter plots.
823
- */
824
- getScatter(options) {
825
- try {
826
- const _this6 = this;
827
- const {
828
- filterOwner,
829
- spatialFilter,
830
- abortController,
831
- ...params
832
- } = options;
833
- const {
834
- xAxisColumn,
835
- xAxisJoinOperation,
836
- yAxisColumn,
837
- yAxisJoinOperation
838
- } = params;
839
- // Make sure this is sync with the same constant in cloud-native/maps-api
840
- const HARD_LIMIT = 500;
841
- return Promise.resolve(executeModel({
842
- model: 'scatterplot',
843
- source: {
844
- ..._this6.getModelSource(filterOwner),
845
- spatialFilter
846
- },
847
- params: {
848
- xAxisColumn,
849
- xAxisJoinOperation,
850
- yAxisColumn,
851
- yAxisJoinOperation,
852
- limit: HARD_LIMIT
853
- },
854
- opts: {
855
- abortController
856
- }
857
- }).then(res => normalizeObjectKeys(res.rows)).then(res => res.map(_ref3 => {
858
- let {
859
- x,
860
- y
861
- } = _ref3;
862
- return [x, y];
863
- })));
864
- } catch (e) {
865
- return Promise.reject(e);
866
- }
867
- }
868
- /****************************************************************************
869
- * TABLE
870
- */
871
- /**
872
- * Returns a list of arbitrary data rows, with support for pagination and
873
- * sorting. Suitable for displaying tables and lists.
874
- */
875
- getTable(options) {
876
- try {
877
- const _this7 = this;
878
- const {
879
- filterOwner,
880
- spatialFilter,
881
- abortController,
882
- ...params
883
- } = options;
884
- const {
885
- columns,
886
- sortBy,
887
- sortDirection,
888
- offset = 0,
889
- limit = 10
890
- } = params;
891
- return Promise.resolve(executeModel({
892
- model: 'table',
893
- source: {
894
- ..._this7.getModelSource(filterOwner),
895
- spatialFilter
896
- },
897
- params: {
898
- column: columns,
899
- sortBy,
900
- sortDirection,
901
- limit,
902
- offset
903
- },
904
- opts: {
905
- abortController
906
- }
907
- }).then(res => ({
908
- // Avoid `normalizeObjectKeys()`, which changes column names.
909
- rows: res.rows ?? res.ROWS,
910
- totalCount: res.metadata?.total ?? res.METADATA?.TOTAL
911
- })));
912
- } catch (e) {
913
- 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
+ }
914
638
  }
915
- }
916
- /****************************************************************************
917
- * TIME SERIES
918
- */
919
- /**
920
- * Returns a series of labeled numerical values, grouped into equally-sized
921
- * time intervals. Suitable for rendering time series charts.
922
- */
923
- getTimeSeries(options) {
924
- try {
925
- const _this8 = this;
926
- const {
927
- filterOwner,
928
- abortController,
929
- spatialFilter,
930
- ...params
931
- } = options;
932
- const {
933
- column,
934
- operationColumn,
935
- joinOperation,
936
- operation,
937
- stepSize,
938
- stepMultiplier,
939
- splitByCategory,
940
- splitByCategoryLimit,
941
- splitByCategoryValues
942
- } = params;
943
- return Promise.resolve(executeModel({
944
- model: 'timeseries',
945
- source: {
946
- ..._this8.getModelSource(filterOwner),
947
- spatialFilter
948
- },
949
- params: {
950
- column,
951
- stepSize,
952
- stepMultiplier,
953
- operationColumn: operationColumn || column,
954
- joinOperation,
955
- operation,
956
- splitByCategory,
957
- splitByCategoryLimit,
958
- splitByCategoryValues
959
- },
960
- opts: {
961
- abortController
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;
697
+ });
962
698
  }
963
- }).then(res => ({
964
- rows: normalizeObjectKeys(res.rows),
965
- categories: res.metadata?.categories
966
- })));
967
- } catch (e) {
968
- return Promise.reject(e);
969
- }
699
+ }();
700
+ return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
701
+ });
702
+ } catch (e) {
703
+ return Promise.reject(e);
970
704
  }
971
- }
972
- WidgetBaseSource.defaultProps = {
973
- apiVersion: exports.ApiVersion.V3,
705
+ };
706
+ const SOURCE_DEFAULTS = {
974
707
  apiBaseUrl: DEFAULT_API_BASE_URL,
975
708
  clientId: getClient(),
976
- filters: {},
977
- filtersLogicalOperator: 'and',
978
- geoColumn: DEFAULT_GEO_COLUMN
709
+ format: 'tilejson',
710
+ headers: {},
711
+ maxLengthURL: DEFAULT_MAX_LENGTH_URL
979
712
  };
980
713
 
981
- /**
982
- * Source for Widget API requests on a data source defined by a SQL query.
983
- *
984
- * Generally not intended to be constructed directly. Instead, call
985
- * {@link vectorQuerySource}, {@link h3QuerySource}, or {@link quadbinQuerySource},
986
- * which can be shared with map layers. Sources contain a `widgetSource` property,
987
- * for use by widget implementations.
988
- *
989
- * Example:
990
- *
991
- * ```javascript
992
- * import { vectorQuerySource } from '@carto/api-client';
993
- *
994
- * const data = vectorQuerySource({
995
- * accessToken: '••••',
996
- * connectionName: 'carto_dw',
997
- * sqlQuery: 'SELECT * FROM carto-demo-data.demo_tables.retail_stores'
998
- * });
999
- *
1000
- * const { widgetSource } = await data;
1001
- * ```
1002
- */
1003
- class WidgetQuerySource extends WidgetBaseSource {
1004
- getModelSource(owner) {
1005
- return {
1006
- ...super._getModelSource(owner),
1007
- type: 'query',
1008
- data: this.props.sqlQuery,
1009
- queryParameters: this.props.queryParameters
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
1010
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;
736
+ }
737
+ return Promise.resolve(baseSource('boundary', options, urlParameters));
738
+ } catch (e) {
739
+ return Promise.reject(e);
1011
740
  }
1012
- }
741
+ };
1013
742
 
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
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
1042
755
  };
756
+ if (columns) {
757
+ urlParameters.columns = columns.join(',');
758
+ }
759
+ if (filters) {
760
+ urlParameters.filters = filters;
761
+ }
762
+ return Promise.resolve(baseSource('boundary', options, urlParameters));
763
+ } catch (e) {
764
+ return Promise.reject(e);
1043
765
  }
1044
- }
766
+ };
1045
767
 
1046
- // deck.gl
1047
- // SPDX-License-Identifier: MIT
1048
- // Copyright (c) vis.gl contributors
1049
768
  /**
1050
- *
1051
- * Custom error for reported errors in CARTO Maps API.
1052
- * Provides useful debugging information in console and context for applications.
1053
- *
769
+ * Return more descriptive error from API
770
+ * @internalRemarks Source: @carto/react-api
1054
771
  */
1055
- class CartoAPIError extends Error {
1056
- constructor(error, errorContext, response, responseJson) {
1057
- let responseString = 'Failed to connect';
1058
- if (response) {
1059
- responseString = 'Server returned: ';
1060
- if (response.status === 400) {
1061
- responseString += 'Bad request';
1062
- } else if (response.status === 401 || response.status === 403) {
1063
- responseString += 'Unauthorized access';
1064
- } else if (response.status === 404) {
1065
- responseString += 'Not found';
1066
- } else {
1067
- responseString += 'Error';
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
+ });
1068
801
  }
1069
- responseString += ` (${response.status}):`;
1070
- }
1071
- responseString += ` ${error.message || error}`;
1072
- let message = `${errorContext.requestType} API request failed`;
1073
- message += `\n${responseString}`;
1074
- for (const key of Object.keys(errorContext)) {
1075
- if (key === 'requestType') continue;
1076
- message += `\n${formatErrorKey(key)}: ${errorContext[key]}`;
802
+ return data;
1077
803
  }
1078
- message += '\n';
1079
- super(message);
1080
- /** Source error from server */
1081
- this.error = void 0;
1082
- /** Context (API call & parameters) in which error occured */
1083
- this.errorContext = void 0;
1084
- /** Response from server */
1085
- this.response = void 0;
1086
- /** JSON Response from server */
1087
- this.responseJson = void 0;
1088
- this.name = 'CartoAPIError';
1089
- this.response = response;
1090
- this.responseJson = responseJson;
1091
- this.error = error;
1092
- this.errorContext = errorContext;
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);
1093
855
  }
1094
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;
1095
864
  /**
1096
- * Converts camelCase to Camel Case
865
+ * Execute a SQL model request.
866
+ * @internalRemarks Source: @carto/react-api
1097
867
  */
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 {
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,
1114
882
  apiBaseUrl,
1115
- cartoMapId
1116
- } = _ref;
1117
- return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId);
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
+ spatialDataType = 'geo',
897
+ spatialFiltersMode = 'intersects',
898
+ spatialFiltersResolution = 0
899
+ } = source;
900
+ const queryParams = {
901
+ type,
902
+ client: clientId,
903
+ source: data,
904
+ params,
905
+ queryParameters: source.queryParameters || '',
906
+ filters,
907
+ filtersLogicalOperator
908
+ };
909
+ const spatialDataColumn = source.spatialDataColumn || DEFAULT_GEO_COLUMN;
910
+ // Picking Model API requires 'spatialDataColumn'.
911
+ if (model === 'pick') {
912
+ queryParams.spatialDataColumn = spatialDataColumn;
913
+ }
914
+ // API supports multiple filters, we apply it only to spatialDataColumn
915
+ const spatialFilters = source.spatialFilter ? {
916
+ [spatialDataColumn]: source.spatialFilter
917
+ } : undefined;
918
+ if (spatialFilters) {
919
+ queryParams.spatialFilters = spatialFilters;
920
+ queryParams.spatialDataColumn = spatialDataColumn;
921
+ queryParams.spatialDataType = spatialDataType;
922
+ }
923
+ if (spatialDataType !== 'geo') {
924
+ if (spatialFiltersResolution > 0) {
925
+ queryParams.spatialFiltersResolution = spatialFiltersResolution;
926
+ }
927
+ queryParams.spatialFiltersMode = spatialFiltersMode;
928
+ }
929
+ const urlWithSearchParams = url + '?' + objectToURLSearchParams(queryParams).toString();
930
+ const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
931
+ if (isGet) {
932
+ url = urlWithSearchParams;
933
+ }
934
+ return makeCall({
935
+ url,
936
+ accessToken: source.accessToken,
937
+ opts: {
938
+ ...opts,
939
+ method: isGet ? 'GET' : 'POST',
940
+ ...(!isGet && {
941
+ body: JSON.stringify(queryParams)
942
+ })
943
+ }
944
+ });
1118
945
  }
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);
946
+ function objectToURLSearchParams(object) {
947
+ const params = new URLSearchParams();
948
+ for (const key in object) {
949
+ if (isPureObject(object[key])) {
950
+ params.append(key, JSON.stringify(object[key]));
951
+ } else if (Array.isArray(object[key])) {
952
+ params.append(key, JSON.stringify(object[key]));
953
+ } else if (object[key] === null) {
954
+ params.append(key, 'null');
955
+ } else if (object[key] !== undefined) {
956
+ params.append(key, String(object[key]));
957
+ }
1130
958
  }
1131
- // type === 'table'
1132
- return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, source, attribute);
959
+ return params;
1133
960
  }
1134
- function buildSourceUrl(_ref3) {
1135
- let {
1136
- apiBaseUrl,
1137
- connectionName,
1138
- endpoint
1139
- } = _ref3;
1140
- return buildV3Path(apiBaseUrl, 'v3', 'maps', connectionName, endpoint);
961
+
962
+ const DEFAULT_TILE_SIZE = 512;
963
+ const QUADBIN_ZOOM_MAX_OFFSET = 4;
964
+ function getSpatialFiltersResolution(source, viewState) {
965
+ const dataResolution = source.dataResolution ?? Number.MAX_VALUE;
966
+ const aggregationResLevel = source.aggregationResLevel ?? (source.spatialDataType === 'h3' ? DEFAULT_AGGREGATION_RES_LEVEL_H3 : DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN);
967
+ const aggregationResLevelOffset = Math.max(0, Math.floor(aggregationResLevel));
968
+ const currentZoomInt = Math.ceil(viewState.zoom);
969
+ if (source.spatialDataType === 'h3') {
970
+ const tileSize = DEFAULT_TILE_SIZE;
971
+ const maxResolutionForZoom = maxH3SpatialFiltersResolutions.find(_ref => {
972
+ let [zoom] = _ref;
973
+ return zoom === currentZoomInt;
974
+ })?.[1] ?? Math.max(0, currentZoomInt - 3);
975
+ const maxSpatialFiltersResolution = maxResolutionForZoom ? Math.min(dataResolution, maxResolutionForZoom) : dataResolution;
976
+ const hexagonResolution = getHexagonResolution(viewState, tileSize) + aggregationResLevelOffset;
977
+ return Math.min(hexagonResolution, maxSpatialFiltersResolution);
978
+ }
979
+ if (source.spatialDataType === 'quadbin') {
980
+ const maxResolutionForZoom = currentZoomInt + QUADBIN_ZOOM_MAX_OFFSET;
981
+ const maxSpatialFiltersResolution = Math.min(dataResolution, maxResolutionForZoom);
982
+ const quadsResolution = Math.floor(viewState.zoom) + aggregationResLevelOffset;
983
+ return Math.min(quadsResolution, maxSpatialFiltersResolution);
984
+ }
985
+ return undefined;
1141
986
  }
1142
- function buildQueryUrl(_ref4) {
1143
- let {
1144
- apiBaseUrl,
1145
- connectionName
1146
- } = _ref4;
1147
- return buildV3Path(apiBaseUrl, 'v3', 'sql', connectionName, 'query');
987
+ const maxH3SpatialFiltersResolutions = [[20, 14], [19, 13], [18, 12], [17, 11], [16, 10], [15, 9], [14, 8], [13, 7], [12, 7], [11, 7], [10, 6], [9, 6], [8, 5], [7, 4], [6, 4], [5, 3], [4, 2], [3, 1], [2, 1], [1, 0]];
988
+ // stolen from https://github.com/visgl/deck.gl/blob/master/modules/carto/src/layers/h3-tileset-2d.ts
989
+ // Relative scale factor (0 = no biasing, 2 = a few hexagons cover view)
990
+ const BIAS = 2;
991
+ // Resolution conversion function. Takes a WebMercatorViewport and returns
992
+ // a H3 resolution such that the screen space size of the hexagons is
993
+ // similar
994
+ function getHexagonResolution(viewport, tileSize) {
995
+ // Difference in given tile size compared to deck's internal 512px tile size,
996
+ // expressed as an offset to the viewport zoom.
997
+ const zoomOffset = Math.log2(tileSize / DEFAULT_TILE_SIZE);
998
+ const hexagonScaleFactor = 2 / 3 * (viewport.zoom - zoomOffset);
999
+ const latitudeScaleFactor = Math.log(1 / Math.cos(Math.PI * viewport.latitude / 180));
1000
+ // Clip and bias
1001
+ return Math.max(0, Math.floor(hexagonScaleFactor + latitudeScaleFactor - BIAS));
1148
1002
  }
1149
1003
 
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
1004
+ /**
1005
+ * Source for Widget API requests on a data source defined by a SQL query.
1006
+ *
1007
+ * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
1008
+ */
1009
+ class WidgetBaseSource {
1010
+ constructor(props) {
1011
+ this.props = void 0;
1012
+ this.props = {
1013
+ ...WidgetBaseSource.defaultProps,
1014
+ ...props
1171
1015
  };
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
1016
+ }
1017
+ _getModelSource(owner) {
1018
+ const props = this.props;
1019
+ return {
1020
+ apiVersion: props.apiVersion,
1021
+ apiBaseUrl: props.apiBaseUrl,
1022
+ clientId: props.clientId,
1023
+ accessToken: props.accessToken,
1024
+ connectionName: props.connectionName,
1025
+ filters: getApplicableFilters(owner, props.filters),
1026
+ filtersLogicalOperator: props.filtersLogicalOperator,
1027
+ spatialDataType: props.spatialDataType,
1028
+ spatialDataColumn: props.spatialDataColumn,
1029
+ dataResolution: props.dataResolution
1186
1030
  };
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);
1031
+ }
1032
+ _getSpatialFiltersResolution(source, spatialFilter, referenceViewState) {
1033
+ // spatialFiltersResolution applies only to spatial index sources.
1034
+ if (!spatialFilter || source.spatialDataType === 'geo') {
1035
+ return;
1036
+ }
1037
+ if (!referenceViewState) {
1038
+ throw new Error('Missing required option, "spatialIndexReferenceViewState".');
1039
+ }
1040
+ return getSpatialFiltersResolution(source, referenceViewState);
1041
+ }
1042
+ /****************************************************************************
1043
+ * CATEGORIES
1044
+ */
1045
+ /**
1046
+ * Returns a list of labeled datapoints for categorical data. Suitable for
1047
+ * charts including grouped bar charts, pie charts, and tree charts.
1048
+ */
1049
+ getCategories(options) {
1050
+ try {
1051
+ const _this = this;
1052
+ const {
1053
+ filterOwner,
1054
+ spatialFilter,
1055
+ spatialFiltersMode,
1056
+ spatialIndexReferenceViewState,
1057
+ abortController,
1058
+ ...params
1059
+ } = options;
1060
+ const {
1061
+ column,
1062
+ operation,
1063
+ operationColumn
1064
+ } = params;
1065
+ const source = _this.getModelSource(filterOwner);
1066
+ const spatialFiltersResolution = _this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1067
+ return Promise.resolve(executeModel({
1068
+ model: 'category',
1069
+ source: {
1070
+ ...source,
1071
+ spatialFiltersResolution,
1072
+ spatialFiltersMode,
1073
+ spatialFilter
1074
+ },
1075
+ params: {
1076
+ column,
1077
+ operation,
1078
+ operationColumn: operationColumn || column
1079
+ },
1080
+ opts: {
1081
+ abortController
1082
+ }
1083
+ }).then(res => normalizeObjectKeys(res.rows)));
1084
+ } catch (e) {
1085
+ return Promise.reject(e);
1086
+ }
1087
+ }
1088
+ /****************************************************************************
1089
+ * FEATURES
1090
+ */
1091
+ /**
1092
+ * Given a list of feature IDs (as found in `_carto_feature_id`) returns all
1093
+ * matching features. In datasets containing features with duplicate geometries,
1094
+ * feature IDs may be duplicated (IDs are a hash of geometry) and so more
1095
+ * results may be returned than IDs in the request.
1096
+ * @internal
1097
+ * @experimental
1098
+ */
1099
+ getFeatures(options) {
1100
+ try {
1101
+ const _this2 = this;
1102
+ const {
1103
+ filterOwner,
1104
+ spatialFilter,
1105
+ spatialFiltersMode,
1106
+ spatialIndexReferenceViewState,
1107
+ abortController,
1108
+ ...params
1109
+ } = options;
1110
+ const {
1111
+ columns,
1112
+ dataType,
1113
+ featureIds,
1114
+ z,
1115
+ limit,
1116
+ tileResolution
1117
+ } = params;
1118
+ const source = _this2.getModelSource(filterOwner);
1119
+ const spatialFiltersResolution = _this2._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1120
+ return Promise.resolve(executeModel({
1121
+ model: 'pick',
1122
+ source: {
1123
+ ...source,
1124
+ spatialFiltersResolution,
1125
+ spatialFiltersMode,
1126
+ spatialFilter
1127
+ },
1128
+ params: {
1129
+ columns,
1130
+ dataType,
1131
+ featureIds,
1132
+ z,
1133
+ limit: limit || 1000,
1134
+ tileResolution: tileResolution || DEFAULT_TILE_RESOLUTION
1135
+ },
1136
+ opts: {
1137
+ abortController
1138
+ }
1139
+ // Avoid `normalizeObjectKeys()`, which changes column names.
1140
+ }).then(_ref => {
1141
+ let {
1142
+ rows
1143
+ } = _ref;
1144
+ return {
1145
+ rows
1146
+ };
1147
+ }));
1148
+ } catch (e) {
1149
+ return Promise.reject(e);
1150
+ }
1151
+ }
1152
+ /****************************************************************************
1153
+ * FORMULA
1154
+ */
1155
+ /**
1156
+ * Returns a scalar numerical statistic over all matching data. Suitable
1157
+ * for 'headline' or 'scorecard' figures such as counts and sums.
1158
+ */
1159
+ getFormula(options) {
1160
+ try {
1161
+ const _this3 = this;
1162
+ const {
1163
+ filterOwner,
1164
+ spatialFilter,
1165
+ spatialFiltersMode,
1166
+ spatialIndexReferenceViewState,
1167
+ abortController,
1168
+ operationExp,
1169
+ ...params
1170
+ } = options;
1171
+ const {
1172
+ column,
1173
+ operation
1174
+ } = params;
1175
+ const source = _this3.getModelSource(filterOwner);
1176
+ const spatialFiltersResolution = _this3._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1177
+ return Promise.resolve(executeModel({
1178
+ model: 'formula',
1179
+ source: {
1180
+ ...source,
1181
+ spatialFiltersResolution,
1182
+ spatialFiltersMode,
1183
+ spatialFilter
1184
+ },
1185
+ params: {
1186
+ column: column ?? '*',
1187
+ operation,
1188
+ operationExp
1189
+ },
1190
+ opts: {
1191
+ abortController
1192
+ }
1193
+ }).then(res => normalizeObjectKeys(res.rows[0])));
1194
+ } catch (e) {
1195
+ return Promise.reject(e);
1214
1196
  }
1215
- return Promise.resolve(jsonPromise);
1216
- } catch (e) {
1217
- return Promise.reject(e);
1218
1197
  }
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());
1198
+ /****************************************************************************
1199
+ * HISTOGRAM
1200
+ */
1201
+ /**
1202
+ * Returns a list of labeled datapoints for 'bins' of data defined as ticks
1203
+ * over a numerical range. Suitable for histogram charts.
1204
+ */
1205
+ getHistogram(options) {
1206
+ try {
1207
+ const _this4 = this;
1208
+ const {
1209
+ filterOwner,
1210
+ spatialFilter,
1211
+ spatialFiltersMode,
1212
+ spatialIndexReferenceViewState,
1213
+ abortController,
1214
+ ...params
1215
+ } = options;
1216
+ const {
1217
+ column,
1218
+ operation,
1219
+ ticks
1220
+ } = params;
1221
+ const source = _this4.getModelSource(filterOwner);
1222
+ const spatialFiltersResolution = _this4._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1223
+ return Promise.resolve(executeModel({
1224
+ model: 'histogram',
1225
+ source: {
1226
+ ...source,
1227
+ spatialFiltersResolution,
1228
+ spatialFiltersMode,
1229
+ spatialFilter
1230
+ },
1231
+ params: {
1232
+ column,
1233
+ operation,
1234
+ ticks
1235
+ },
1236
+ opts: {
1237
+ abortController
1238
+ }
1239
+ }).then(res => normalizeObjectKeys(res.rows))).then(function (data) {
1240
+ if (data.length) {
1241
+ // Given N ticks the API returns up to N+1 bins, omitting any empty bins. Bins
1242
+ // include 1 bin below the lowest tick, N-1 between ticks, and 1 bin above the highest tick.
1243
+ const result = Array(ticks.length + 1).fill(0);
1244
+ data.forEach(_ref2 => {
1245
+ let {
1246
+ tick,
1247
+ value
1248
+ } = _ref2;
1249
+ return result[tick] = value;
1250
+ });
1251
+ return result;
1252
+ }
1253
+ return [];
1254
+ });
1255
+ } catch (e) {
1256
+ return Promise.reject(e);
1264
1257
  }
1265
1258
  }
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);
1259
+ /****************************************************************************
1260
+ * RANGE
1261
+ */
1262
+ /**
1263
+ * Returns a range (min and max) for a numerical column of matching rows.
1264
+ * Suitable for displaying certain 'headline' or 'scorecard' statistics,
1265
+ * or rendering a range slider UI for filtering.
1266
+ */
1267
+ getRange(options) {
1268
+ try {
1269
+ const _this5 = this;
1270
+ const {
1271
+ filterOwner,
1272
+ spatialFilter,
1273
+ spatialFiltersMode,
1274
+ spatialIndexReferenceViewState,
1275
+ abortController,
1276
+ ...params
1277
+ } = options;
1278
+ const {
1279
+ column
1280
+ } = params;
1281
+ const source = _this5.getModelSource(filterOwner);
1282
+ const spatialFiltersResolution = _this5._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1283
+ return Promise.resolve(executeModel({
1284
+ model: 'range',
1285
+ source: {
1286
+ ...source,
1287
+ spatialFiltersResolution,
1288
+ spatialFiltersMode,
1289
+ spatialFilter
1290
+ },
1291
+ params: {
1292
+ column
1293
+ },
1294
+ opts: {
1295
+ abortController
1296
+ }
1297
+ }).then(res => normalizeObjectKeys(res.rows[0])));
1298
+ } catch (e) {
1299
+ return Promise.reject(e);
1276
1300
  }
1277
1301
  }
1278
- return baseUrl.toString();
1279
- }
1280
-
1281
- // deck.gl
1282
- const baseSource = function (endpoint, options, urlParameters) {
1283
- try {
1284
- const {
1285
- accessToken,
1286
- connectionName,
1287
- cache,
1288
- ...optionalOptions
1289
- } = options;
1290
- const mergedOptions = {
1291
- ...SOURCE_DEFAULTS,
1292
- accessToken,
1293
- connectionName,
1294
- endpoint
1295
- };
1296
- for (const key in optionalOptions) {
1297
- if (optionalOptions[key]) {
1298
- mergedOptions[key] = optionalOptions[key];
1299
- }
1302
+ /****************************************************************************
1303
+ * SCATTER
1304
+ */
1305
+ /**
1306
+ * Returns a list of bivariate datapoints defined as numerical 'x' and 'y'
1307
+ * values. Suitable for rendering scatter plots.
1308
+ */
1309
+ getScatter(options) {
1310
+ try {
1311
+ const _this6 = this;
1312
+ const {
1313
+ filterOwner,
1314
+ spatialFilter,
1315
+ spatialFiltersMode,
1316
+ spatialIndexReferenceViewState,
1317
+ abortController,
1318
+ ...params
1319
+ } = options;
1320
+ const {
1321
+ xAxisColumn,
1322
+ xAxisJoinOperation,
1323
+ yAxisColumn,
1324
+ yAxisJoinOperation
1325
+ } = params;
1326
+ const source = _this6.getModelSource(filterOwner);
1327
+ const spatialFiltersResolution = _this6._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1328
+ // Make sure this is sync with the same constant in cloud-native/maps-api
1329
+ const HARD_LIMIT = 500;
1330
+ return Promise.resolve(executeModel({
1331
+ model: 'scatterplot',
1332
+ source: {
1333
+ ...source,
1334
+ spatialFiltersResolution,
1335
+ spatialFiltersMode,
1336
+ spatialFilter
1337
+ },
1338
+ params: {
1339
+ xAxisColumn,
1340
+ xAxisJoinOperation,
1341
+ yAxisColumn,
1342
+ yAxisJoinOperation,
1343
+ limit: HARD_LIMIT
1344
+ },
1345
+ opts: {
1346
+ abortController
1347
+ }
1348
+ }).then(res => normalizeObjectKeys(res.rows)).then(res => res.map(_ref3 => {
1349
+ let {
1350
+ x,
1351
+ y
1352
+ } = _ref3;
1353
+ return [x, y];
1354
+ })));
1355
+ } catch (e) {
1356
+ return Promise.reject(e);
1357
+ }
1358
+ }
1359
+ /****************************************************************************
1360
+ * TABLE
1361
+ */
1362
+ /**
1363
+ * Returns a list of arbitrary data rows, with support for pagination and
1364
+ * sorting. Suitable for displaying tables and lists.
1365
+ */
1366
+ getTable(options) {
1367
+ try {
1368
+ const _this7 = this;
1369
+ const {
1370
+ filterOwner,
1371
+ spatialFilter,
1372
+ spatialFiltersMode,
1373
+ spatialIndexReferenceViewState,
1374
+ abortController,
1375
+ ...params
1376
+ } = options;
1377
+ const {
1378
+ columns,
1379
+ sortBy,
1380
+ sortDirection,
1381
+ offset = 0,
1382
+ limit = 10
1383
+ } = params;
1384
+ const source = _this7.getModelSource(filterOwner);
1385
+ const spatialFiltersResolution = _this7._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1386
+ return Promise.resolve(executeModel({
1387
+ model: 'table',
1388
+ source: {
1389
+ ...source,
1390
+ spatialFiltersResolution,
1391
+ spatialFiltersMode,
1392
+ spatialFilter
1393
+ },
1394
+ params: {
1395
+ column: columns,
1396
+ sortBy,
1397
+ sortDirection,
1398
+ limit,
1399
+ offset
1400
+ },
1401
+ opts: {
1402
+ abortController
1403
+ }
1404
+ }).then(res => ({
1405
+ // Avoid `normalizeObjectKeys()`, which changes column names.
1406
+ rows: res.rows ?? res.ROWS,
1407
+ totalCount: res.metadata?.total ?? res.METADATA?.TOTAL
1408
+ })));
1409
+ } catch (e) {
1410
+ return Promise.reject(e);
1300
1411
  }
1301
- const baseUrl = buildSourceUrl(mergedOptions);
1302
- const {
1303
- clientId,
1304
- maxLengthURL,
1305
- format,
1306
- localCache
1307
- } = mergedOptions;
1308
- const headers = {
1309
- Authorization: `Bearer ${options.accessToken}`,
1310
- ...options.headers
1311
- };
1312
- const parameters = {
1313
- client: clientId,
1314
- ...urlParameters
1315
- };
1316
- const errorContext = {
1317
- requestType: 'Map instantiation',
1318
- connection: options.connectionName,
1319
- type: endpoint,
1320
- source: JSON.stringify(parameters, undefined, 2)
1321
- };
1322
- return Promise.resolve(requestWithParameters({
1323
- baseUrl,
1324
- parameters,
1325
- headers,
1326
- errorContext,
1327
- maxLengthURL,
1328
- localCache
1329
- })).then(function (mapInstantiation) {
1330
- let _exit;
1331
- function _temp2(_result) {
1332
- return _exit ? _result : Promise.resolve(requestWithParameters({
1333
- baseUrl: dataUrl,
1334
- headers,
1335
- errorContext,
1336
- maxLengthURL,
1337
- localCache
1338
- }));
1339
- }
1340
- const dataUrl = mapInstantiation[format].url[0];
1341
- if (cache) {
1342
- cache.value = parseInt(new URL(dataUrl).searchParams.get('cache') || '', 10);
1343
- }
1344
- errorContext.requestType = 'Map data';
1345
- const _temp = function () {
1346
- if (format === 'tilejson') {
1347
- return Promise.resolve(requestWithParameters({
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
- });
1412
+ }
1413
+ /****************************************************************************
1414
+ * TIME SERIES
1415
+ */
1416
+ /**
1417
+ * Returns a series of labeled numerical values, grouped into equally-sized
1418
+ * time intervals. Suitable for rendering time series charts.
1419
+ */
1420
+ getTimeSeries(options) {
1421
+ try {
1422
+ const _this8 = this;
1423
+ const {
1424
+ filterOwner,
1425
+ abortController,
1426
+ spatialFilter,
1427
+ spatialFiltersMode,
1428
+ spatialIndexReferenceViewState,
1429
+ ...params
1430
+ } = options;
1431
+ const {
1432
+ column,
1433
+ operationColumn,
1434
+ joinOperation,
1435
+ operation,
1436
+ stepSize,
1437
+ stepMultiplier,
1438
+ splitByCategory,
1439
+ splitByCategoryLimit,
1440
+ splitByCategoryValues
1441
+ } = params;
1442
+ const source = _this8.getModelSource(filterOwner);
1443
+ const spatialFiltersResolution = _this8._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1444
+ return Promise.resolve(executeModel({
1445
+ model: 'timeseries',
1446
+ source: {
1447
+ ...source,
1448
+ spatialFiltersResolution,
1449
+ spatialFiltersMode,
1450
+ spatialFilter
1451
+ },
1452
+ params: {
1453
+ column,
1454
+ stepSize,
1455
+ stepMultiplier,
1456
+ operationColumn: operationColumn || column,
1457
+ joinOperation,
1458
+ operation,
1459
+ splitByCategory,
1460
+ splitByCategoryLimit,
1461
+ splitByCategoryValues
1462
+ },
1463
+ opts: {
1464
+ abortController
1360
1465
  }
1361
- }();
1362
- return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
1363
- });
1364
- } catch (e) {
1365
- return Promise.reject(e);
1466
+ }).then(res => ({
1467
+ rows: normalizeObjectKeys(res.rows),
1468
+ categories: res.metadata?.categories
1469
+ })));
1470
+ } catch (e) {
1471
+ return Promise.reject(e);
1472
+ }
1366
1473
  }
1367
- };
1368
- const SOURCE_DEFAULTS = {
1474
+ }
1475
+ WidgetBaseSource.defaultProps = {
1476
+ apiVersion: exports.ApiVersion.V3,
1369
1477
  apiBaseUrl: DEFAULT_API_BASE_URL,
1370
1478
  clientId: getClient(),
1371
- format: 'tilejson',
1372
- headers: {},
1373
- maxLengthURL: DEFAULT_MAX_LENGTH_URL
1479
+ filters: {},
1480
+ filtersLogicalOperator: 'and'
1374
1481
  };
1375
1482
 
1376
- // deck.gl
1377
- const boundaryQuerySource = function (options) {
1378
- try {
1379
- const {
1380
- columns,
1381
- filters,
1382
- tilesetTableName,
1383
- propertiesSqlQuery,
1384
- queryParameters
1385
- } = options;
1386
- const urlParameters = {
1387
- tilesetTableName,
1388
- propertiesSqlQuery
1483
+ /**
1484
+ * Source for Widget API requests on a data source defined by a SQL query.
1485
+ *
1486
+ * Generally not intended to be constructed directly. Instead, call
1487
+ * {@link vectorQuerySource}, {@link h3QuerySource}, or {@link quadbinQuerySource},
1488
+ * which can be shared with map layers. Sources contain a `widgetSource` property,
1489
+ * for use by widget implementations.
1490
+ *
1491
+ * Example:
1492
+ *
1493
+ * ```javascript
1494
+ * import { vectorQuerySource } from '@carto/api-client';
1495
+ *
1496
+ * const data = vectorQuerySource({
1497
+ * accessToken: '••••',
1498
+ * connectionName: 'carto_dw',
1499
+ * sqlQuery: 'SELECT * FROM carto-demo-data.demo_tables.retail_stores'
1500
+ * });
1501
+ *
1502
+ * const { widgetSource } = await data;
1503
+ * ```
1504
+ */
1505
+ class WidgetQuerySource extends WidgetBaseSource {
1506
+ getModelSource(owner) {
1507
+ return {
1508
+ ...super._getModelSource(owner),
1509
+ type: 'query',
1510
+ data: this.props.sqlQuery,
1511
+ queryParameters: this.props.queryParameters
1389
1512
  };
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
1513
  }
1403
- };
1514
+ }
1404
1515
 
1405
- // deck.gl
1406
- const boundaryTableSource = function (options) {
1407
- try {
1408
- const {
1409
- filters,
1410
- tilesetTableName,
1411
- columns,
1412
- propertiesTableName
1413
- } = options;
1414
- const urlParameters = {
1415
- tilesetTableName,
1416
- propertiesTableName
1516
+ /**
1517
+ * Source for Widget API requests on a data source defined as a table.
1518
+ *
1519
+ * Generally not intended to be constructed directly. Instead, call
1520
+ * {@link vectorTableSource}, {@link h3TableSource}, or {@link quadbinTableSource},
1521
+ * which can be shared with map layers. Sources contain a `widgetSource` property,
1522
+ * for use by widget implementations.
1523
+ *
1524
+ * Example:
1525
+ *
1526
+ * ```javascript
1527
+ * import { vectorTableSource } from '@carto/api-client';
1528
+ *
1529
+ * const data = vectorTableSource({
1530
+ * accessToken: '••••',
1531
+ * connectionName: 'carto_dw',
1532
+ * tableName: 'carto-demo-data.demo_tables.retail_stores'
1533
+ * });
1534
+ *
1535
+ * const { widgetSource } = await data;
1536
+ * ```
1537
+ */
1538
+ class WidgetTableSource extends WidgetBaseSource {
1539
+ getModelSource(owner) {
1540
+ return {
1541
+ ...super._getModelSource(owner),
1542
+ type: 'table',
1543
+ data: this.props.tableName
1417
1544
  };
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
1545
  }
1428
- };
1546
+ }
1429
1547
 
1430
1548
  // deck.gl
1431
1549
  const h3QuerySource = function (options) {
@@ -1455,7 +1573,12 @@ const h3QuerySource = function (options) {
1455
1573
  }
1456
1574
  return Promise.resolve(baseSource('query', options, urlParameters).then(result => ({
1457
1575
  ...result,
1458
- widgetSource: new WidgetQuerySource(options)
1576
+ widgetSource: new WidgetQuerySource({
1577
+ ...options,
1578
+ // NOTE: passing redundant spatialDataColumn here to apply the default value 'h3'
1579
+ spatialDataColumn,
1580
+ spatialDataType: 'h3'
1581
+ })
1459
1582
  })));
1460
1583
  } catch (e) {
1461
1584
  return Promise.reject(e);
@@ -1486,7 +1609,12 @@ const h3TableSource = function (options) {
1486
1609
  }
1487
1610
  return Promise.resolve(baseSource('table', options, urlParameters).then(result => ({
1488
1611
  ...result,
1489
- widgetSource: new WidgetTableSource(options)
1612
+ widgetSource: new WidgetTableSource({
1613
+ ...options,
1614
+ // NOTE: passing redundant spatialDataColumn here to apply the default value 'h3'
1615
+ spatialDataColumn,
1616
+ spatialDataType: 'h3'
1617
+ })
1490
1618
  })));
1491
1619
  } catch (e) {
1492
1620
  return Promise.reject(e);
@@ -1555,7 +1683,12 @@ const quadbinQuerySource = function (options) {
1555
1683
  }
1556
1684
  return Promise.resolve(baseSource('query', options, urlParameters).then(result => ({
1557
1685
  ...result,
1558
- widgetSource: new WidgetQuerySource(options)
1686
+ widgetSource: new WidgetQuerySource({
1687
+ ...options,
1688
+ // NOTE: passing redundant spatialDataColumn here to apply the default value 'quadbin'
1689
+ spatialDataColumn,
1690
+ spatialDataType: 'quadbin'
1691
+ })
1559
1692
  })));
1560
1693
  } catch (e) {
1561
1694
  return Promise.reject(e);
@@ -1586,7 +1719,12 @@ const quadbinTableSource = function (options) {
1586
1719
  }
1587
1720
  return Promise.resolve(baseSource('table', options, urlParameters).then(result => ({
1588
1721
  ...result,
1589
- widgetSource: new WidgetTableSource(options)
1722
+ widgetSource: new WidgetTableSource({
1723
+ ...options,
1724
+ // NOTE: passing redundant spatialDataColumn here to apply the default value 'quadbin'
1725
+ spatialDataColumn,
1726
+ spatialDataType: 'quadbin'
1727
+ })
1590
1728
  })));
1591
1729
  } catch (e) {
1592
1730
  return Promise.reject(e);
@@ -1617,7 +1755,8 @@ const vectorQuerySource = function (options) {
1617
1755
  spatialDataColumn = 'geom',
1618
1756
  sqlQuery,
1619
1757
  tileResolution = DEFAULT_TILE_RESOLUTION,
1620
- queryParameters
1758
+ queryParameters,
1759
+ aggregationExp
1621
1760
  } = options;
1622
1761
  const urlParameters = {
1623
1762
  spatialDataColumn,
@@ -1634,9 +1773,15 @@ const vectorQuerySource = function (options) {
1634
1773
  if (queryParameters) {
1635
1774
  urlParameters.queryParameters = queryParameters;
1636
1775
  }
1776
+ if (aggregationExp) {
1777
+ urlParameters.aggregationExp = aggregationExp;
1778
+ }
1637
1779
  return Promise.resolve(baseSource('query', options, urlParameters).then(result => ({
1638
1780
  ...result,
1639
- widgetSource: new WidgetQuerySource(options)
1781
+ widgetSource: new WidgetQuerySource({
1782
+ ...options,
1783
+ spatialDataType: 'geo'
1784
+ })
1640
1785
  })));
1641
1786
  } catch (e) {
1642
1787
  return Promise.reject(e);
@@ -1651,7 +1796,8 @@ const vectorTableSource = function (options) {
1651
1796
  filters,
1652
1797
  spatialDataColumn = 'geom',
1653
1798
  tableName,
1654
- tileResolution = DEFAULT_TILE_RESOLUTION
1799
+ tileResolution = DEFAULT_TILE_RESOLUTION,
1800
+ aggregationExp
1655
1801
  } = options;
1656
1802
  const urlParameters = {
1657
1803
  name: tableName,
@@ -1665,9 +1811,15 @@ const vectorTableSource = function (options) {
1665
1811
  if (filters) {
1666
1812
  urlParameters.filters = filters;
1667
1813
  }
1814
+ if (aggregationExp) {
1815
+ urlParameters.aggregationExp = aggregationExp;
1816
+ }
1668
1817
  return Promise.resolve(baseSource('table', options, urlParameters).then(result => ({
1669
1818
  ...result,
1670
- widgetSource: new WidgetTableSource(options)
1819
+ widgetSource: new WidgetTableSource({
1820
+ ...options,
1821
+ spatialDataType: 'geo'
1822
+ })
1671
1823
  })));
1672
1824
  } catch (e) {
1673
1825
  return Promise.reject(e);