@carto/api-client 0.4.1 → 0.4.2-alpha.0

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.
@@ -1,3 +1,3 @@
1
1
  import type { SourceOptions, QuerySourceOptions, QueryResult } from '../sources/types';
2
- export type QueryOptions = SourceOptions & Omit<QuerySourceOptions, 'spatialDataColumn'>;
2
+ export type QueryOptions = SourceOptions & QuerySourceOptions;
3
3
  export declare const query: (options: QueryOptions) => Promise<QueryResult>;
@@ -509,41 +509,43 @@ function executeModel(props) {
509
509
  data,
510
510
  filters,
511
511
  filtersLogicalOperator = 'and',
512
- geoColumn = DEFAULT_GEO_COLUMN
512
+ spatialDataType = 'geo',
513
+ spatialFiltersMode = 'intersects',
514
+ spatialFiltersResolution = 0
513
515
  } = source;
514
- const queryParameters = source.queryParameters ? JSON.stringify(source.queryParameters) : '';
515
516
  const queryParams = {
516
517
  type,
517
518
  client: clientId,
518
519
  source: data,
519
- params: JSON.stringify(params),
520
- queryParameters,
521
- filters: JSON.stringify(filters),
520
+ params,
521
+ queryParameters: source.queryParameters || '',
522
+ filters,
522
523
  filtersLogicalOperator
523
524
  };
525
+ const spatialDataColumn = source.spatialDataColumn || DEFAULT_GEO_COLUMN;
524
526
  // Picking Model API requires 'spatialDataColumn'.
525
527
  if (model === 'pick') {
526
- queryParams.spatialDataColumn = geoColumn;
528
+ queryParams.spatialDataColumn = spatialDataColumn;
527
529
  }
528
- // API supports multiple filters, we apply it only to geoColumn
530
+ // API supports multiple filters, we apply it only to spatialDataColumn
529
531
  const spatialFilters = source.spatialFilter ? {
530
- [geoColumn]: source.spatialFilter
532
+ [spatialDataColumn]: source.spatialFilter
531
533
  } : undefined;
532
534
  if (spatialFilters) {
533
- queryParams.spatialFilters = JSON.stringify(spatialFilters);
535
+ queryParams.spatialFilters = spatialFilters; // JSON.stringify(spatialFilters);
536
+ queryParams.spatialDataColumn = spatialDataColumn;
537
+ queryParams.spatialDataType = spatialDataType;
534
538
  }
535
- const urlWithSearchParams = url + '?' + new URLSearchParams(queryParams).toString();
539
+ if (spatialDataType !== 'geo') {
540
+ if (spatialFiltersResolution > 0) {
541
+ queryParams.spatialFiltersResolution = spatialFiltersResolution;
542
+ }
543
+ queryParams.spatialFiltersMode = spatialFiltersMode;
544
+ }
545
+ const urlWithSearchParams = url + '?' + objectToURLSearchParams(queryParams).toString();
536
546
  const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
537
547
  if (isGet) {
538
548
  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;
546
- }
547
549
  }
548
550
  return makeCall({
549
551
  url,
@@ -557,6 +559,68 @@ function executeModel(props) {
557
559
  }
558
560
  });
559
561
  }
