@carto/api-client 0.5.0-alpha.3 → 0.5.0-alpha.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 (106) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/build/api/carto-api-error.d.ts +1 -1
  3. package/build/api/query.d.ts +1 -1
  4. package/build/api/request-with-parameters.d.ts +2 -2
  5. package/build/api-client.cjs +2324 -2188
  6. package/build/api-client.cjs.map +1 -1
  7. package/build/api-client.modern.js +2196 -2055
  8. package/build/api-client.modern.js.map +1 -1
  9. package/build/client.d.ts +2 -2
  10. package/build/constants-internal.d.ts +5 -5
  11. package/build/constants.d.ts +6 -6
  12. package/build/deck/get-data-filter-extension-props.d.ts +18 -0
  13. package/build/deck/index.d.ts +1 -0
  14. package/build/filters/Filter.d.ts +15 -3
  15. package/build/filters/FilterTypes.d.ts +1 -1
  16. package/build/filters/geosjonFeatures.d.ts +2 -2
  17. package/build/filters/tileFeatures.d.ts +8 -8
  18. package/build/filters/tileFeaturesRaster.d.ts +3 -3
  19. package/build/filters.d.ts +2 -2
  20. package/build/geo.d.ts +1 -1
  21. package/build/index.d.ts +1 -0
  22. package/build/models/common.d.ts +4 -3
  23. package/build/models/model.d.ts +2 -2
  24. package/build/operations/aggregation.d.ts +5 -5
  25. package/build/operations/applySorting.d.ts +3 -3
  26. package/build/operations/groupBy.d.ts +4 -4
  27. package/build/operations/groupByDate.d.ts +1 -1
  28. package/build/operations/histogram.d.ts +3 -3
  29. package/build/operations/scatterPlot.d.ts +3 -3
  30. package/build/sources/base-source.d.ts +2 -2
  31. package/build/sources/boundary-query-source.d.ts +1 -1
  32. package/build/sources/boundary-table-source.d.ts +1 -1
  33. package/build/sources/h3-query-source.d.ts +2 -2
  34. package/build/sources/h3-table-source.d.ts +2 -2
  35. package/build/sources/h3-tileset-source.d.ts +2 -2
  36. package/build/sources/index.d.ts +26 -26
  37. package/build/sources/quadbin-query-source.d.ts +2 -2
  38. package/build/sources/quadbin-table-source.d.ts +2 -2
  39. package/build/sources/quadbin-tileset-source.d.ts +2 -2
  40. package/build/sources/raster-source.d.ts +2 -2
  41. package/build/sources/types.d.ts +3 -3
  42. package/build/sources/vector-query-source.d.ts +1 -1
  43. package/build/sources/vector-table-source.d.ts +1 -1
  44. package/build/sources/vector-tileset-source.d.ts +2 -2
  45. package/build/spatial-index.d.ts +3 -3
  46. package/build/types-internal.d.ts +5 -5
  47. package/build/types.d.ts +15 -15
  48. package/build/utils/makeIntervalComplete.d.ts +1 -1
  49. package/build/utils.d.ts +3 -3
  50. package/build/widget-sources/types.d.ts +4 -2
  51. package/build/widget-sources/widget-query-source.d.ts +2 -1
  52. package/build/widget-sources/widget-remote-source.d.ts +3 -0
  53. package/build/widget-sources/widget-source.d.ts +3 -3
  54. package/build/widget-sources/widget-table-source.d.ts +2 -1
  55. package/build/widget-sources/widget-tileset-source.d.ts +22 -24
  56. package/package.json +36 -30
  57. package/src/api/carto-api-error.ts +1 -1
  58. package/src/api/query.ts +5 -5
  59. package/src/api/request-with-parameters.ts +6 -6
  60. package/src/client.ts +3 -3
  61. package/src/constants-internal.ts +5 -5
  62. package/src/constants.ts +6 -6
  63. package/src/deck/get-data-filter-extension-props.ts +146 -0
  64. package/src/deck/index.ts +1 -0
  65. package/src/filters/Filter.ts +18 -8
  66. package/src/filters/FilterTypes.ts +2 -2
  67. package/src/filters/geosjonFeatures.ts +2 -2
  68. package/src/filters/tileFeatures.ts +13 -19
  69. package/src/filters/tileFeaturesRaster.ts +7 -7
  70. package/src/filters.ts +4 -4
  71. package/src/geo.ts +12 -14
  72. package/src/index.ts +1 -0
  73. package/src/models/common.ts +9 -7
  74. package/src/models/model.ts +3 -4
  75. package/src/operations/aggregation.ts +5 -5
  76. package/src/operations/applySorting.ts +4 -4
  77. package/src/operations/groupBy.ts +4 -4
  78. package/src/operations/groupByDate.ts +1 -1
  79. package/src/operations/histogram.ts +4 -4
  80. package/src/operations/scatterPlot.ts +4 -4
  81. package/src/sources/base-source.ts +8 -8
  82. package/src/sources/boundary-query-source.ts +2 -2
  83. package/src/sources/boundary-table-source.ts +2 -2
  84. package/src/sources/h3-query-source.ts +7 -5
  85. package/src/sources/h3-table-source.ts +7 -5
  86. package/src/sources/h3-tileset-source.ts +4 -4
  87. package/src/sources/index.ts +26 -26
  88. package/src/sources/quadbin-query-source.ts +7 -5
  89. package/src/sources/quadbin-table-source.ts +7 -5
  90. package/src/sources/quadbin-tileset-source.ts +4 -4
  91. package/src/sources/raster-source.ts +8 -4
  92. package/src/sources/types.ts +3 -3
  93. package/src/sources/vector-query-source.ts +2 -3
  94. package/src/sources/vector-table-source.ts +2 -3
  95. package/src/sources/vector-tileset-source.ts +5 -5
  96. package/src/spatial-index.ts +4 -4
  97. package/src/types-internal.ts +5 -5
  98. package/src/types.ts +15 -15
  99. package/src/utils/makeIntervalComplete.ts +1 -1
  100. package/src/utils.ts +3 -3
  101. package/src/widget-sources/types.ts +5 -3
  102. package/src/widget-sources/widget-query-source.ts +6 -2
  103. package/src/widget-sources/widget-remote-source.ts +31 -16
  104. package/src/widget-sources/widget-source.ts +13 -4
  105. package/src/widget-sources/widget-table-source.ts +6 -2
  106. package/src/widget-sources/widget-tileset-source.ts +132 -81
@@ -1,24 +1,25 @@
1
- import bboxClip from '@turf/bbox-clip';
2
- import bboxPolygon from '@turf/bbox-polygon';
3
- import union from '@turf/union';
4
- import { getType } from '@turf/invariant';
5
- import { featureCollection, feature, polygon, multiPolygon } from '@turf/helpers';
6
1
  import intersects from '@turf/boolean-intersects';
2
+ import bboxPolygon from '@turf/bbox-polygon';
7
3
  import booleanWithin from '@turf/boolean-within';
8
4
  import intersect from '@turf/intersect';
5
+ import { featureCollection, feature, polygon, multiPolygon } from '@turf/helpers';
9
6
  import { getResolution as getResolution$1, geometryToCells, cellToChildren, cellToTile } from 'quadbin';
7
+ import bboxClip from '@turf/bbox-clip';
10
8
  import { getResolution as getResolution$2, polygonToCells } from 'h3-js';
9
+ import union from '@turf/union';
10
+ import { getType } from '@turf/invariant';
11
+ import { booleanEqual } from '@turf/boolean-equal';
11
12
 
12
13
  /**
13
14
  * @internal
14
- * @internalRemarks Source: @carto/react-core, @carto/constants, @deck.gl/carto
15
+ * @privateRemarks Source: @carto/react-core, @carto/constants, @deck.gl/carto
15
16
  */
16
17
  let client = 'deck-gl-carto';
17
18
  /**
18
19
  * Returns current client ID, used to categorize API requests. For internal use only.
19
20
  *
20
21
  * @internal
21
- * @internalRemarks Source: @carto/react-core
22
+ * @privateRemarks Source: @carto/react-core
22
23
  */