562
+ function objectToURLSearchParams(object) {
563
+ const params = new URLSearchParams();
564
+ for (const key in object) {
565
+ if (isPureObject(object[key])) {
566
+ params.append(key, JSON.stringify(object[key]));
567
+ } else if (Array.isArray(object[key])) {
568
+ params.append(key, JSON.stringify(object[key]));
569
+ } else if (object[key] === null) {
570
+ params.append(key, 'null');
571
+ } else if (object[key] !== undefined) {
572
+ params.append(key, String(object[key]));
573
+ }
574
+ }
575
+ return params;
576
+ }
577
+
578
+ const DEFAULT_TILE_SIZE = 512;
579
+ const QUADBIN_ZOOM_MAX_OFFSET = 4;
580
+ function getSpatialFiltersResolution(_ref) {
581
+ let {
582
+ source,
583
+ viewState
584
+ } = _ref;
585
+ assert(viewState, 'viewState prop is required to compute automatic spatialFiltersResolution when using spatialFilter with spatial indexes. Either pass a `spatialFiltersResolution` prop or a `viewState` prop to avoid this error');
586
+ const dataResolution = source.dataResolution ?? Number.MAX_VALUE;
587
+ const aggregationResLevel = source.aggregationResLevel ?? (source.spatialDataType === 'h3' ? DEFAULT_AGGREGATION_RES_LEVEL_H3 : DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN);
588
+ const aggregationResLevelOffset = Math.max(0, Math.floor(aggregationResLevel));
589
+ const currentZoomInt = Math.ceil(viewState.zoom);
590
+ if (source.spatialDataType === 'h3') {
591
+ const tileSize = DEFAULT_TILE_SIZE;
592
+ const maxResolutionForZoom = maxH3SpatialFiltersResolutions.find(_ref2 => {
593
+ let [zoom] = _ref2;
594
+ return zoom === currentZoomInt;
595
+ })?.[1] ?? Math.max(0, currentZoomInt - 3);
596
+ const maxSpatialFiltersResolution = maxResolutionForZoom ? Math.min(dataResolution, maxResolutionForZoom) : dataResolution;
597
+ const hexagonResolution = getHexagonResolution(viewState, tileSize) + aggregationResLevelOffset;
598
+ return Math.min(hexagonResolution, maxSpatialFiltersResolution);
599
+ }
600
+ if (source.spatialDataType === 'quadbin') {
601
+ const maxResolutionForZoom = currentZoomInt + QUADBIN_ZOOM_MAX_OFFSET;
602
+ const maxSpatialFiltersResolution = Math.min(dataResolution, maxResolutionForZoom);
603
+ const quadsResolution = Math.floor(viewState.zoom) + aggregationResLevelOffset;
604
+ return Math.min(quadsResolution, maxSpatialFiltersResolution);
605
+ }
606
+ return undefined;
607
+ }
608
+ 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]];
609
+ // stolen from https://github.com/visgl/deck.gl/blob/master/modules/carto/src/layers/h3-tileset-2d.ts
610
+ // Relative scale factor (0 = no biasing, 2 = a few hexagons cover view)
611
+ const BIAS = 2;
612
+ // Resolution conversion function. Takes a WebMercatorViewport and returns
613
+ // a H3 resolution such that the screen space size of the hexagons is
614
+ // similar
615
+ function getHexagonResolution(viewport, tileSize) {
616
+ // Difference in given tile size compared to deck's internal 512px tile size,
617
+ // expressed as an offset to the viewport zoom.
618
+ const zoomOffset = Math.log2(tileSize / DEFAULT_TILE_SIZE);
619
+ const hexagonScaleFactor = 2 / 3 * (viewport.zoom - zoomOffset);
620
+ const latitudeScaleFactor = Math.log(1 / Math.cos(Math.PI * viewport.latitude / 180));
621
+ // Clip and bias
622
+ return Math.max(0, Math.floor(hexagonScaleFactor + latitudeScaleFactor - BIAS));
623
+ }
560
624
 
561
625
  /**
562
626
  * Source for Widget API requests on a data source defined by a SQL query.
@@ -581,7 +645,9 @@ class WidgetBaseSource {
581
645
  connectionName: props.connectionName,
582
646
  filters: getApplicableFilters(owner, props.filters),
583
647
  filtersLogicalOperator: props.filtersLogicalOperator,
584
- geoColumn: props.geoColumn
648
+ spatialDataType: props.spatialDataType,
649
+ spatialDataColumn: props.spatialDataColumn,
650
+ dataResolution: props.dataResolution
585
651
  };
586
652
  }
587
653
  /****************************************************************************
@@ -597,7 +663,9 @@ class WidgetBaseSource {
597
663
  const {
598
664
  filterOwner,
599
665
  spatialFilter,
666
+ spatialFiltersMode,
600
667
  abortController,
668
+ viewState,
601
669
  ...params
602
670
  } = options;
603
671
  const {
@@ -605,10 +673,20 @@ class WidgetBaseSource {
605
673
  operation,
606
674
  operationColumn
607
675
  } = params;
676
+ const source = _this.getModelSource(filterOwner);
677
+ let spatialFiltersResolution;
678
+ if (spatialFilter && source.spatialDataType !== 'geo') {
679
+ spatialFiltersResolution = getSpatialFiltersResolution({
680
+ source,
681
+ viewState
682
+ });
683
+ }
608
684
  return Promise.resolve(executeModel({
609
685
  model: 'category',
610
686
  source: {
611
- ..._this.getModelSource(filterOwner),
687
+ ...source,
688
+ spatialFiltersResolution,
689
+ spatialFiltersMode,
612
690
  spatialFilter
613
691
  },
614
692
  params: {
@@ -641,7 +719,9 @@ class WidgetBaseSource {
641
719
  const {
642
720
  filterOwner,
643
721
  spatialFilter,
722
+ spatialFiltersMode,
644
723
  abortController,
724
+ viewState,
645
725
  ...params
646
726
  } = options;
647
727
  const {
@@ -652,10 +732,20 @@ class WidgetBaseSource {
652
732
  limit,
653
733
  tileResolution
654
734
  } = params;
735
+ const source = _this2.getModelSource(filterOwner);
736
+ let spatialFiltersResolution;
737
+ if (spatialFilter && source.spatialDataType !== 'geo') {
738
+ spatialFiltersResolution = getSpatialFiltersResolution({
739
+ source,
740
+ viewState
741
+ });
742
+ }
655
743
  return Promise.resolve(executeModel({
656
744
  model: 'pick',
657
745
  source: {
658
- ..._this2.getModelSource(filterOwner),
746
+ ...source,
747
+ spatialFiltersResolution,
748
+ spatialFiltersMode,
659
749
  spatialFilter
660
750
  },
661
751
  params: {
@@ -689,18 +779,30 @@ class WidgetBaseSource {
689
779
  const {
690
780
  filterOwner,
691
781
  spatialFilter,
782
+ spatialFiltersMode,
692
783
  abortController,
693
784
  operationExp,
785
+ viewState,
694
786
  ...params
695
787
  } = options;
696
788
  const {
697
789
  column,
698
790
  operation
699
791
  } = params;
792
+ const source = _this3.getModelSource(filterOwner);
793
+ let spatialFiltersResolution;
794
+ if (spatialFilter && source.spatialDataType !== 'geo') {
795
+ spatialFiltersResolution = getSpatialFiltersResolution({
796
+ source,
797
+ viewState
798
+ });
799
+ }
700
800
  return Promise.resolve(executeModel({
701
801
  model: 'formula',
702
802
  source: {
703
- ..._this3.getModelSource(filterOwner),
803
+ ...source,
804
+ spatialFiltersResolution,
805
+ spatialFiltersMode,
704
806
  spatialFilter
705
807
  },
706
808
  params: {
@@ -729,7 +831,9 @@ class WidgetBaseSource {
729
831
  const {
730
832
  filterOwner,
731
833
  spatialFilter,
834
+ spatialFiltersMode,
732
835
  abortController,
836
+ viewState,
733
837
  ...params
734
838
  } = options;
735
839
  const {
@@ -737,10 +841,20 @@ class WidgetBaseSource {
737
841
  operation,
738
842
  ticks
739
843
  } = params;
844
+ const source = _this4.getModelSource(filterOwner);
845
+ let spatialFiltersResolution;
846
+ if (spatialFilter && source.spatialDataType !== 'geo') {
847
+ spatialFiltersResolution = getSpatialFiltersResolution({
848
+ source,
849
+ viewState
850
+ });
851
+ }
740
852
  return Promise.resolve(executeModel({
741
853
  model: 'histogram',
742
854
  source: {
743
- ..._this4.getModelSource(filterOwner),
855
+ ...source,
856
+ spatialFiltersResolution,
857
+ spatialFiltersMode,
744
858
  spatialFilter
745
859
  },
746
860
  params: {
@@ -785,16 +899,28 @@ class WidgetBaseSource {
785
899
  const {
786
900
  filterOwner,
787
901
  spatialFilter,
902
+ spatialFiltersMode,
788
903
  abortController,
904
+ viewState,
789
905
  ...params
790
906
  } = options;
791
907
  const {
792
908
  column
793
909
  } = params;
910
+ const source = _this5.getModelSource(filterOwner);
911
+ let spatialFiltersResolution;
912
+ if (spatialFilter && source.spatialDataType !== 'geo') {
913
+ spatialFiltersResolution = getSpatialFiltersResolution({
914
+ source,
915
+ viewState
916
+ });
917
+ }
794
918
  return Promise.resolve(executeModel({
795
919
  model: 'range',
796
920
  source: {
797
- ..._this5.getModelSource(filterOwner),
921
+ ...source,
922
+ spatialFiltersResolution,
923
+ spatialFiltersMode,
798
924
  spatialFilter
799
925
  },
800
926
  params: {
@@ -821,7 +947,9 @@ class WidgetBaseSource {
821
947
  const {
822
948
  filterOwner,
823
949
  spatialFilter,
950
+ spatialFiltersMode,
824
951
  abortController,
952
+ viewState,
825
953
  ...params
826
954
  } = options;
827
955
  const {
@@ -830,12 +958,22 @@ class WidgetBaseSource {
830
958
  yAxisColumn,
831
959
  yAxisJoinOperation
832
960
  } = params;
961
+ const source = _this6.getModelSource(filterOwner);
962
+ let spatialFiltersResolution;
963
+ if (spatialFilter && source.spatialDataType !== 'geo') {
964
+ spatialFiltersResolution = getSpatialFiltersResolution({
965
+ source,
966
+ viewState
967
+ });
968
+ }
833
969
  // Make sure this is sync with the same constant in cloud-native/maps-api
834
970
  const HARD_LIMIT = 500;
835
971
  return Promise.resolve(executeModel({
836
972
  model: 'scatterplot',
837
973
  source: {
838
- ..._this6.getModelSource(filterOwner),
974
+ ...source,
975
+ spatialFiltersResolution,
976
+ spatialFiltersMode,
839
977
  spatialFilter
840
978
  },
841
979
  params: {
@@ -872,7 +1010,9 @@ class WidgetBaseSource {
872
1010
  const {
873
1011
  filterOwner,
874
1012
  spatialFilter,
1013
+ spatialFiltersMode,
875
1014
  abortController,
1015
+ viewState,
876
1016
  ...params
877
1017
  } = options;
878
1018
  const {
@@ -882,10 +1022,20 @@ class WidgetBaseSource {
882
1022
  offset = 0,
883
1023
  limit = 10
884
1024
  } = params;
1025
+ const source = _this7.getModelSource(filterOwner);
1026
+ let spatialFiltersResolution;
1027
+ if (spatialFilter && source.spatialDataType !== 'geo') {
1028
+ spatialFiltersResolution = getSpatialFiltersResolution({
1029
+ source,
1030
+ viewState
1031
+ });
1032
+ }
885
1033
  return Promise.resolve(executeModel({
886
1034
  model: 'table',
887
1035
  source: {
888
- ..._this7.getModelSource(filterOwner),
1036
+ ...source,
1037
+ spatialFiltersResolution,
1038
+ spatialFiltersMode,
889
1039
  spatialFilter
890
1040
  },
891
1041
  params: {
@@ -921,6 +1071,8 @@ class WidgetBaseSource {
921
1071
  filterOwner,
922
1072
  abortController,
923
1073
  spatialFilter,
1074
+ spatialFiltersMode,
1075
+ viewState,
924
1076
  ...params
925
1077
  } = options;
926
1078
  const {
@@ -934,10 +1086,20 @@ class WidgetBaseSource {
934
1086
  splitByCategoryLimit,
935
1087
  splitByCategoryValues
936
1088
  } = params;
1089
+ const source = _this8.getModelSource(filterOwner);
1090
+ let spatialFiltersResolution;
1091
+ if (spatialFilter && source.spatialDataType !== 'geo') {
1092
+ spatialFiltersResolution = getSpatialFiltersResolution({
1093
+ source,
1094
+ viewState
1095
+ });
1096
+ }
937
1097
  return Promise.resolve(executeModel({
938
1098
  model: 'timeseries',
939
1099
  source: {
940
- ..._this8.getModelSource(filterOwner),
1100
+ ...source,
1101
+ spatialFiltersResolution,
1102
+ spatialFiltersMode,
941
1103
  spatialFilter
942
1104
  },
943
1105
  params: {
@@ -968,8 +1130,7 @@ WidgetBaseSource.defaultProps = {
968
1130
  apiBaseUrl: DEFAULT_API_BASE_URL,
969
1131
  clientId: getClient(),
970
1132
  filters: {},
971
- filtersLogicalOperator: 'and',
972
- geoColumn: DEFAULT_GEO_COLUMN
1133
+ filtersLogicalOperator: 'and'
973
1134
  };
974
1135
 
975
1136
  /**
@@ -1449,7 +1610,12 @@ const h3QuerySource = function (options) {
1449
1610
  }
1450
1611
  return Promise.resolve(baseSource('query', options, urlParameters).then(result => ({
1451
1612
  ...result,
1452
- widgetSource: new WidgetQuerySource(options)
1613
+ widgetSource: new WidgetQuerySource({
1614
+ ...options,
1615
+ // NOTE: passing redundant spatialDataColumn here to apply the default value 'h3'
1616
+ spatialDataColumn,
1617
+ spatialDataType: 'h3'
1618
+ })
1453
1619
  })));
1454
1620
  } catch (e) {
1455
1621
  return Promise.reject(e);
@@ -1480,7 +1646,12 @@ const h3TableSource = function (options) {
1480
1646
  }
1481
1647
  return Promise.resolve(baseSource('table', options, urlParameters).then(result => ({
1482
1648
  ...result,
1483
- widgetSource: new WidgetTableSource(options)
1649
+ widgetSource: new WidgetTableSource({
1650
+ ...options,
1651
+ // NOTE: passing redundant spatialDataColumn here to apply the default value 'h3'
1652
+ spatialDataColumn,
1653
+ spatialDataType: 'h3'
1654
+ })
1484
1655
  })));
1485
1656
  } catch (e) {
1486
1657
  return Promise.reject(e);
@@ -1549,7 +1720,12 @@ const quadbinQuerySource = function (options) {
1549
1720
  }
1550
1721
  return Promise.resolve(baseSource('query', options, urlParameters).then(result => ({
1551
1722
  ...result,
1552
- widgetSource: new WidgetQuerySource(options)
1723
+ widgetSource: new WidgetQuerySource({
1724
+ ...options,
1725
+ // NOTE: passing redundant spatialDataColumn here to apply the default value 'quadbin'
1726
+ spatialDataColumn,
1727
+ spatialDataType: 'quadbin'
1728
+ })
1553
1729
  })));
1554
1730
  } catch (e) {
1555
1731
  return Promise.reject(e);
@@ -1580,7 +1756,12 @@ const quadbinTableSource = function (options) {
1580
1756
  }
1581
1757
  return Promise.resolve(baseSource('table', options, urlParameters).then(result => ({
1582
1758
  ...result,
1583
- widgetSource: new WidgetTableSource(options)
1759
+ widgetSource: new WidgetTableSource({
1760
+ ...options,
1761
+ // NOTE: passing redundant spatialDataColumn here to apply the default value 'quadbin'
1762
+ spatialDataColumn,
1763
+ spatialDataType: 'quadbin'
1764
+ })
1584
1765
  })));
1585
1766
  } catch (e) {
1586
1767
  return Promise.reject(e);
@@ -1630,7 +1811,10 @@ const vectorQuerySource = function (options) {
1630
1811
  }
1631
1812
  return Promise.resolve(baseSource('query', options, urlParameters).then(result => ({
1632
1813
  ...result,
1633
- widgetSource: new WidgetQuerySource(options)
1814
+ widgetSource: new WidgetQuerySource({
1815
+ ...options,
1816
+ spatialDataType: 'geo'
1817
+ })
1634
1818
  })));
1635
1819
  } catch (e) {
1636
1820
  return Promise.reject(e);
@@ -1661,7 +1845,10 @@ const vectorTableSource = function (options) {
1661
1845
  }
1662
1846
  return Promise.resolve(baseSource('table', options, urlParameters).then(result => ({
1663
1847
  ...result,
1664
- widgetSource: new WidgetTableSource(options)
1848
+ widgetSource: new WidgetTableSource({
1849
+ ...options,
1850
+ spatialDataType: 'geo'
1851
+ })
1665
1852
  })));
1666
1853
  } catch (e) {
1667
1854
  return Promise.reject(e);