23
24
  function getClient() {
24
25
  return client;
@@ -27,7 +28,7 @@ function getClient() {
27
28
  * Sets current client ID, used to categorize API requests. For internal use only.
28
29
  *
29
30
  * @internal
30
- * @internalRemarks Source: @carto/react-core
31
+ * @privateRemarks Source: @carto/react-core
31
32
  */
32
33
  function setClient(c) {
33
34
  client = c;
@@ -45,7 +46,7 @@ function setClient(c) {
45
46
  * };
46
47
  * ```
47
48
  *
48
- * @internalRemarks Source: @carto/react-api, @deck.gl/carto
49
+ * @privateRemarks Source: @carto/react-api, @deck.gl/carto
49
50
  */
50
51
  var FilterType;
51
52
  (function (FilterType) {
@@ -57,16 +58,16 @@ var FilterType;
57
58
  FilterType["TIME"] = "time";
58
59
  FilterType["STRING_SEARCH"] = "stringSearch";
59
60
  })(FilterType || (FilterType = {}));
60
- /** @internalRemarks Source: @carto/constants */
61
+ /** @privateRemarks Source: @carto/constants */
61
62
  var ApiVersion;
62
63
  (function (ApiVersion) {
63
64
  ApiVersion["V1"] = "v1";
64
65
  ApiVersion["V2"] = "v2";
65
66
  ApiVersion["V3"] = "v3";
66
67
  })(ApiVersion || (ApiVersion = {}));
67
- /** @internalRemarks Source: @carto/constants, @deck.gl/carto */
68
+ /** @privateRemarks Source: @carto/constants, @deck.gl/carto */
68
69
  const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com';
69
- /** @internalRemarks Source: @carto/react-core */
70
+ /** @privateRemarks Source: @carto/react-core */
70
71
  var TileFormat;
71
72
  (function (TileFormat) {
72
73
  TileFormat["MVT"] = "mvt";
@@ -74,14 +75,14 @@ var TileFormat;
74
75
  TileFormat["GEOJSON"] = "geojson";
75
76
  TileFormat["BINARY"] = "binary";
76
77
  })(TileFormat || (TileFormat = {}));
77
- /** @internalRemarks Source: @carto/react-core */
78
+ /** @privateRemarks Source: @carto/react-core */
78
79
  var SpatialIndex;
79
80
  (function (SpatialIndex) {
80
81
  SpatialIndex["H3"] = "h3";
81
82
  SpatialIndex["S2"] = "s2";
82
83
  SpatialIndex["QUADBIN"] = "quadbin";
83
84
  })(SpatialIndex || (SpatialIndex = {}));
84
- /** @internalRemarks Source: @carto/react-core */
85
+ /** @privateRemarks Source: @carto/react-core */
85
86
  var Provider;
86
87
  (function (Provider) {
87
88
  Provider["BIGQUERY"] = "bigquery";
@@ -92,2187 +93,2314 @@ var Provider;
92
93
  Provider["DATABRICKS_REST"] = "databricksRest";
93
94
  })(Provider || (Provider = {}));
94
95
 
95
- const FILTER_TYPES = new Set(Object.values(FilterType));
96
- const isFilterType = type => FILTER_TYPES.has(type);
97
- /**
98
- * @privateRemarks Source: @carto/react-widgets
99
- * @internal
100
- */
101
- function getApplicableFilters(owner, filters) {
102
- if (!filters) return {};
103
- const applicableFilters = {};
104
- for (const column in filters) {
105
- for (const type in filters[column]) {
106
- if (!isFilterType(type)) continue;
107
- const filter = filters[column][type];
108
- const isApplicable = !owner || !(filter != null && filter.owner) || (filter == null ? void 0 : filter.owner) !== owner;
109
- if (filter && isApplicable) {
110
- applicableFilters[column] || (applicableFilters[column] = {});
111
- applicableFilters[column][type] = filter;
112
- }
96
+ function _extends() {
97
+ return _extends = Object.assign ? Object.assign.bind() : function (n) {
98
+ for (var e = 1; e < arguments.length; e++) {
99
+ var t = arguments[e];
100
+ for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
113
101
  }
114
- }
115
- return applicableFilters;
102
+ return n;
103
+ }, _extends.apply(null, arguments);
116
104
  }
117
- /**
118
- * Due to each data warehouse having its own behavior with columns,
119
- * we need to normalize them and transform every key to lowercase.
120
- *
121
- * @internalRemarks Source: @carto/react-widgets
122
- * @internal
123
- */
124
- function normalizeObjectKeys(el) {
125
- if (Array.isArray(el)) {
126
- return el.map(value => normalizeObjectKeys(value));
127
- } else if (typeof el !== 'object') {
128
- return el;
105
+ function _objectWithoutPropertiesLoose(r, e) {
106
+ if (null == r) return {};
107
+ var t = {};
108
+ for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
109
+ if (e.includes(n)) continue;
110
+ t[n] = r[n];
129
111
  }
130
- return Object.entries(el).reduce((acc, [key, value]) => {
131
- acc[key.toLowerCase()] = typeof value === 'object' && value ? normalizeObjectKeys(value) : value;
132
- return acc;
133
- }, {});
112
+ return t;
134
113
  }
135
- /** @internalRemarks Source: @carto/react-core */
136
- function assert$1(condition, message) {
137
- if (!condition) {
138
- throw new Error(message);
139
- }
114
+
115
+ function makeIntervalComplete(intervals) {
116
+ return intervals.map(val => {
117
+ if (val[0] === undefined || val[0] === null) {
118
+ return [Number.MIN_SAFE_INTEGER, val[1]];
119
+ }
120
+ if (val[1] === undefined || val[1] === null) {
121
+ return [val[0], Number.MAX_SAFE_INTEGER];
122
+ }
123
+ return val;
124
+ });
140
125
  }
141
- /**
142
- * @internalRemarks Source: @carto/react-core
143
- * @internal
144
- */
145
- class InvalidColumnError extends Error {
146
- constructor(message) {
147
- super(`${InvalidColumnError.NAME}: ${message}`);
148
- this.name = InvalidColumnError.NAME;
149
- }
150
- static is(error) {
151
- var _error$message;
152
- return error instanceof InvalidColumnError || ((_error$message = error.message) == null ? void 0 : _error$message.includes(InvalidColumnError.NAME));
153
- }
126
+
127
+ const filterFunctions = {
128
+ [FilterType.IN]: filterIn,
129
+ [FilterType.BETWEEN]: filterBetween,
130
+ [FilterType.TIME]: filterTime,
131
+ [FilterType.CLOSED_OPEN]: filterClosedOpen,
132
+ [FilterType.STRING_SEARCH]: filterStringSearch
133
+ };
134
+ function filterIn(filterValues, featureValue) {
135
+ return filterValues.includes(featureValue);
154
136
  }
155
- InvalidColumnError.NAME = 'InvalidColumnError';
156
- function isEmptyObject(object) {
157
- for (const _ in object) {
158
- return false;
137
+ // FilterTypes.BETWEEN
138
+ function filterBetween(filterValues, featureValue) {
139
+ const checkRange = range => {
140
+ const [lowerBound, upperBound] = range;
141
+ return featureValue >= lowerBound && featureValue <= upperBound;
142
+ };
143
+ return makeIntervalComplete(filterValues).some(checkRange);
144
+ }
145
+ function filterTime(filterValues, featureValue) {
146
+ const featureValueAsTimestamp = new Date(featureValue).getTime();
147
+ if (isFinite(featureValueAsTimestamp)) {
148
+ return filterBetween(filterValues, featureValueAsTimestamp);
149
+ } else {
150
+ throw new Error(`Column used to filter by time isn't well formatted.`);
159
151
  }
160
- return true;
161
152
  }
162
- /** @internal */
163
- const isObject = x => x !== null && typeof x === 'object';
164
- /** @internal */
165
- const isPureObject = x => isObject(x) && x.constructor === {}.constructor;
153
+ // FilterTypes.CLOSED_OPEN
154
+ function filterClosedOpen(filterValues, featureValue) {
155
+ const checkRange = range => {
156
+ const [lowerBound, upperBound] = range;
157
+ return featureValue >= lowerBound && featureValue < upperBound;
158
+ };
159
+ return makeIntervalComplete(filterValues).some(checkRange);
160
+ }
161
+ // FilterTypes.STRING_SEARCH
162
+ function filterStringSearch(filterValues, featureValue, params = {}) {
163
+ const normalizedFeatureValue = normalize(featureValue, params);
164
+ const stringRegExp = params.useRegExp ? filterValues : filterValues.map(filterValue => {
165
+ let stringRegExp = escapeRegExp(normalize(filterValue, params));
166
+ if (params.mustStart) stringRegExp = `^${stringRegExp}`;
167
+ if (params.mustEnd) stringRegExp = `${stringRegExp}$`;
168
+ return stringRegExp;
169
+ });
170
+ const regex = new RegExp(stringRegExp.join('|'), params.caseSensitive ? 'g' : 'gi');
171
+ return !!normalizedFeatureValue.match(regex);
172
+ }
173
+ // Aux
174
+ const specialCharRegExp = /[.*+?^${}()|[\]\\]/g;
175
+ const normalizeRegExp = /(?:[\^`\xA8\xAF\xB4\xB7\xB8\u02B0-\u034E\u0350-\u0357\u035D-\u0362\u0374\u0375\u037A\u0384\u0385\u0483-\u0487\u0559\u0591-\u05A1\u05A3-\u05BD\u05BF\u05C1\u05C2\u05C4\u064B-\u0652\u0657\u0658\u06DF\u06E0\u06E5\u06E6\u06EA-\u06EC\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F5\u0818\u0819\u0898-\u089F\u08C9-\u08D2\u08E3-\u08FE\u093C\u094D\u0951-\u0954\u0971\u09BC\u09CD\u0A3C\u0A4D\u0ABC\u0ACD\u0AFD-\u0AFF\u0B3C\u0B4D\u0B55\u0BCD\u0C3C\u0C4D\u0CBC\u0CCD\u0D3B\u0D3C\u0D4D\u0DCA\u0E3A\u0E47-\u0E4C\u0E4E\u0EBA\u0EC8-\u0ECC\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F82-\u0F84\u0F86\u0F87\u0FC6\u1037\u1039\u103A\u1063\u1064\u1069-\u106D\u1087-\u108D\u108F\u109A\u109B\u135D-\u135F\u1714\u1715\u1734\u17C9-\u17D3\u17DD\u1939-\u193B\u1A60\u1A75-\u1A7C\u1A7F\u1AB0-\u1ABE\u1AC1-\u1ACB\u1B34\u1B44\u1B6B-\u1B73\u1BAA\u1BAB\u1BE6\u1BF2\u1BF3\u1C36\u1C37\u1C78-\u1C7D\u1CD0-\u1CE8\u1CED\u1CF4\u1CF7-\u1CF9\u1D2C-\u1D6A\u1DC4-\u1DCF\u1DF5-\u1DFF\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2CEF-\u2CF1\u2E2F\u302A-\u302F\u3099-\u309C\u30FC\uA66F\uA67C\uA67D\uA67F\uA69C\uA69D\uA6F0\uA6F1\uA700-\uA721\uA788-\uA78A\uA7F8\uA7F9\uA806\uA82C\uA8C4\uA8E0-\uA8F1\uA92B-\uA92E\uA953\uA9B3\uA9C0\uA9E5\uAA7B-\uAA7D\uAABF-\uAAC2\uAAF6\uAB5B-\uAB5F\uAB69-\uAB6B\uABEC\uABED\uFB1E\uFE20-\uFE2F\uFF3E\uFF40\uFF70\uFF9E\uFF9F\uFFE3]|\uD800\uDEE0|\uD801[\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDE38-\uDE3A\uDE3F\uDEE5\uDEE6]|\uD803[\uDD22-\uDD27\uDD4E\uDD69-\uDD6D\uDEFD-\uDEFF\uDF46-\uDF50\uDF82-\uDF85]|\uD804[\uDC46\uDC70\uDCB9\uDCBA\uDD33\uDD34\uDD73\uDDC0\uDDCA-\uDDCC\uDE35\uDE36\uDEE9\uDEEA\uDF3B\uDF3C\uDF4D\uDF66-\uDF6C\uDF70-\uDF74\uDFCE-\uDFD0\uDFD2\uDFD3\uDFE1\uDFE2]|\uD805[\uDC42\uDC46\uDCC2\uDCC3\uDDBF\uDDC0\uDE3F\uDEB6\uDEB7\uDF2B]|\uD806[\uDC39\uDC3A\uDD3D\uDD3E\uDD43\uDDE0\uDE34\uDE47\uDE99]|\uD807[\uDC3F\uDD42\uDD44\uDD45\uDD97\uDF41\uDF42\uDF5A]|\uD80D[\uDC47-\uDC55]|\uD818\uDD2F|\uD81A[\uDEF0-\uDEF4\uDF30-\uDF36]|\uD81B[\uDD6B\uDD6C\uDF8F-\uDF9F\uDFF0\uDFF1]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD833[\uDF00-\uDF2D\uDF30-\uDF46]|\uD834[\uDD67-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD]|\uD838[\uDC30-\uDC6D\uDD30-\uDD36\uDEAE\uDEEC-\uDEEF]|\uD839[\uDDEE\uDDEF]|\uD83A[\uDCD0-\uDCD6\uDD44-\uDD46\uDD48-\uDD4A])/g;
176
+ function escapeRegExp(value) {
177
+ return value.replace(specialCharRegExp, '\\$&');
178
+ }
179
+ function normalize(data, params) {
180
+ let normalizedData = String(data);
181
+ if (!params.keepSpecialCharacters) normalizedData = normalizedData.normalize('NFD').replace(normalizeRegExp, '');
182
+ return normalizedData;
183
+ }
166
184
 
185
+ const LOGICAL_OPERATOR_METHODS = {
186
+ and: 'every',
187
+ or: 'some'
188
+ };
189
+ function passesFilter(columns, filters, feature, filtersLogicalOperator) {
190
+ const method = LOGICAL_OPERATOR_METHODS[filtersLogicalOperator];
191
+ return columns[method](column => {
192
+ const columnFilters = filters[column];
193
+ const columnFilterTypes = Object.keys(columnFilters);
194
+ if (!feature || feature[column] === null || feature[column] === undefined) {
195
+ return false;
196
+ }
197
+ return columnFilterTypes.every(filter => {
198
+ const filterFunction = filterFunctions[filter];
199
+ if (!filterFunction) {
200
+ throw new Error(`"${filter}" filter is not implemented.`);
201
+ }
202
+ return filterFunction(columnFilters[filter].values, feature[column], columnFilters[filter].params);
203
+ });
204
+ });
205
+ }
167
206
  /**
168
- * Adds a {@link Filter} to the filter set. Any previous filters with the same
169
- * `column` and `type` will be replaced.
207
+ * @internal
208
+ * @privateRemarks Exported for use in @deck.gl/carto's getDataFilterExtensionProps.
170
209
  */
171
- function addFilter(filters, {
172
- column,
173
- type,
174
- values,
175
- owner
210
+ function _buildFeatureFilter({
211
+ filters = {},
212
+ type = 'boolean',
213
+ filtersLogicalOperator = 'and'
176
214
  }) {
177
- if (!filters[column]) {
178
- filters[column] = {};
215
+ const columns = Object.keys(filters);
216
+ if (!columns.length) {
217
+ return () => type === 'number' ? 1 : true;
179
218
  }
180
- const filter = {
181
- values,
182
- owner
219
+ return feature => {
220
+ const f = feature.properties || feature;
221
+ const featurePassesFilter = passesFilter(columns, filters, f, filtersLogicalOperator);
222
+ return type === 'number' ? Number(featurePassesFilter) : featurePassesFilter;
183
223
  };
184
- filters[column][type] = filter;
185
- return filters;
186
224
  }
187
225
  /**
188
- * Removes one or more {@link Filter filters} from the filter set. If only
189
- * `column` is specified, then all filters on that column are removed. If both
190
- * `column` and `owner` are specified, then only filters for that column
191
- * associated with the owner are removed.
226
+ * Apply certain filters to a collection of features.
227
+ * @internal
192
228
  */
193
- function removeFilter(filters, {
194
- column,
195
- owner
196
- }) {
197
- const filter = filters[column];
198
- if (!filter) {
199
- return filters;
200
- }
201
- if (owner) {
202
- for (const type of Object.values(FilterType)) {
203
- var _filter$type;
204
- if (owner === ((_filter$type = filter[type]) == null ? void 0 : _filter$type.owner)) {
205
- delete filter[type];
206
- }
207
- }
208
- }
209
- if (!owner || isEmptyObject(filter)) {
210
- delete filters[column];
211
- }
212
- return filters;
229
+ function applyFilters(features, filters, filtersLogicalOperator) {
230
+ return Object.keys(filters).length ? features.filter(_buildFeatureFilter({
231
+ filters,
232
+ filtersLogicalOperator
233
+ })) : features;
213
234
  }
214
235
  /**
215
- * Clears all {@link Filter filters} from the filter set.
236
+ * Binary.
237
+ * @internal
216
238
  */
217
- function clearFilters(filters) {
218
- for (const column of Object.keys(filters)) {
219
- delete filters[column];
220
- }
221
- return filters;
222
- }
223
- function hasFilter(filters, {
224
- column,
225
- owner
239
+ function buildBinaryFeatureFilter({
240
+ filters = {}
226
241
  }) {
227
- const filter = filters[column];
228
- if (!filter) {
229
- return false;
230
- }
231
- if (!owner) {
232
- return true;
233
- }
234
- for (const type of Object.values(FilterType)) {
235
- var _filter$type2;
236
- if (owner === ((_filter$type2 = filter[type]) == null ? void 0 : _filter$type2.owner)) {
237
- return true;
238
- }
242
+ const columns = Object.keys(filters);
243
+ if (!columns.length) {
244
+ return () => 1;
239
245
  }
240
- return false;
246
+ return (featureIdIdx, binaryData) => passesFilterUsingBinary(columns, filters, featureIdIdx, binaryData);
241
247
  }
242
- function getFilter(filters, {
243
- column,
244
- type,
245
- owner
248
+ function getValueFromNumericProps(featureIdIdx, binaryData, {
249
+ column
246
250
  }) {
247
- var _filter$type3;
248
- const filter = filters[column];
249
- if (!filter) {
250
- return null;
251
+ var _binaryData$numericPr;
252
+ return (_binaryData$numericPr = binaryData.numericProps) == null || (_binaryData$numericPr = _binaryData$numericPr[column]) == null ? void 0 : _binaryData$numericPr.value[featureIdIdx];
253
+ }
254
+ function getValueFromProperties(featureIdIdx, binaryData, {
255
+ column
256
+ }) {
257
+ var _binaryData$propertie;
258
+ const propertyIdx = binaryData.featureIds.value[featureIdIdx];
259
+ return (_binaryData$propertie = binaryData.properties[propertyIdx]) == null ? void 0 : _binaryData$propertie[column];
260
+ }
261
+ const GET_VALUE_BY_BINARY_PROP = {
262
+ properties: getValueFromProperties,
263
+ numericProps: getValueFromNumericProps
264
+ };
265
+ function getBinaryPropertyByFilterValues(filterValues) {
266
+ return typeof filterValues.flat()[0] === 'string' ? 'properties' : 'numericProps';
267
+ }
268
+ function getFeatureValue(featureIdIdx, binaryData, filter) {
269
+ const {
270
+ column,
271
+ values
272
+ } = filter;
273
+ const binaryProp = getBinaryPropertyByFilterValues(values);
274
+ const getFeatureValueFn = GET_VALUE_BY_BINARY_PROP[binaryProp];
275
+ return getFeatureValueFn(featureIdIdx, binaryData, {
276
+ column
277
+ });
278
+ }
279
+ function passesFilterUsingBinary(columns, filters, featureIdIdx, binaryData) {
280
+ return columns.every(column => {
281
+ const columnFilters = filters[column];
282
+ return Object.entries(columnFilters).every(([type, {
283
+ values
284
+ }]) => {
285
+ const filterFn = filterFunctions[type];
286
+ if (!filterFn) {
287
+ throw new Error(`"${type}" filter is not implemented.`);
288
+ }
289
+ if (!values) return 0;
290
+ const featureValue = getFeatureValue(featureIdIdx, binaryData, {
291
+ type: type,
292
+ column,
293
+ values
294
+ });
295
+ if (featureValue === undefined || featureValue === null) return 0;
296
+ return filterFn(values, featureValue);
297
+ });
298
+ });
299
+ }
300
+
301
+ function geojsonFeatures({
302
+ geojson,
303
+ spatialFilter,
304
+ uniqueIdProperty
305
+ }) {
306
+ let uniqueIdx = 0;
307
+ const map = new Map();
308
+ if (!spatialFilter) {
309
+ return [];
251
310
  }
252
- if (!owner || owner === ((_filter$type3 = filter[type]) == null ? void 0 : _filter$type3.owner)) {
253
- return filter[type] || null;
311
+ for (const feature of geojson.features) {
312
+ const uniqueId = uniqueIdProperty ? feature.properties[uniqueIdProperty] : ++uniqueIdx;
313
+ if (!map.has(uniqueId) && intersects(spatialFilter, feature)) {
314
+ map.set(uniqueId, feature.properties);
315
+ }
254
316
  }
255
- return null;
317
+ return Array.from(map.values());
256
318
  }
257
319
 
320
+ // math.gl
321
+ // SPDX-License-Identifier: MIT
322
+ // Copyright (c) vis.gl contributors
323
+ const DEFAULT_CONFIG = {
324
+ EPSILON: 1e-12,
325
+ debug: false,
326
+ precision: 4,
327
+ printTypes: false,
328
+ printDegrees: false,
329
+ printRowMajor: true,
330
+ _cartographicRadians: false
331
+ };
332
+ // Configuration is truly global as of v3.6 to ensure single config even if multiple copies of math.gl
333
+ // Multiple copies of config can be quite tricky to debug...
334
+ globalThis.mathgl = globalThis.mathgl || {
335
+ config: {
336
+ ...DEFAULT_CONFIG
337
+ }
338
+ };
258
339
  /**
259
- * Returns a {@link SpatialFilter} for a given viewport, typically obtained
260
- * from deck.gl's `viewport.getBounds()` method ([west, south, east, north]).
261
- * If the viewport covers the entire world (to some margin of error in Web
262
- * Mercator space), `undefined` is returned instead.
263
- *
264
- * If the viewport extends beyond longitude range [-180, +180], the polygon
265
- * may be reformatted for compatibility with CARTO APIs.
340
+ * Check if value is an "array"
341
+ * Returns `true` if value is either an array or a typed array
342
+ * Note: returns `false` for `ArrayBuffer` and `DataView` instances
343
+ * @note isTypedArray and isNumericArray are often more useful in TypeScript
266
344
  */
267
- function createViewportSpatialFilter(viewport) {
268
- if (_isGlobalViewport(viewport)) {
269
- return;
345
+ function isArray(value) {
346
+ return Array.isArray(value) || ArrayBuffer.isView(value) && !(value instanceof DataView);
347
+ }
348
+ function lerp(a, b, t) {
349
+ if (isArray(a)) {
350
+ return a.map((ai, i) => lerp(ai, b[i], t));
351
+ }
352
+ return t * b + (1 - t) * a;
353
+ }
354
+
355
+ // Replacement for the external assert method to reduce bundle size
356
+ // Note: We don't use the second "message" argument in calling code,
357
+ // so no need to support it here
358
+ function assert$1(condition, message) {
359
+ if (!condition) {
360
+ throw new Error(message || '@math.gl/web-mercator: assertion failed.');
270
361
  }
271
- return createPolygonSpatialFilter(bboxPolygon(viewport).geometry);
272
362
  }
363
+
364
+ // TODO - THE UTILITIES IN THIS FILE SHOULD BE IMPORTED FROM WEB-MERCATOR-VIEWPORT MODULE
365
+ // CONSTANTS
366
+ const PI = Math.PI;
367
+ const PI_4 = PI / 4;
368
+ const DEGREES_TO_RADIANS = PI / 180;
369
+ const RADIANS_TO_DEGREES = 180 / PI;
370
+ const TILE_SIZE = 512;
273
371
  /**
274
- * Returns a {@link SpatialFilter} for a given {@link Polygon} or
275
- * {@link MultiPolygon}. If the polygon(s) extend outside longitude
276
- * range [-180, +180], the result may be reformatted for compatibility
277
- * with CARTO APIs.
372
+ * Project [lng,lat] on sphere onto [x,y] on 512*512 Mercator Zoom 0 tile.
373
+ * Performs the nonlinear part of the web mercator projection.
374
+ * Remaining projection is done with 4x4 matrices which also handles
375
+ * perspective.
376
+ *
377
+ * @param lngLat - [lng, lat] coordinates
378
+ * Specifies a point on the sphere to project onto the map.
379
+ * @return [x,y] coordinates.
278
380
  */
279
- function createPolygonSpatialFilter(spatialFilter) {
280
- return spatialFilter && _normalizeGeometry(spatialFilter) || undefined;
381
+ function lngLatToWorld(lngLat) {
382
+ const [lng, lat] = lngLat;
383
+ assert$1(Number.isFinite(lng));
384
+ assert$1(Number.isFinite(lat) && lat >= -90 && lat <= 90, 'invalid latitude');
385
+ const lambda2 = lng * DEGREES_TO_RADIANS;
386
+ const phi2 = lat * DEGREES_TO_RADIANS;
387
+ const x = TILE_SIZE * (lambda2 + PI) / (2 * PI);
388
+ const y = TILE_SIZE * (PI + Math.log(Math.tan(PI_4 + phi2 * 0.5))) / (2 * PI);
389
+ return [x, y];
281
390
  }
282
391
  /**
283
- * Check if a viewport is large enough to represent a global coverage.
284
- * In this case the spatial filter parameter for widget calculation is removed.
392
+ * Unproject world point [x,y] on map onto {lat, lon} on sphere
285
393
  *
286
- * @internalRemarks Source: @carto/react-core
394
+ * @param xy - array with [x,y] members
395
+ * representing point on projected map plane
396
+ * @return - array with [x,y] of point on sphere.
397
+ * Has toArray method if you need a GeoJSON Array.
398
+ * Per cartographic tradition, lat and lon are specified as degrees.
287
399
  */
288
- function _isGlobalViewport(viewport) {
289
- const [minx, miny, maxx, maxy] = viewport;
290
- return maxx - minx > 179.5 * 2 && maxy - miny > 85.05 * 2;
400
+ function worldToLngLat(xy) {
401
+ const [x, y] = xy;
402
+ const lambda2 = x / TILE_SIZE * (2 * PI) - PI;
403
+ const phi2 = 2 * (Math.atan(Math.exp(y / TILE_SIZE * (2 * PI) - PI)) - PI_4);
404
+ return [lambda2 * RADIANS_TO_DEGREES, phi2 * RADIANS_TO_DEGREES];
291
405
  }
406
+
407
+ const TRANSFORM_FN$1 = {
408
+ Point: transformPoint$1,
409
+ MultiPoint: transformMultiPoint$1,
410
+ LineString: transformLineString$1,
411
+ MultiLineString: transformMultiLineString$1,
412
+ Polygon: transformPolygon$1,
413
+ MultiPolygon: transformMultiPolygon$1
414
+ };
292
415
  /**
293
- * Normalized a geometry, coming from a mask or a viewport. The parts
294
- * spanning outside longitude range [-180, +180] are clipped and "folded"
295
- * back to the valid range and unioned to the polygons inide that range.
296
- *
297
- * It results in a Polygon or MultiPolygon strictly inside the validity range.
416
+ * Transform WGS84 coordinates to tile coords.
417
+ * It's the inverse of deck.gl coordinate-transform (https://github.com/visgl/deck.gl/blob/master/modules/geo-layers/src/mvt-layer/coordinate-transform.js)
298
418
  *
299
- * @internalRemarks Source: @carto/react-core
419
+ * @param geometry - any valid geojson geometry
420
+ * @param bbox - geojson bbox
300
421
  */
301
- function _normalizeGeometry(geometry) {
302
- const WORLD = [-180, -90, +180, +90];
303
- const worldClip = _clean(bboxClip(geometry, WORLD).geometry);
304
- const geometryTxWest = _tx(geometry, 360);
305
- const geometryTxEast = _tx(geometry, -360);
306
- let result = worldClip;
307
- if (result && geometryTxWest) {
308
- const worldWestClip = _clean(bboxClip(geometryTxWest, WORLD).geometry);
309
- if (worldWestClip) {
310
- const collection = featureCollection([feature(result), feature(worldWestClip)]);
311
- const merged = union(collection);
312
- result = merged ? _clean(merged.geometry) : result;
313
- }
314
- }
315
- if (result && geometryTxEast) {
316
- const worldEastClip = _clean(bboxClip(geometryTxEast, WORLD).geometry);
317
- if (worldEastClip) {
318
- const collection = featureCollection([feature(result), feature(worldEastClip)]);
319
- const merged = union(collection);
320
- result = merged ? _clean(merged.geometry) : result;
321
- }
422
+ function transformToTileCoords(geometry, bbox) {
423
+ const [west, south, east, north] = bbox;
424
+ const nw = projectFlat([west, north]);
425
+ const se = projectFlat([east, south]);
426
+ const projectedBbox = [nw, se];
427
+ if (geometry.type === 'GeometryCollection') {
428
+ throw new Error('Unsupported geometry type GeometryCollection');
322
429
  }
323
- return result;
430
+ const transformFn = TRANSFORM_FN$1[geometry.type];
431
+ const coordinates = transformFn(geometry.coordinates, projectedBbox);
432
+ return _extends({}, geometry, {
433
+ coordinates
434
+ });
324
435
  }
325
- /** @internalRemarks Source: @carto/react-core */
326
- function _cleanPolygonCoords(cc) {
327
- const coords = cc.filter(c => c.length > 0);
328
- return coords.length > 0 ? coords : null;
436
+ function transformPoint$1([pointX, pointY], [nw, se]) {
437
+ const x = inverseLerp(nw[0], se[0], pointX);
438
+ const y = inverseLerp(nw[1], se[1], pointY);
439
+ return [x, y];
329
440
  }
330
- /** @internalRemarks Source: @carto/react-core */
331
- function _cleanMultiPolygonCoords(ccc) {
332
- const coords = ccc.map(_cleanPolygonCoords).filter(cc => cc);
333
- return coords.length > 0 ? coords : null;
441
+ function getPoints$1(geometry, bbox) {
442
+ return geometry.map(g => transformPoint$1(projectFlat(g), bbox));
334
443
  }
335
- /** @internalRemarks Source: @carto/react-core */
336
- function _clean(geometry) {
337
- if (!geometry) {
338
- return null;
339
- }
340
- if (_isPolygon(geometry)) {
341
- const coords = _cleanPolygonCoords(geometry.coordinates);
342
- return coords ? polygon(coords).geometry : null;
343
- }
344
- if (_isMultiPolygon(geometry)) {
345
- const coords = _cleanMultiPolygonCoords(geometry.coordinates);
346
- return coords ? multiPolygon(coords).geometry : null;
347
- }
348
- return null;
444
+ function transformMultiPoint$1(multiPoint, bbox) {
445
+ return getPoints$1(multiPoint, bbox);
349
446
  }
350
- /** @internalRemarks Source: @carto/react-core */
351
- function _txContourCoords(cc, distance) {
352
- return cc.map(c => [c[0] + distance, c[1]]);
447
+ function transformLineString$1(line, bbox) {
448
+ return getPoints$1(line, bbox);
353
449
  }
354
- /** @internalRemarks Source: @carto/react-core */
355
- function _txPolygonCoords(ccc, distance) {
356
- return ccc.map(cc => _txContourCoords(cc, distance));
357
- }
358
- /** @internalRemarks Source: @carto/react-core */
359
- function _txMultiPolygonCoords(cccc, distance) {
360
- return cccc.map(ccc => _txPolygonCoords(ccc, distance));
361
- }
362
- /** @internalRemarks Source: @carto/react-core */
363
- function _tx(geometry, distance) {
364
- if (geometry && getType(geometry) === 'Polygon') {
365
- const coords = _txPolygonCoords(geometry.coordinates, distance);
366
- return polygon(coords).geometry;
367
- } else if (geometry && getType(geometry) === 'MultiPolygon') {
368
- const coords = _txMultiPolygonCoords(geometry.coordinates, distance);
369
- return multiPolygon(coords).geometry;
370
- } else {
371
- return null;
372
- }
450
+ function transformMultiLineString$1(multiLineString, bbox) {
451
+ return multiLineString.map(lineString => transformLineString$1(lineString, bbox));
373
452
  }
374
- function _isPolygon(geometry) {
375
- return getType(geometry) === 'Polygon';
453
+ function transformPolygon$1(polygon, bbox) {
454
+ return polygon.map(polygonRing => getPoints$1(polygonRing, bbox));
376
455
  }
377
- function _isMultiPolygon(geometry) {
378
- return getType(geometry) === 'MultiPolygon';
456
+ function transformMultiPolygon$1(multiPolygon, bbox) {
457
+ return multiPolygon.map(polygon => transformPolygon$1(polygon, bbox));
379
458
  }
380
-
381
- function _extends() {
382
- return _extends = Object.assign ? Object.assign.bind() : function (n) {
383
- for (var e = 1; e < arguments.length; e++) {
384
- var t = arguments[e];
385
- for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
386
- }
387
- return n;
388
- }, _extends.apply(null, arguments);
459
+ function projectFlat(xyz) {
460
+ return lngLatToWorld(xyz);
389
461
  }
390
- function _objectWithoutPropertiesLoose(r, e) {
391
- if (null == r) return {};
392
- var t = {};
393
- for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
394
- if (e.includes(n)) continue;
395
- t[n] = r[n];
396
- }
397
- return t;
462
+ function inverseLerp(a, b, x) {
463
+ return (x - a) / (b - a);
398
464
  }
399
465
 
466
+ const TRANSFORM_FN = {
467
+ Point: transformPoint,
468
+ MultiPoint: transformMultiPoint,
469
+ LineString: transformLineString,
470
+ MultiLineString: transformMultiLineString,
471
+ Polygon: transformPolygon,
472
+ MultiPolygon: transformMultiPolygon
473
+ };
400
474
  /**
401
- * Current version of @carto/api-client.
402
- * @internal
403
- */
404
- /** @internal */
405
- const V3_MINOR_VERSION = '3.4';
406
- /** @internalRemarks Source: @carto/constants, @deck.gl/carto */
407
- const DEFAULT_GEO_COLUMN = 'geom';
408
- /**
409
- * Fastly default limit is 8192; leave some padding.
410
- * @internalRemarks Source: @deck.gl/carto
411
- */
412
- const DEFAULT_MAX_LENGTH_URL = 7000;
413
- /** @internalRemarks Source: @deck.gl/carto */
414
- const DEFAULT_TILE_RESOLUTION = 0.5;
415
- /**
416
- * @internalRemarks Source: @deck.gl/carto
417
- * @internal
418
- */
419
- const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4;
420
- /**
421
- * @internalRemarks Source: @deck.gl/carto
422
- * @internal
475
+ * Transform tile coords to WGS84 coordinates.
476
+ *
477
+ * @param geometry - any valid geojson geometry
478
+ * @param bbox - geojson bbox
423
479
  */
424
- const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6;
425
-
426
- // deck.gl
427
- // SPDX-License-Identifier: MIT
428
- // Copyright (c) vis.gl contributors
429
- function joinPath(...args) {
430
- return args.map(part => part.endsWith('/') ? part.slice(0, -1) : part).join('/');
480
+ function transformTileCoordsToWGS84(geometry, bbox) {
481
+ const [west, south, east, north] = bbox;
482
+ const nw = lngLatToWorld([west, north]);
483
+ const se = lngLatToWorld([east, south]);
484
+ const projectedBbox = [nw, se];
485
+ if (geometry.type === 'GeometryCollection') {
486
+ throw new Error('Unsupported geometry type GeometryCollection');
487
+ }
488
+ const transformFn = TRANSFORM_FN[geometry.type];
489
+ const coordinates = transformFn(geometry.coordinates, projectedBbox);
490
+ return _extends({}, geometry, {
491
+ coordinates
492
+ });
431
493
  }
432
- function buildV3Path(apiBaseUrl, version, endpoint, ...rest) {
433
- return joinPath(apiBaseUrl, version, endpoint, ...rest);
494
+ function transformPoint([pointX, pointY], [nw, se]) {
495
+ const x = lerp(nw[0], se[0], pointX);
496
+ const y = lerp(nw[1], se[1], pointY);
497
+ return worldToLngLat([x, y]);
434
498
  }
435
- /** @internal Required by fetchMap(). */
436
- function buildPublicMapUrl({
437
- apiBaseUrl,
438
- cartoMapId
439
- }) {
440
- return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId);
499
+ function getPoints(geometry, bbox) {
500
+ return geometry.map(g => transformPoint(g, bbox));
441
501
  }
442
- /** @internal Required by fetchMap(). */
443
- function buildStatsUrl({
444
- attribute,
445
- apiBaseUrl,
446
- connectionName,
447
- source,
448
- type
449
- }) {
450
- if (type === 'query') {
451
- return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, attribute);
452
- }
453
- // type === 'table'
454
- return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, source, attribute);
502
+ function transformMultiPoint(multiPoint, bbox) {
503
+ return getPoints(multiPoint, bbox);
455
504
  }
456
- function buildSourceUrl({
457
- apiBaseUrl,
458
- connectionName,
459
- endpoint
460
- }) {
461
- return buildV3Path(apiBaseUrl, 'v3', 'maps', connectionName, endpoint);
505
+ function transformLineString(line, bbox) {
506
+ return getPoints(line, bbox);
462
507
  }
463
- function buildQueryUrl({
464
- apiBaseUrl,
465
- connectionName
466
- }) {
467
- return buildV3Path(apiBaseUrl, 'v3', 'sql', connectionName, 'query');
508
+ function transformMultiLineString(multiLineString, bbox) {
509
+ return multiLineString.map(lineString => transformLineString(lineString, bbox));
510
+ }
511
+ function transformPolygon(polygon, bbox) {
512
+ return polygon.map(polygonRing => getPoints(polygonRing, bbox));
513
+ }
514
+ function transformMultiPolygon(multiPolygon, bbox) {
515
+ return multiPolygon.map(polygon => transformPolygon(polygon, bbox));
468
516
  }
469
517
 
470
- // deck.gl
471
- // SPDX-License-Identifier: MIT
472
- // Copyright (c) vis.gl contributors
473
- /**
474
- *
475
- * Custom error for reported errors in CARTO Maps API.
476
- * Provides useful debugging information in console and context for applications.
477
- *
478
- */
479
- class CartoAPIError extends Error {
480
- constructor(error, errorContext, response, responseJson) {
481
- let responseString = 'Failed to connect';
482
- if (response) {
483
- responseString = 'Server returned: ';
484
- if (response.status === 400) {
485
- responseString += 'Bad request';
486
- } else if (response.status === 401 || response.status === 403) {
487
- responseString += 'Unauthorized access';
488
- } else if (response.status === 404) {
489
- responseString += 'Not found';
490
- } else {
491
- responseString += 'Error';
492
- }
493
- responseString += ` (${response.status}):`;
518
+ const FEATURE_GEOM_PROPERTY = '__geomValue';
519
+ function tileFeaturesGeometries({
520
+ tiles,
521
+ tileFormat,
522
+ spatialFilter,
523
+ uniqueIdProperty,
524
+ options
525
+ }) {
526
+ const map = new Map();
527
+ for (const tile of tiles) {
528
+ // Discard if it's not a visible tile (only check false value, not undefined)
529
+ // or tile has not data
530
+ if (tile.isVisible === false || !tile.data) {
531
+ continue;
494
532
  }
495
- responseString += ` ${error.message || error}`;
496
- let message = `${errorContext.requestType} API request failed`;
497
- message += `\n${responseString}`;
498
- for (const key of Object.keys(errorContext)) {
499
- if (key === 'requestType') continue;
500
- message += `\n${formatErrorKey(key)}: ${errorContext[key]}`;
533
+ const bbox = [tile.bbox.west, tile.bbox.south, tile.bbox.east, tile.bbox.north];
534
+ const bboxToGeom = bboxPolygon(bbox);
535
+ const tileIsFullyVisible = booleanWithin(bboxToGeom, spatialFilter);
536
+ // Clip the geometry to intersect with the tile
537
+ const spatialFilterFeature = {
538
+ type: 'Feature',
539
+ geometry: spatialFilter,
540
+ properties: {}
541
+ };
542
+ const clippedGeometryToIntersect = intersect(featureCollection([bboxToGeom, spatialFilterFeature]));
543
+ if (!clippedGeometryToIntersect) {
544
+ continue;
501
545
  }
502
- message += '\n';
503
- super(message);
504
- /** Source error from server */
505
- this.error = void 0;
506
- /** Context (API call & parameters) in which error occured */
507
- this.errorContext = void 0;
508
- /** Response from server */
509
- this.response = void 0;
510
- /** JSON Response from server */
511
- this.responseJson = void 0;
512
- this.name = 'CartoAPIError';
513
- this.response = response;
514
- this.responseJson = responseJson;
515
- this.error = error;
516
- this.errorContext = errorContext;
546
+ // We assume that MVT tileFormat uses local coordinates so we transform the geometry to intersect to tile coordinates [0..1],
547
+ // while in the case of 'geojson' or binary, the geometries are already in WGS84
548
+ const transformedGeometryToIntersect = tileFormat === TileFormat.MVT ? transformToTileCoords(clippedGeometryToIntersect.geometry, bbox) : clippedGeometryToIntersect.geometry;
549
+ createIndicesForPoints(tile.data.points);
550
+ calculateFeatures({
551
+ map,
552
+ tileIsFullyVisible,
553
+ geometryIntersection: transformedGeometryToIntersect,
554
+ data: tile.data.points,
555
+ type: 'Point',
556
+ bbox,
557
+ tileFormat,
558
+ uniqueIdProperty,
559
+ options
560
+ });
561
+ calculateFeatures({
562
+ map,
563
+ tileIsFullyVisible,
564
+ geometryIntersection: transformedGeometryToIntersect,
565
+ data: tile.data.lines,
566
+ type: 'LineString',
567
+ bbox,
568
+ tileFormat,
569
+ uniqueIdProperty,
570
+ options
571
+ });
572
+ calculateFeatures({
573
+ map,
574
+ tileIsFullyVisible,
575
+ geometryIntersection: transformedGeometryToIntersect,
576
+ data: tile.data.polygons,
577
+ type: 'Polygon',
578
+ bbox,
579
+ tileFormat,
580
+ uniqueIdProperty,
581
+ options
582
+ });
517
583
  }
584
+ return Array.from(map.values());
518
585
  }
519
- /**
520
- * Converts camelCase to Camel Case
521
- */
522
- function formatErrorKey(key) {
523
- return key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
524
- }
525
-
526
- const DEFAULT_HEADERS = {
527
- Accept: 'application/json',
528
- 'Content-Type': 'application/json'
529
- };
530
- const DEFAULT_REQUEST_CACHE = new Map();
531
- async function requestWithParameters({
532
- baseUrl,
533
- parameters = {},
534
- headers: customHeaders = {},
535
- errorContext,
536
- maxLengthURL = DEFAULT_MAX_LENGTH_URL,
537
- localCache
586
+ function processTileFeatureProperties({
587
+ map,
588
+ data,
589
+ startIndex,
590
+ endIndex,
591
+ type,
592
+ bbox,
593
+ tileFormat,
594
+ uniqueIdProperty,
595
+ storeGeometry,
596
+ geometryIntersection
538
597
  }) {
539
- // Parameters added to all requests issued with `requestWithParameters()`.
540
- // These parameters override parameters already in the base URL, but not
541
- // user-provided parameters.
542
- parameters = _extends({
543
- v: V3_MINOR_VERSION,
544
- client: getClient()
545
- }, typeof deck !== 'undefined' && deck.VERSION && {
546
- deckglVersion: deck.VERSION
547
- }, parameters);
548
- baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters));
549
- const key = createCacheKey(baseUrl, parameters, customHeaders);
550
- const {
551
- cache: REQUEST_CACHE,
552
- canReadCache,
553
- canStoreInCache
554
- } = getCacheSettings(localCache);
555
- if (canReadCache && REQUEST_CACHE.has(key)) {
556
- return REQUEST_CACHE.get(key);
598
+ const tileProps = getPropertiesFromTile(data, startIndex);
599
+ const uniquePropertyValue = getUniquePropertyValue(tileProps, uniqueIdProperty, map);
600
+ if (!uniquePropertyValue || map.has(uniquePropertyValue)) {
601
+ return;
557
602
  }
558
- const url = createURLWithParameters(baseUrl, parameters);
559
- const headers = _extends({}, DEFAULT_HEADERS, customHeaders);
560
- /* global fetch */
561
- const fetchPromise = url.length > maxLengthURL ? fetch(baseUrl, {
562
- method: 'POST',
563
- body: JSON.stringify(parameters),
564
- headers
565
- }) : fetch(url, {
566
- headers
567
- });
568
- let response;
569
- let responseJson;
570
- const jsonPromise = fetchPromise.then(_response => {
571
- response = _response;
572
- return response.json();
573
- }).then(json => {
574
- responseJson = json;
575
- if (!response || !response.ok) {
576
- throw new Error(json.error);
577
- }
578
- return json;
579
- }).catch(error => {
580
- if (canStoreInCache) {
581
- REQUEST_CACHE.delete(key);
582
- }
583
- throw new CartoAPIError(error, errorContext, response, responseJson);
584
- });
585
- if (canStoreInCache) {
586
- REQUEST_CACHE.set(key, jsonPromise);
603
+ let geometry = null;
604
+ // Only calculate geometry if necessary
605
+ if (storeGeometry || geometryIntersection) {
606
+ const {
607
+ positions
608
+ } = data;
609
+ const ringCoordinates = getRingCoordinatesFor(startIndex, endIndex, positions);
610
+ geometry = getFeatureByType(ringCoordinates, type);
587
611
  }
588
- return jsonPromise;
589
- }
590
- function getCacheSettings(localCache) {
591
- var _localCache$cacheCont, _localCache$cacheCont2;
592
- const canReadCache = localCache != null && (_localCache$cacheCont = localCache.cacheControl) != null && _localCache$cacheCont.includes('no-cache') ? false : true;
593
- const canStoreInCache = localCache != null && (_localCache$cacheCont2 = localCache.cacheControl) != null && _localCache$cacheCont2.includes('no-store') ? false : true;
594
- const cache = (localCache == null ? void 0 : localCache.cache) || DEFAULT_REQUEST_CACHE;
595
- return {
596
- cache,
597
- canReadCache,
598
- canStoreInCache
599
- };
612
+ // If intersection is required, check before proceeding
613
+ if (geometry && geometryIntersection && !intersects(geometry, geometryIntersection)) {
614
+ return;
615
+ }
616
+ const properties = parseProperties(tileProps);
617
+ // Only save geometry if necessary
618
+ if (storeGeometry && geometry) {
619
+ properties[FEATURE_GEOM_PROPERTY] = tileFormat === TileFormat.MVT ? transformTileCoordsToWGS84(geometry, bbox) : geometry;
620
+ }
621
+ map.set(uniquePropertyValue, properties);
600
622
  }
601
- function createCacheKey(baseUrl, parameters, headers) {
602
- const parameterEntries = Object.entries(parameters).sort(([a], [b]) => a > b ? 1 : -1);
603
- const headerEntries = Object.entries(headers).sort(([a], [b]) => a > b ? 1 : -1);
604
- return JSON.stringify({
605
- baseUrl,
606
- parameters: parameterEntries,
607
- headers: headerEntries
608
- });
623
+ function addIntersectedFeaturesInTile({
624
+ map,
625
+ data,
626
+ geometryIntersection,
627
+ type,
628
+ bbox,
629
+ tileFormat,
630
+ uniqueIdProperty,
631
+ options
632
+ }) {
633
+ const indices = getIndices(data);
634
+ const storeGeometry = (options == null ? void 0 : options.storeGeometry) || false;
635
+ for (let i = 0; i < indices.length - 1; i++) {
636
+ const startIndex = indices[i];
637
+ const endIndex = indices[i + 1];
638
+ processTileFeatureProperties({
639
+ map,
640
+ data,
641
+ startIndex,
642
+ endIndex,
643
+ type,
644
+ bbox,
645
+ tileFormat,
646
+ uniqueIdProperty,
647
+ storeGeometry,
648
+ geometryIntersection
649
+ });
650
+ }
609
651
  }
610
- /**
611
- * Appends query string parameters to a URL. Existing URL parameters are kept,
612
- * unless there is a conflict, in which case the new parameters override
613
- * those already in the URL.
614
- */
615
- function createURLWithParameters(baseUrlString, parameters) {
616
- const baseUrl = new URL(baseUrlString);
617
- for (const [key, value] of Object.entries(parameters)) {
618
- if (isPureObject(value) || Array.isArray(value)) {
619
- baseUrl.searchParams.set(key, JSON.stringify(value));
620
- } else {
621
- baseUrl.searchParams.set(key, value.toString());
622
- }
652
+ function getIndices(data) {
653
+ let indices;
654
+ switch (data.type) {
655
+ case 'Point':
656
+ // @ts-expect-error Missing or changed types?
657
+ indices = data.pointIndices;
658
+ break;
659
+ case 'LineString':
660
+ indices = data.pathIndices;
661
+ break;
662
+ case 'Polygon':
663
+ indices = data.primitivePolygonIndices;
664
+ break;
665
+ default:
666
+ throw new Error(`Unexpected type, "${data.type}"`);
623
667
  }
624
- return baseUrl.toString();
668
+ return indices.value;
625
669
  }
626
- /**
627
- * Deletes query string parameters from a URL.
628
- */
629
- function excludeURLParameters(baseUrlString, parameters) {
630
- const baseUrl = new URL(baseUrlString);
631
- for (const param of parameters) {
632
- if (baseUrl.searchParams.has(param)) {
633
- baseUrl.searchParams.delete(param);
634
- }
670
+ function getFeatureId(data, startIndex) {
671
+ return data.featureIds.value[startIndex];
672
+ }
673
+ function getPropertiesFromTile(data, startIndex) {
674
+ var _fields$featureId;
675
+ const featureId = getFeatureId(data, startIndex);
676
+ const {
677
+ properties,
678
+ numericProps,
679
+ fields
680
+ } = data;
681
+ const result = {
682
+ uniqueId: fields == null || (_fields$featureId = fields[featureId]) == null ? void 0 : _fields$featureId.id,
683
+ properties: properties[featureId],
684
+ numericProps: {}
685
+ };
686
+ for (const key in numericProps) {
687
+ result.numericProps[key] = numericProps[key].value[startIndex];
635
688
  }
636
- return baseUrl.toString();
689
+ return result;
637
690
  }
638
-
639
- const _excluded$3 = ["accessToken", "connectionName", "cache"];
640
- const SOURCE_DEFAULTS = {
641
- apiBaseUrl: DEFAULT_API_BASE_URL,
642
- clientId: getClient(),
643
- format: 'tilejson',
644
- headers: {},
645
- maxLengthURL: DEFAULT_MAX_LENGTH_URL
646
- };
647
- async function baseSource(endpoint, options, urlParameters) {
691
+ function parseProperties(tileProps) {
648
692
  const {
649
- accessToken,
650
- connectionName,
651
- cache
652
- } = options,
653
- optionalOptions = _objectWithoutPropertiesLoose(options, _excluded$3);
654
- const mergedOptions = _extends({}, SOURCE_DEFAULTS, {
655
- accessToken,
656
- connectionName,
657
- endpoint
658
- });
659
- for (const key in optionalOptions) {
660
- if (optionalOptions[key]) {
661
- mergedOptions[key] = optionalOptions[key];
662
- }
693
+ properties,
694
+ numericProps
695
+ } = tileProps;
696
+ return Object.assign({}, properties, numericProps);
697
+ }
698
+ function getUniquePropertyValue(tileProps, uniqueIdProperty, map) {
699
+ if (uniqueIdProperty) {
700
+ return getValueFromTileProps(tileProps, uniqueIdProperty);
663
701
  }
664
- const baseUrl = buildSourceUrl(mergedOptions);
702
+ if (tileProps.uniqueId) {
703
+ return tileProps.uniqueId;
704
+ }
705
+ const artificialId = map.size + 1; // a counter, assumed as a valid new id
706
+ return getValueFromTileProps(tileProps, 'cartodb_id') || getValueFromTileProps(tileProps, 'geoid') || artificialId;
707
+ }
708
+ function getValueFromTileProps(tileProps, propertyName) {
665
709
  const {
666
- clientId,
667
- maxLengthURL,
668
- format,
669
- localCache
670
- } = mergedOptions;
671
- const headers = _extends({
672
- Authorization: `Bearer ${options.accessToken}`
673
- }, options.headers);
674
- const parameters = _extends({
675
- client: clientId
676
- }, urlParameters);
677
- const errorContext = {
678
- requestType: 'Map instantiation',
679
- connection: options.connectionName,
680
- type: endpoint,
681
- source: JSON.stringify(parameters, undefined, 2)
682
- };
683
- const mapInstantiation = await requestWithParameters({
684
- baseUrl,
685
- parameters,
686
- headers,
687
- errorContext,
688
- maxLengthURL,
689
- localCache
690
- });
691
- const dataUrl = mapInstantiation[format].url[0];
692
- if (cache) {
693
- cache.value = parseInt(new URL(dataUrl).searchParams.get('cache') || '', 10);
710
+ properties,
711
+ numericProps
712
+ } = tileProps;
713
+ return numericProps[propertyName] || properties[propertyName];
714
+ }
715
+ function getFeatureByType(coordinates, type) {
716
+ switch (type) {
717
+ case 'Polygon':
718
+ return {
719
+ type: 'Polygon',
720
+ coordinates: [coordinates]
721
+ };
722
+ case 'LineString':
723
+ return {
724
+ type: 'LineString',
725
+ coordinates
726
+ };
727
+ case 'Point':
728
+ return {
729
+ type: 'Point',
730
+ coordinates: coordinates[0]
731
+ };
732
+ default:
733
+ throw new Error('Invalid geometry type');
694
734
  }
695
- errorContext.requestType = 'Map data';
696
- if (format === 'tilejson') {
697
- const json = await requestWithParameters({
698
- baseUrl: dataUrl,
699
- headers,
700
- errorContext,
701
- maxLengthURL,
702
- localCache
703
- });
704
- if (accessToken) {
705
- json.accessToken = accessToken;
706
- }
707
- return json;
735
+ }
736
+ function getRingCoordinatesFor(startIndex, endIndex, positions) {
737
+ const ringCoordinates = [];
738
+ for (let j = startIndex; j < endIndex; j++) {
739
+ ringCoordinates.push(Array.from(positions.value.subarray(j * positions.size, (j + 1) * positions.size)));
708
740
  }
709
- return await requestWithParameters({
710
- baseUrl: dataUrl,
711
- headers,
712
- errorContext,
713
- maxLengthURL,
714
- localCache
715
- });
741
+ return ringCoordinates;
716
742
  }
717
-
718
- // deck.gl
719
- // SPDX-License-Identifier: MIT
720
- // Copyright (c) vis.gl contributors
721
- const boundaryQuerySource = async function boundaryQuerySource(options) {
722
- const {
723
- columns,
724
- filters,
725
- tilesetTableName,
726
- propertiesSqlQuery,
727
- queryParameters
728
- } = options;
729
- const urlParameters = {
730
- tilesetTableName,
731
- propertiesSqlQuery
732
- };
733
- if (columns) {
734
- urlParameters.columns = columns.join(',');
743
+ function calculateFeatures({
744
+ map,
745
+ tileIsFullyVisible,
746
+ geometryIntersection,
747
+ data,
748
+ type,
749
+ bbox,
750
+ tileFormat,
751
+ uniqueIdProperty,
752
+ options
753
+ }) {
754
+ if (!(data != null && data.properties.length)) {
755
+ return;
735
756
  }
736
- if (filters) {
737
- urlParameters.filters = filters;
757
+ if (tileIsFullyVisible) {
758
+ addAllFeaturesInTile({
759
+ map,
760
+ data,
761
+ type,
762
+ bbox,
763
+ tileFormat,
764
+ uniqueIdProperty,
765
+ options
766
+ });
767
+ } else {
768
+ addIntersectedFeaturesInTile({
769
+ map,
770
+ data,
771
+ geometryIntersection,
772
+ type,
773
+ bbox,
774
+ tileFormat,
775
+ uniqueIdProperty,
776
+ options
777
+ });
738
778
  }
739
- if (queryParameters) {
740
- urlParameters.queryParameters = queryParameters;
779
+ }
780
+ function addAllFeaturesInTile({
781
+ map,
782
+ data,
783
+ type,
784
+ bbox,
785
+ tileFormat,
786
+ uniqueIdProperty,
787
+ options
788
+ }) {
789
+ const indices = getIndices(data);
790
+ const storeGeometry = (options == null ? void 0 : options.storeGeometry) || false;
791
+ for (let i = 0; i < indices.length - 1; i++) {
792
+ const startIndex = indices[i];
793
+ const endIndex = indices[i + 1];
794
+ processTileFeatureProperties({
795
+ map,
796
+ data,
797
+ startIndex,
798
+ endIndex,
799
+ type,
800
+ bbox,
801
+ tileFormat,
802
+ uniqueIdProperty,
803
+ storeGeometry
804
+ });
741
805
  }
742
- return baseSource('boundary', options, urlParameters);
743
- };
744
-
745
- // deck.gl
746
- // SPDX-License-Identifier: MIT
747
- // Copyright (c) vis.gl contributors
748
- const boundaryTableSource = async function boundaryTableSource(options) {
749
- const {
750
- filters,
751
- tilesetTableName,
752
- columns,
753
- propertiesTableName
754
- } = options;
755
- const urlParameters = {
756
- tilesetTableName,
757
- propertiesTableName
806
+ }
807
+ function createIndicesForPoints(data) {
808
+ const featureIds = data.featureIds.value;
809
+ const lastFeatureId = featureIds[featureIds.length - 1];
810
+ const PointIndicesArray = featureIds.constructor;
811
+ const pointIndices = {
812
+ value: new PointIndicesArray(featureIds.length + 1),
813
+ size: 1
758
814
  };
759
- if (columns) {
760
- urlParameters.columns = columns.join(',');
815
+ pointIndices.value.set(featureIds);
816
+ pointIndices.value.set([lastFeatureId + 1], featureIds.length);
817
+ // @ts-expect-error Missing or changed types?
818
+ data.pointIndices = pointIndices;
819
+ }
820
+
821
+ function tileFeaturesSpatialIndex({
822
+ tiles,
823
+ spatialFilter,
824
+ spatialDataColumn,
825
+ spatialDataType
826
+ }) {
827
+ const map = new Map();
828
+ const spatialIndex = getSpatialIndex(spatialDataType);
829
+ const resolution = getResolution(tiles, spatialIndex);
830
+ const spatialIndexIDName = spatialDataColumn ? spatialDataColumn : spatialIndex;
831
+ if (!resolution) {
832
+ return [];
761
833
  }
762
- if (filters) {
763
- urlParameters.filters = filters;
834
+ const cells = getCellsCoverGeometry(spatialFilter, spatialIndex, resolution);
835
+ if (!(cells != null && cells.length)) {
836
+ return [];
764
837
  }
765
- return baseSource('boundary', options, urlParameters);
766
- };
767
-
768
- const DEFAULT_TILE_SIZE = 512;
769
- const QUADBIN_ZOOM_MAX_OFFSET = 4;
770
- function getSpatialFiltersResolution(source, viewState) {
771
- var _source$dataResolutio, _source$aggregationRe;
772
- const dataResolution = (_source$dataResolutio = source.dataResolution) != null ? _source$dataResolutio : Number.MAX_VALUE;
773
- const aggregationResLevel = (_source$aggregationRe = source.aggregationResLevel) != null ? _source$aggregationRe : source.spatialDataType === 'h3' ? DEFAULT_AGGREGATION_RES_LEVEL_H3 : DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN;
774
- const aggregationResLevelOffset = Math.max(0, Math.floor(aggregationResLevel));
775
- const currentZoomInt = Math.ceil(viewState.zoom);
776
- if (source.spatialDataType === 'h3') {
777
- var _maxH3SpatialFiltersR, _maxH3SpatialFiltersR2;
778
- const tileSize = DEFAULT_TILE_SIZE;
779
- const maxResolutionForZoom = (_maxH3SpatialFiltersR = (_maxH3SpatialFiltersR2 = maxH3SpatialFiltersResolutions.find(([zoom]) => zoom === currentZoomInt)) == null ? void 0 : _maxH3SpatialFiltersR2[1]) != null ? _maxH3SpatialFiltersR : Math.max(0, currentZoomInt - 3);
780
- const maxSpatialFiltersResolution = maxResolutionForZoom ? Math.min(dataResolution, maxResolutionForZoom) : dataResolution;
781
- const hexagonResolution = _getHexagonResolution(viewState, tileSize) + aggregationResLevelOffset;
782
- return Math.min(hexagonResolution, maxSpatialFiltersResolution);
838
+ // We transform cells to Set to improve the performace
839
+ const cellsSet = new Set(cells);
840
+ for (const tile of tiles) {
841
+ if (tile.isVisible === false || !tile.data) {
842
+ continue;
843
+ }
844
+ tile.data.forEach(d => {
845
+ if (cellsSet.has(d.id)) {
846
+ map.set(d.id, _extends({}, d.properties, {
847
+ [spatialIndexIDName]: d.id
848
+ }));
849
+ }
850
+ });
783
851
  }
784
- if (source.spatialDataType === 'quadbin') {
785
- const maxResolutionForZoom = currentZoomInt + QUADBIN_ZOOM_MAX_OFFSET;
786
- const maxSpatialFiltersResolution = Math.min(dataResolution, maxResolutionForZoom);
787
- const quadsResolution = Math.floor(viewState.zoom) + aggregationResLevelOffset;
788
- return Math.min(quadsResolution, maxSpatialFiltersResolution);
852
+ return Array.from(map.values());
853
+ }
854
+ function getResolution(tiles, spatialIndex) {
855
+ var _tiles$find;
856
+ const data = (_tiles$find = tiles.find(tile => {
857
+ var _tile$data;
858
+ return (_tile$data = tile.data) == null ? void 0 : _tile$data.length;
859
+ })) == null ? void 0 : _tiles$find.data;
860
+ if (!data) {
861
+ return;
862
+ }
863
+ if (spatialIndex === SpatialIndex.QUADBIN) {
864
+ return Number(getResolution$1(data[0].id));
865
+ }
866
+ if (spatialIndex === SpatialIndex.H3) {
867
+ return getResolution$2(data[0].id);
789
868
  }
790
- return undefined;
791
869
  }
792
- 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]];
793
- // stolen from https://github.com/visgl/deck.gl/blob/master/modules/carto/src/layers/h3-tileset-2d.ts
794
- // Relative scale factor (0 = no biasing, 2 = a few hexagons cover view)
795
- const BIAS = 2;
796
- /**
797
- * Resolution conversion function. Takes a WebMercatorViewport and returns
798
- * a H3 resolution such that the screen space size of the hexagons is
799
- * "similar" to the given tileSize on screen. Intended for use with deck.gl.
800
- * @internal
801
- */
802
- function _getHexagonResolution(viewport, tileSize) {
803
- // Difference in given tile size compared to deck's internal 512px tile size,
804
- // expressed as an offset to the viewport zoom.
805
- const zoomOffset = Math.log2(tileSize / DEFAULT_TILE_SIZE);
806
- const hexagonScaleFactor = 2 / 3 * (viewport.zoom - zoomOffset);
807
- const latitudeScaleFactor = Math.log(1 / Math.cos(Math.PI * viewport.latitude / 180));
808
- // Clip and bias
809
- return Math.max(0, Math.floor(hexagonScaleFactor + latitudeScaleFactor - BIAS));
870
+ const bboxWest = [-180, -90, 0, 90];
871
+ const bboxEast = [0, -90, 180, 90];
872
+ function getCellsCoverGeometry(geometry, spatialIndex, resolution) {
873
+ if (spatialIndex === SpatialIndex.QUADBIN) {
874
+ // @ts-expect-error TODO: Probably ought to be stricter about number vs. bigint types in this file.
875
+ return geometryToCells(geometry, resolution);
876
+ }
877
+ if (spatialIndex === SpatialIndex.H3) {
878
+ // The current H3 polyfill algorithm can't deal with polygon segments of greater than 180 degrees longitude
879
+ // so we clip the geometry to be sure that none of them is greater than 180 degrees
880
+ // https://github.com/uber/h3-js/issues/24#issuecomment-431893796
881
+ return polygonToCells(bboxClip(geometry, bboxWest).geometry.coordinates, resolution, true).concat(polygonToCells(bboxClip(geometry, bboxEast).geometry.coordinates, resolution, true));
882
+ }
883
+ }
884
+ function getSpatialIndex(spatialDataType) {
885
+ switch (spatialDataType) {
886
+ case 'h3':
887
+ return SpatialIndex.H3;
888
+ case 'quadbin':
889
+ return SpatialIndex.QUADBIN;
890
+ default:
891
+ throw new Error('Unexpected spatial data type');
892
+ }
810
893
  }
811
894
 
812
895
  /**
813
- * Source for Widget API requests on a data source defined by a SQL query.
814
- *
815
- * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
896
+ * Current version of @carto/api-client.
897
+ * @internal
816
898
  */
817
- class WidgetSource {
818
- constructor(props) {
819
- this.props = void 0;
820
- this.props = _extends({}, WidgetSource.defaultProps, props);
821
- }
822
- _getModelSource(owner) {
823
- const props = this.props;
824
- return {
825
- apiVersion: props.apiVersion,
826
- apiBaseUrl: props.apiBaseUrl,
827
- clientId: props.clientId,
828
- accessToken: props.accessToken,
829
- connectionName: props.connectionName,
830
- filters: getApplicableFilters(owner, props.filters),
831
- filtersLogicalOperator: props.filtersLogicalOperator,
832
- spatialDataType: props.spatialDataType,
833
- spatialDataColumn: props.spatialDataColumn,
834
- dataResolution: props.dataResolution
835
- };
899
+ /** @internal */
900
+ const V3_MINOR_VERSION = '3.4';
901
+ /** @privateRemarks Source: @carto/constants, @deck.gl/carto */
902
+ const DEFAULT_GEO_COLUMN = 'geom';
903
+ /**
904
+ * Fastly default limit is 8192; leave some padding.
905
+ * @privateRemarks Source: @deck.gl/carto
906
+ */
907
+ const DEFAULT_MAX_LENGTH_URL = 7000;
908
+ /** @privateRemarks Source: @deck.gl/carto */
909
+ const DEFAULT_TILE_RESOLUTION = 0.5;
910
+ /**
911
+ * @privateRemarks Source: @deck.gl/carto
912
+ * @internal
913
+ */
914
+ const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4;
915
+ /**
916
+ * @privateRemarks Source: @deck.gl/carto
917
+ * @internal
918
+ */
919
+ const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6;
920
+
921
+ const _excluded$2 = ["tiles"];
922
+ function tileFeaturesRaster(_ref) {
923
+ let {
924
+ tiles
925
+ } = _ref,
926
+ options = _objectWithoutPropertiesLoose(_ref, _excluded$2);
927
+ // Cache band metadata for faster lookup while iterating over pixels.
928
+ const bandMetadataByName = {};
929
+ for (const band of options.rasterMetadata.bands) {
930
+ bandMetadataByName[band.name] = band;
836
931
  }
837
- _getSpatialFiltersResolution(source, spatialFilter, referenceViewState) {
838
- // spatialFiltersResolution applies only to spatial index sources.
839
- if (!spatialFilter || source.spatialDataType === 'geo') {
840
- return;
841
- }
842
- if (!referenceViewState) {
843
- throw new Error('Missing required option, "spatialIndexReferenceViewState".');
932
+ // Omit empty and invisible tiles for simpler processing and types.
933
+ tiles = tiles.filter(isRasterTileVisible);
934
+ if (tiles.length === 0) return [];
935
+ // Raster tiles, and all pixels, are quadbin cells. Resolution of a pixel is
936
+ // the resolution of the tile, plus the number of subdivisions. Block size
937
+ // must be square, N x N, where N is a power of two.
938
+ const tileResolution = getResolution$1(tiles[0].index.q);
939
+ const tileBlockSize = tiles[0].data.blockSize;
940
+ const cellResolution = tileResolution + BigInt(Math.log2(tileBlockSize));
941
+ // Compute covering cells for the spatial filter, at same resolution as the
942
+ // raster pixels, to be used as a mask.
943
+ const spatialFilterCells = new Set(geometryToCells(options.spatialFilter, cellResolution));
944
+ const data = new Map();
945
+ for (const tile of tiles) {
946
+ const parent = tile.index.q;
947
+ const children = cellToChildrenSorted(parent, cellResolution);
948
+ // For each pixel/cell within the spatial filter, create a FeatureData.
949
+ // Order is row-major, starting from NW and ending at SE.
950
+ for (let i = 0; i < children.length; i++) {
951
+ if (!spatialFilterCells.has(children[i])) continue;
952
+ const cellData = {};
953
+ let cellDataExists = false;
954
+ for (const band in tile.data.cells.numericProps) {
955
+ const value = tile.data.cells.numericProps[band].value[i];
956
+ // TODO(cleanup): nodata should be a number, not a string.
957
+ if (Number(bandMetadataByName[band].nodata) !== value) {
958
+ cellData[band] = tile.data.cells.numericProps[band].value[i];
959
+ cellDataExists = true;
960
+ }
961
+ }
962
+ if (cellDataExists) {
963
+ data.set(children[i], cellData);
964
+ }
844
965
  }
845
- return getSpatialFiltersResolution(source, referenceViewState);
846
966
  }
967
+ return Array.from(data.values());
968
+ }
969
+ /**
970
+ * Detects whether a given {@link Tile} is a {@link RasterTile}.
971
+ * @privateRemarks Method of detection is arbitrary, and may be changed.
972
+ */
973
+ function isRasterTile(tile) {
974
+ var _tile$data;
975
+ return !!((_tile$data = tile.data) != null && _tile$data.cells);
976
+ }
977
+ function isRasterTileVisible(tile) {
978
+ var _tile$data2;
979
+ return !!(tile.isVisible && (_tile$data2 = tile.data) != null && (_tile$data2 = _tile$data2.cells) != null && _tile$data2.numericProps);
980
+ }
981
+ /**
982
+ * For the raster format, children are sorted in row-major order, starting from
983
+ * NW and ending at SE. Order returned by quadbin's cellToChildren() is not
984
+ * defined (and not related to the raster format), so sort explicitly here.
985
+ */
986
+ function cellToChildrenSorted(parent, resolution) {
987
+ return cellToChildren(parent, resolution).sort((cellA, cellB) => {
988
+ const tileA = cellToTile(cellA);
989
+ const tileB = cellToTile(cellB);
990
+ if (tileA.y !== tileB.y) {
991
+ return tileA.y > tileB.y ? 1 : -1;
992
+ }
993
+ return tileA.x > tileB.x ? 1 : -1;
994
+ });
847
995
  }
848
- WidgetSource.defaultProps = {
849
- apiVersion: ApiVersion.V3,
850
- apiBaseUrl: DEFAULT_API_BASE_URL,
851
- clientId: getClient(),
852
- filters: {},
853
- filtersLogicalOperator: 'and'
854
- };
855
996
 
997
+ const FILTER_TYPES = new Set(Object.values(FilterType));
998
+ const isFilterType = type => FILTER_TYPES.has(type);
856
999
  /**
857
- * Return more descriptive error from API
858
- * @internalRemarks Source: @carto/react-api
1000
+ * @privateRemarks Source: @carto/react-widgets
1001
+ * @internal
859
1002
  */
860
- function dealWithApiError({
861
- response,
862
- data
863
- }) {
864
- var _data$error, _data$error2;
865
- if (data.error === 'Column not found') {
866
- throw new InvalidColumnError(`${data.error} ${data.column_name}`);
867
- }
868
- if (typeof data.error === 'string' && (_data$error = data.error) != null && _data$error.includes('Missing columns')) {
869
- throw new InvalidColumnError(data.error);
870
- }
871
- switch (response.status) {
872
- case 401:
873
- throw new Error('Unauthorized access. Invalid credentials');
874
- case 403:
875
- throw new Error('Forbidden access to the requested data');
876
- default:
877
- const msg = data && data.error && typeof data.error === 'string' ? data.error : JSON.stringify((data == null ? void 0 : data.hint) || ((_data$error2 = data.error) == null ? void 0 : _data$error2[0]));
878
- throw new Error(msg);
1003
+ function getApplicableFilters(owner, filters) {
1004
+ if (!filters) return {};
1005
+ const applicableFilters = {};
1006
+ for (const column in filters) {
1007
+ for (const type in filters[column]) {
1008
+ if (!isFilterType(type)) continue;
1009
+ const filter = filters[column][type];
1010
+ const isApplicable = !owner || !(filter != null && filter.owner) || (filter == null ? void 0 : filter.owner) !== owner;
1011
+ if (filter && isApplicable) {
1012
+ applicableFilters[column] || (applicableFilters[column] = {});
1013
+ applicableFilters[column][type] = filter;
1014
+ }
1015
+ }
879
1016
  }
1017
+ return applicableFilters;
880
1018
  }
881
- /** @internalRemarks Source: @carto/react-api */
882
- async function makeCall({
883
- url,
884
- accessToken,
885
- opts
886
- }) {
887
- let response;
888
- let data;
889
- const isPost = (opts == null ? void 0 : opts.method) === 'POST';
890
- try {
891
- var _opts$abortController;
892
- response = await fetch(url.toString(), _extends({
893
- headers: _extends({
894
- Authorization: `Bearer ${accessToken}`
895
- }, isPost && {
896
- 'Content-Type': 'application/json'
897
- })
898
- }, isPost && {
899
- method: opts == null ? void 0 : opts.method,
900
- body: opts == null ? void 0 : opts.body
901
- }, {
902
- signal: opts == null || (_opts$abortController = opts.abortController) == null ? void 0 : _opts$abortController.signal
903
- }, opts == null ? void 0 : opts.otherOptions));
904
- data = await response.json();
905
- } catch (error) {
906
- if (error.name === 'AbortError') throw error;
907
- throw new Error(`Failed request: ${error}`);
1019
+ /**
1020
+ * Due to each data warehouse having its own behavior with columns,
1021
+ * we need to normalize them and transform every key to lowercase.
1022
+ *
1023
+ * @privateRemarks Source: @carto/react-widgets
1024
+ * @internal
1025
+ */
1026
+ function normalizeObjectKeys(el) {
1027
+ if (Array.isArray(el)) {
1028
+ return el.map(value => normalizeObjectKeys(value));
1029
+ } else if (typeof el !== 'object') {
1030
+ return el;
908
1031
  }
909
- if (!response.ok) {
910
- dealWithApiError({
911
- response,
912
- data
913
- });
1032
+ return Object.entries(el).reduce((acc, [key, value]) => {
1033
+ acc[key.toLowerCase()] = typeof value === 'object' && value ? normalizeObjectKeys(value) : value;
1034
+ return acc;
1035
+ }, {});
1036
+ }
1037
+ /** @privateRemarks Source: @carto/react-core */
1038
+ function assert(condition, message) {
1039
+ if (!condition) {
1040
+ throw new Error(message);
914
1041
  }
915
- return data;
916
1042
  }
917
-
918
- /** @internalRemarks Source: @carto/react-api */
919
- const AVAILABLE_MODELS = ['category', 'histogram', 'formula', 'pick', 'timeseries', 'range', 'scatterplot', 'table'];
920
- const {
921
- V3
922
- } = ApiVersion;
923
- const REQUEST_GET_MAX_URL_LENGTH = 2048;
924
1043
  /**
925
- * Execute a SQL model request.
926
- * @internalRemarks Source: @carto/react-api
1044
+ * @privateRemarks Source: @carto/react-core
1045
+ * @internal
927
1046
  */
928
- function executeModel(props) {
929
- assert$1(props.source, 'executeModel: missing source');
930
- assert$1(props.model, 'executeModel: missing model');
931
- assert$1(props.params, 'executeModel: missing params');
932
- assert$1(AVAILABLE_MODELS.includes(props.model), `executeModel: model provided isn't valid. Available models: ${AVAILABLE_MODELS.join(', ')}`);
933
- const {
934
- model,
935
- source,
936
- params,
937
- opts
938
- } = props;
939
- const {
940
- type,
941
- apiVersion,
942
- apiBaseUrl,
943
- accessToken,
944
- connectionName,
945
- clientId
946
- } = source;
947
- assert$1(apiBaseUrl, 'executeModel: missing apiBaseUrl');
948
- assert$1(accessToken, 'executeModel: missing accessToken');
949
- assert$1(apiVersion === V3, 'executeModel: SQL Model API requires CARTO 3+');
950
- assert$1(type !== 'tileset', 'executeModel: Tilesets not supported');
951
- let url = `${apiBaseUrl}/v3/sql/${connectionName}/model/${model}`;
1047
+ class InvalidColumnError extends Error {
1048
+ constructor(message) {
1049
+ super(`${InvalidColumnError.NAME}: ${message}`);
1050
+ this.name = InvalidColumnError.NAME;
1051
+ }
1052
+ static is(error) {
1053
+ var _error$message;
1054
+ return error instanceof InvalidColumnError || ((_error$message = error.message) == null ? void 0 : _error$message.includes(InvalidColumnError.NAME));
1055
+ }
1056
+ }
1057
+ InvalidColumnError.NAME = 'InvalidColumnError';
1058
+ function isEmptyObject(object) {
1059
+ for (const _ in object) {
1060
+ return false;
1061
+ }
1062
+ return true;
1063
+ }
1064
+ /** @internal */
1065
+ const isObject = x => x !== null && typeof x === 'object';
1066
+ /** @internal */
1067
+ const isPureObject = x => isObject(x) && x.constructor === {}.constructor;
1068
+
1069
+ /** @privateRemarks Source: @carto/react-core */
1070
+ function tileFeatures({
1071
+ tiles,
1072
+ spatialFilter,
1073
+ uniqueIdProperty,
1074
+ tileFormat,
1075
+ spatialDataColumn = DEFAULT_GEO_COLUMN,
1076
+ spatialDataType,
1077
+ rasterMetadata,
1078
+ options = {}
1079
+ }) {
1080
+ if (spatialDataType === 'geo') {
1081
+ return tileFeaturesGeometries({
1082
+ tiles,
1083
+ tileFormat,
1084
+ spatialFilter,
1085
+ uniqueIdProperty,
1086
+ options
1087
+ });
1088
+ }
1089
+ if (tiles.some(isRasterTile)) {
1090
+ assert(rasterMetadata, 'Missing raster metadata');
1091
+ return tileFeaturesRaster({
1092
+ tiles: tiles,
1093
+ spatialFilter,
1094
+ spatialDataColumn,
1095
+ spatialDataType,
1096
+ rasterMetadata
1097
+ });
1098
+ }
1099
+ return tileFeaturesSpatialIndex({
1100
+ tiles: tiles,
1101
+ spatialFilter,
1102
+ spatialDataColumn,
1103
+ spatialDataType
1104
+ });
1105
+ }
1106
+
1107
+ /**
1108
+ * Creates props for DataFilterExtension, from `@deck.gl/extensions`, given
1109
+ * a set of filters.
1110
+ *
1111
+ * @privateRemarks DataFilterExtension accepts up to 4 values to filter. This
1112
+ * implementation uses the 1st for all filters except the time filter, and the
1113
+ * 2nd for the time filter.
1114
+ */
1115
+ function getDataFilterExtensionProps(filters, filtersLogicalOperator, filterSize) {
1116
+ var _filterSize;
1117
+ (_filterSize = filterSize) != null ? _filterSize : filterSize = 4;
952
1118
  const {
953
- data,
954
- filters,
955
- filtersLogicalOperator = 'and',
956
- spatialDataType = 'geo',
957
- spatialFiltersMode = 'intersects',
958
- spatialFiltersResolution = 0
959
- } = source;
960
- const queryParams = {
961
- type,
962
- client: clientId,
963
- source: data,
964
- params,
965
- queryParameters: source.queryParameters || '',
966
- filters,
1119
+ filtersWithoutTimeType,
1120
+ timeColumn,
1121
+ timeFilter
1122
+ } = getFiltersByType(filters);
1123
+ return {
1124
+ filterRange: getFilterRange(timeFilter, filterSize),
1125
+ updateTriggers: getUpdateTriggers(filtersWithoutTimeType, timeColumn, timeFilter),
1126
+ getFilterValue: getFilterValue(filtersWithoutTimeType, timeColumn, timeFilter, filterSize, filtersLogicalOperator)
1127
+ };
1128
+ }
1129
+ /** @internal */
1130
+ function getFiltersByType(filters) {
1131
+ const filtersWithoutTimeType = {};
1132
+ let timeColumn = null;
1133
+ let timeFilter = null;
1134
+ for (const [column, columnData] of Object.entries(filters)) {
1135
+ for (const [type, typeData] of Object.entries(columnData)) {
1136
+ if (type === FilterType.TIME) {
1137
+ timeColumn = column;
1138
+ timeFilter = typeData;
1139
+ } else {
1140
+ filtersWithoutTimeType[column] = {
1141
+ [type]: typeData
1142
+ };
1143
+ }
1144
+ }
1145
+ }
1146
+ return {
1147
+ filtersWithoutTimeType,
1148
+ timeColumn,
1149
+ timeFilter
1150
+ };
1151
+ }
1152
+ /** @internal */
1153
+ function getFilterRange(timeFilter, filterSize) {
1154
+ const result = Array(filterSize).fill([0, 0]);
1155
+ // According to getFilterValue all filters are resolved as 0 or 1 in the first position of the array
1156
+ // except the time filter value that is resolved with the real value of the feature in the second position of the array
1157
+ result[0] = [1, 1];
1158
+ if (timeFilter) {
1159
+ var _timeFilter$params;
1160
+ const offsetBy = ((_timeFilter$params = timeFilter.params) == null ? void 0 : _timeFilter$params.offsetBy) || 0;
1161
+ result[1] = timeFilter.values[0].map(v => v - offsetBy);
1162
+ }
1163
+ return result;
1164
+ }
1165
+ /** @internal */
1166
+ function getUpdateTriggers(filtersWithoutTimeType, timeColumn, timeFilter) {
1167
+ const result = _extends({}, filtersWithoutTimeType);
1168
+ // We don't want to change the layer UpdateTriggers every time that the time filter changes
1169
+ // because this filter is changed by the time series widget during its animation
1170
+ // so we remove the time filter value from the `updateTriggers`
1171
+ if (timeColumn && timeFilter) {
1172
+ var _timeFilter$params2;
1173
+ result[timeColumn] = _extends({}, result[timeColumn], {
1174
+ offsetBy: (_timeFilter$params2 = timeFilter.params) == null ? void 0 : _timeFilter$params2.offsetBy,
1175
+ [FilterType.TIME]: {} // Allows working with other filters, without an impact on performance.
1176
+ });
1177
+ }
1178
+ return {
1179
+ getFilterValue: JSON.stringify(result)
1180
+ };
1181
+ }
1182
+ /** @internal */
1183
+ function getFilterValue(filtersWithoutTimeType, timeColumn, timeFilter, filterSize, filtersLogicalOperator) {
1184
+ const result = Array(filterSize).fill(0);
1185
+ const featureFilter = _buildFeatureFilter({
1186
+ filters: filtersWithoutTimeType,
1187
+ type: 'number',
967
1188
  filtersLogicalOperator
1189
+ });
1190
+ // We evaluate all filters except the time filter using _buildFeatureFilter function.
1191
+ // For the time filter, we return the value of the feature and we will change the getFilterRange result
1192
+ // every time this filter changes
1193
+ return feature => {
1194
+ result[0] = featureFilter(feature);
1195
+ if (timeColumn && timeFilter) {
1196
+ var _timeFilter$params3;
1197
+ const offsetBy = ((_timeFilter$params3 = timeFilter.params) == null ? void 0 : _timeFilter$params3.offsetBy) || 0;
1198
+ const f = feature.properties || feature;
1199
+ result[1] = f[timeColumn] - offsetBy;
1200
+ }
1201
+ return result;
968
1202
  };
969
- const spatialDataColumn = source.spatialDataColumn || DEFAULT_GEO_COLUMN;
970
- // Picking Model API requires 'spatialDataColumn'.
971
- if (model === 'pick') {
972
- queryParams.spatialDataColumn = spatialDataColumn;
1203
+ }
1204
+
1205
+ /**
1206
+ * Adds a {@link Filter} to the filter set. Any previous filters with the same
1207
+ * `column` and `type` will be replaced.
1208
+ */
1209
+ function addFilter(filters, {
1210
+ column,
1211
+ type,
1212
+ values,
1213
+ owner
1214
+ }) {
1215
+ if (!filters[column]) {
1216
+ filters[column] = {};
973
1217
  }
974
- // API supports multiple filters, we apply it only to spatialDataColumn
975
- const spatialFilters = source.spatialFilter ? {
976
- [spatialDataColumn]: source.spatialFilter
977
- } : undefined;
978
- if (spatialFilters) {
979
- queryParams.spatialFilters = spatialFilters;
980
- queryParams.spatialDataColumn = spatialDataColumn;
981
- queryParams.spatialDataType = spatialDataType;
1218
+ const filter = {
1219
+ values,
1220
+ owner
1221
+ };
1222
+ filters[column][type] = filter;
1223
+ return filters;
1224
+ }
1225
+ /**
1226
+ * Removes one or more {@link Filter filters} from the filter set. If only
1227
+ * `column` is specified, then all filters on that column are removed. If both
1228
+ * `column` and `owner` are specified, then only filters for that column
1229
+ * associated with the owner are removed.
1230
+ */
1231
+ function removeFilter(filters, {
1232
+ column,
1233
+ owner
1234
+ }) {
1235
+ const filter = filters[column];
1236
+ if (!filter) {
1237
+ return filters;
982
1238
  }
983
- if (spatialDataType !== 'geo') {
984
- if (spatialFiltersResolution > 0) {
985
- queryParams.spatialFiltersResolution = spatialFiltersResolution;
1239
+ if (owner) {
1240
+ for (const type of Object.values(FilterType)) {
1241
+ var _filter$type;
1242
+ if (owner === ((_filter$type = filter[type]) == null ? void 0 : _filter$type.owner)) {
1243
+ delete filter[type];
1244
+ }
986
1245
  }
987
- queryParams.spatialFiltersMode = spatialFiltersMode;
988
1246
  }
989
- const urlWithSearchParams = url + '?' + objectToURLSearchParams(queryParams).toString();
990
- const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
991
- if (isGet) {
992
- url = urlWithSearchParams;
1247
+ if (!owner || isEmptyObject(filter)) {
1248
+ delete filters[column];
993
1249
  }
994
- return makeCall({
995
- url,
996
- accessToken: source.accessToken,
997
- opts: _extends({}, opts, {
998
- method: isGet ? 'GET' : 'POST'
999
- }, !isGet && {
1000
- body: JSON.stringify(queryParams)
1001
- })
1002
- });
1250
+ return filters;
1003
1251
  }
1004
- function objectToURLSearchParams(object) {
1005
- const params = new URLSearchParams();
1006
- for (const key in object) {
1007
- if (isPureObject(object[key])) {
1008
- params.append(key, JSON.stringify(object[key]));
1009
- } else if (Array.isArray(object[key])) {
1010
- params.append(key, JSON.stringify(object[key]));
1011
- } else if (object[key] === null) {
1012
- params.append(key, 'null');
1013
- } else if (object[key] !== undefined) {
1014
- params.append(key, String(object[key]));
1252
+ /**
1253
+ * Clears all {@link Filter filters} from the filter set.
1254
+ */
1255
+ function clearFilters(filters) {
1256
+ for (const column of Object.keys(filters)) {
1257
+ delete filters[column];
1258
+ }
1259
+ return filters;
1260
+ }
1261
+ function hasFilter(filters, {
1262
+ column,
1263
+ owner
1264
+ }) {
1265
+ const filter = filters[column];
1266
+ if (!filter) {
1267
+ return false;
1268
+ }
1269
+ if (!owner) {
1270
+ return true;
1271
+ }
1272
+ for (const type of Object.values(FilterType)) {
1273
+ var _filter$type2;
1274
+ if (owner === ((_filter$type2 = filter[type]) == null ? void 0 : _filter$type2.owner)) {
1275
+ return true;
1015
1276
  }
1016
1277
  }
1017
- return params;
1278
+ return false;
1279
+ }
1280
+ function getFilter(filters, {
1281
+ column,
1282
+ type,
1283
+ owner
1284
+ }) {
1285
+ var _filter$type3;
1286
+ const filter = filters[column];
1287
+ if (!filter) {
1288
+ return null;
1289
+ }
1290
+ if (!owner || owner === ((_filter$type3 = filter[type]) == null ? void 0 : _filter$type3.owner)) {
1291
+ return filter[type] || null;
1292
+ }
1293
+ return null;
1018
1294
  }
1019
1295
 
1020
- const _excluded$2 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController"],
1021
- _excluded2 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController"],
1022
- _excluded3 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController", "operationExp"],
1023
- _excluded4 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController"],
1024
- _excluded5 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController"],
1025
- _excluded6 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController"],
1026
- _excluded7 = ["filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController"],
1027
- _excluded8 = ["filterOwner", "abortController", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState"];
1028
1296
  /**
1029
- * Source for Widget API requests.
1297
+ * Returns a {@link SpatialFilter} for a given viewport, typically obtained
1298
+ * from deck.gl's `viewport.getBounds()` method ([west, south, east, north]).
1299
+ * If the viewport covers the entire world (to some margin of error in Web
1300
+ * Mercator space), `undefined` is returned instead.
1030
1301
  *
1031
- * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
1302
+ * If the viewport extends beyond longitude range [-180, +180], the polygon
1303
+ * may be reformatted for compatibility with CARTO APIs.
1032
1304
  */
1033
- class WidgetRemoteSource extends WidgetSource {
1034
- async getCategories(options) {
1035
- const {
1036
- filterOwner,
1037
- spatialFilter,
1038
- spatialFiltersMode,
1039
- spatialIndexReferenceViewState,
1040
- abortController
1041
- } = options,
1042
- params = _objectWithoutPropertiesLoose(options, _excluded$2);
1043
- const {
1044
- column,
1045
- operation,
1046
- operationColumn
1047
- } = params;
1048
- const source = this.getModelSource(filterOwner);
1049
- const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1050
- return executeModel({
1051
- model: 'category',
1052
- source: _extends({}, source, {
1053
- spatialFiltersResolution,
1054
- spatialFiltersMode,
1055
- spatialFilter
1056
- }),
1057
- params: {
1058
- column,
1059
- operation,
1060
- operationColumn: operationColumn || column
1061
- },
1062
- opts: {
1063
- abortController
1064
- }
1065
- }).then(res => normalizeObjectKeys(res.rows));
1066
- }
1067
- async getFeatures(options) {
1068
- const {
1069
- filterOwner,
1070
- spatialFilter,
1071
- spatialFiltersMode,
1072
- spatialIndexReferenceViewState,
1073
- abortController
1074
- } = options,
1075
- params = _objectWithoutPropertiesLoose(options, _excluded2);
1076
- const {
1077
- columns,
1078
- dataType,
1079
- featureIds,
1080
- z,
1081
- limit,
1082
- tileResolution
1083
- } = params;
1084
- const source = this.getModelSource(filterOwner);
1085
- const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1086
- return executeModel({
1087
- model: 'pick',
1088
- source: _extends({}, source, {
1089
- spatialFiltersResolution,
1090
- spatialFiltersMode,
1091
- spatialFilter
1092
- }),
1093
- params: {
1094
- columns,
1095
- dataType,
1096
- featureIds,
1097
- z,
1098
- limit: limit || 1000,
1099
- tileResolution: tileResolution || DEFAULT_TILE_RESOLUTION
1100
- },
1101
- opts: {
1102
- abortController
1103
- }
1104
- // Avoid `normalizeObjectKeys()`, which changes column names.
1105
- }).then(({
1106
- rows
1107
- }) => ({
1108
- rows
1109
- }));
1110
- }
1111
- async getFormula(options) {
1112
- const {
1113
- filterOwner,
1114
- spatialFilter,
1115
- spatialFiltersMode,
1116
- spatialIndexReferenceViewState,
1117
- abortController,
1118
- operationExp
1119
- } = options,
1120
- params = _objectWithoutPropertiesLoose(options, _excluded3);
1121
- const {
1122
- column,
1123
- operation
1124
- } = params;
1125
- const source = this.getModelSource(filterOwner);
1126
- const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1127
- return executeModel({
1128
- model: 'formula',
1129
- source: _extends({}, source, {
1130
- spatialFiltersResolution,
1131
- spatialFiltersMode,
1132
- spatialFilter
1133
- }),
1134
- params: {
1135
- column: column != null ? column : '*',
1136
- operation: operation != null ? operation : 'count',
1137
- operationExp
1138
- },
1139
- opts: {
1140
- abortController
1141
- }
1142
- }).then(res => normalizeObjectKeys(res.rows[0]));
1143
- }
1144
- async getHistogram(options) {
1145
- const {
1146
- filterOwner,
1147
- spatialFilter,
1148
- spatialFiltersMode,
1149
- spatialIndexReferenceViewState,
1150
- abortController
1151
- } = options,
1152
- params = _objectWithoutPropertiesLoose(options, _excluded4);
1153
- const {
1154
- column,
1155
- operation,
1156
- ticks
1157
- } = params;
1158
- const source = this.getModelSource(filterOwner);
1159
- const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1160
- const data = await executeModel({
1161
- model: 'histogram',
1162
- source: _extends({}, source, {
1163
- spatialFiltersResolution,
1164
- spatialFiltersMode,
1165
- spatialFilter
1166
- }),
1167
- params: {
1168
- column,
1169
- operation,
1170
- ticks
1171
- },
1172
- opts: {
1173
- abortController
1174
- }
1175
- }).then(res => normalizeObjectKeys(res.rows));
1176
- if (data.length) {
1177
- // Given N ticks the API returns up to N+1 bins, omitting any empty bins. Bins
1178
- // include 1 bin below the lowest tick, N-1 between ticks, and 1 bin above the highest tick.
1179
- const result = Array(ticks.length + 1).fill(0);
1180
- data.forEach(({
1181
- tick,
1182
- value
1183
- }) => result[tick] = value);
1184
- return result;
1185
- }
1186
- return [];
1187
- }
1188
- async getRange(options) {
1189
- const {
1190
- filterOwner,
1191
- spatialFilter,
1192
- spatialFiltersMode,
1193
- spatialIndexReferenceViewState,
1194
- abortController
1195
- } = options,
1196
- params = _objectWithoutPropertiesLoose(options, _excluded5);
1197
- const {
1198
- column
1199
- } = params;
1200
- const source = this.getModelSource(filterOwner);
1201
- const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1202
- return executeModel({
1203
- model: 'range',
1204
- source: _extends({}, source, {
1205
- spatialFiltersResolution,
1206
- spatialFiltersMode,
1207
- spatialFilter
1208
- }),
1209
- params: {
1210
- column
1211
- },
1212
- opts: {
1213
- abortController
1214
- }
1215
- }).then(res => normalizeObjectKeys(res.rows[0]));
1216
- }
1217
- async getScatter(options) {
1218
- const {
1219
- filterOwner,
1220
- spatialFilter,
1221
- spatialFiltersMode,
1222
- spatialIndexReferenceViewState,
1223
- abortController
1224
- } = options,
1225
- params = _objectWithoutPropertiesLoose(options, _excluded6);
1226
- const {
1227
- xAxisColumn,
1228
- xAxisJoinOperation,
1229
- yAxisColumn,
1230
- yAxisJoinOperation
1231
- } = params;
1232
- const source = this.getModelSource(filterOwner);
1233
- const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1234
- // Make sure this is sync with the same constant in cloud-native/maps-api
1235
- const HARD_LIMIT = 500;
1236
- return executeModel({
1237
- model: 'scatterplot',
1238
- source: _extends({}, source, {
1239
- spatialFiltersResolution,
1240
- spatialFiltersMode,
1241
- spatialFilter
1242
- }),
1243
- params: {
1244
- xAxisColumn,
1245
- xAxisJoinOperation,
1246
- yAxisColumn,
1247
- yAxisJoinOperation,
1248
- limit: HARD_LIMIT
1249
- },
1250
- opts: {
1251
- abortController
1252
- }
1253
- }).then(res => normalizeObjectKeys(res.rows)).then(res => res.map(({
1254
- x,
1255
- y
1256
- }) => [x, y]));
1257
- }
1258
- async getTable(options) {
1259
- const {
1260
- filterOwner,
1261
- spatialFilter,
1262
- spatialFiltersMode,
1263
- spatialIndexReferenceViewState,
1264
- abortController
1265
- } = options,
1266
- params = _objectWithoutPropertiesLoose(options, _excluded7);
1267
- const {
1268
- columns,
1269
- sortBy,
1270
- sortDirection,
1271
- offset = 0,
1272
- limit = 10
1273
- } = params;
1274
- const source = this.getModelSource(filterOwner);
1275
- const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1276
- return executeModel({
1277
- model: 'table',
1278
- source: _extends({}, source, {
1279
- spatialFiltersResolution,
1280
- spatialFiltersMode,
1281
- spatialFilter
1282
- }),
1283
- params: {
1284
- column: columns,
1285
- sortBy,
1286
- sortDirection,
1287
- limit,
1288
- offset
1289
- },
1290
- opts: {
1291
- abortController
1292
- }
1293
- }).then(res => {
1294
- var _res$rows, _res$metadata$total, _res$metadata, _res$METADATA;
1295
- return {
1296
- // Avoid `normalizeObjectKeys()`, which changes column names.
1297
- rows: (_res$rows = res.rows) != null ? _res$rows : res.ROWS,
1298
- totalCount: (_res$metadata$total = (_res$metadata = res.metadata) == null ? void 0 : _res$metadata.total) != null ? _res$metadata$total : (_res$METADATA = res.METADATA) == null ? void 0 : _res$METADATA.TOTAL
1299
- };
1300
- });
1301
- }
1302
- async getTimeSeries(options) {
1303
- const {
1304
- filterOwner,
1305
- abortController,
1306
- spatialFilter,
1307
- spatialFiltersMode,
1308
- spatialIndexReferenceViewState
1309
- } = options,
1310
- params = _objectWithoutPropertiesLoose(options, _excluded8);
1311
- const {
1312
- column,
1313
- operationColumn,
1314
- joinOperation,
1315
- operation,
1316
- stepSize,
1317
- stepMultiplier,
1318
- splitByCategory,
1319
- splitByCategoryLimit,
1320
- splitByCategoryValues
1321
- } = params;
1322
- const source = this.getModelSource(filterOwner);
1323
- const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1324
- return executeModel({
1325
- model: 'timeseries',
1326
- source: _extends({}, source, {
1327
- spatialFiltersResolution,
1328
- spatialFiltersMode,
1329
- spatialFilter
1330
- }),
1331
- params: {
1332
- column,
1333
- stepSize,
1334
- stepMultiplier,
1335
- operationColumn: operationColumn || column,
1336
- joinOperation,
1337
- operation,
1338
- splitByCategory,
1339
- splitByCategoryLimit,
1340
- splitByCategoryValues
1341
- },
1342
- opts: {
1343
- abortController
1344
- }
1345
- }).then(res => {
1346
- var _res$metadata2;
1347
- return {
1348
- rows: normalizeObjectKeys(res.rows),
1349
- categories: (_res$metadata2 = res.metadata) == null ? void 0 : _res$metadata2.categories
1350
- };
1351
- });
1305
+ function createViewportSpatialFilter(viewport) {
1306
+ if (_isGlobalViewport(viewport)) {
1307
+ return;
1352
1308
  }
1309
+ return createPolygonSpatialFilter(bboxPolygon(viewport).geometry);
1310
+ }
1311
+ /**
1312
+ * Returns a {@link SpatialFilter} for a given {@link Polygon} or
1313
+ * {@link MultiPolygon}. If the polygon(s) extend outside longitude
1314
+ * range [-180, +180], the result may be reformatted for compatibility
1315
+ * with CARTO APIs.
1316
+ */
1317
+ function createPolygonSpatialFilter(spatialFilter) {
1318
+ return spatialFilter && _normalizeGeometry(spatialFilter) || undefined;
1353
1319
  }
1354
-
1355
1320
  /**
1356
- * Source for Widget API requests on a data source defined by a SQL query.
1357
- *
1358
- * Generally not intended to be constructed directly. Instead, call
1359
- * {@link vectorQuerySource}, {@link h3QuerySource}, or {@link quadbinQuerySource},
1360
- * which can be shared with map layers. Sources contain a `widgetSource` property,
1361
- * for use by widget implementations.
1362
- *
1363
- * Example:
1321
+ * Check if a viewport is large enough to represent a global coverage.
1322
+ * In this case the spatial filter parameter for widget calculation is removed.
1364
1323
  *
1365
- * ```javascript
1366
- * import { vectorQuerySource } from '@carto/api-client';
1324
+ * @privateRemarks Source: @carto/react-core
1325
+ */
1326
+ function _isGlobalViewport(viewport) {
1327
+ const [minx, miny, maxx, maxy] = viewport;
1328
+ return maxx - minx > 179.5 * 2 && maxy - miny > 85.05 * 2;
1329
+ }
1330
+ /**
1331
+ * Normalized a geometry, coming from a mask or a viewport. The parts
1332
+ * spanning outside longitude range [-180, +180] are clipped and "folded"
1333
+ * back to the valid range and unioned to the polygons inide that range.
1367
1334
  *
1368
- * const data = vectorQuerySource({
1369
- * accessToken: '••••',
1370
- * connectionName: 'carto_dw',
1371
- * sqlQuery: 'SELECT * FROM carto-demo-data.demo_tables.retail_stores'
1372
- * });
1335
+ * It results in a Polygon or MultiPolygon strictly inside the validity range.
1373
1336
  *
1374
- * const { widgetSource } = await data;
1375
- * ```
1337
+ * @privateRemarks Source: @carto/react-core
1376
1338
  */
1377
- class WidgetQuerySource extends WidgetRemoteSource {
1378
- getModelSource(owner) {
1379
- return _extends({}, super._getModelSource(owner), {
1380
- type: 'query',
1381
- data: this.props.sqlQuery,
1382
- queryParameters: this.props.queryParameters
1383
- });
1384
- }
1385
- }
1386
-
1387
- function makeIntervalComplete(intervals) {
1388
- return intervals.map(val => {
1389
- if (val[0] === undefined || val[0] === null) {
1390
- return [Number.MIN_SAFE_INTEGER, val[1]];
1339
+ function _normalizeGeometry(geometry) {
1340
+ const WORLD = [-180, -90, +180, +90];
1341
+ const worldClip = _clean(bboxClip(geometry, WORLD).geometry);
1342
+ const geometryTxWest = _tx(geometry, 360);
1343
+ const geometryTxEast = _tx(geometry, -360);
1344
+ let result = worldClip;
1345
+ if (result && geometryTxWest) {
1346
+ const worldWestClip = _clean(bboxClip(geometryTxWest, WORLD).geometry);
1347
+ if (worldWestClip) {
1348
+ const collection = featureCollection([feature(result), feature(worldWestClip)]);
1349
+ const merged = union(collection);
1350
+ result = merged ? _clean(merged.geometry) : result;
1391
1351
  }
1392
- if (val[1] === undefined || val[1] === null) {
1393
- return [val[0], Number.MAX_SAFE_INTEGER];
1352
+ }
1353
+ if (result && geometryTxEast) {
1354
+ const worldEastClip = _clean(bboxClip(geometryTxEast, WORLD).geometry);
1355
+ if (worldEastClip) {
1356
+ const collection = featureCollection([feature(result), feature(worldEastClip)]);
1357
+ const merged = union(collection);
1358
+ result = merged ? _clean(merged.geometry) : result;
1394
1359
  }
1395
- return val;
1396
- });
1397
- }
1398
-
1399
- const filterFunctions = {
1400
- [FilterType.IN]: filterIn,
1401
- [FilterType.BETWEEN]: filterBetween,
1402
- [FilterType.TIME]: filterTime,
1403
- [FilterType.CLOSED_OPEN]: filterClosedOpen,
1404
- [FilterType.STRING_SEARCH]: filterStringSearch
1405
- };
1406
- function filterIn(filterValues, featureValue) {
1407
- return filterValues.includes(featureValue);
1408
- }
1409
- // FilterTypes.BETWEEN
1410
- function filterBetween(filterValues, featureValue) {
1411
- const checkRange = range => {
1412
- const [lowerBound, upperBound] = range;
1413
- return featureValue >= lowerBound && featureValue <= upperBound;
1414
- };
1415
- return makeIntervalComplete(filterValues).some(checkRange);
1416
- }
1417
- function filterTime(filterValues, featureValue) {
1418
- const featureValueAsTimestamp = new Date(featureValue).getTime();
1419
- if (isFinite(featureValueAsTimestamp)) {
1420
- return filterBetween(filterValues, featureValueAsTimestamp);
1421
- } else {
1422
- throw new Error(`Column used to filter by time isn't well formatted.`);
1423
1360
  }
1361
+ return result;
1424
1362
  }
1425
- // FilterTypes.CLOSED_OPEN
1426
- function filterClosedOpen(filterValues, featureValue) {
1427
- const checkRange = range => {
1428
- const [lowerBound, upperBound] = range;
1429
- return featureValue >= lowerBound && featureValue < upperBound;
1430
- };
1431
- return makeIntervalComplete(filterValues).some(checkRange);
1432
- }
1433
- // FilterTypes.STRING_SEARCH
1434
- function filterStringSearch(filterValues, featureValue, params = {}) {
1435
- const normalizedFeatureValue = normalize(featureValue, params);
1436
- const stringRegExp = params.useRegExp ? filterValues : filterValues.map(filterValue => {
1437
- let stringRegExp = escapeRegExp(normalize(filterValue, params));
1438
- if (params.mustStart) stringRegExp = `^${stringRegExp}`;
1439
- if (params.mustEnd) stringRegExp = `${stringRegExp}$`;
1440
- return stringRegExp;
1441
- });
1442
- const regex = new RegExp(stringRegExp.join('|'), params.caseSensitive ? 'g' : 'gi');
1443
- return !!normalizedFeatureValue.match(regex);
1444
- }
1445
- // Aux
1446
- const specialCharRegExp = /[.*+?^${}()|[\]\\]/g;
1447
- const normalizeRegExp = /(?:[\^`\xA8\xAF\xB4\xB7\xB8\u02B0-\u034E\u0350-\u0357\u035D-\u0362\u0374\u0375\u037A\u0384\u0385\u0483-\u0487\u0559\u0591-\u05A1\u05A3-\u05BD\u05BF\u05C1\u05C2\u05C4\u064B-\u0652\u0657\u0658\u06DF\u06E0\u06E5\u06E6\u06EA-\u06EC\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F5\u0818\u0819\u0898-\u089F\u08C9-\u08D2\u08E3-\u08FE\u093C\u094D\u0951-\u0954\u0971\u09BC\u09CD\u0A3C\u0A4D\u0ABC\u0ACD\u0AFD-\u0AFF\u0B3C\u0B4D\u0B55\u0BCD\u0C3C\u0C4D\u0CBC\u0CCD\u0D3B\u0D3C\u0D4D\u0DCA\u0E3A\u0E47-\u0E4C\u0E4E\u0EBA\u0EC8-\u0ECC\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F82-\u0F84\u0F86\u0F87\u0FC6\u1037\u1039\u103A\u1063\u1064\u1069-\u106D\u1087-\u108D\u108F\u109A\u109B\u135D-\u135F\u1714\u1715\u1734\u17C9-\u17D3\u17DD\u1939-\u193B\u1A60\u1A75-\u1A7C\u1A7F\u1AB0-\u1ABE\u1AC1-\u1ACB\u1B34\u1B44\u1B6B-\u1B73\u1BAA\u1BAB\u1BE6\u1BF2\u1BF3\u1C36\u1C37\u1C78-\u1C7D\u1CD0-\u1CE8\u1CED\u1CF4\u1CF7-\u1CF9\u1D2C-\u1D6A\u1DC4-\u1DCF\u1DF5-\u1DFF\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2CEF-\u2CF1\u2E2F\u302A-\u302F\u3099-\u309C\u30FC\uA66F\uA67C\uA67D\uA67F\uA69C\uA69D\uA6F0\uA6F1\uA700-\uA721\uA788-\uA78A\uA7F8\uA7F9\uA806\uA82C\uA8C4\uA8E0-\uA8F1\uA92B-\uA92E\uA953\uA9B3\uA9C0\uA9E5\uAA7B-\uAA7D\uAABF-\uAAC2\uAAF6\uAB5B-\uAB5F\uAB69-\uAB6B\uABEC\uABED\uFB1E\uFE20-\uFE2F\uFF3E\uFF40\uFF70\uFF9E\uFF9F\uFFE3]|\uD800\uDEE0|\uD801[\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDE38-\uDE3A\uDE3F\uDEE5\uDEE6]|\uD803[\uDD22-\uDD27\uDD4E\uDD69-\uDD6D\uDEFD-\uDEFF\uDF46-\uDF50\uDF82-\uDF85]|\uD804[\uDC46\uDC70\uDCB9\uDCBA\uDD33\uDD34\uDD73\uDDC0\uDDCA-\uDDCC\uDE35\uDE36\uDEE9\uDEEA\uDF3B\uDF3C\uDF4D\uDF66-\uDF6C\uDF70-\uDF74\uDFCE-\uDFD0\uDFD2\uDFD3\uDFE1\uDFE2]|\uD805[\uDC42\uDC46\uDCC2\uDCC3\uDDBF\uDDC0\uDE3F\uDEB6\uDEB7\uDF2B]|\uD806[\uDC39\uDC3A\uDD3D\uDD3E\uDD43\uDDE0\uDE34\uDE47\uDE99]|\uD807[\uDC3F\uDD42\uDD44\uDD45\uDD97\uDF41\uDF42\uDF5A]|\uD80D[\uDC47-\uDC55]|\uD818\uDD2F|\uD81A[\uDEF0-\uDEF4\uDF30-\uDF36]|\uD81B[\uDD6B\uDD6C\uDF8F-\uDF9F\uDFF0\uDFF1]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD833[\uDF00-\uDF2D\uDF30-\uDF46]|\uD834[\uDD67-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD]|\uD838[\uDC30-\uDC6D\uDD30-\uDD36\uDEAE\uDEEC-\uDEEF]|\uD839[\uDDEE\uDDEF]|\uD83A[\uDCD0-\uDCD6\uDD44-\uDD46\uDD48-\uDD4A])/g;
1448
- function escapeRegExp(value) {
1449
- return value.replace(specialCharRegExp, '\\$&');
1450
- }
1451
- function normalize(data, params) {
1452
- let normalizedData = String(data);
1453
- if (!params.keepSpecialCharacters) normalizedData = normalizedData.normalize('NFD').replace(normalizeRegExp, '');
1454
- return normalizedData;
1363
+ /** @privateRemarks Source: @carto/react-core */
1364
+ function _cleanPolygonCoords(cc) {
1365
+ const coords = cc.filter(c => c.length > 0);
1366
+ return coords.length > 0 ? coords : null;
1455
1367
  }
1456
-
1457
- const LOGICAL_OPERATOR_METHODS = {
1458
- and: 'every',
1459
- or: 'some'
1460
- };
1461
- function passesFilter(columns, filters, feature, filtersLogicalOperator) {
1462
- const method = LOGICAL_OPERATOR_METHODS[filtersLogicalOperator];
1463
- return columns[method](column => {
1464
- const columnFilters = filters[column];
1465
- const columnFilterTypes = Object.keys(columnFilters);
1466
- if (!feature || feature[column] === null || feature[column] === undefined) {
1467
- return false;
1468
- }
1469
- return columnFilterTypes.every(filter => {
1470
- const filterFunction = filterFunctions[filter];
1471
- if (!filterFunction) {
1472
- throw new Error(`"${filter}" filter is not implemented.`);
1473
- }
1474
- return filterFunction(columnFilters[filter].values, feature[column], columnFilters[filter].params);
1475
- });
1476
- });
1368
+ /** @privateRemarks Source: @carto/react-core */
1369
+ function _cleanMultiPolygonCoords(ccc) {
1370
+ const coords = ccc.map(_cleanPolygonCoords).filter(cc => cc);
1371
+ return coords.length > 0 ? coords : null;
1477
1372
  }
1478
- function buildFeatureFilter({
1479
- filters = {},
1480
- type = 'boolean',
1481
- filtersLogicalOperator = 'and'
1482
- }) {
1483
- const columns = Object.keys(filters);
1484
- if (!columns.length) {
1485
- return () => type === 'number' ? 1 : true;
1373
+ /** @privateRemarks Source: @carto/react-core */
1374
+ function _clean(geometry) {
1375
+ if (!geometry) {
1376
+ return null;
1486
1377
  }
1487
- return feature => {
1488
- const f = feature.properties || feature;
1489
- const featurePassesFilter = passesFilter(columns, filters, f, filtersLogicalOperator);
1490
- return type === 'number' ? Number(featurePassesFilter) : featurePassesFilter;
1491
- };
1492
- }
1493
- // Apply certain filters to a collection of features
1494
- function applyFilters(features, filters, filtersLogicalOperator) {
1495
- return Object.keys(filters).length ? features.filter(buildFeatureFilter({
1496
- filters,
1497
- filtersLogicalOperator
1498
- })) : features;
1499
- }
1500
- // Binary
1501
- function buildBinaryFeatureFilter({
1502
- filters = {}
1503
- }) {
1504
- const columns = Object.keys(filters);
1505
- if (!columns.length) {
1506
- return () => 1;
1378
+ if (_isPolygon(geometry)) {
1379
+ const coords = _cleanPolygonCoords(geometry.coordinates);
1380
+ return coords ? polygon(coords).geometry : null;
1507
1381
  }
1508
- return (featureIdIdx, binaryData) => passesFilterUsingBinary(columns, filters, featureIdIdx, binaryData);
1509
- }
1510
- function getValueFromNumericProps(featureIdIdx, binaryData, {
1511
- column
1512
- }) {
1513
- var _binaryData$numericPr;
1514
- return (_binaryData$numericPr = binaryData.numericProps) == null || (_binaryData$numericPr = _binaryData$numericPr[column]) == null ? void 0 : _binaryData$numericPr.value[featureIdIdx];
1515
- }
1516
- function getValueFromProperties(featureIdIdx, binaryData, {
1517
- column
1518
- }) {
1519
- var _binaryData$propertie;
1520
- const propertyIdx = binaryData.featureIds.value[featureIdIdx];
1521
- return (_binaryData$propertie = binaryData.properties[propertyIdx]) == null ? void 0 : _binaryData$propertie[column];
1522
- }
1523
- const GET_VALUE_BY_BINARY_PROP = {
1524
- properties: getValueFromProperties,
1525
- numericProps: getValueFromNumericProps
1526
- };
1527
- function getBinaryPropertyByFilterValues(filterValues) {
1528
- return typeof filterValues.flat()[0] === 'string' ? 'properties' : 'numericProps';
1529
- }
1530
- function getFeatureValue(featureIdIdx, binaryData, filter) {
1531
- const {
1532
- column,
1533
- values
1534
- } = filter;
1535
- const binaryProp = getBinaryPropertyByFilterValues(values);
1536
- const getFeatureValueFn = GET_VALUE_BY_BINARY_PROP[binaryProp];
1537
- return getFeatureValueFn(featureIdIdx, binaryData, {
1538
- column
1539
- });
1540
- }
1541
- function passesFilterUsingBinary(columns, filters, featureIdIdx, binaryData) {
1542
- return columns.every(column => {
1543
- const columnFilters = filters[column];
1544
- return Object.entries(columnFilters).every(([type, {
1545
- values
1546
- }]) => {
1547
- const filterFn = filterFunctions[type];
1548
- if (!filterFn) {
1549
- throw new Error(`"${type}" filter is not implemented.`);
1550
- }
1551
- if (!values) return 0;
1552
- const featureValue = getFeatureValue(featureIdIdx, binaryData, {
1553
- type: type,
1554
- column,
1555
- values
1556
- });
1557
- if (featureValue === undefined || featureValue === null) return 0;
1558
- return filterFn(values, featureValue);
1559
- });
1560
- });
1561
- }
1562
-
1563
- function geojsonFeatures({
1564
- geojson,
1565
- spatialFilter,
1566
- uniqueIdProperty
1567
- }) {
1568
- let uniqueIdx = 0;
1569
- const map = new Map();
1570
- if (!spatialFilter) {
1571
- return [];
1382
+ if (_isMultiPolygon(geometry)) {
1383
+ const coords = _cleanMultiPolygonCoords(geometry.coordinates);
1384
+ return coords ? multiPolygon(coords).geometry : null;
1572
1385
  }
1573
- for (const feature of geojson.features) {
1574
- const uniqueId = uniqueIdProperty ? feature.properties[uniqueIdProperty] : ++uniqueIdx;
1575
- if (!map.has(uniqueId) && intersects(spatialFilter, feature)) {
1576
- map.set(uniqueId, feature.properties);
1577
- }
1386
+ return null;
1387
+ }
1388
+ /** @privateRemarks Source: @carto/react-core */
1389
+ function _txContourCoords(cc, distance) {
1390
+ return cc.map(c => [c[0] + distance, c[1]]);
1391
+ }
1392
+ /** @privateRemarks Source: @carto/react-core */
1393
+ function _txPolygonCoords(ccc, distance) {
1394
+ return ccc.map(cc => _txContourCoords(cc, distance));
1395
+ }
1396
+ /** @privateRemarks Source: @carto/react-core */
1397
+ function _txMultiPolygonCoords(cccc, distance) {
1398
+ return cccc.map(ccc => _txPolygonCoords(ccc, distance));
1399
+ }
1400
+ /** @privateRemarks Source: @carto/react-core */
1401
+ function _tx(geometry, distance) {
1402
+ if (geometry && getType(geometry) === 'Polygon') {
1403
+ const coords = _txPolygonCoords(geometry.coordinates, distance);
1404
+ return polygon(coords).geometry;
1405
+ } else if (geometry && getType(geometry) === 'MultiPolygon') {
1406
+ const coords = _txMultiPolygonCoords(geometry.coordinates, distance);
1407
+ return multiPolygon(coords).geometry;
1408
+ } else {
1409
+ return null;
1578
1410
  }
1579
- return Array.from(map.values());
1411
+ }
1412
+ function _isPolygon(geometry) {
1413
+ return getType(geometry) === 'Polygon';
1414
+ }
1415
+ function _isMultiPolygon(geometry) {
1416
+ return getType(geometry) === 'MultiPolygon';
1580
1417
  }
1581
1418
 
1582
- // math.gl
1419
+ // deck.gl
1583
1420
  // SPDX-License-Identifier: MIT
1584
1421
  // Copyright (c) vis.gl contributors
1585
- const DEFAULT_CONFIG = {
1586
- EPSILON: 1e-12,
1587
- debug: false,
1588
- precision: 4,
1589
- printTypes: false,
1590
- printDegrees: false,
1591
- printRowMajor: true,
1592
- _cartographicRadians: false
1593
- };
1594
- // Configuration is truly global as of v3.6 to ensure single config even if multiple copies of math.gl
1595
- // Multiple copies of config can be quite tricky to debug...
1596
- globalThis.mathgl = globalThis.mathgl || {
1597
- config: {
1598
- ...DEFAULT_CONFIG
1599
- }
1600
- };
1601
- /**
1602
- * Check if value is an "array"
1603
- * Returns `true` if value is either an array or a typed array
1604
- * Note: returns `false` for `ArrayBuffer` and `DataView` instances
1605
- * @note isTypedArray and isNumericArray are often more useful in TypeScript
1606
- */
1607
- function isArray(value) {
1608
- return Array.isArray(value) || ArrayBuffer.isView(value) && !(value instanceof DataView);
1422
+ function joinPath(...args) {
1423
+ return args.map(part => part.endsWith('/') ? part.slice(0, -1) : part).join('/');
1609
1424
  }
1610
- function lerp(a, b, t) {
1611
- if (isArray(a)) {
1612
- return a.map((ai, i) => lerp(ai, b[i], t));
1613
- }
1614
- return t * b + (1 - t) * a;
1425
+ function buildV3Path(apiBaseUrl, version, endpoint, ...rest) {
1426
+ return joinPath(apiBaseUrl, version, endpoint, ...rest);
1615
1427
  }
1616
-
1617
- // Replacement for the external assert method to reduce bundle size
1618
- // Note: We don't use the second "message" argument in calling code,
1619
- // so no need to support it here
1620
- function assert(condition, message) {
1621
- if (!condition) {
1622
- throw new Error(message || '@math.gl/web-mercator: assertion failed.');
1428
+ /** @internal Required by fetchMap(). */
1429
+ function buildPublicMapUrl({
1430
+ apiBaseUrl,
1431
+ cartoMapId
1432
+ }) {
1433
+ return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId);
1434
+ }
1435
+ /** @internal Required by fetchMap(). */
1436
+ function buildStatsUrl({
1437
+ attribute,
1438
+ apiBaseUrl,
1439
+ connectionName,
1440
+ source,
1441
+ type
1442
+ }) {
1443
+ if (type === 'query') {
1444
+ return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, attribute);
1623
1445
  }
1446
+ // type === 'table'
1447
+ return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, source, attribute);
1448
+ }
1449
+ function buildSourceUrl({
1450
+ apiBaseUrl,
1451
+ connectionName,
1452
+ endpoint
1453
+ }) {
1454
+ return buildV3Path(apiBaseUrl, 'v3', 'maps', connectionName, endpoint);
1455
+ }
1456
+ function buildQueryUrl({
1457
+ apiBaseUrl,
1458
+ connectionName
1459
+ }) {
1460
+ return buildV3Path(apiBaseUrl, 'v3', 'sql', connectionName, 'query');
1624
1461
  }
1625
1462
 
1626
- // TODO - THE UTILITIES IN THIS FILE SHOULD BE IMPORTED FROM WEB-MERCATOR-VIEWPORT MODULE
1627
- // CONSTANTS
1628
- const PI = Math.PI;
1629
- const PI_4 = PI / 4;
1630
- const DEGREES_TO_RADIANS = PI / 180;
1631
- const RADIANS_TO_DEGREES = 180 / PI;
1632
- const TILE_SIZE = 512;
1463
+ // deck.gl
1464
+ // SPDX-License-Identifier: MIT
1465
+ // Copyright (c) vis.gl contributors
1633
1466
  /**
1634
- * Project [lng,lat] on sphere onto [x,y] on 512*512 Mercator Zoom 0 tile.
1635
- * Performs the nonlinear part of the web mercator projection.
1636
- * Remaining projection is done with 4x4 matrices which also handles
1637
- * perspective.
1638
1467
  *
1639
- * @param lngLat - [lng, lat] coordinates
1640
- * Specifies a point on the sphere to project onto the map.
1641
- * @return [x,y] coordinates.
1468
+ * Custom error for reported errors in CARTO Maps API.
1469
+ * Provides useful debugging information in console and context for applications.
1470
+ *
1642
1471
  */
1643
- function lngLatToWorld(lngLat) {
1644
- const [lng, lat] = lngLat;
1645
- assert(Number.isFinite(lng));
1646
- assert(Number.isFinite(lat) && lat >= -90 && lat <= 90, 'invalid latitude');
1647
- const lambda2 = lng * DEGREES_TO_RADIANS;
1648
- const phi2 = lat * DEGREES_TO_RADIANS;
1649
- const x = TILE_SIZE * (lambda2 + PI) / (2 * PI);
1650
- const y = TILE_SIZE * (PI + Math.log(Math.tan(PI_4 + phi2 * 0.5))) / (2 * PI);
1651
- return [x, y];
1472
+ class CartoAPIError extends Error {
1473
+ constructor(error, errorContext, response, responseJson) {
1474
+ let responseString = 'Failed to connect';
1475
+ if (response) {
1476
+ responseString = 'Server returned: ';
1477
+ if (response.status === 400) {
1478
+ responseString += 'Bad request';
1479
+ } else if (response.status === 401 || response.status === 403) {
1480
+ responseString += 'Unauthorized access';
1481
+ } else if (response.status === 404) {
1482
+ responseString += 'Not found';
1483
+ } else {
1484
+ responseString += 'Error';
1485
+ }
1486
+ responseString += ` (${response.status}):`;
1487
+ }
1488
+ responseString += ` ${error.message || error}`;
1489
+ let message = `${errorContext.requestType} API request failed`;
1490
+ message += `\n${responseString}`;
1491
+ for (const key of Object.keys(errorContext)) {
1492
+ if (key === 'requestType') continue;
1493
+ message += `\n${formatErrorKey(key)}: ${errorContext[key]}`;
1494
+ }
1495
+ message += '\n';
1496
+ super(message);
1497
+ /** Source error from server */
1498
+ this.error = void 0;
1499
+ /** Context (API call & parameters) in which error occured */
1500
+ this.errorContext = void 0;
1501
+ /** Response from server */
1502
+ this.response = void 0;
1503
+ /** JSON Response from server */
1504
+ this.responseJson = void 0;
1505
+ this.name = 'CartoAPIError';
1506
+ this.response = response;
1507
+ this.responseJson = responseJson;
1508
+ this.error = error;
1509
+ this.errorContext = errorContext;
1510
+ }
1652
1511
  }
1653
1512
  /**
1654
- * Unproject world point [x,y] on map onto {lat, lon} on sphere
1655
- *
1656
- * @param xy - array with [x,y] members
1657
- * representing point on projected map plane
1658
- * @return - array with [x,y] of point on sphere.
1659
- * Has toArray method if you need a GeoJSON Array.
1660
- * Per cartographic tradition, lat and lon are specified as degrees.
1513
+ * Converts camelCase to Camel Case
1661
1514
  */
1662
- function worldToLngLat(xy) {
1663
- const [x, y] = xy;
1664
- const lambda2 = x / TILE_SIZE * (2 * PI) - PI;
1665
- const phi2 = 2 * (Math.atan(Math.exp(y / TILE_SIZE * (2 * PI) - PI)) - PI_4);
1666
- return [lambda2 * RADIANS_TO_DEGREES, phi2 * RADIANS_TO_DEGREES];
1515
+ function formatErrorKey(key) {
1516
+ return key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
1667
1517
  }
1668
1518
 
1669
- const TRANSFORM_FN$1 = {
1670
- Point: transformPoint$1,
1671
- MultiPoint: transformMultiPoint$1,
1672
- LineString: transformLineString$1,
1673
- MultiLineString: transformMultiLineString$1,
1674
- Polygon: transformPolygon$1,
1675
- MultiPolygon: transformMultiPolygon$1
1519
+ const DEFAULT_HEADERS = {
1520
+ Accept: 'application/json',
1521
+ 'Content-Type': 'application/json'
1676
1522
  };
1677
- /**
1678
- * Transform WGS84 coordinates to tile coords.
1679
- * It's the inverse of deck.gl coordinate-transform (https://github.com/visgl/deck.gl/blob/master/modules/geo-layers/src/mvt-layer/coordinate-transform.js)
1680
- *
1681
- * @param geometry - any valid geojson geometry
1682
- * @param bbox - geojson bbox
1683
- */
1684
- function transformToTileCoords(geometry, bbox) {
1685
- const [west, south, east, north] = bbox;
1686
- const nw = projectFlat([west, north]);
1687
- const se = projectFlat([east, south]);
1688
- const projectedBbox = [nw, se];
1689
- if (geometry.type === 'GeometryCollection') {
1690
- throw new Error('Unsupported geometry type GeometryCollection');
1523
+ const DEFAULT_REQUEST_CACHE = new Map();
1524
+ async function requestWithParameters({
1525
+ baseUrl,
1526
+ parameters = {},
1527
+ headers: customHeaders = {},
1528
+ errorContext,
1529
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL,
1530
+ localCache
1531
+ }) {
1532
+ // Parameters added to all requests issued with `requestWithParameters()`.
1533
+ // These parameters override parameters already in the base URL, but not
1534
+ // user-provided parameters.
1535
+ parameters = _extends({
1536
+ v: V3_MINOR_VERSION,
1537
+ client: getClient()
1538
+ }, typeof deck !== 'undefined' && deck.VERSION && {
1539
+ deckglVersion: deck.VERSION
1540
+ }, parameters);
1541
+ baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters));
1542
+ const key = createCacheKey(baseUrl, parameters, customHeaders);
1543
+ const {
1544
+ cache: REQUEST_CACHE,
1545
+ canReadCache,
1546
+ canStoreInCache
1547
+ } = getCacheSettings(localCache);
1548
+ if (canReadCache && REQUEST_CACHE.has(key)) {
1549
+ return REQUEST_CACHE.get(key);
1691
1550
  }
1692
- const transformFn = TRANSFORM_FN$1[geometry.type];
1693
- const coordinates = transformFn(geometry.coordinates, projectedBbox);
1694
- return _extends({}, geometry, {
1695
- coordinates
1551
+ const url = createURLWithParameters(baseUrl, parameters);
1552
+ const headers = _extends({}, DEFAULT_HEADERS, customHeaders);
1553
+ /* global fetch */
1554
+ const fetchPromise = url.length > maxLengthURL ? fetch(baseUrl, {
1555
+ method: 'POST',
1556
+ body: JSON.stringify(parameters),
1557
+ headers
1558
+ }) : fetch(url, {
1559
+ headers
1560
+ });
1561
+ let response;
1562
+ let responseJson;
1563
+ const jsonPromise = fetchPromise.then(_response => {
1564
+ response = _response;
1565
+ return response.json();
1566
+ }).then(json => {
1567
+ responseJson = json;
1568
+ if (!response || !response.ok) {
1569
+ throw new Error(json.error);
1570
+ }
1571
+ return json;
1572
+ }).catch(error => {
1573
+ if (canStoreInCache) {
1574
+ REQUEST_CACHE.delete(key);
1575
+ }
1576
+ throw new CartoAPIError(error, errorContext, response, responseJson);
1696
1577
  });
1578
+ if (canStoreInCache) {
1579
+ REQUEST_CACHE.set(key, jsonPromise);
1580
+ }
1581
+ return jsonPromise;
1697
1582
  }
1698
- function transformPoint$1([pointX, pointY], [nw, se]) {
1699
- const x = inverseLerp(nw[0], se[0], pointX);
1700
- const y = inverseLerp(nw[1], se[1], pointY);
1701
- return [x, y];
1702
- }
1703
- function getPoints$1(geometry, bbox) {
1704
- return geometry.map(g => transformPoint$1(projectFlat(g), bbox));
1705
- }
1706
- function transformMultiPoint$1(multiPoint, bbox) {
1707
- return getPoints$1(multiPoint, bbox);
1708
- }
1709
- function transformLineString$1(line, bbox) {
1710
- return getPoints$1(line, bbox);
1711
- }
1712
- function transformMultiLineString$1(multiLineString, bbox) {
1713
- return multiLineString.map(lineString => transformLineString$1(lineString, bbox));
1714
- }
1715
- function transformPolygon$1(polygon, bbox) {
1716
- return polygon.map(polygonRing => getPoints$1(polygonRing, bbox));
1717
- }
1718
- function transformMultiPolygon$1(multiPolygon, bbox) {
1719
- return multiPolygon.map(polygon => transformPolygon$1(polygon, bbox));
1720
- }
1721
- function projectFlat(xyz) {
1722
- return lngLatToWorld(xyz);
1583
+ function getCacheSettings(localCache) {
1584
+ var _localCache$cacheCont, _localCache$cacheCont2;
1585
+ const canReadCache = localCache != null && (_localCache$cacheCont = localCache.cacheControl) != null && _localCache$cacheCont.includes('no-cache') ? false : true;
1586
+ const canStoreInCache = localCache != null && (_localCache$cacheCont2 = localCache.cacheControl) != null && _localCache$cacheCont2.includes('no-store') ? false : true;
1587
+ const cache = (localCache == null ? void 0 : localCache.cache) || DEFAULT_REQUEST_CACHE;
1588
+ return {
1589
+ cache,
1590
+ canReadCache,
1591
+ canStoreInCache
1592
+ };
1723
1593
  }
1724
- function inverseLerp(a, b, x) {
1725
- return (x - a) / (b - a);
1594
+ function createCacheKey(baseUrl, parameters, headers) {
1595
+ const parameterEntries = Object.entries(parameters).sort(([a], [b]) => a > b ? 1 : -1);
1596
+ const headerEntries = Object.entries(headers).sort(([a], [b]) => a > b ? 1 : -1);
1597
+ return JSON.stringify({
1598
+ baseUrl,
1599
+ parameters: parameterEntries,
1600
+ headers: headerEntries
1601
+ });
1726
1602
  }
1727
-
1728
- const TRANSFORM_FN = {
1729
- Point: transformPoint,
1730
- MultiPoint: transformMultiPoint,
1731
- LineString: transformLineString,
1732
- MultiLineString: transformMultiLineString,
1733
- Polygon: transformPolygon,
1734
- MultiPolygon: transformMultiPolygon
1735
- };
1736
1603
  /**
1737
- * Transform tile coords to WGS84 coordinates.
1738
- *
1739
- * @param geometry - any valid geojson geometry
1740
- * @param bbox - geojson bbox
1604
+ * Appends query string parameters to a URL. Existing URL parameters are kept,
1605
+ * unless there is a conflict, in which case the new parameters override
1606
+ * those already in the URL.
1741
1607
  */
1742
- function transformTileCoordsToWGS84(geometry, bbox) {
1743
- const [west, south, east, north] = bbox;
1744
- const nw = lngLatToWorld([west, north]);
1745
- const se = lngLatToWorld([east, south]);
1746
- const projectedBbox = [nw, se];
1747
- if (geometry.type === 'GeometryCollection') {
1748
- throw new Error('Unsupported geometry type GeometryCollection');
1608
+ function createURLWithParameters(baseUrlString, parameters) {
1609
+ const baseUrl = new URL(baseUrlString);
1610
+ for (const [key, value] of Object.entries(parameters)) {
1611
+ if (isPureObject(value) || Array.isArray(value)) {
1612
+ baseUrl.searchParams.set(key, JSON.stringify(value));
1613
+ } else {
1614
+ baseUrl.searchParams.set(key, value.toString());
1615
+ }
1749
1616
  }
1750
- const transformFn = TRANSFORM_FN[geometry.type];
1751
- const coordinates = transformFn(geometry.coordinates, projectedBbox);
1752
- return _extends({}, geometry, {
1753
- coordinates
1754
- });
1755
- }
1756
- function transformPoint([pointX, pointY], [nw, se]) {
1757
- const x = lerp(nw[0], se[0], pointX);
1758
- const y = lerp(nw[1], se[1], pointY);
1759
- return worldToLngLat([x, y]);
1760
- }
1761
- function getPoints(geometry, bbox) {
1762
- return geometry.map(g => transformPoint(g, bbox));
1763
- }
1764
- function transformMultiPoint(multiPoint, bbox) {
1765
- return getPoints(multiPoint, bbox);
1766
- }
1767
- function transformLineString(line, bbox) {
1768
- return getPoints(line, bbox);
1769
- }
1770
- function transformMultiLineString(multiLineString, bbox) {
1771
- return multiLineString.map(lineString => transformLineString(lineString, bbox));
1772
- }
1773
- function transformPolygon(polygon, bbox) {
1774
- return polygon.map(polygonRing => getPoints(polygonRing, bbox));
1775
- }
1776
- function transformMultiPolygon(multiPolygon, bbox) {
1777
- return multiPolygon.map(polygon => transformPolygon(polygon, bbox));
1617
+ return baseUrl.toString();
1778
1618
  }
1779
-
1780
- const FEATURE_GEOM_PROPERTY = '__geomValue';
1781
- function tileFeaturesGeometries({
1782
- tiles,
1783
- tileFormat,
1784
- spatialFilter,
1785
- uniqueIdProperty,
1786
- options
1787
- }) {
1788
- const map = new Map();
1789
- for (const tile of tiles) {
1790
- // Discard if it's not a visible tile (only check false value, not undefined)
1791
- // or tile has not data
1792
- if (tile.isVisible === false || !tile.data) {
1793
- continue;
1794
- }
1795
- const bbox = [tile.bbox.west, tile.bbox.south, tile.bbox.east, tile.bbox.north];
1796
- const bboxToGeom = bboxPolygon(bbox);
1797
- const tileIsFullyVisible = booleanWithin(bboxToGeom, spatialFilter);
1798
- // Clip the geometry to intersect with the tile
1799
- const spatialFilterFeature = {
1800
- type: 'Feature',
1801
- geometry: spatialFilter,
1802
- properties: {}
1803
- };
1804
- const clippedGeometryToIntersect = intersect(featureCollection([bboxToGeom, spatialFilterFeature]));
1805
- if (!clippedGeometryToIntersect) {
1806
- continue;
1619
+ /**
1620
+ * Deletes query string parameters from a URL.
1621
+ */
1622
+ function excludeURLParameters(baseUrlString, parameters) {
1623
+ const baseUrl = new URL(baseUrlString);
1624
+ for (const param of parameters) {
1625
+ if (baseUrl.searchParams.has(param)) {
1626
+ baseUrl.searchParams.delete(param);
1807
1627
  }
1808
- // We assume that MVT tileFormat uses local coordinates so we transform the geometry to intersect to tile coordinates [0..1],
1809
- // while in the case of 'geojson' or binary, the geometries are already in WGS84
1810
- const transformedGeometryToIntersect = tileFormat === TileFormat.MVT ? transformToTileCoords(clippedGeometryToIntersect.geometry, bbox) : clippedGeometryToIntersect.geometry;
1811
- createIndicesForPoints(tile.data.points);
1812
- calculateFeatures({
1813
- map,
1814
- tileIsFullyVisible,
1815
- geometryIntersection: transformedGeometryToIntersect,
1816
- data: tile.data.points,
1817
- type: 'Point',
1818
- bbox,
1819
- tileFormat,
1820
- uniqueIdProperty,
1821
- options
1822
- });
1823
- calculateFeatures({
1824
- map,
1825
- tileIsFullyVisible,
1826
- geometryIntersection: transformedGeometryToIntersect,
1827
- data: tile.data.lines,
1828
- type: 'LineString',
1829
- bbox,
1830
- tileFormat,
1831
- uniqueIdProperty,
1832
- options
1833
- });
1834
- calculateFeatures({
1835
- map,
1836
- tileIsFullyVisible,
1837
- geometryIntersection: transformedGeometryToIntersect,
1838
- data: tile.data.polygons,
1839
- type: 'Polygon',
1840
- bbox,
1841
- tileFormat,
1842
- uniqueIdProperty,
1843
- options
1844
- });
1845
1628
  }
1846
- return Array.from(map.values());
1629
+ return baseUrl.toString();
1847
1630
  }
1848
- function processTileFeatureProperties({
1849
- map,
1850
- data,
1851
- startIndex,
1852
- endIndex,
1853
- type,
1854
- bbox,
1855
- tileFormat,
1856
- uniqueIdProperty,
1857
- storeGeometry,
1858
- geometryIntersection
1859
- }) {
1860
- const tileProps = getPropertiesFromTile(data, startIndex);
1861
- const uniquePropertyValue = getUniquePropertyValue(tileProps, uniqueIdProperty, map);
1862
- if (!uniquePropertyValue || map.has(uniquePropertyValue)) {
1863
- return;
1864
- }
1865
- let geometry = null;
1866
- // Only calculate geometry if necessary
1867
- if (storeGeometry || geometryIntersection) {
1868
- const {
1869
- positions
1870
- } = data;
1871
- const ringCoordinates = getRingCoordinatesFor(startIndex, endIndex, positions);
1872
- geometry = getFeatureByType(ringCoordinates, type);
1873
- }
1874
- // If intersection is required, check before proceeding
1875
- if (geometry && geometryIntersection && !intersects(geometry, geometryIntersection)) {
1876
- return;
1631
+
1632
+ const _excluded$1 = ["accessToken", "connectionName", "cache"];
1633
+ const SOURCE_DEFAULTS = {
1634
+ apiBaseUrl: DEFAULT_API_BASE_URL,
1635
+ clientId: getClient(),
1636
+ format: 'tilejson',
1637
+ headers: {},
1638
+ maxLengthURL: DEFAULT_MAX_LENGTH_URL
1639
+ };
1640
+ async function baseSource(endpoint, options, urlParameters) {
1641
+ const {
1642
+ accessToken,
1643
+ connectionName,
1644
+ cache
1645
+ } = options,
1646
+ optionalOptions = _objectWithoutPropertiesLoose(options, _excluded$1);
1647
+ const mergedOptions = _extends({}, SOURCE_DEFAULTS, {
1648
+ accessToken,
1649
+ connectionName,
1650
+ endpoint
1651
+ });
1652
+ for (const key in optionalOptions) {
1653
+ if (optionalOptions[key]) {
1654
+ mergedOptions[key] = optionalOptions[key];
1655
+ }
1877
1656
  }
1878
- const properties = parseProperties(tileProps);
1879
- // Only save geometry if necessary
1880
- if (storeGeometry && geometry) {
1881
- properties[FEATURE_GEOM_PROPERTY] = tileFormat === TileFormat.MVT ? transformTileCoordsToWGS84(geometry, bbox) : geometry;
1657
+ const baseUrl = buildSourceUrl(mergedOptions);
1658
+ const {
1659
+ clientId,
1660
+ maxLengthURL,
1661
+ format,
1662
+ localCache
1663
+ } = mergedOptions;
1664
+ const headers = _extends({
1665
+ Authorization: `Bearer ${options.accessToken}`
1666
+ }, options.headers);
1667
+ const parameters = _extends({
1668
+ client: clientId
1669
+ }, urlParameters);
1670
+ const errorContext = {
1671
+ requestType: 'Map instantiation',
1672
+ connection: options.connectionName,
1673
+ type: endpoint,
1674
+ source: JSON.stringify(parameters, undefined, 2)
1675
+ };
1676
+ const mapInstantiation = await requestWithParameters({
1677
+ baseUrl,
1678
+ parameters,
1679
+ headers,
1680
+ errorContext,
1681
+ maxLengthURL,
1682
+ localCache
1683
+ });
1684
+ const dataUrl = mapInstantiation[format].url[0];
1685
+ if (cache) {
1686
+ cache.value = parseInt(new URL(dataUrl).searchParams.get('cache') || '', 10);
1882
1687
  }
1883
- map.set(uniquePropertyValue, properties);
1884
- }
1885
- function addIntersectedFeaturesInTile({
1886
- map,
1887
- data,
1888
- geometryIntersection,
1889
- type,
1890
- bbox,
1891
- tileFormat,
1892
- uniqueIdProperty,
1893
- options
1894
- }) {
1895
- const indices = getIndices(data);
1896
- const storeGeometry = (options == null ? void 0 : options.storeGeometry) || false;
1897
- for (let i = 0; i < indices.length - 1; i++) {
1898
- const startIndex = indices[i];
1899
- const endIndex = indices[i + 1];
1900
- processTileFeatureProperties({
1901
- map,
1902
- data,
1903
- startIndex,
1904
- endIndex,
1905
- type,
1906
- bbox,
1907
- tileFormat,
1908
- uniqueIdProperty,
1909
- storeGeometry,
1910
- geometryIntersection
1688
+ errorContext.requestType = 'Map data';
1689
+ if (format === 'tilejson') {
1690
+ const json = await requestWithParameters({
1691
+ baseUrl: dataUrl,
1692
+ headers,
1693
+ errorContext,
1694
+ maxLengthURL,
1695
+ localCache
1911
1696
  });
1697
+ if (accessToken) {
1698
+ json.accessToken = accessToken;
1699
+ }
1700
+ return json;
1912
1701
  }
1702
+ return await requestWithParameters({
1703
+ baseUrl: dataUrl,
1704
+ headers,
1705
+ errorContext,
1706
+ maxLengthURL,
1707
+ localCache
1708
+ });
1913
1709
  }
1914
- function getIndices(data) {
1915
- let indices;
1916
- switch (data.type) {
1917
- case 'Point':
1918
- // @ts-expect-error Missing or changed types?
1919
- indices = data.pointIndices;
1920
- break;
1921
- case 'LineString':
1922
- indices = data.pathIndices;
1923
- break;
1924
- case 'Polygon':
1925
- indices = data.primitivePolygonIndices;
1926
- break;
1927
- default:
1928
- throw new Error(`Unexpected type, "${data.type}"`);
1929
- }
1930
- return indices.value;
1931
- }
1932
- function getFeatureId(data, startIndex) {
1933
- return data.featureIds.value[startIndex];
1934
- }
1935
- function getPropertiesFromTile(data, startIndex) {
1936
- var _fields$featureId;
1937
- const featureId = getFeatureId(data, startIndex);
1710
+
1711
+ // deck.gl
1712
+ // SPDX-License-Identifier: MIT
1713
+ // Copyright (c) vis.gl contributors
1714
+ const boundaryQuerySource = async function boundaryQuerySource(options) {
1938
1715
  const {
1939
- properties,
1940
- numericProps,
1941
- fields
1942
- } = data;
1943
- const result = {
1944
- uniqueId: fields == null || (_fields$featureId = fields[featureId]) == null ? void 0 : _fields$featureId.id,
1945
- properties: properties[featureId],
1946
- numericProps: {}
1716
+ columns,
1717
+ filters,
1718
+ tilesetTableName,
1719
+ propertiesSqlQuery,
1720
+ queryParameters
1721
+ } = options;
1722
+ const urlParameters = {
1723
+ tilesetTableName,
1724
+ propertiesSqlQuery
1947
1725
  };
1948
- for (const key in numericProps) {
1949
- result.numericProps[key] = numericProps[key].value[startIndex];
1726
+ if (columns) {
1727
+ urlParameters.columns = columns.join(',');
1950
1728
  }
1951
- return result;
1952
- }
1953
- function parseProperties(tileProps) {
1729
+ if (filters) {
1730
+ urlParameters.filters = filters;
1731
+ }
1732
+ if (queryParameters) {
1733
+ urlParameters.queryParameters = queryParameters;
1734
+ }
1735
+ return baseSource('boundary', options, urlParameters);
1736
+ };
1737
+
1738
+ // deck.gl
1739
+ // SPDX-License-Identifier: MIT
1740
+ // Copyright (c) vis.gl contributors
1741
+ const boundaryTableSource = async function boundaryTableSource(options) {
1954
1742
  const {
1955
- properties,
1956
- numericProps
1957
- } = tileProps;
1958
- return Object.assign({}, properties, numericProps);
1959
- }
1960
- function getUniquePropertyValue(tileProps, uniqueIdProperty, map) {
1961
- if (uniqueIdProperty) {
1962
- return getValueFromTileProps(tileProps, uniqueIdProperty);
1743
+ filters,
1744
+ tilesetTableName,
1745
+ columns,
1746
+ propertiesTableName
1747
+ } = options;
1748
+ const urlParameters = {
1749
+ tilesetTableName,
1750
+ propertiesTableName
1751
+ };
1752
+ if (columns) {
1753
+ urlParameters.columns = columns.join(',');
1963
1754
  }
1964
- if (tileProps.uniqueId) {
1965
- return tileProps.uniqueId;
1755
+ if (filters) {
1756
+ urlParameters.filters = filters;
1966
1757
  }
1967
- const artificialId = map.size + 1; // a counter, assumed as a valid new id
1968
- return getValueFromTileProps(tileProps, 'cartodb_id') || getValueFromTileProps(tileProps, 'geoid') || artificialId;
1758
+ return baseSource('boundary', options, urlParameters);
1759
+ };
1760
+
1761
+ const DEFAULT_TILE_SIZE = 512;
1762
+ const QUADBIN_ZOOM_MAX_OFFSET = 4;
1763
+ function getSpatialFiltersResolution(source, viewState) {
1764
+ var _source$dataResolutio, _source$aggregationRe;
1765
+ const dataResolution = (_source$dataResolutio = source.dataResolution) != null ? _source$dataResolutio : Number.MAX_VALUE;
1766
+ const aggregationResLevel = (_source$aggregationRe = source.aggregationResLevel) != null ? _source$aggregationRe : source.spatialDataType === 'h3' ? DEFAULT_AGGREGATION_RES_LEVEL_H3 : DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN;
1767
+ const aggregationResLevelOffset = Math.max(0, Math.floor(aggregationResLevel));
1768
+ const currentZoomInt = Math.ceil(viewState.zoom);
1769
+ if (source.spatialDataType === 'h3') {
1770
+ var _maxH3SpatialFiltersR, _maxH3SpatialFiltersR2;
1771
+ const tileSize = DEFAULT_TILE_SIZE;
1772
+ const maxResolutionForZoom = (_maxH3SpatialFiltersR = (_maxH3SpatialFiltersR2 = maxH3SpatialFiltersResolutions.find(([zoom]) => zoom === currentZoomInt)) == null ? void 0 : _maxH3SpatialFiltersR2[1]) != null ? _maxH3SpatialFiltersR : Math.max(0, currentZoomInt - 3);
1773
+ const maxSpatialFiltersResolution = maxResolutionForZoom ? Math.min(dataResolution, maxResolutionForZoom) : dataResolution;
1774
+ const hexagonResolution = _getHexagonResolution(viewState, tileSize) + aggregationResLevelOffset;
1775
+ return Math.min(hexagonResolution, maxSpatialFiltersResolution);
1776
+ }
1777
+ if (source.spatialDataType === 'quadbin') {
1778
+ const maxResolutionForZoom = currentZoomInt + QUADBIN_ZOOM_MAX_OFFSET;
1779
+ const maxSpatialFiltersResolution = Math.min(dataResolution, maxResolutionForZoom);
1780
+ const quadsResolution = Math.floor(viewState.zoom) + aggregationResLevelOffset;
1781
+ return Math.min(quadsResolution, maxSpatialFiltersResolution);
1782
+ }
1783
+ return undefined;
1969
1784
  }
1970
- function getValueFromTileProps(tileProps, propertyName) {
1971
- const {
1972
- properties,
1973
- numericProps
1974
- } = tileProps;
1975
- return numericProps[propertyName] || properties[propertyName];
1785
+ 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]];
1786
+ // stolen from https://github.com/visgl/deck.gl/blob/master/modules/carto/src/layers/h3-tileset-2d.ts
1787
+ // Relative scale factor (0 = no biasing, 2 = a few hexagons cover view)
1788
+ const BIAS = 2;
1789
+ /**
1790
+ * Resolution conversion function. Takes a WebMercatorViewport and returns
1791
+ * a H3 resolution such that the screen space size of the hexagons is
1792
+ * "similar" to the given tileSize on screen. Intended for use with deck.gl.
1793
+ * @internal
1794
+ */
1795
+ function _getHexagonResolution(viewport, tileSize) {
1796
+ // Difference in given tile size compared to deck's internal 512px tile size,
1797
+ // expressed as an offset to the viewport zoom.
1798
+ const zoomOffset = Math.log2(tileSize / DEFAULT_TILE_SIZE);
1799
+ const hexagonScaleFactor = 2 / 3 * (viewport.zoom - zoomOffset);
1800
+ const latitudeScaleFactor = Math.log(1 / Math.cos(Math.PI * viewport.latitude / 180));
1801
+ // Clip and bias
1802
+ return Math.max(0, Math.floor(hexagonScaleFactor + latitudeScaleFactor - BIAS));
1976
1803
  }
1977
- function getFeatureByType(coordinates, type) {
1978
- switch (type) {
1979
- case 'Polygon':
1980
- return {
1981
- type: 'Polygon',
1982
- coordinates: [coordinates]
1983
- };
1984
- case 'LineString':
1985
- return {
1986
- type: 'LineString',
1987
- coordinates
1988
- };
1989
- case 'Point':
1990
- return {
1991
- type: 'Point',
1992
- coordinates: coordinates[0]
1993
- };
1994
- default:
1995
- throw new Error('Invalid geometry type');
1804
+
1805
+ /**
1806
+ * Source for Widget API requests on a data source defined by a SQL query.
1807
+ *
1808
+ * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
1809
+ */
1810
+ class WidgetSource {
1811
+ constructor(props) {
1812
+ this.props = void 0;
1813
+ this.props = _extends({}, WidgetSource.defaultProps, props);
1996
1814
  }
1997
- }
1998
- function getRingCoordinatesFor(startIndex, endIndex, positions) {
1999
- const ringCoordinates = [];
2000
- for (let j = startIndex; j < endIndex; j++) {
2001
- ringCoordinates.push(Array.from(positions.value.subarray(j * positions.size, (j + 1) * positions.size)));
1815
+ _getModelSource(filters, filterOwner) {
1816
+ const props = this.props;
1817
+ return {
1818
+ apiVersion: props.apiVersion,
1819
+ apiBaseUrl: props.apiBaseUrl,
1820
+ clientId: props.clientId,
1821
+ accessToken: props.accessToken,
1822
+ connectionName: props.connectionName,
1823
+ filters: getApplicableFilters(filterOwner, filters || props.filters),
1824
+ filtersLogicalOperator: props.filtersLogicalOperator,
1825
+ spatialDataType: props.spatialDataType,
1826
+ spatialDataColumn: props.spatialDataColumn,
1827
+ dataResolution: props.dataResolution
1828
+ };
1829
+ }
1830
+ _getSpatialFiltersResolution(source, spatialFilter, referenceViewState) {
1831
+ // spatialFiltersResolution applies only to spatial index sources.
1832
+ if (!spatialFilter || source.spatialDataType === 'geo') {
1833
+ return;
1834
+ }
1835
+ if (!referenceViewState) {
1836
+ throw new Error('Missing required option, "spatialIndexReferenceViewState".');
1837
+ }
1838
+ return getSpatialFiltersResolution(source, referenceViewState);
2002
1839
  }
2003
- return ringCoordinates;
2004
1840
  }
2005
- function calculateFeatures({
2006
- map,
2007
- tileIsFullyVisible,
2008
- geometryIntersection,
2009
- data,
2010
- type,
2011
- bbox,
2012
- tileFormat,
2013
- uniqueIdProperty,
2014
- options
1841
+ WidgetSource.defaultProps = {
1842
+ apiVersion: ApiVersion.V3,
1843
+ apiBaseUrl: DEFAULT_API_BASE_URL,
1844
+ clientId: getClient(),
1845
+ filters: {},
1846
+ filtersLogicalOperator: 'and'
1847
+ };
1848
+
1849
+ /**
1850
+ * Return more descriptive error from API
1851
+ * @privateRemarks Source: @carto/react-api
1852
+ */
1853
+ function dealWithApiError({
1854
+ response,
1855
+ data
2015
1856
  }) {
2016
- if (!(data != null && data.properties.length)) {
2017
- return;
1857
+ var _data$error, _data$error2;
1858
+ if (data.error === 'Column not found') {
1859
+ throw new InvalidColumnError(`${data.error} ${data.column_name}`);
2018
1860
  }
2019
- if (tileIsFullyVisible) {
2020
- addAllFeaturesInTile({
2021
- map,
2022
- data,
2023
- type,
2024
- bbox,
2025
- tileFormat,
2026
- uniqueIdProperty,
2027
- options
2028
- });
2029
- } else {
2030
- addIntersectedFeaturesInTile({
2031
- map,
2032
- data,
2033
- geometryIntersection,
2034
- type,
2035
- bbox,
2036
- tileFormat,
2037
- uniqueIdProperty,
2038
- options
2039
- });
1861
+ if (typeof data.error === 'string' && (_data$error = data.error) != null && _data$error.includes('Missing columns')) {
1862
+ throw new InvalidColumnError(data.error);
1863
+ }
1864
+ switch (response.status) {
1865
+ case 401:
1866
+ throw new Error('Unauthorized access. Invalid credentials');
1867
+ case 403:
1868
+ throw new Error('Forbidden access to the requested data');
1869
+ default:
1870
+ throw new Error(data && data.error && typeof data.error === 'string' ? data.error : JSON.stringify((data == null ? void 0 : data.hint) || ((_data$error2 = data.error) == null ? void 0 : _data$error2[0])));
2040
1871
  }
2041
1872
  }
2042
- function addAllFeaturesInTile({
2043
- map,
2044
- data,
2045
- type,
2046
- bbox,
2047
- tileFormat,
2048
- uniqueIdProperty,
2049
- options
1873
+ /** @privateRemarks Source: @carto/react-api */
1874
+ async function makeCall({
1875
+ url,
1876
+ accessToken,
1877
+ opts
2050
1878
  }) {
2051
- const indices = getIndices(data);
2052
- const storeGeometry = (options == null ? void 0 : options.storeGeometry) || false;
2053
- for (let i = 0; i < indices.length - 1; i++) {
2054
- const startIndex = indices[i];
2055
- const endIndex = indices[i + 1];
2056
- processTileFeatureProperties({
2057
- map,
2058
- data,
2059
- startIndex,
2060
- endIndex,
2061
- type,
2062
- bbox,
2063
- tileFormat,
2064
- uniqueIdProperty,
2065
- storeGeometry
1879
+ let response;
1880
+ let data;
1881
+ const isPost = (opts == null ? void 0 : opts.method) === 'POST';
1882
+ try {
1883
+ var _opts$abortController;
1884
+ response = await fetch(url.toString(), _extends({
1885
+ headers: _extends({
1886
+ Authorization: `Bearer ${accessToken}`
1887
+ }, isPost && {
1888
+ 'Content-Type': 'application/json'
1889
+ }, opts.headers)
1890
+ }, isPost && {
1891
+ method: opts == null ? void 0 : opts.method,
1892
+ body: opts == null ? void 0 : opts.body
1893
+ }, {
1894
+ signal: opts == null || (_opts$abortController = opts.abortController) == null ? void 0 : _opts$abortController.signal
1895
+ }, opts == null ? void 0 : opts.otherOptions));
1896
+ data = await response.json();
1897
+ } catch (error) {
1898
+ if (error.name === 'AbortError') throw error;
1899
+ throw new Error(`Failed request: ${error}`);
1900
+ }
1901
+ if (!response.ok) {
1902
+ dealWithApiError({
1903
+ response,
1904
+ data
2066
1905
  });
2067
1906
  }
2068
- }
2069
- function createIndicesForPoints(data) {
2070
- const featureIds = data.featureIds.value;
2071
- const lastFeatureId = featureIds[featureIds.length - 1];
2072
- const PointIndicesArray = featureIds.constructor;
2073
- const pointIndices = {
2074
- value: new PointIndicesArray(featureIds.length + 1),
2075
- size: 1
2076
- };
2077
- pointIndices.value.set(featureIds);
2078
- pointIndices.value.set([lastFeatureId + 1], featureIds.length);
2079
- // @ts-expect-error Missing or changed types?
2080
- data.pointIndices = pointIndices;
1907
+ return data;
2081
1908
  }
2082
1909
 
2083
- function tileFeaturesSpatialIndex({
2084
- tiles,
2085
- spatialFilter,
2086
- spatialDataColumn,
2087
- spatialDataType
2088
- }) {
2089
- const map = new Map();
2090
- const spatialIndex = getSpatialIndex(spatialDataType);
2091
- const resolution = getResolution(tiles, spatialIndex);
2092
- const spatialIndexIDName = spatialDataColumn ? spatialDataColumn : spatialIndex;
2093
- if (!resolution) {
2094
- return [];
1910
+ /** @privateRemarks Source: @carto/react-api */
1911
+ const AVAILABLE_MODELS = ['category', 'histogram', 'formula', 'pick', 'timeseries', 'range', 'scatterplot', 'table'];
1912
+ const {
1913
+ V3
1914
+ } = ApiVersion;
1915
+ const REQUEST_GET_MAX_URL_LENGTH = 2048;
1916
+ /**
1917
+ * Execute a SQL model request.
1918
+ * @privateRemarks Source: @carto/react-api
1919
+ */
1920
+ function executeModel(props) {
1921
+ assert(props.source, 'executeModel: missing source');
1922
+ assert(props.model, 'executeModel: missing model');
1923
+ assert(props.params, 'executeModel: missing params');
1924
+ assert(AVAILABLE_MODELS.includes(props.model), `executeModel: model provided isn't valid. Available models: ${AVAILABLE_MODELS.join(', ')}`);
1925
+ const {
1926
+ model,
1927
+ source,
1928
+ params,
1929
+ opts
1930
+ } = props;
1931
+ const {
1932
+ type,
1933
+ apiVersion,
1934
+ apiBaseUrl,
1935
+ accessToken,
1936
+ connectionName,
1937
+ clientId
1938
+ } = source;
1939
+ assert(apiBaseUrl, 'executeModel: missing apiBaseUrl');
1940
+ assert(accessToken, 'executeModel: missing accessToken');
1941
+ assert(apiVersion === V3, 'executeModel: SQL Model API requires CARTO 3+');
1942
+ assert(type !== 'tileset', 'executeModel: Tilesets not supported');
1943
+ let url = `${apiBaseUrl}/v3/sql/${connectionName}/model/${model}`;
1944
+ const {
1945
+ data,
1946
+ filters,
1947
+ filtersLogicalOperator = 'and',
1948
+ spatialDataType = 'geo',
1949
+ spatialFiltersMode = 'intersects',
1950
+ spatialFiltersResolution = 0
1951
+ } = source;
1952
+ const queryParams = {
1953
+ type,
1954
+ client: clientId,
1955
+ source: data,
1956
+ params,
1957
+ queryParameters: source.queryParameters || '',
1958
+ filters,
1959
+ filtersLogicalOperator
1960
+ };
1961
+ const spatialDataColumn = source.spatialDataColumn || DEFAULT_GEO_COLUMN;
1962
+ // Picking Model API requires 'spatialDataColumn'.
1963
+ if (model === 'pick') {
1964
+ queryParams.spatialDataColumn = spatialDataColumn;
2095
1965
  }
2096
- const cells = getCellsCoverGeometry(spatialFilter, spatialIndex, resolution);
2097
- if (!(cells != null && cells.length)) {
2098
- return [];
1966
+ // API supports multiple filters, we apply it only to spatialDataColumn
1967
+ const spatialFilters = source.spatialFilter ? {
1968
+ [spatialDataColumn]: source.spatialFilter
1969
+ } : undefined;
1970
+ if (spatialFilters) {
1971
+ queryParams.spatialFilters = spatialFilters;
1972
+ queryParams.spatialDataColumn = spatialDataColumn;
1973
+ queryParams.spatialDataType = spatialDataType;
2099
1974
  }
2100
- // We transform cells to Set to improve the performace
2101
- const cellsSet = new Set(cells);
2102
- for (const tile of tiles) {
2103
- if (tile.isVisible === false || !tile.data) {
2104
- continue;
1975
+ if (spatialDataType !== 'geo') {
1976
+ if (spatialFiltersResolution > 0) {
1977
+ queryParams.spatialFiltersResolution = spatialFiltersResolution;
2105
1978
  }
2106
- tile.data.forEach(d => {
2107
- if (cellsSet.has(d.id)) {
2108
- map.set(d.id, _extends({}, d.properties, {
2109
- [spatialIndexIDName]: d.id
2110
- }));
2111
- }
2112
- });
1979
+ queryParams.spatialFiltersMode = spatialFiltersMode;
1980
+ }
1981
+ const urlWithSearchParams = url + '?' + objectToURLSearchParams(queryParams).toString();
1982
+ const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
1983
+ if (isGet) {
1984
+ url = urlWithSearchParams;
1985
+ }
1986
+ return makeCall({
1987
+ url,
1988
+ accessToken: source.accessToken,
1989
+ opts: _extends({}, opts, {
1990
+ method: isGet ? 'GET' : 'POST'
1991
+ }, !isGet && {
1992
+ body: JSON.stringify(queryParams)
1993
+ })
1994
+ });
1995
+ }
1996
+ function objectToURLSearchParams(object) {
1997
+ const params = new URLSearchParams();
1998
+ for (const key in object) {
1999
+ if (isPureObject(object[key])) {
2000
+ params.append(key, JSON.stringify(object[key]));
2001
+ } else if (Array.isArray(object[key])) {
2002
+ params.append(key, JSON.stringify(object[key]));
2003
+ } else if (object[key] === null) {
2004
+ params.append(key, 'null');
2005
+ } else if (object[key] !== undefined) {
2006
+ params.append(key, String(object[key]));
2007
+ }
2008
+ }
2009
+ return params;
2010
+ }
2011
+
2012
+ const _excluded = ["filters", "filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController"],
2013
+ _excluded2 = ["filters", "filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController"],
2014
+ _excluded3 = ["filters", "filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController", "operationExp"],
2015
+ _excluded4 = ["filters", "filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController"],
2016
+ _excluded5 = ["filters", "filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController"],
2017
+ _excluded6 = ["filters", "filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController"],
2018
+ _excluded7 = ["filters", "filterOwner", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState", "abortController"],
2019
+ _excluded8 = ["filters", "filterOwner", "abortController", "spatialFilter", "spatialFiltersMode", "spatialIndexReferenceViewState"];
2020
+ /**
2021
+ * Source for Widget API requests.
2022
+ *
2023
+ * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
2024
+ */
2025
+ class WidgetRemoteSource extends WidgetSource {
2026
+ constructor(...args) {
2027
+ super(...args);
2028
+ this._headers = {};
2113
2029
  }
2114
- return Array.from(map.values());
2115
- }
2116
- function getResolution(tiles, spatialIndex) {
2117
- var _tiles$find;
2118
- const data = (_tiles$find = tiles.find(tile => {
2119
- var _tile$data;
2120
- return (_tile$data = tile.data) == null ? void 0 : _tile$data.length;
2121
- })) == null ? void 0 : _tiles$find.data;
2122
- if (!data) {
2123
- return;
2030
+ /** Assigns HTTP headers to be included on API requests from this source. */
2031
+ setRequestHeaders(headers) {
2032
+ this._headers = headers;
2124
2033
  }
2125
- if (spatialIndex === SpatialIndex.QUADBIN) {
2126
- return Number(getResolution$1(data[0].id));
2034
+ async getCategories(options) {
2035
+ const {
2036
+ filters = this.props.filters,
2037
+ filterOwner,
2038
+ spatialFilter,
2039
+ spatialFiltersMode,
2040
+ spatialIndexReferenceViewState,
2041
+ abortController
2042
+ } = options,
2043
+ params = _objectWithoutPropertiesLoose(options, _excluded);
2044
+ const {
2045
+ column,
2046
+ operation,
2047
+ operationColumn
2048
+ } = params;
2049
+ const source = this.getModelSource(filters, filterOwner);
2050
+ const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2051
+ return executeModel({
2052
+ model: 'category',
2053
+ source: _extends({}, source, {
2054
+ spatialFiltersResolution,
2055
+ spatialFiltersMode,
2056
+ spatialFilter
2057
+ }),
2058
+ params: {
2059
+ column,
2060
+ operation,
2061
+ operationColumn: operationColumn || column
2062
+ },
2063
+ opts: {
2064
+ abortController,
2065
+ headers: this._headers
2066
+ }
2067
+ }).then(res => normalizeObjectKeys(res.rows));
2127
2068
  }
2128
- if (spatialIndex === SpatialIndex.H3) {
2129
- return getResolution$2(data[0].id);
2069
+ async getFeatures(options) {
2070
+ const {
2071
+ filters = this.props.filters,
2072
+ filterOwner,
2073
+ spatialFilter,
2074
+ spatialFiltersMode,
2075
+ spatialIndexReferenceViewState,
2076
+ abortController
2077
+ } = options,
2078
+ params = _objectWithoutPropertiesLoose(options, _excluded2);
2079
+ const {
2080
+ columns,
2081
+ dataType,
2082
+ featureIds,
2083
+ z,
2084
+ limit,
2085
+ tileResolution
2086
+ } = params;
2087
+ const source = this.getModelSource(filters, filterOwner);
2088
+ const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2089
+ return executeModel({
2090
+ model: 'pick',
2091
+ source: _extends({}, source, {
2092
+ spatialFiltersResolution,
2093
+ spatialFiltersMode,
2094
+ spatialFilter
2095
+ }),
2096
+ params: {
2097
+ columns,
2098
+ dataType,
2099
+ featureIds,
2100
+ z,
2101
+ limit: limit || 1000,
2102
+ tileResolution: tileResolution || DEFAULT_TILE_RESOLUTION
2103
+ },
2104
+ opts: {
2105
+ abortController,
2106
+ headers: this._headers
2107
+ }
2108
+ // Avoid `normalizeObjectKeys()`, which changes column names.
2109
+ }).then(({
2110
+ rows
2111
+ }) => ({
2112
+ rows
2113
+ }));
2130
2114
  }
2131
- }
2132
- const bboxWest = [-180, -90, 0, 90];
2133
- const bboxEast = [0, -90, 180, 90];
2134
- function getCellsCoverGeometry(geometry, spatialIndex, resolution) {
2135
- if (spatialIndex === SpatialIndex.QUADBIN) {
2136
- // @ts-expect-error TODO: Probably ought to be stricter about number vs. bigint types in this file.
2137
- return geometryToCells(geometry, resolution);
2115
+ async getFormula(options) {
2116
+ const {
2117
+ filters = this.props.filters,
2118
+ filterOwner,
2119
+ spatialFilter,
2120
+ spatialFiltersMode,
2121
+ spatialIndexReferenceViewState,
2122
+ abortController,
2123
+ operationExp
2124
+ } = options,
2125
+ params = _objectWithoutPropertiesLoose(options, _excluded3);
2126
+ const {
2127
+ column,
2128
+ operation
2129
+ } = params;
2130
+ const source = this.getModelSource(filters, filterOwner);
2131
+ const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2132
+ return executeModel({
2133
+ model: 'formula',
2134
+ source: _extends({}, source, {
2135
+ spatialFiltersResolution,
2136
+ spatialFiltersMode,
2137
+ spatialFilter
2138
+ }),
2139
+ params: {
2140
+ column: column != null ? column : '*',
2141
+ operation: operation != null ? operation : 'count',
2142
+ operationExp
2143
+ },
2144
+ opts: {
2145
+ abortController,
2146
+ headers: this._headers
2147
+ }
2148
+ }).then(res => normalizeObjectKeys(res.rows[0]));
2138
2149
  }
2139
- if (spatialIndex === SpatialIndex.H3) {
2140
- // The current H3 polyfill algorithm can't deal with polygon segments of greater than 180 degrees longitude
2141
- // so we clip the geometry to be sure that none of them is greater than 180 degrees
2142
- // https://github.com/uber/h3-js/issues/24#issuecomment-431893796
2143
- return polygonToCells(bboxClip(geometry, bboxWest).geometry.coordinates, resolution, true).concat(polygonToCells(bboxClip(geometry, bboxEast).geometry.coordinates, resolution, true));
2150
+ async getHistogram(options) {
2151
+ const {
2152
+ filters = this.props.filters,
2153
+ filterOwner,
2154
+ spatialFilter,
2155
+ spatialFiltersMode,
2156
+ spatialIndexReferenceViewState,
2157
+ abortController
2158
+ } = options,
2159
+ params = _objectWithoutPropertiesLoose(options, _excluded4);
2160
+ const {
2161
+ column,
2162
+ operation,
2163
+ ticks
2164
+ } = params;
2165
+ const source = this.getModelSource(filters, filterOwner);
2166
+ const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2167
+ const data = await executeModel({
2168
+ model: 'histogram',
2169
+ source: _extends({}, source, {
2170
+ spatialFiltersResolution,
2171
+ spatialFiltersMode,
2172
+ spatialFilter
2173
+ }),
2174
+ params: {
2175
+ column,
2176
+ operation,
2177
+ ticks
2178
+ },
2179
+ opts: {
2180
+ abortController,
2181
+ headers: this._headers
2182
+ }
2183
+ }).then(res => normalizeObjectKeys(res.rows));
2184
+ if (data.length) {
2185
+ // Given N ticks the API returns up to N+1 bins, omitting any empty bins. Bins
2186
+ // include 1 bin below the lowest tick, N-1 between ticks, and 1 bin above the highest tick.
2187
+ const result = Array(ticks.length + 1).fill(0);
2188
+ data.forEach(({
2189
+ tick,
2190
+ value
2191
+ }) => result[tick] = value);
2192
+ return result;
2193
+ }
2194
+ return [];
2144
2195
  }
2145
- }
2146
- function getSpatialIndex(spatialDataType) {
2147
- switch (spatialDataType) {
2148
- case 'h3':
2149
- return SpatialIndex.H3;
2150
- case 'quadbin':
2151
- return SpatialIndex.QUADBIN;
2152
- default:
2153
- throw new Error('Unexpected spatial data type');
2196
+ async getRange(options) {
2197
+ const {
2198
+ filters = this.props.filters,
2199
+ filterOwner,
2200
+ spatialFilter,
2201
+ spatialFiltersMode,
2202
+ spatialIndexReferenceViewState,
2203
+ abortController
2204
+ } = options,
2205
+ params = _objectWithoutPropertiesLoose(options, _excluded5);
2206
+ const {
2207
+ column
2208
+ } = params;
2209
+ const source = this.getModelSource(filters, filterOwner);
2210
+ const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2211
+ return executeModel({
2212
+ model: 'range',
2213
+ source: _extends({}, source, {
2214
+ spatialFiltersResolution,
2215
+ spatialFiltersMode,
2216
+ spatialFilter
2217
+ }),
2218
+ params: {
2219
+ column
2220
+ },
2221
+ opts: {
2222
+ abortController,
2223
+ headers: this._headers
2224
+ }
2225
+ }).then(res => normalizeObjectKeys(res.rows[0]));
2154
2226
  }
2155
- }
2156
-
2157
- const _excluded$1 = ["tiles"];
2158
- function tileFeaturesRaster(_ref) {
2159
- let {
2160
- tiles
2161
- } = _ref,
2162
- options = _objectWithoutPropertiesLoose(_ref, _excluded$1);
2163
- // Cache band metadata for faster lookup while iterating over pixels.
2164
- const bandMetadataByName = {};
2165
- for (const band of options.rasterMetadata.bands) {
2166
- bandMetadataByName[band.name] = band;
2227
+ async getScatter(options) {
2228
+ const {
2229
+ filters = this.props.filters,
2230
+ filterOwner,
2231
+ spatialFilter,
2232
+ spatialFiltersMode,
2233
+ spatialIndexReferenceViewState,
2234
+ abortController
2235
+ } = options,
2236
+ params = _objectWithoutPropertiesLoose(options, _excluded6);
2237
+ const {
2238
+ xAxisColumn,
2239
+ xAxisJoinOperation,
2240
+ yAxisColumn,
2241
+ yAxisJoinOperation
2242
+ } = params;
2243
+ const source = this.getModelSource(filters, filterOwner);
2244
+ const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2245
+ // Make sure this is sync with the same constant in cloud-native/maps-api
2246
+ const HARD_LIMIT = 500;
2247
+ return executeModel({
2248
+ model: 'scatterplot',
2249
+ source: _extends({}, source, {
2250
+ spatialFiltersResolution,
2251
+ spatialFiltersMode,
2252
+ spatialFilter
2253
+ }),
2254
+ params: {
2255
+ xAxisColumn,
2256
+ xAxisJoinOperation,
2257
+ yAxisColumn,
2258
+ yAxisJoinOperation,
2259
+ limit: HARD_LIMIT
2260
+ },
2261
+ opts: {
2262
+ abortController,
2263
+ headers: this._headers
2264
+ }
2265
+ }).then(res => normalizeObjectKeys(res.rows)).then(res => res.map(({
2266
+ x,
2267
+ y
2268
+ }) => [x, y]));
2167
2269
  }
2168
- // Omit empty and invisible tiles for simpler processing and types.
2169
- tiles = tiles.filter(isRasterTileVisible);
2170
- if (tiles.length === 0) return [];
2171
- // Raster tiles, and all pixels, are quadbin cells. Resolution of a pixel is
2172
- // the resolution of the tile, plus the number of subdivisions. Block size
2173
- // must be square, N x N, where N is a power of two.
2174
- const tileResolution = getResolution$1(tiles[0].index.q);
2175
- const tileBlockSize = tiles[0].data.blockSize;
2176
- const cellResolution = tileResolution + BigInt(Math.log2(tileBlockSize));
2177
- // Compute covering cells for the spatial filter, at same resolution as the
2178
- // raster pixels, to be used as a mask.
2179
- const spatialFilterCells = new Set(geometryToCells(options.spatialFilter, cellResolution));
2180
- const data = new Map();
2181
- for (const tile of tiles) {
2182
- const parent = tile.index.q;
2183
- const children = cellToChildrenSorted(parent, cellResolution);
2184
- // For each pixel/cell within the spatial filter, create a FeatureData.
2185
- // Order is row-major, starting from NW and ending at SE.
2186
- for (let i = 0; i < children.length; i++) {
2187
- if (!spatialFilterCells.has(children[i])) continue;
2188
- const cellData = {};
2189
- let cellDataExists = false;
2190
- for (const band in tile.data.cells.numericProps) {
2191
- const value = tile.data.cells.numericProps[band].value[i];
2192
- // TODO(cleanup): nodata should be a number, not a string.
2193
- if (Number(bandMetadataByName[band].nodata) !== value) {
2194
- cellData[band] = tile.data.cells.numericProps[band].value[i];
2195
- cellDataExists = true;
2196
- }
2270
+ async getTable(options) {
2271
+ const {
2272
+ filters = this.props.filters,
2273
+ filterOwner,
2274
+ spatialFilter,
2275
+ spatialFiltersMode,
2276
+ spatialIndexReferenceViewState,
2277
+ abortController
2278
+ } = options,
2279
+ params = _objectWithoutPropertiesLoose(options, _excluded7);
2280
+ const {
2281
+ columns,
2282
+ sortBy,
2283
+ sortDirection,
2284
+ offset = 0,
2285
+ limit = 10
2286
+ } = params;
2287
+ const source = this.getModelSource(filters, filterOwner);
2288
+ const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2289
+ return executeModel({
2290
+ model: 'table',
2291
+ source: _extends({}, source, {
2292
+ spatialFiltersResolution,
2293
+ spatialFiltersMode,
2294
+ spatialFilter
2295
+ }),
2296
+ params: {
2297
+ column: columns,
2298
+ sortBy,
2299
+ sortDirection,
2300
+ limit,
2301
+ offset
2302
+ },
2303
+ opts: {
2304
+ abortController,
2305
+ headers: this._headers
2197
2306
  }
2198
- if (cellDataExists) {
2199
- data.set(children[i], cellData);
2307
+ }).then(res => {
2308
+ var _res$rows, _res$metadata$total, _res$metadata, _res$METADATA;
2309
+ return {
2310
+ // Avoid `normalizeObjectKeys()`, which changes column names.
2311
+ rows: (_res$rows = res.rows) != null ? _res$rows : res.ROWS,
2312
+ totalCount: (_res$metadata$total = (_res$metadata = res.metadata) == null ? void 0 : _res$metadata.total) != null ? _res$metadata$total : (_res$METADATA = res.METADATA) == null ? void 0 : _res$METADATA.TOTAL
2313
+ };
2314
+ });
2315
+ }
2316
+ async getTimeSeries(options) {
2317
+ const {
2318
+ filters = this.props.filters,
2319
+ filterOwner,
2320
+ abortController,
2321
+ spatialFilter,
2322
+ spatialFiltersMode,
2323
+ spatialIndexReferenceViewState
2324
+ } = options,
2325
+ params = _objectWithoutPropertiesLoose(options, _excluded8);
2326
+ const {
2327
+ column,
2328
+ operationColumn,
2329
+ joinOperation,
2330
+ operation,
2331
+ stepSize,
2332
+ stepMultiplier,
2333
+ splitByCategory,
2334
+ splitByCategoryLimit,
2335
+ splitByCategoryValues
2336
+ } = params;
2337
+ const source = this.getModelSource(filters, filterOwner);
2338
+ const spatialFiltersResolution = this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2339
+ return executeModel({
2340
+ model: 'timeseries',
2341
+ source: _extends({}, source, {
2342
+ spatialFiltersResolution,
2343
+ spatialFiltersMode,
2344
+ spatialFilter
2345
+ }),
2346
+ params: {
2347
+ column,
2348
+ stepSize,
2349
+ stepMultiplier,
2350
+ operationColumn: operationColumn || column,
2351
+ joinOperation,
2352
+ operation,
2353
+ splitByCategory,
2354
+ splitByCategoryLimit,
2355
+ splitByCategoryValues
2356
+ },
2357
+ opts: {
2358
+ abortController,
2359
+ headers: this._headers
2200
2360
  }
2201
- }
2361
+ }).then(res => {
2362
+ var _res$metadata2;
2363
+ return {
2364
+ rows: normalizeObjectKeys(res.rows),
2365
+ categories: (_res$metadata2 = res.metadata) == null ? void 0 : _res$metadata2.categories
2366
+ };
2367
+ });
2202
2368
  }
2203
- return Array.from(data.values());
2204
- }
2205
- /**
2206
- * Detects whether a given {@link Tile} is a {@link RasterTile}.
2207
- * @privateRemarks Method of detection is arbitrary, and may be changed.
2208
- */
2209
- function isRasterTile(tile) {
2210
- return tile.data ? tile.data.hasOwnProperty('cells') : false;
2211
- }
2212
- function isRasterTileVisible(tile) {
2213
- var _tile$data;
2214
- return !!(tile.isVisible && (_tile$data = tile.data) != null && (_tile$data = _tile$data.cells) != null && _tile$data.numericProps);
2215
2369
  }
2370
+
2216
2371
  /**
2217
- * For the raster format, children are sorted in row-major order, starting from
2218
- * NW and ending at SE. Order returned by quadbin's cellToChildren() is not
2219
- * defined (and not related to the raster format), so sort explicitly here.
2372
+ * Source for Widget API requests on a data source defined by a SQL query.
2373
+ *
2374
+ * Generally not intended to be constructed directly. Instead, call
2375
+ * {@link vectorQuerySource}, {@link h3QuerySource}, or {@link quadbinQuerySource},
2376
+ * which can be shared with map layers. Sources contain a `widgetSource` property,
2377
+ * for use by widget implementations.
2378
+ *
2379
+ * Example:
2380
+ *
2381
+ * ```javascript
2382
+ * import { vectorQuerySource } from '@carto/api-client';
2383
+ *
2384
+ * const data = vectorQuerySource({
2385
+ * accessToken: '••••',
2386
+ * connectionName: 'carto_dw',
2387
+ * sqlQuery: 'SELECT * FROM carto-demo-data.demo_tables.retail_stores'
2388
+ * });
2389
+ *
2390
+ * const { widgetSource } = await data;
2391
+ * ```
2220
2392
  */
2221
- function cellToChildrenSorted(parent, resolution) {
2222
- return cellToChildren(parent, resolution).sort((cellA, cellB) => {
2223
- const tileA = cellToTile(cellA);
2224
- const tileB = cellToTile(cellB);
2225
- if (tileA.y !== tileB.y) {
2226
- return tileA.y > tileB.y ? 1 : -1;
2227
- }
2228
- return tileA.x > tileB.x ? 1 : -1;
2229
- });
2230
- }
2231
-
2232
- /** @internalRemarks Source: @carto/react-core */
2233
- function tileFeatures({
2234
- tiles,
2235
- spatialFilter,
2236
- uniqueIdProperty,
2237
- tileFormat,
2238
- spatialDataColumn = DEFAULT_GEO_COLUMN,
2239
- spatialDataType,
2240
- rasterMetadata,
2241
- options = {}
2242
- }) {
2243
- // TODO(cleanup): Is an empty response the expected result when spatialFilter
2244
- // is omitted? Why not make the parameter required, or return the full input?
2245
- if (!spatialFilter) {
2246
- return [];
2247
- }
2248
- if (spatialDataType === 'geo') {
2249
- return tileFeaturesGeometries({
2250
- tiles,
2251
- tileFormat,
2252
- spatialFilter,
2253
- uniqueIdProperty,
2254
- options
2255
- });
2256
- }
2257
- if (tiles.some(isRasterTile)) {
2258
- assert$1(rasterMetadata, 'Missing raster metadata');
2259
- return tileFeaturesRaster({
2260
- tiles: tiles,
2261
- spatialFilter,
2262
- spatialDataColumn,
2263
- spatialDataType,
2264
- rasterMetadata
2393
+ class WidgetQuerySource extends WidgetRemoteSource {
2394
+ getModelSource(filters, filterOwner) {
2395
+ return _extends({}, super._getModelSource(filters, filterOwner), {
2396
+ type: 'query',
2397
+ data: this.props.sqlQuery,
2398
+ queryParameters: this.props.queryParameters
2265
2399
  });
2266
2400
  }
2267
- return tileFeaturesSpatialIndex({
2268
- tiles: tiles,
2269
- spatialFilter,
2270
- spatialDataColumn,
2271
- spatialDataType
2272
- });
2273
2401
  }
2274
2402
 
2275
- /** @internalRemarks Source: @carto/react-core */
2403
+ /** @privateRemarks Source: @carto/react-core */
2276
2404
  const aggregationFunctions = {
2277
2405
  count: values => values.length,
2278
2406
  min: (...args) => applyAggregationFunction(min, ...args),
@@ -2280,7 +2408,7 @@ const aggregationFunctions = {
2280
2408
  sum: (...args) => applyAggregationFunction(sum, ...args),
2281
2409
  avg: (...args) => applyAggregationFunction(avg, ...args)
2282
2410
  };
2283
- /** @internalRemarks Source: @carto/react-core */
2411
+ /** @privateRemarks Source: @carto/react-core */
2284
2412
  function aggregate(feature, keys, operation) {
2285
2413
  if (!(keys != null && keys.length)) {
2286
2414
  throw new Error('Cannot aggregate a feature without having keys');
@@ -2429,7 +2557,7 @@ var thenBy_module = function () {
2429
2557
  * @param [sortOptions.sortByDirection] - Direction by the columns will be sorted
2430
2558
  * @param [sortOptions.sortByColumnType] - Column type
2431
2559
  * @internal
2432
- * @internalRemarks Source: @carto/react-core
2560
+ * @privateRemarks Source: @carto/react-core
2433
2561
  */
2434
2562
  function applySorting(features, {
2435
2563
  sortBy,
@@ -2466,7 +2594,7 @@ function createSortFn({
2466
2594
  sortByColumnType
2467
2595
  });
2468
2596
  let sortFn = thenBy_module.firstBy(...firstSortOption);
2469
- for (let sortOptions of othersSortOptions) {
2597
+ for (const sortOptions of othersSortOptions) {
2470
2598
  sortFn = sortFn.thenBy(...sortOptions);
2471
2599
  }
2472
2600
  return sortFn;
@@ -2508,7 +2636,7 @@ function normalizeSortByOptions({
2508
2636
  });
2509
2637
  }
2510
2638
 
2511
- /** @internalRemarks Source: @carto/react-core */
2639
+ /** @privateRemarks Source: @carto/react-core */
2512
2640
  function groupValuesByColumn({
2513
2641
  data,
2514
2642
  valuesColumns,
@@ -2566,7 +2694,7 @@ const GROUP_KEY_FN_MAPPING = {
2566
2694
  minute: date => Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes()),
2567
2695
  second: date => Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds())
2568
2696
  };
2569
- /** @internalRemarks Source: @carto/react-core */
2697
+ /** @privateRemarks Source: @carto/react-core */
2570
2698
  function groupValuesByDateColumn({
2571
2699
  data,
2572
2700
  valuesColumns,
@@ -2610,7 +2738,7 @@ function groupValuesByDateColumn({
2610
2738
 
2611
2739
  /**
2612
2740
  * Histogram computation.
2613
- * @internalRemarks Source: @carto/react-core
2741
+ * @privateRemarks Source: @carto/react-core
2614
2742
  */
2615
2743
  function histogram({
2616
2744
  data,
@@ -2647,7 +2775,7 @@ function histogram({
2647
2775
 
2648
2776
  /**
2649
2777
  * Filters invalid features and formats data.
2650
- * @internalRemarks Source: @carto/react-core
2778
+ * @privateRemarks Source: @carto/react-core
2651
2779
  */
2652
2780
  function scatterPlot({
2653
2781
  data,
@@ -2668,7 +2796,6 @@ function scatterPlot({
2668
2796
  }, []);
2669
2797
  }
2670
2798
 
2671
- const _excluded = ["filterOwner", "spatialFilter", "abortController"];
2672
2799
  /**
2673
2800
  * Source for Widget API requests on a data source defined by a tileset.
2674
2801
  *
@@ -2696,9 +2823,11 @@ class WidgetTilesetSource extends WidgetSource {
2696
2823
  super(...args);
2697
2824
  this._tiles = [];
2698
2825
  this._features = [];
2826
+ this._tileFeatureExtractOptions = {};
2827
+ this._tileFeatureExtractPreviousInputs = {};
2699
2828
  }
2700
- getModelSource(owner) {
2701
- return _extends({}, super._getModelSource(owner), {
2829
+ getModelSource(filters, filterOwner) {
2830
+ return _extends({}, super._getModelSource(filters, filterOwner), {
2702
2831
  type: 'tileset',
2703
2832
  data: this.props.tableName
2704
2833
  });
@@ -2710,57 +2839,60 @@ class WidgetTilesetSource extends WidgetSource {
2710
2839
  */
2711
2840
  loadTiles(tiles) {
2712
2841
  this._tiles = tiles;
2842
+ this._features.length = 0;
2843
+ }
2844
+ /** Configures options used to extract features from tiles. */
2845
+ setTileFeatureExtractOptions(options) {
2846
+ this._tileFeatureExtractOptions = options;
2847
+ this._features.length = 0;
2848
+ }
2849
+ _extractTileFeatures(spatialFilter) {
2850
+ // When spatial filter has not changed, don't redo extraction. If tiles or
2851
+ // tile extract options change, features will have been cleared already.
2852
+ const prevInputs = this._tileFeatureExtractPreviousInputs;
2853
+ if (this._features.length && prevInputs.spatialFilter && booleanEqual(prevInputs.spatialFilter, spatialFilter)) {
2854
+ return;
2855
+ }
2856
+ this._features = tileFeatures(_extends({}, this.props, this._tileFeatureExtractOptions, {
2857
+ tiles: this._tiles,
2858
+ spatialFilter
2859
+ }));
2860
+ prevInputs.spatialFilter = spatialFilter;
2713
2861
  }
2714
2862
  /**
2715
- * Extracts feature data from tiles previously loaded with {@link loadTiles}.
2716
- * Must be called before computing statistics on tiles.
2863
+ * Loads features as GeoJSON (used for testing).
2864
+ * @experimental
2865
+ * @internal Not for public use. Spatial filters in other method calls will be ignored.
2717
2866
  */
2718
- extractTileFeatures({
2719
- spatialFilter,
2720
- uniqueIdProperty,
2721
- options
2722
- }) {
2723
- this._features = tileFeatures(_extends({
2724
- tiles: this._tiles,
2725
- options,
2726
- spatialFilter,
2727
- uniqueIdProperty
2728
- }, this.props));
2729
- }
2730
- /** Loads features as GeoJSON (used for testing). */
2731
2867
  loadGeoJSON({
2732
2868
  geojson,
2733
- spatialFilter,
2734
- uniqueIdProperty
2869
+ spatialFilter
2735
2870
  }) {
2736
- this._features = geojsonFeatures({
2871
+ this._features = geojsonFeatures(_extends({
2737
2872
  geojson,
2738
- spatialFilter,
2739
- uniqueIdProperty
2740
- });
2873
+ spatialFilter
2874
+ }, this._tileFeatureExtractOptions));
2875
+ this._tileFeatureExtractPreviousInputs.spatialFilter = spatialFilter;
2741
2876
  }
2742
- async getFeatures(options) {
2877
+ async getFeatures() {
2743
2878
  throw new Error('getFeatures not supported for tilesets');
2744
2879
  }
2745
2880
  async getFormula({
2746
2881
  column = '*',
2747
2882
  operation = 'count',
2748
2883
  joinOperation,
2749
- filterOwner
2884
+ filters,
2885
+ filterOwner,
2886
+ spatialFilter
2750
2887
  }) {
2751
2888
  if (operation === 'custom') {
2752
2889
  throw new Error('Custom aggregation not supported for tilesets');
2753
2890
  }
2754
- if (!this._features.length) {
2755
- return {
2756
- value: null
2757
- };
2758
- }
2759
2891
  // Column is required except when operation is 'count'.
2760
2892
  if (column && column !== '*' || operation !== 'count') {
2761
2893
  assertColumn(this._features, column);
2762
2894
  }
2763
- const filteredFeatures = this._getFilteredFeatures(filterOwner);
2895
+ const filteredFeatures = this._getFilteredFeatures(spatialFilter, filters, filterOwner);
2764
2896
  if (filteredFeatures.length === 0 && operation !== 'count') {
2765
2897
  return {
2766
2898
  value: null
@@ -2776,13 +2908,15 @@ class WidgetTilesetSource extends WidgetSource {
2776
2908
  ticks,
2777
2909
  column,
2778
2910
  joinOperation,
2779
- filterOwner
2911
+ filters,
2912
+ filterOwner,
2913
+ spatialFilter
2780
2914
  }) {
2915
+ const filteredFeatures = this._getFilteredFeatures(spatialFilter, filters, filterOwner);
2916
+ assertColumn(this._features, column);
2781
2917
  if (!this._features.length) {
2782
2918
  return [];
2783
2919
  }
2784
- const filteredFeatures = this._getFilteredFeatures(filterOwner);
2785
- assertColumn(this._features, column);
2786
2920
  return histogram({
2787
2921
  data: filteredFeatures,
2788
2922
  valuesColumns: normalizeColumns(column),
@@ -2796,12 +2930,14 @@ class WidgetTilesetSource extends WidgetSource {
2796
2930
  operation = 'count',
2797
2931
  operationColumn,
2798
2932
  joinOperation,
2799
- filterOwner
2933
+ filters,
2934
+ filterOwner,
2935
+ spatialFilter
2800
2936
  }) {
2801
- if (!this._features.length) {
2937
+ const filteredFeatures = this._getFilteredFeatures(spatialFilter, filters, filterOwner);
2938
+ if (!filteredFeatures.length) {
2802
2939
  return [];
2803
2940
  }
2804
- const filteredFeatures = this._getFilteredFeatures(filterOwner);
2805
2941
  assertColumn(this._features, column, operationColumn);
2806
2942
  const groups = groupValuesByColumn({
2807
2943
  data: filteredFeatures,
@@ -2817,12 +2953,14 @@ class WidgetTilesetSource extends WidgetSource {
2817
2953
  yAxisColumn,
2818
2954
  xAxisJoinOperation,
2819
2955
  yAxisJoinOperation,
2820
- filterOwner
2956
+ filters,
2957
+ filterOwner,
2958
+ spatialFilter
2821
2959
  }) {
2822
- if (!this._features.length) {
2960
+ const filteredFeatures = this._getFilteredFeatures(spatialFilter, filters, filterOwner);
2961
+ if (!filteredFeatures.length) {
2823
2962
  return [];
2824
2963
  }
2825
- const filteredFeatures = this._getFilteredFeatures(filterOwner);
2826
2964
  assertColumn(this._features, xAxisColumn, yAxisColumn);
2827
2965
  return scatterPlot({
2828
2966
  data: filteredFeatures,
@@ -2832,31 +2970,28 @@ class WidgetTilesetSource extends WidgetSource {
2832
2970
  yAxisJoinOperation
2833
2971
  });
2834
2972
  }
2835
- async getTable(options) {
2836
- const {
2837
- filterOwner
2838
- } = options,
2839
- params = _objectWithoutPropertiesLoose(options, _excluded);
2840
- const {
2841
- columns,
2842
- searchFilterColumn,
2843
- searchFilterText,
2844
- sortBy,
2845
- sortDirection,
2846
- sortByColumnType,
2847
- offset = 0,
2848
- limit = 10
2849
- } = params;
2850
- if (!this._features.length) {
2973
+ async getTable({
2974
+ columns,
2975
+ searchFilterColumn,
2976
+ searchFilterText,
2977
+ sortBy,
2978
+ sortDirection,
2979
+ sortByColumnType,
2980
+ offset = 0,
2981
+ limit = 10,
2982
+ filters,
2983
+ filterOwner,
2984
+ spatialFilter
2985
+ }) {
2986
+ // Filter.
2987
+ let filteredFeatures = this._getFilteredFeatures(spatialFilter, filters, filterOwner);
2988
+ if (!filteredFeatures.length) {
2851
2989
  return {
2852
2990
  rows: [],
2853
2991
  totalCount: 0
2854
2992
  };
2855
2993
  }
2856
- // Filter.
2857
- let filteredFeatures = this._getFilteredFeatures(filterOwner);
2858
2994
  // Search.
2859
- // TODO: Could we get the same behavior by applying filters in loadTiles()?
2860
2995
  if (searchFilterColumn && searchFilterText) {
2861
2996
  filteredFeatures = filteredFeatures.filter(row => row[searchFilterColumn] && String(row[searchFilterColumn]).toLowerCase().includes(String(searchFilterText).toLowerCase()));
2862
2997
  }
@@ -2888,14 +3023,16 @@ class WidgetTilesetSource extends WidgetSource {
2888
3023
  operation,
2889
3024
  operationColumn,
2890
3025
  joinOperation,
2891
- filterOwner
3026
+ filters,
3027
+ filterOwner,
3028
+ spatialFilter
2892
3029
  }) {
2893
- if (!this._features.length) {
3030
+ const filteredFeatures = this._getFilteredFeatures(spatialFilter, filters, filterOwner);
3031
+ if (!filteredFeatures.length) {
2894
3032
  return {
2895
3033
  rows: []
2896
3034
  };
2897
3035
  }
2898
- const filteredFeatures = this._getFilteredFeatures(filterOwner);
2899
3036
  assertColumn(this._features, column, operationColumn);
2900
3037
  const rows = groupValuesByDateColumn({
2901
3038
  data: filteredFeatures,
@@ -2911,15 +3048,17 @@ class WidgetTilesetSource extends WidgetSource {
2911
3048
  }
2912
3049
  async getRange({
2913
3050
  column,
2914
- filterOwner
3051
+ filters,
3052
+ filterOwner,
3053
+ spatialFilter
2915
3054
  }) {
3055
+ assertColumn(this._features, column);
3056
+ const filteredFeatures = this._getFilteredFeatures(spatialFilter, filters, filterOwner);
2916
3057
  if (!this._features.length) {
2917
3058
  // TODO: Is this the only nullable response in the Widgets API? If so,
2918
3059
  // can we do something more consistent?
2919
3060
  return null;
2920
3061
  }
2921
- assertColumn(this._features, column);
2922
- const filteredFeatures = this._getFilteredFeatures(filterOwner);
2923
3062
  return {
2924
3063
  min: aggregationFunctions.min(filteredFeatures, column),
2925
3064
  max: aggregationFunctions.max(filteredFeatures, column)
@@ -2928,8 +3067,10 @@ class WidgetTilesetSource extends WidgetSource {
2928
3067
  /****************************************************************************
2929
3068
  * INTERNAL
2930
3069
  */
2931
- _getFilteredFeatures(filterOwner) {
2932
- return applyFilters(this._features, getApplicableFilters(filterOwner, this.props.filters), this.props.filtersLogicalOperator || 'and');
3070
+ _getFilteredFeatures(spatialFilter, filters, filterOwner) {
3071
+ assert(spatialFilter, 'spatialFilter required for tilesets');
3072
+ this._extractTileFeatures(spatialFilter);
3073
+ return applyFilters(this._features, getApplicableFilters(filterOwner, filters || this.props.filters), this.props.filtersLogicalOperator || 'and');
2933
3074
  }
2934
3075
  }
2935
3076
  function assertColumn(features, ...columnArgs) {
@@ -2971,8 +3112,8 @@ class WidgetRasterSource extends WidgetTilesetSource {}
2971
3112
  * ```
2972
3113
  */
2973
3114
  class WidgetTableSource extends WidgetRemoteSource {
2974
- getModelSource(owner) {
2975
- return _extends({}, super._getModelSource(owner), {
3115
+ getModelSource(filters, filterOwner) {
3116
+ return _extends({}, super._getModelSource(filters, filterOwner), {
2976
3117
  type: 'table',
2977
3118
  data: this.props.tableName
2978
3119
  });
@@ -3300,5 +3441,5 @@ const query = async function query(options) {
3300
3441
  });
3301
3442
  };
3302
3443
 
3303
- export { ApiVersion, CartoAPIError, DEFAULT_API_BASE_URL, FEATURE_GEOM_PROPERTY, FilterType, Provider, SOURCE_DEFAULTS, SpatialIndex, TileFormat, WidgetQuerySource, WidgetRasterSource, WidgetRemoteSource, WidgetSource, WidgetTableSource, WidgetTilesetSource, _getHexagonResolution, addFilter, aggregate, aggregationFunctions, applyFilters, applySorting, boundaryQuerySource, boundaryTableSource, buildBinaryFeatureFilter, buildFeatureFilter, buildPublicMapUrl, buildStatsUrl, clearFilters, createPolygonSpatialFilter, createViewportSpatialFilter, filterFunctions, geojsonFeatures, getClient, getFilter, groupValuesByColumn, groupValuesByDateColumn, h3QuerySource, h3TableSource, h3TilesetSource, hasFilter, histogram, makeIntervalComplete, quadbinQuerySource, quadbinTableSource, quadbinTilesetSource, query, rasterSource, removeFilter, requestWithParameters, scatterPlot, setClient, tileFeatures, tileFeaturesGeometries, tileFeaturesSpatialIndex, transformToTileCoords, vectorQuerySource, vectorTableSource, vectorTilesetSource };
3444
+ export { ApiVersion, CartoAPIError, DEFAULT_API_BASE_URL, FEATURE_GEOM_PROPERTY, FilterType, Provider, SOURCE_DEFAULTS, SpatialIndex, TileFormat, WidgetQuerySource, WidgetRasterSource, WidgetRemoteSource, WidgetSource, WidgetTableSource, WidgetTilesetSource, _buildFeatureFilter, _getHexagonResolution, addFilter, aggregate, aggregationFunctions, applyFilters, applySorting, boundaryQuerySource, boundaryTableSource, buildBinaryFeatureFilter, buildPublicMapUrl, buildStatsUrl, clearFilters, createPolygonSpatialFilter, createViewportSpatialFilter, filterFunctions, geojsonFeatures, getClient, getDataFilterExtensionProps, getFilter, groupValuesByColumn, groupValuesByDateColumn, h3QuerySource, h3TableSource, h3TilesetSource, hasFilter, histogram, makeIntervalComplete, quadbinQuerySource, quadbinTableSource, quadbinTilesetSource, query, rasterSource, removeFilter, requestWithParameters, scatterPlot, setClient, tileFeatures, tileFeaturesGeometries, tileFeaturesSpatialIndex, transformToTileCoords, vectorQuerySource, vectorTableSource, vectorTilesetSource };
3304
3445
  //# sourceMappingURL=api-client.modern.js.map