@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
- var bboxClip = require('@turf/bbox-clip');
2
- var bboxPolygon = require('@turf/bbox-polygon');
3
- var union = require('@turf/union');
4
- var invariant = require('@turf/invariant');
5
- var helpers = require('@turf/helpers');
6
1
  var intersects = require('@turf/boolean-intersects');
2
+ var bboxPolygon = require('@turf/bbox-polygon');
7
3
  var booleanWithin = require('@turf/boolean-within');
8
4
  var intersect = require('@turf/intersect');
5
+ var helpers = require('@turf/helpers');
9
6
  var quadbin = require('quadbin');
7
+ var bboxClip = require('@turf/bbox-clip');
10
8
  var h3Js = require('h3-js');
9
+ var union = require('@turf/union');
10
+ var invariant = require('@turf/invariant');
11
+ var booleanEqual = require('@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
  exports.FilterType = void 0;
51
52
  (function (FilterType) {
@@ -57,16 +58,16 @@ exports.FilterType = void 0;
57
58
  FilterType["TIME"] = "time";
58
59
  FilterType["STRING_SEARCH"] = "stringSearch";
59
60
  })(exports.FilterType || (exports.FilterType = {}));
60
- /** @internalRemarks Source: @carto/constants */
61
+ /** @privateRemarks Source: @carto/constants */
61
62
  exports.ApiVersion = void 0;
62
63
  (function (ApiVersion) {
63
64
  ApiVersion["V1"] = "v1";
64
65
  ApiVersion["V2"] = "v2";
65
66
  ApiVersion["V3"] = "v3";
66
67
  })(exports.ApiVersion || (exports.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
  exports.TileFormat = void 0;
71
72
  (function (TileFormat) {
72
73
  TileFormat["MVT"] = "mvt";
@@ -74,14 +75,14 @@ exports.TileFormat = void 0;
74
75
  TileFormat["GEOJSON"] = "geojson";
75
76
  TileFormat["BINARY"] = "binary";
76
77
  })(exports.TileFormat || (exports.TileFormat = {}));
77
- /** @internalRemarks Source: @carto/react-core */
78
+ /** @privateRemarks Source: @carto/react-core */
78
79
  exports.SpatialIndex = void 0;
79
80
  (function (SpatialIndex) {
80
81
  SpatialIndex["H3"] = "h3";
81
82
  SpatialIndex["S2"] = "s2";
82
83
  SpatialIndex["QUADBIN"] = "quadbin";
83
84
  })(exports.SpatialIndex || (exports.SpatialIndex = {}));
84
- /** @internalRemarks Source: @carto/react-core */
85
+ /** @privateRemarks Source: @carto/react-core */
85
86
  exports.Provider = void 0;
86
87
  (function (Provider) {
87
88
  Provider["BIGQUERY"] = "bigquery";
@@ -92,2312 +93,2438 @@ exports.Provider = void 0;
92
93
  Provider["DATABRICKS_REST"] = "databricksRest";
93
94
  })(exports.Provider || (exports.Provider = {}));
94
95
 
95
- const FILTER_TYPES = new Set(Object.values(exports.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?.owner || filter?.owner !== owner;
109
- if (filter && isApplicable) {
110
- applicableFilters[column] ||= {};
111
- applicableFilters[column][type] = filter;
112
- }
96
+ function makeIntervalComplete(intervals) {
97
+ return intervals.map(val => {
98
+ if (val[0] === undefined || val[0] === null) {
99
+ return [Number.MIN_SAFE_INTEGER, val[1]];
113
100
  }
114
- }
115
- return applicableFilters;
101
+ if (val[1] === undefined || val[1] === null) {
102
+ return [val[0], Number.MAX_SAFE_INTEGER];
103
+ }
104
+ return val;
105
+ });
116
106
  }
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;
129
- }
130
- return Object.entries(el).reduce((acc, _ref) => {
131
- let [key, value] = _ref;
132
- acc[key.toLowerCase()] = typeof value === 'object' && value ? normalizeObjectKeys(value) : value;
133
- return acc;
134
- }, {});
107
+
108
+ const filterFunctions = {
109
+ [exports.FilterType.IN]: filterIn,
110
+ [exports.FilterType.BETWEEN]: filterBetween,
111
+ [exports.FilterType.TIME]: filterTime,
112
+ [exports.FilterType.CLOSED_OPEN]: filterClosedOpen,
113
+ [exports.FilterType.STRING_SEARCH]: filterStringSearch
114
+ };
115
+ function filterIn(filterValues, featureValue) {
116
+ return filterValues.includes(featureValue);
135
117
  }
136
- /** @internalRemarks Source: @carto/react-core */
137
- function assert$1(condition, message) {
138
- if (!condition) {
139
- throw new Error(message);
140
- }
118
+ // FilterTypes.BETWEEN
119
+ function filterBetween(filterValues, featureValue) {
120
+ const checkRange = range => {
121
+ const [lowerBound, upperBound] = range;
122
+ return featureValue >= lowerBound && featureValue <= upperBound;
123
+ };
124
+ return makeIntervalComplete(filterValues).some(checkRange);
141
125
  }
142
- /**
143
- * @internalRemarks Source: @carto/react-core
144
- * @internal
145
- */
146
- class InvalidColumnError extends Error {
147
- constructor(message) {
148
- super(`${InvalidColumnError.NAME}: ${message}`);
149
- this.name = InvalidColumnError.NAME;
150
- }
151
- static is(error) {
152
- return error instanceof InvalidColumnError || error.message?.includes(InvalidColumnError.NAME);
126
+ function filterTime(filterValues, featureValue) {
127
+ const featureValueAsTimestamp = new Date(featureValue).getTime();
128
+ if (isFinite(featureValueAsTimestamp)) {
129
+ return filterBetween(filterValues, featureValueAsTimestamp);
130
+ } else {
131
+ throw new Error(`Column used to filter by time isn't well formatted.`);
153
132
  }
154
133
  }
155
- InvalidColumnError.NAME = 'InvalidColumnError';
156
- function isEmptyObject(object) {
157
- for (const _ in object) {
158
- return false;
134
+ // FilterTypes.CLOSED_OPEN
135
+ function filterClosedOpen(filterValues, featureValue) {
136
+ const checkRange = range => {
137
+ const [lowerBound, upperBound] = range;
138
+ return featureValue >= lowerBound && featureValue < upperBound;
139
+ };
140
+ return makeIntervalComplete(filterValues).some(checkRange);
141
+ }
142
+ // FilterTypes.STRING_SEARCH
143
+ function filterStringSearch(filterValues, featureValue, params) {
144
+ if (params === void 0) {
145
+ params = {};
159
146
  }
160
- return true;
147
+ const normalizedFeatureValue = normalize(featureValue, params);
148
+ const stringRegExp = params.useRegExp ? filterValues : filterValues.map(filterValue => {
149
+ let stringRegExp = escapeRegExp(normalize(filterValue, params));
150
+ if (params.mustStart) stringRegExp = `^${stringRegExp}`;
151
+ if (params.mustEnd) stringRegExp = `${stringRegExp}$`;
152
+ return stringRegExp;
153
+ });
154
+ const regex = new RegExp(stringRegExp.join('|'), params.caseSensitive ? 'g' : 'gi');
155
+ return !!normalizedFeatureValue.match(regex);
156
+ }
157
+ // Aux
158
+ const specialCharRegExp = /[.*+?^${}()|[\]\\]/g;
159
+ const normalizeRegExp = /\p{Diacritic}/gu;
160
+ function escapeRegExp(value) {
161
+ return value.replace(specialCharRegExp, '\\$&');
162
+ }
163
+ function normalize(data, params) {
164
+ let normalizedData = String(data);
165
+ if (!params.keepSpecialCharacters) normalizedData = normalizedData.normalize('NFD').replace(normalizeRegExp, '');
166
+ return normalizedData;
161
167
  }
162
- /** @internal */
163
- const isObject = x => x !== null && typeof x === 'object';
164
- /** @internal */
165
- const isPureObject = x => isObject(x) && x.constructor === {}.constructor;
166
168
 
169
+ const LOGICAL_OPERATOR_METHODS = {
170
+ and: 'every',
171
+ or: 'some'
172
+ };
173
+ function passesFilter(columns, filters, feature, filtersLogicalOperator) {
174
+ const method = LOGICAL_OPERATOR_METHODS[filtersLogicalOperator];
175
+ return columns[method](column => {
176
+ const columnFilters = filters[column];
177
+ const columnFilterTypes = Object.keys(columnFilters);
178
+ if (!feature || feature[column] === null || feature[column] === undefined) {
179
+ return false;
180
+ }
181
+ return columnFilterTypes.every(filter => {
182
+ const filterFunction = filterFunctions[filter];
183
+ if (!filterFunction) {
184
+ throw new Error(`"${filter}" filter is not implemented.`);
185
+ }
186
+ return filterFunction(columnFilters[filter].values, feature[column], columnFilters[filter].params);
187
+ });
188
+ });
189
+ }
167
190
  /**
168
- * Adds a {@link Filter} to the filter set. Any previous filters with the same
169
- * `column` and `type` will be replaced.
191
+ * @internal
192
+ * @privateRemarks Exported for use in @deck.gl/carto's getDataFilterExtensionProps.
170
193
  */
171
- function addFilter(filters, _ref) {
194
+ function _buildFeatureFilter(_ref) {
172
195
  let {
173
- column,
174
- type,
175
- values,
176
- owner
196
+ filters = {},
197
+ type = 'boolean',
198
+ filtersLogicalOperator = 'and'
177
199
  } = _ref;
178
- if (!filters[column]) {
179
- filters[column] = {};
200
+ const columns = Object.keys(filters);
201
+ if (!columns.length) {
202
+ return () => type === 'number' ? 1 : true;
180
203
  }
181
- const filter = {
182
- values,
183
- owner
204
+ return feature => {
205
+ const f = feature.properties || feature;
206
+ const featurePassesFilter = passesFilter(columns, filters, f, filtersLogicalOperator);
207
+ return type === 'number' ? Number(featurePassesFilter) : featurePassesFilter;
184
208
  };
185
- filters[column][type] = filter;
186
- return filters;
187
209
  }
188
210
  /**
189
- * Removes one or more {@link Filter filters} from the filter set. If only
190
- * `column` is specified, then all filters on that column are removed. If both
191
- * `column` and `owner` are specified, then only filters for that column
192
- * associated with the owner are removed.
211
+ * Apply certain filters to a collection of features.
212
+ * @internal
193
213
  */
194
- function removeFilter(filters, _ref2) {
195
- let {
196
- column,
197
- owner
198
- } = _ref2;
199
- const filter = filters[column];
200
- if (!filter) {
201
- return filters;
202
- }
203
- if (owner) {
204
- for (const type of Object.values(exports.FilterType)) {
205
- if (owner === filter[type]?.owner) {
206
- delete filter[type];
207
- }
208
- }
209
- }
210
- if (!owner || isEmptyObject(filter)) {
211
- delete filters[column];
212
- }
213
- return filters;
214
+ function applyFilters(features, filters, filtersLogicalOperator) {
215
+ return Object.keys(filters).length ? features.filter(_buildFeatureFilter({
216
+ filters,
217
+ filtersLogicalOperator
218
+ })) : features;
214
219
  }
215
220
  /**
216
- * Clears all {@link Filter filters} from the filter set.
221
+ * Binary.
222
+ * @internal
217
223
  */
218
- function clearFilters(filters) {
219
- for (const column of Object.keys(filters)) {
220
- delete filters[column];
224
+ function buildBinaryFeatureFilter(_ref2) {
225
+ let {
226
+ filters = {}
227
+ } = _ref2;
228
+ const columns = Object.keys(filters);
229
+ if (!columns.length) {
230
+ return () => 1;
221
231
  }
222
- return filters;
232
+ return (featureIdIdx, binaryData) => passesFilterUsingBinary(columns, filters, featureIdIdx, binaryData);
223
233
  }
224
- function hasFilter(filters, _ref3) {
234
+ function getValueFromNumericProps(featureIdIdx, binaryData, _ref3) {
225
235
  let {
226
- column,
227
- owner
236
+ column
228
237
  } = _ref3;
229
- const filter = filters[column];
230
- if (!filter) {
231
- return false;
232
- }
233
- if (!owner) {
234
- return true;
235
- }
236
- for (const type of Object.values(exports.FilterType)) {
237
- if (owner === filter[type]?.owner) {
238
- return true;
239
- }
240
- }
241
- return false;
238
+ return binaryData.numericProps?.[column]?.value[featureIdIdx];
242
239
  }
243
- function getFilter(filters, _ref4) {
240
+ function getValueFromProperties(featureIdIdx, binaryData, _ref4) {
244
241
  let {
245
- column,
246
- type,
247
- owner
242
+ column
248
243
  } = _ref4;
249
- const filter = filters[column];
250
- if (!filter) {
251
- return null;
252
- }
253
- if (!owner || owner === filter[type]?.owner) {
254
- return filter[type] || null;
255
- }
256
- return null;
244
+ const propertyIdx = binaryData.featureIds.value[featureIdIdx];
245
+ return binaryData.properties[propertyIdx]?.[column];
246
+ }
247
+ const GET_VALUE_BY_BINARY_PROP = {
248
+ properties: getValueFromProperties,
249
+ numericProps: getValueFromNumericProps
250
+ };
251
+ function getBinaryPropertyByFilterValues(filterValues) {
252
+ return typeof filterValues.flat()[0] === 'string' ? 'properties' : 'numericProps';
253
+ }
254
+ function getFeatureValue(featureIdIdx, binaryData, filter) {
255
+ const {
256
+ column,
257
+ values
258
+ } = filter;
259
+ const binaryProp = getBinaryPropertyByFilterValues(values);
260
+ const getFeatureValueFn = GET_VALUE_BY_BINARY_PROP[binaryProp];
261
+ return getFeatureValueFn(featureIdIdx, binaryData, {
262
+ column
263
+ });
264
+ }
265
+ function passesFilterUsingBinary(columns, filters, featureIdIdx, binaryData) {
266
+ return columns.every(column => {
267
+ const columnFilters = filters[column];
268
+ return Object.entries(columnFilters).every(_ref5 => {
269
+ let [type, {
270
+ values
271
+ }] = _ref5;
272
+ const filterFn = filterFunctions[type];
273
+ if (!filterFn) {
274
+ throw new Error(`"${type}" filter is not implemented.`);
275
+ }
276
+ if (!values) return 0;
277
+ const featureValue = getFeatureValue(featureIdIdx, binaryData, {
278
+ type: type,
279
+ column,
280
+ values
281
+ });
282
+ if (featureValue === undefined || featureValue === null) return 0;
283
+ return filterFn(values, featureValue);
284
+ });
285
+ });
286
+ }
287
+
288
+ function geojsonFeatures(_ref) {
289
+ let {
290
+ geojson,
291
+ spatialFilter,
292
+ uniqueIdProperty
293
+ } = _ref;
294
+ let uniqueIdx = 0;
295
+ const map = new Map();
296
+ if (!spatialFilter) {
297
+ return [];
298
+ }
299
+ for (const feature of geojson.features) {
300
+ const uniqueId = uniqueIdProperty ? feature.properties[uniqueIdProperty] : ++uniqueIdx;
301
+ if (!map.has(uniqueId) && intersects(spatialFilter, feature)) {
302
+ map.set(uniqueId, feature.properties);
303
+ }
304
+ }
305
+ return Array.from(map.values());
257
306
  }
258
307
 
308
+ // math.gl
309
+ // SPDX-License-Identifier: MIT
310
+ // Copyright (c) vis.gl contributors
311
+ const DEFAULT_CONFIG = {
312
+ EPSILON: 1e-12,
313
+ debug: false,
314
+ precision: 4,
315
+ printTypes: false,
316
+ printDegrees: false,
317
+ printRowMajor: true,
318
+ _cartographicRadians: false
319
+ };
320
+ // Configuration is truly global as of v3.6 to ensure single config even if multiple copies of math.gl
321
+ // Multiple copies of config can be quite tricky to debug...
322
+ globalThis.mathgl = globalThis.mathgl || {
323
+ config: {
324
+ ...DEFAULT_CONFIG
325
+ }
326
+ };
259
327
  /**
260
- * Returns a {@link SpatialFilter} for a given viewport, typically obtained
261
- * from deck.gl's `viewport.getBounds()` method ([west, south, east, north]).
262
- * If the viewport covers the entire world (to some margin of error in Web
263
- * Mercator space), `undefined` is returned instead.
264
- *
265
- * If the viewport extends beyond longitude range [-180, +180], the polygon
266
- * may be reformatted for compatibility with CARTO APIs.
328
+ * Check if value is an "array"
329
+ * Returns `true` if value is either an array or a typed array
330
+ * Note: returns `false` for `ArrayBuffer` and `DataView` instances
331
+ * @note isTypedArray and isNumericArray are often more useful in TypeScript
267
332
  */
268
- function createViewportSpatialFilter(viewport) {
269
- if (_isGlobalViewport(viewport)) {
270
- return;
333
+ function isArray(value) {
334
+ return Array.isArray(value) || ArrayBuffer.isView(value) && !(value instanceof DataView);
335
+ }
336
+ function lerp(a, b, t) {
337
+ if (isArray(a)) {
338
+ return a.map((ai, i) => lerp(ai, b[i], t));
339
+ }
340
+ return t * b + (1 - t) * a;
341
+ }
342
+
343
+ // Replacement for the external assert method to reduce bundle size
344
+ // Note: We don't use the second "message" argument in calling code,
345
+ // so no need to support it here
346
+ function assert$1(condition, message) {
347
+ if (!condition) {
348
+ throw new Error(message || '@math.gl/web-mercator: assertion failed.');
271
349
  }
272
- return createPolygonSpatialFilter(bboxPolygon(viewport).geometry);
273
350
  }
351
+
352
+ // TODO - THE UTILITIES IN THIS FILE SHOULD BE IMPORTED FROM WEB-MERCATOR-VIEWPORT MODULE
353
+ // CONSTANTS
354
+ const PI = Math.PI;
355
+ const PI_4 = PI / 4;
356
+ const DEGREES_TO_RADIANS = PI / 180;
357
+ const RADIANS_TO_DEGREES = 180 / PI;
358
+ const TILE_SIZE = 512;
274
359
  /**
275
- * Returns a {@link SpatialFilter} for a given {@link Polygon} or
276
- * {@link MultiPolygon}. If the polygon(s) extend outside longitude
277
- * range [-180, +180], the result may be reformatted for compatibility
278
- * with CARTO APIs.
360
+ * Project [lng,lat] on sphere onto [x,y] on 512*512 Mercator Zoom 0 tile.
361
+ * Performs the nonlinear part of the web mercator projection.
362
+ * Remaining projection is done with 4x4 matrices which also handles
363
+ * perspective.
364
+ *
365
+ * @param lngLat - [lng, lat] coordinates
366
+ * Specifies a point on the sphere to project onto the map.
367
+ * @return [x,y] coordinates.
279
368
  */
280
- function createPolygonSpatialFilter(spatialFilter) {
281
- return spatialFilter && _normalizeGeometry(spatialFilter) || undefined;
369
+ function lngLatToWorld(lngLat) {
370
+ const [lng, lat] = lngLat;
371
+ assert$1(Number.isFinite(lng));
372
+ assert$1(Number.isFinite(lat) && lat >= -90 && lat <= 90, 'invalid latitude');
373
+ const lambda2 = lng * DEGREES_TO_RADIANS;
374
+ const phi2 = lat * DEGREES_TO_RADIANS;
375
+ const x = TILE_SIZE * (lambda2 + PI) / (2 * PI);
376
+ const y = TILE_SIZE * (PI + Math.log(Math.tan(PI_4 + phi2 * 0.5))) / (2 * PI);
377
+ return [x, y];
282
378
  }
283
379
  /**
284
- * Check if a viewport is large enough to represent a global coverage.
285
- * In this case the spatial filter parameter for widget calculation is removed.
380
+ * Unproject world point [x,y] on map onto {lat, lon} on sphere
286
381
  *
287
- * @internalRemarks Source: @carto/react-core
382
+ * @param xy - array with [x,y] members
383
+ * representing point on projected map plane
384
+ * @return - array with [x,y] of point on sphere.
385
+ * Has toArray method if you need a GeoJSON Array.
386
+ * Per cartographic tradition, lat and lon are specified as degrees.
288
387
  */
289
- function _isGlobalViewport(viewport) {
290
- const [minx, miny, maxx, maxy] = viewport;
291
- return maxx - minx > 179.5 * 2 && maxy - miny > 85.05 * 2;
388
+ function worldToLngLat(xy) {
389
+ const [x, y] = xy;
390
+ const lambda2 = x / TILE_SIZE * (2 * PI) - PI;
391
+ const phi2 = 2 * (Math.atan(Math.exp(y / TILE_SIZE * (2 * PI) - PI)) - PI_4);
392
+ return [lambda2 * RADIANS_TO_DEGREES, phi2 * RADIANS_TO_DEGREES];
292
393
  }
394
+
395
+ const TRANSFORM_FN$1 = {
396
+ Point: transformPoint$1,
397
+ MultiPoint: transformMultiPoint$1,
398
+ LineString: transformLineString$1,
399
+ MultiLineString: transformMultiLineString$1,
400
+ Polygon: transformPolygon$1,
401
+ MultiPolygon: transformMultiPolygon$1
402
+ };
293
403
  /**
294
- * Normalized a geometry, coming from a mask or a viewport. The parts
295
- * spanning outside longitude range [-180, +180] are clipped and "folded"
296
- * back to the valid range and unioned to the polygons inide that range.
297
- *
298
- * It results in a Polygon or MultiPolygon strictly inside the validity range.
404
+ * Transform WGS84 coordinates to tile coords.
405
+ * 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)
299
406
  *
300
- * @internalRemarks Source: @carto/react-core
407
+ * @param geometry - any valid geojson geometry
408
+ * @param bbox - geojson bbox
301
409
  */
302
- function _normalizeGeometry(geometry) {
303
- const WORLD = [-180, -90, +180, +90];
304
- const worldClip = _clean(bboxClip(geometry, WORLD).geometry);
305
- const geometryTxWest = _tx(geometry, 360);
306
- const geometryTxEast = _tx(geometry, -360);
307
- let result = worldClip;
308
- if (result && geometryTxWest) {
309
- const worldWestClip = _clean(bboxClip(geometryTxWest, WORLD).geometry);
310
- if (worldWestClip) {
311
- const collection = helpers.featureCollection([helpers.feature(result), helpers.feature(worldWestClip)]);
312
- const merged = union(collection);
313
- result = merged ? _clean(merged.geometry) : result;
314
- }
315
- }
316
- if (result && geometryTxEast) {
317
- const worldEastClip = _clean(bboxClip(geometryTxEast, WORLD).geometry);
318
- if (worldEastClip) {
319
- const collection = helpers.featureCollection([helpers.feature(result), helpers.feature(worldEastClip)]);
320
- const merged = union(collection);
321
- result = merged ? _clean(merged.geometry) : result;
322
- }
410
+ function transformToTileCoords(geometry, bbox) {
411
+ const [west, south, east, north] = bbox;
412
+ const nw = projectFlat([west, north]);
413
+ const se = projectFlat([east, south]);
414
+ const projectedBbox = [nw, se];
415
+ if (geometry.type === 'GeometryCollection') {
416
+ throw new Error('Unsupported geometry type GeometryCollection');
323
417
  }
324
- return result;
418
+ const transformFn = TRANSFORM_FN$1[geometry.type];
419
+ const coordinates = transformFn(geometry.coordinates, projectedBbox);
420
+ return {
421
+ ...geometry,
422
+ coordinates
423
+ };
325
424
  }
326
- /** @internalRemarks Source: @carto/react-core */
327
- function _cleanPolygonCoords(cc) {
328
- const coords = cc.filter(c => c.length > 0);
329
- return coords.length > 0 ? coords : null;
425
+ function transformPoint$1(_ref, _ref2) {
426
+ let [pointX, pointY] = _ref;
427
+ let [nw, se] = _ref2;
428
+ const x = inverseLerp(nw[0], se[0], pointX);
429
+ const y = inverseLerp(nw[1], se[1], pointY);
430
+ return [x, y];
330
431
  }
331
- /** @internalRemarks Source: @carto/react-core */
332
- function _cleanMultiPolygonCoords(ccc) {
333
- const coords = ccc.map(_cleanPolygonCoords).filter(cc => cc);
334
- return coords.length > 0 ? coords : null;
432
+ function getPoints$1(geometry, bbox) {
433
+ return geometry.map(g => transformPoint$1(projectFlat(g), bbox));
335
434
  }
336
- /** @internalRemarks Source: @carto/react-core */
337
- function _clean(geometry) {
338
- if (!geometry) {
339
- return null;
340
- }
341
- if (_isPolygon(geometry)) {
342
- const coords = _cleanPolygonCoords(geometry.coordinates);
343
- return coords ? helpers.polygon(coords).geometry : null;
344
- }
345
- if (_isMultiPolygon(geometry)) {
346
- const coords = _cleanMultiPolygonCoords(geometry.coordinates);
347
- return coords ? helpers.multiPolygon(coords).geometry : null;
348
- }
349
- return null;
435
+ function transformMultiPoint$1(multiPoint, bbox) {
436
+ return getPoints$1(multiPoint, bbox);
350
437
  }
351
- /** @internalRemarks Source: @carto/react-core */
352
- function _txContourCoords(cc, distance) {
353
- return cc.map(c => [c[0] + distance, c[1]]);
438
+ function transformLineString$1(line, bbox) {
439
+ return getPoints$1(line, bbox);
354
440
  }
355
- /** @internalRemarks Source: @carto/react-core */
356
- function _txPolygonCoords(ccc, distance) {
357
- return ccc.map(cc => _txContourCoords(cc, distance));
441
+ function transformMultiLineString$1(multiLineString, bbox) {
442
+ return multiLineString.map(lineString => transformLineString$1(lineString, bbox));
358
443
  }
359
- /** @internalRemarks Source: @carto/react-core */
360
- function _txMultiPolygonCoords(cccc, distance) {
361
- return cccc.map(ccc => _txPolygonCoords(ccc, distance));
444
+ function transformPolygon$1(polygon, bbox) {
445
+ return polygon.map(polygonRing => getPoints$1(polygonRing, bbox));
362
446
  }
363
- /** @internalRemarks Source: @carto/react-core */
364
- function _tx(geometry, distance) {
365
- if (geometry && invariant.getType(geometry) === 'Polygon') {
366
- const coords = _txPolygonCoords(geometry.coordinates, distance);
367
- return helpers.polygon(coords).geometry;
368
- } else if (geometry && invariant.getType(geometry) === 'MultiPolygon') {
369
- const coords = _txMultiPolygonCoords(geometry.coordinates, distance);
370
- return helpers.multiPolygon(coords).geometry;
371
- } else {
372
- return null;
373
- }
447
+ function transformMultiPolygon$1(multiPolygon, bbox) {
448
+ return multiPolygon.map(polygon => transformPolygon$1(polygon, bbox));
374
449
  }
375
- function _isPolygon(geometry) {
376
- return invariant.getType(geometry) === 'Polygon';
450
+ function projectFlat(xyz) {
451
+ return lngLatToWorld(xyz);
377
452
  }
378
- function _isMultiPolygon(geometry) {
379
- return invariant.getType(geometry) === 'MultiPolygon';
453
+ function inverseLerp(a, b, x) {
454
+ return (x - a) / (b - a);
380
455
  }
381
456
 
457
+ const TRANSFORM_FN = {
458
+ Point: transformPoint,
459
+ MultiPoint: transformMultiPoint,
460
+ LineString: transformLineString,
461
+ MultiLineString: transformMultiLineString,
462
+ Polygon: transformPolygon,
463
+ MultiPolygon: transformMultiPolygon
464
+ };
382
465
  /**
383
- * Current version of @carto/api-client.
384
- * @internal
385
- */
386
- /** @internal */
387
- const V3_MINOR_VERSION = '3.4';
388
- /** @internalRemarks Source: @carto/constants, @deck.gl/carto */
389
- const DEFAULT_GEO_COLUMN = 'geom';
390
- /**
391
- * Fastly default limit is 8192; leave some padding.
392
- * @internalRemarks Source: @deck.gl/carto
393
- */
394
- const DEFAULT_MAX_LENGTH_URL = 7000;
395
- /** @internalRemarks Source: @deck.gl/carto */
396
- const DEFAULT_TILE_RESOLUTION = 0.5;
397
- /**
398
- * @internalRemarks Source: @deck.gl/carto
399
- * @internal
400
- */
401
- const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4;
402
- /**
403
- * @internalRemarks Source: @deck.gl/carto
404
- * @internal
466
+ * Transform tile coords to WGS84 coordinates.
467
+ *
468
+ * @param geometry - any valid geojson geometry
469
+ * @param bbox - geojson bbox
405
470
  */
406
- const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6;
407
-
408
- // deck.gl
409
- // SPDX-License-Identifier: MIT
410
- // Copyright (c) vis.gl contributors
411
- function joinPath() {
412
- return [].slice.call(arguments).map(part => part.endsWith('/') ? part.slice(0, -1) : part).join('/');
471
+ function transformTileCoordsToWGS84(geometry, bbox) {
472
+ const [west, south, east, north] = bbox;
473
+ const nw = lngLatToWorld([west, north]);
474
+ const se = lngLatToWorld([east, south]);
475
+ const projectedBbox = [nw, se];
476
+ if (geometry.type === 'GeometryCollection') {
477
+ throw new Error('Unsupported geometry type GeometryCollection');
478
+ }
479
+ const transformFn = TRANSFORM_FN[geometry.type];
480
+ const coordinates = transformFn(geometry.coordinates, projectedBbox);
481
+ return {
482
+ ...geometry,
483
+ coordinates
484
+ };
413
485
  }
414
- function buildV3Path(apiBaseUrl, version, endpoint) {
415
- return joinPath(apiBaseUrl, version, endpoint, ...[].slice.call(arguments, 3));
486
+ function transformPoint(_ref, _ref2) {
487
+ let [pointX, pointY] = _ref;
488
+ let [nw, se] = _ref2;
489
+ const x = lerp(nw[0], se[0], pointX);
490
+ const y = lerp(nw[1], se[1], pointY);
491
+ return worldToLngLat([x, y]);
416
492
  }
417
- /** @internal Required by fetchMap(). */
418
- function buildPublicMapUrl(_ref) {
419
- let {
420
- apiBaseUrl,
421
- cartoMapId
422
- } = _ref;
423
- return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId);
493
+ function getPoints(geometry, bbox) {
494
+ return geometry.map(g => transformPoint(g, bbox));
424
495
  }
425
- /** @internal Required by fetchMap(). */
426
- function buildStatsUrl(_ref2) {
427
- let {
428
- attribute,
429
- apiBaseUrl,
430
- connectionName,
431
- source,
432
- type
433
- } = _ref2;
434
- if (type === 'query') {
435
- return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, attribute);
436
- }
437
- // type === 'table'
438
- return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, source, attribute);
496
+ function transformMultiPoint(multiPoint, bbox) {
497
+ return getPoints(multiPoint, bbox);
439
498
  }
440
- function buildSourceUrl(_ref3) {
441
- let {
442
- apiBaseUrl,
443
- connectionName,
444
- endpoint
445
- } = _ref3;
446
- return buildV3Path(apiBaseUrl, 'v3', 'maps', connectionName, endpoint);
499
+ function transformLineString(line, bbox) {
500
+ return getPoints(line, bbox);
447
501
  }
448
- function buildQueryUrl(_ref4) {
449
- let {
450
- apiBaseUrl,
451
- connectionName
452
- } = _ref4;
453
- return buildV3Path(apiBaseUrl, 'v3', 'sql', connectionName, 'query');
502
+ function transformMultiLineString(multiLineString, bbox) {
503
+ return multiLineString.map(lineString => transformLineString(lineString, bbox));
454
504
  }
455
-
456
- // deck.gl
457
- // SPDX-License-Identifier: MIT
458
- // Copyright (c) vis.gl contributors
459
- /**
460
- *
461
- * Custom error for reported errors in CARTO Maps API.
462
- * Provides useful debugging information in console and context for applications.
463
- *
464
- */
465
- class CartoAPIError extends Error {
466
- constructor(error, errorContext, response, responseJson) {
467
- let responseString = 'Failed to connect';
468
- if (response) {
469
- responseString = 'Server returned: ';
470
- if (response.status === 400) {
471
- responseString += 'Bad request';
472
- } else if (response.status === 401 || response.status === 403) {
473
- responseString += 'Unauthorized access';
474
- } else if (response.status === 404) {
475
- responseString += 'Not found';
476
- } else {
477
- responseString += 'Error';
478
- }
479
- responseString += ` (${response.status}):`;
480
- }
481
- responseString += ` ${error.message || error}`;
482
- let message = `${errorContext.requestType} API request failed`;
483
- message += `\n${responseString}`;
484
- for (const key of Object.keys(errorContext)) {
485
- if (key === 'requestType') continue;
486
- message += `\n${formatErrorKey(key)}: ${errorContext[key]}`;
487
- }
488
- message += '\n';
489
- super(message);
490
- /** Source error from server */
491
- this.error = void 0;
492
- /** Context (API call & parameters) in which error occured */
493
- this.errorContext = void 0;
494
- /** Response from server */
495
- this.response = void 0;
496
- /** JSON Response from server */
497
- this.responseJson = void 0;
498
- this.name = 'CartoAPIError';
499
- this.response = response;
500
- this.responseJson = responseJson;
501
- this.error = error;
502
- this.errorContext = errorContext;
503
- }
505
+ function transformPolygon(polygon, bbox) {
506
+ return polygon.map(polygonRing => getPoints(polygonRing, bbox));
504
507
  }
505
- /**
506
- * Converts camelCase to Camel Case
507
- */
508
- function formatErrorKey(key) {
509
- return key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
508
+ function transformMultiPolygon(multiPolygon, bbox) {
509
+ return multiPolygon.map(polygon => transformPolygon(polygon, bbox));
510
510
  }
511
511
 
512
- // deck.gl
513
- // SPDX-License-Identifier: MIT
514
- // Copyright (c) vis.gl contributors
515
- const requestWithParameters = function (_ref) {
512
+ const FEATURE_GEOM_PROPERTY = '__geomValue';
513
+ function tileFeaturesGeometries(_ref) {
516
514
  let {
517
- baseUrl,
518
- parameters = {},
519
- headers: customHeaders = {},
520
- errorContext,
521
- maxLengthURL = DEFAULT_MAX_LENGTH_URL,
522
- localCache
515
+ tiles,
516
+ tileFormat,
517
+ spatialFilter,
518
+ uniqueIdProperty,
519
+ options
523
520
  } = _ref;
524
- try {
525
- // Parameters added to all requests issued with `requestWithParameters()`.
526
- // These parameters override parameters already in the base URL, but not
527
- // user-provided parameters.
528
- parameters = {
529
- v: V3_MINOR_VERSION,
530
- client: getClient(),
531
- ...(typeof deck !== 'undefined' && deck.VERSION && {
532
- deckglVersion: deck.VERSION
533
- }),
534
- ...parameters
535
- };
536
- baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters));
537
- const key = createCacheKey(baseUrl, parameters, customHeaders);
538
- const {
539
- cache: REQUEST_CACHE,
540
- canReadCache,
541
- canStoreInCache
542
- } = getCacheSettings(localCache);
543
- if (canReadCache && REQUEST_CACHE.has(key)) {
544
- return Promise.resolve(REQUEST_CACHE.get(key));
521
+ const map = new Map();
522
+ for (const tile of tiles) {
523
+ // Discard if it's not a visible tile (only check false value, not undefined)
524
+ // or tile has not data
525
+ if (tile.isVisible === false || !tile.data) {
526
+ continue;
545
527
  }
546
- const url = createURLWithParameters(baseUrl, parameters);
547
- const headers = {
548
- ...DEFAULT_HEADERS,
549
- ...customHeaders
528
+ const bbox = [tile.bbox.west, tile.bbox.south, tile.bbox.east, tile.bbox.north];
529
+ const bboxToGeom = bboxPolygon(bbox);
530
+ const tileIsFullyVisible = booleanWithin(bboxToGeom, spatialFilter);
531
+ // Clip the geometry to intersect with the tile
532
+ const spatialFilterFeature = {
533
+ type: 'Feature',
534
+ geometry: spatialFilter,
535
+ properties: {}
550
536
  };
551
- /* global fetch */
552
- const fetchPromise = url.length > maxLengthURL ? fetch(baseUrl, {
553
- method: 'POST',
554
- body: JSON.stringify(parameters),
555
- headers
556
- }) : fetch(url, {
557
- headers
537
+ const clippedGeometryToIntersect = intersect(helpers.featureCollection([bboxToGeom, spatialFilterFeature]));
538
+ if (!clippedGeometryToIntersect) {
539
+ continue;
540
+ }
541
+ // We assume that MVT tileFormat uses local coordinates so we transform the geometry to intersect to tile coordinates [0..1],
542
+ // while in the case of 'geojson' or binary, the geometries are already in WGS84
543
+ const transformedGeometryToIntersect = tileFormat === exports.TileFormat.MVT ? transformToTileCoords(clippedGeometryToIntersect.geometry, bbox) : clippedGeometryToIntersect.geometry;
544
+ createIndicesForPoints(tile.data.points);
545
+ calculateFeatures({
546
+ map,
547
+ tileIsFullyVisible,
548
+ geometryIntersection: transformedGeometryToIntersect,
549
+ data: tile.data.points,
550
+ type: 'Point',
551
+ bbox,
552
+ tileFormat,
553
+ uniqueIdProperty,
554
+ options
558
555
  });
559
- let response;
560
- let responseJson;
561
- const jsonPromise = fetchPromise.then(_response => {
562
- response = _response;
563
- return response.json();
564
- }).then(json => {
565
- responseJson = json;
566
- if (!response || !response.ok) {
567
- throw new Error(json.error);
568
- }
569
- return json;
570
- }).catch(error => {
571
- if (canStoreInCache) {
572
- REQUEST_CACHE.delete(key);
573
- }
574
- throw new CartoAPIError(error, errorContext, response, responseJson);
556
+ calculateFeatures({
557
+ map,
558
+ tileIsFullyVisible,
559
+ geometryIntersection: transformedGeometryToIntersect,
560
+ data: tile.data.lines,
561
+ type: 'LineString',
562
+ bbox,
563
+ tileFormat,
564
+ uniqueIdProperty,
565
+ options
566
+ });
567
+ calculateFeatures({
568
+ map,
569
+ tileIsFullyVisible,
570
+ geometryIntersection: transformedGeometryToIntersect,
571
+ data: tile.data.polygons,
572
+ type: 'Polygon',
573
+ bbox,
574
+ tileFormat,
575
+ uniqueIdProperty,
576
+ options
575
577
  });
576
- if (canStoreInCache) {
577
- REQUEST_CACHE.set(key, jsonPromise);
578
- }
579
- return Promise.resolve(jsonPromise);
580
- } catch (e) {
581
- return Promise.reject(e);
582
578
  }
583
- };
584
- const DEFAULT_HEADERS = {
585
- Accept: 'application/json',
586
- 'Content-Type': 'application/json'
587
- };
588
- const DEFAULT_REQUEST_CACHE = new Map();
589
- function getCacheSettings(localCache) {
590
- const canReadCache = localCache?.cacheControl?.includes('no-cache') ? false : true;
591
- const canStoreInCache = localCache?.cacheControl?.includes('no-store') ? false : true;
592
- const cache = localCache?.cache || DEFAULT_REQUEST_CACHE;
593
- return {
594
- cache,
595
- canReadCache,
596
- canStoreInCache
579
+ return Array.from(map.values());
580
+ }
581
+ function processTileFeatureProperties(_ref2) {
582
+ let {
583
+ map,
584
+ data,
585
+ startIndex,
586
+ endIndex,
587
+ type,
588
+ bbox,
589
+ tileFormat,
590
+ uniqueIdProperty,
591
+ storeGeometry,
592
+ geometryIntersection
593
+ } = _ref2;
594
+ const tileProps = getPropertiesFromTile(data, startIndex);
595
+ const uniquePropertyValue = getUniquePropertyValue(tileProps, uniqueIdProperty, map);
596
+ if (!uniquePropertyValue || map.has(uniquePropertyValue)) {
597
+ return;
598
+ }
599
+ let geometry = null;
600
+ // Only calculate geometry if necessary
601
+ if (storeGeometry || geometryIntersection) {
602
+ const {
603
+ positions
604
+ } = data;
605
+ const ringCoordinates = getRingCoordinatesFor(startIndex, endIndex, positions);
606
+ geometry = getFeatureByType(ringCoordinates, type);
607
+ }
608
+ // If intersection is required, check before proceeding
609
+ if (geometry && geometryIntersection && !intersects(geometry, geometryIntersection)) {
610
+ return;
611
+ }
612
+ const properties = parseProperties(tileProps);
613
+ // Only save geometry if necessary
614
+ if (storeGeometry && geometry) {
615
+ properties[FEATURE_GEOM_PROPERTY] = tileFormat === exports.TileFormat.MVT ? transformTileCoordsToWGS84(geometry, bbox) : geometry;
616
+ }
617
+ map.set(uniquePropertyValue, properties);
618
+ }
619
+ function addIntersectedFeaturesInTile(_ref3) {
620
+ let {
621
+ map,
622
+ data,
623
+ geometryIntersection,
624
+ type,
625
+ bbox,
626
+ tileFormat,
627
+ uniqueIdProperty,
628
+ options
629
+ } = _ref3;
630
+ const indices = getIndices(data);
631
+ const storeGeometry = options?.storeGeometry || false;
632
+ for (let i = 0; i < indices.length - 1; i++) {
633
+ const startIndex = indices[i];
634
+ const endIndex = indices[i + 1];
635
+ processTileFeatureProperties({
636
+ map,
637
+ data,
638
+ startIndex,
639
+ endIndex,
640
+ type,
641
+ bbox,
642
+ tileFormat,
643
+ uniqueIdProperty,
644
+ storeGeometry,
645
+ geometryIntersection
646
+ });
647
+ }
648
+ }
649
+ function getIndices(data) {
650
+ let indices;
651
+ switch (data.type) {
652
+ case 'Point':
653
+ // @ts-expect-error Missing or changed types?
654
+ indices = data.pointIndices;
655
+ break;
656
+ case 'LineString':
657
+ indices = data.pathIndices;
658
+ break;
659
+ case 'Polygon':
660
+ indices = data.primitivePolygonIndices;
661
+ break;
662
+ default:
663
+ throw new Error(`Unexpected type, "${data.type}"`);
664
+ }
665
+ return indices.value;
666
+ }
667
+ function getFeatureId(data, startIndex) {
668
+ return data.featureIds.value[startIndex];
669
+ }
670
+ function getPropertiesFromTile(data, startIndex) {
671
+ const featureId = getFeatureId(data, startIndex);
672
+ const {
673
+ properties,
674
+ numericProps,
675
+ fields
676
+ } = data;
677
+ const result = {
678
+ uniqueId: fields?.[featureId]?.id,
679
+ properties: properties[featureId],
680
+ numericProps: {}
597
681
  };
682
+ for (const key in numericProps) {
683
+ result.numericProps[key] = numericProps[key].value[startIndex];
684
+ }
685
+ return result;
598
686
  }
599
- function createCacheKey(baseUrl, parameters, headers) {
600
- const parameterEntries = Object.entries(parameters).sort((_ref2, _ref3) => {
601
- let [a] = _ref2;
602
- let [b] = _ref3;
603
- return a > b ? 1 : -1;
604
- });
605
- const headerEntries = Object.entries(headers).sort((_ref4, _ref5) => {
606
- let [a] = _ref4;
607
- let [b] = _ref5;
608
- return a > b ? 1 : -1;
609
- });
610
- return JSON.stringify({
611
- baseUrl,
612
- parameters: parameterEntries,
613
- headers: headerEntries
614
- });
687
+ function parseProperties(tileProps) {
688
+ const {
689
+ properties,
690
+ numericProps
691
+ } = tileProps;
692
+ return Object.assign({}, properties, numericProps);
615
693
  }
616
- /**
617
- * Appends query string parameters to a URL. Existing URL parameters are kept,
618
- * unless there is a conflict, in which case the new parameters override
619
- * those already in the URL.
620
- */
621
- function createURLWithParameters(baseUrlString, parameters) {
622
- const baseUrl = new URL(baseUrlString);
623
- for (const [key, value] of Object.entries(parameters)) {
624
- if (isPureObject(value) || Array.isArray(value)) {
625
- baseUrl.searchParams.set(key, JSON.stringify(value));
626
- } else {
627
- baseUrl.searchParams.set(key, value.toString());
628
- }
694
+ function getUniquePropertyValue(tileProps, uniqueIdProperty, map) {
695
+ if (uniqueIdProperty) {
696
+ return getValueFromTileProps(tileProps, uniqueIdProperty);
629
697
  }
630
- return baseUrl.toString();
698
+ if (tileProps.uniqueId) {
699
+ return tileProps.uniqueId;
700
+ }
701
+ const artificialId = map.size + 1; // a counter, assumed as a valid new id
702
+ return getValueFromTileProps(tileProps, 'cartodb_id') || getValueFromTileProps(tileProps, 'geoid') || artificialId;
631
703
  }
632
- /**
633
- * Deletes query string parameters from a URL.
634
- */
635
- function excludeURLParameters(baseUrlString, parameters) {
636
- const baseUrl = new URL(baseUrlString);
637
- for (const param of parameters) {
638
- if (baseUrl.searchParams.has(param)) {
639
- baseUrl.searchParams.delete(param);
640
- }
704
+ function getValueFromTileProps(tileProps, propertyName) {
705
+ const {
706
+ properties,
707
+ numericProps
708
+ } = tileProps;
709
+ return numericProps[propertyName] || properties[propertyName];
710
+ }
711
+ function getFeatureByType(coordinates, type) {
712
+ switch (type) {
713
+ case 'Polygon':
714
+ return {
715
+ type: 'Polygon',
716
+ coordinates: [coordinates]
717
+ };
718
+ case 'LineString':
719
+ return {
720
+ type: 'LineString',
721
+ coordinates
722
+ };
723
+ case 'Point':
724
+ return {
725
+ type: 'Point',
726
+ coordinates: coordinates[0]
727
+ };
728
+ default:
729
+ throw new Error('Invalid geometry type');
641
730
  }
642
- return baseUrl.toString();
643
731
  }
644
-
645
- // deck.gl
646
- // SPDX-License-Identifier: MIT
647
- // Copyright (c) vis.gl contributors
648
- const baseSource = function (endpoint, options, urlParameters) {
649
- try {
650
- const {
651
- accessToken,
652
- connectionName,
653
- cache,
654
- ...optionalOptions
655
- } = options;
656
- const mergedOptions = {
657
- ...SOURCE_DEFAULTS,
658
- accessToken,
659
- connectionName,
660
- endpoint
661
- };
662
- for (const key in optionalOptions) {
663
- if (optionalOptions[key]) {
664
- mergedOptions[key] = optionalOptions[key];
665
- }
666
- }
667
- const baseUrl = buildSourceUrl(mergedOptions);
668
- const {
669
- clientId,
670
- maxLengthURL,
671
- format,
672
- localCache
673
- } = mergedOptions;
674
- const headers = {
675
- Authorization: `Bearer ${options.accessToken}`,
676
- ...options.headers
677
- };
678
- const parameters = {
679
- client: clientId,
680
- ...urlParameters
681
- };
682
- const errorContext = {
683
- requestType: 'Map instantiation',
684
- connection: options.connectionName,
685
- type: endpoint,
686
- source: JSON.stringify(parameters, undefined, 2)
687
- };
688
- return Promise.resolve(requestWithParameters({
689
- baseUrl,
690
- parameters,
691
- headers,
692
- errorContext,
693
- maxLengthURL,
694
- localCache
695
- })).then(function (mapInstantiation) {
696
- let _exit;
697
- function _temp2(_result) {
698
- return _exit ? _result : Promise.resolve(requestWithParameters({
699
- baseUrl: dataUrl,
700
- headers,
701
- errorContext,
702
- maxLengthURL,
703
- localCache
704
- }));
705
- }
706
- const dataUrl = mapInstantiation[format].url[0];
707
- if (cache) {
708
- cache.value = parseInt(new URL(dataUrl).searchParams.get('cache') || '', 10);
709
- }
710
- errorContext.requestType = 'Map data';
711
- const _temp = function () {
712
- if (format === 'tilejson') {
713
- return Promise.resolve(requestWithParameters({
714
- baseUrl: dataUrl,
715
- headers,
716
- errorContext,
717
- maxLengthURL,
718
- localCache
719
- })).then(function (json) {
720
- if (accessToken) {
721
- json.accessToken = accessToken;
722
- }
723
- _exit = 1;
724
- return json;
725
- });
726
- }
727
- }();
728
- return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
732
+ function getRingCoordinatesFor(startIndex, endIndex, positions) {
733
+ const ringCoordinates = [];
734
+ for (let j = startIndex; j < endIndex; j++) {
735
+ ringCoordinates.push(Array.from(positions.value.subarray(j * positions.size, (j + 1) * positions.size)));
736
+ }
737
+ return ringCoordinates;
738
+ }
739
+ function calculateFeatures(_ref4) {
740
+ let {
741
+ map,
742
+ tileIsFullyVisible,
743
+ geometryIntersection,
744
+ data,
745
+ type,
746
+ bbox,
747
+ tileFormat,
748
+ uniqueIdProperty,
749
+ options
750
+ } = _ref4;
751
+ if (!data?.properties.length) {
752
+ return;
753
+ }
754
+ if (tileIsFullyVisible) {
755
+ addAllFeaturesInTile({
756
+ map,
757
+ data,
758
+ type,
759
+ bbox,
760
+ tileFormat,
761
+ uniqueIdProperty,
762
+ options
763
+ });
764
+ } else {
765
+ addIntersectedFeaturesInTile({
766
+ map,
767
+ data,
768
+ geometryIntersection,
769
+ type,
770
+ bbox,
771
+ tileFormat,
772
+ uniqueIdProperty,
773
+ options
729
774
  });
730
- } catch (e) {
731
- return Promise.reject(e);
732
775
  }
733
- };
734
- const SOURCE_DEFAULTS = {
735
- apiBaseUrl: DEFAULT_API_BASE_URL,
736
- clientId: getClient(),
737
- format: 'tilejson',
738
- headers: {},
739
- maxLengthURL: DEFAULT_MAX_LENGTH_URL
740
- };
741
-
742
- // deck.gl
743
- // SPDX-License-Identifier: MIT
744
- // Copyright (c) vis.gl contributors
745
- const boundaryQuerySource = function (options) {
746
- try {
747
- const {
748
- columns,
749
- filters,
750
- tilesetTableName,
751
- propertiesSqlQuery,
752
- queryParameters
753
- } = options;
754
- const urlParameters = {
755
- tilesetTableName,
756
- propertiesSqlQuery
757
- };
758
- if (columns) {
759
- urlParameters.columns = columns.join(',');
760
- }
761
- if (filters) {
762
- urlParameters.filters = filters;
763
- }
764
- if (queryParameters) {
765
- urlParameters.queryParameters = queryParameters;
766
- }
767
- return Promise.resolve(baseSource('boundary', options, urlParameters));
768
- } catch (e) {
769
- return Promise.reject(e);
776
+ }
777
+ function addAllFeaturesInTile(_ref5) {
778
+ let {
779
+ map,
780
+ data,
781
+ type,
782
+ bbox,
783
+ tileFormat,
784
+ uniqueIdProperty,
785
+ options
786
+ } = _ref5;
787
+ const indices = getIndices(data);
788
+ const storeGeometry = options?.storeGeometry || false;
789
+ for (let i = 0; i < indices.length - 1; i++) {
790
+ const startIndex = indices[i];
791
+ const endIndex = indices[i + 1];
792
+ processTileFeatureProperties({
793
+ map,
794
+ data,
795
+ startIndex,
796
+ endIndex,
797
+ type,
798
+ bbox,
799
+ tileFormat,
800
+ uniqueIdProperty,
801
+ storeGeometry
802
+ });
770
803
  }
771
- };
804
+ }
805
+ function createIndicesForPoints(data) {
806
+ const featureIds = data.featureIds.value;
807
+ const lastFeatureId = featureIds[featureIds.length - 1];
808
+ const PointIndicesArray = featureIds.constructor;
809
+ const pointIndices = {
810
+ value: new PointIndicesArray(featureIds.length + 1),
811
+ size: 1
812
+ };
813
+ pointIndices.value.set(featureIds);
814
+ pointIndices.value.set([lastFeatureId + 1], featureIds.length);
815
+ // @ts-expect-error Missing or changed types?
816
+ data.pointIndices = pointIndices;
817
+ }
772
818
 
773
- // deck.gl
774
- // SPDX-License-Identifier: MIT
775
- // Copyright (c) vis.gl contributors
776
- const boundaryTableSource = function (options) {
777
- try {
778
- const {
779
- filters,
780
- tilesetTableName,
781
- columns,
782
- propertiesTableName
783
- } = options;
784
- const urlParameters = {
785
- tilesetTableName,
786
- propertiesTableName
787
- };
788
- if (columns) {
789
- urlParameters.columns = columns.join(',');
790
- }
791
- if (filters) {
792
- urlParameters.filters = filters;
793
- }
794
- return Promise.resolve(baseSource('boundary', options, urlParameters));
795
- } catch (e) {
796
- return Promise.reject(e);
819
+ function tileFeaturesSpatialIndex(_ref) {
820
+ let {
821
+ tiles,
822
+ spatialFilter,
823
+ spatialDataColumn,
824
+ spatialDataType
825
+ } = _ref;
826
+ const map = new Map();
827
+ const spatialIndex = getSpatialIndex(spatialDataType);
828
+ const resolution = getResolution(tiles, spatialIndex);
829
+ const spatialIndexIDName = spatialDataColumn ? spatialDataColumn : spatialIndex;
830
+ if (!resolution) {
831
+ return [];
797
832
  }
798
- };
799
-
800
- const DEFAULT_TILE_SIZE = 512;
801
- const QUADBIN_ZOOM_MAX_OFFSET = 4;
802
- function getSpatialFiltersResolution(source, viewState) {
803
- const dataResolution = source.dataResolution ?? Number.MAX_VALUE;
804
- const aggregationResLevel = source.aggregationResLevel ?? (source.spatialDataType === 'h3' ? DEFAULT_AGGREGATION_RES_LEVEL_H3 : DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN);
805
- const aggregationResLevelOffset = Math.max(0, Math.floor(aggregationResLevel));
806
- const currentZoomInt = Math.ceil(viewState.zoom);
807
- if (source.spatialDataType === 'h3') {
808
- const tileSize = DEFAULT_TILE_SIZE;
809
- const maxResolutionForZoom = maxH3SpatialFiltersResolutions.find(_ref => {
810
- let [zoom] = _ref;
811
- return zoom === currentZoomInt;
812
- })?.[1] ?? Math.max(0, currentZoomInt - 3);
813
- const maxSpatialFiltersResolution = maxResolutionForZoom ? Math.min(dataResolution, maxResolutionForZoom) : dataResolution;
814
- const hexagonResolution = _getHexagonResolution(viewState, tileSize) + aggregationResLevelOffset;
815
- return Math.min(hexagonResolution, maxSpatialFiltersResolution);
833
+ const cells = getCellsCoverGeometry(spatialFilter, spatialIndex, resolution);
834
+ if (!cells?.length) {
835
+ return [];
816
836
  }
817
- if (source.spatialDataType === 'quadbin') {
818
- const maxResolutionForZoom = currentZoomInt + QUADBIN_ZOOM_MAX_OFFSET;
819
- const maxSpatialFiltersResolution = Math.min(dataResolution, maxResolutionForZoom);
820
- const quadsResolution = Math.floor(viewState.zoom) + aggregationResLevelOffset;
821
- return Math.min(quadsResolution, maxSpatialFiltersResolution);
837
+ // We transform cells to Set to improve the performace
838
+ const cellsSet = new Set(cells);
839
+ for (const tile of tiles) {
840
+ if (tile.isVisible === false || !tile.data) {
841
+ continue;
842
+ }
843
+ tile.data.forEach(d => {
844
+ if (cellsSet.has(d.id)) {
845
+ map.set(d.id, {
846
+ ...d.properties,
847
+ [spatialIndexIDName]: d.id
848
+ });
849
+ }
850
+ });
822
851
  }
823
- return undefined;
824
- }
825
- 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]];
826
- // stolen from https://github.com/visgl/deck.gl/blob/master/modules/carto/src/layers/h3-tileset-2d.ts
827
- // Relative scale factor (0 = no biasing, 2 = a few hexagons cover view)
828
- const BIAS = 2;
829
- /**
830
- * Resolution conversion function. Takes a WebMercatorViewport and returns
831
- * a H3 resolution such that the screen space size of the hexagons is
832
- * "similar" to the given tileSize on screen. Intended for use with deck.gl.
833
- * @internal
834
- */
835
- function _getHexagonResolution(viewport, tileSize) {
836
- // Difference in given tile size compared to deck's internal 512px tile size,
837
- // expressed as an offset to the viewport zoom.
838
- const zoomOffset = Math.log2(tileSize / DEFAULT_TILE_SIZE);
839
- const hexagonScaleFactor = 2 / 3 * (viewport.zoom - zoomOffset);
840
- const latitudeScaleFactor = Math.log(1 / Math.cos(Math.PI * viewport.latitude / 180));
841
- // Clip and bias
842
- return Math.max(0, Math.floor(hexagonScaleFactor + latitudeScaleFactor - BIAS));
852
+ return Array.from(map.values());
843
853
  }
844
-
845
- /**
846
- * Source for Widget API requests on a data source defined by a SQL query.
847
- *
848
- * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
849
- */
850
- class WidgetSource {
851
- constructor(props) {
852
- this.props = void 0;
853
- this.props = {
854
- ...WidgetSource.defaultProps,
855
- ...props
856
- };
854
+ function getResolution(tiles, spatialIndex) {
855
+ const data = tiles.find(tile => tile.data?.length)?.data;
856
+ if (!data) {
857
+ return;
857
858
  }
858
- _getModelSource(owner) {
859
- const props = this.props;
860
- return {
861
- apiVersion: props.apiVersion,
862
- apiBaseUrl: props.apiBaseUrl,
863
- clientId: props.clientId,
864
- accessToken: props.accessToken,
865
- connectionName: props.connectionName,
866
- filters: getApplicableFilters(owner, props.filters),
867
- filtersLogicalOperator: props.filtersLogicalOperator,
868
- spatialDataType: props.spatialDataType,
869
- spatialDataColumn: props.spatialDataColumn,
870
- dataResolution: props.dataResolution
871
- };
859
+ if (spatialIndex === exports.SpatialIndex.QUADBIN) {
860
+ return Number(quadbin.getResolution(data[0].id));
872
861
  }
873
- _getSpatialFiltersResolution(source, spatialFilter, referenceViewState) {
874
- // spatialFiltersResolution applies only to spatial index sources.
875
- if (!spatialFilter || source.spatialDataType === 'geo') {
876
- return;
877
- }
878
- if (!referenceViewState) {
879
- throw new Error('Missing required option, "spatialIndexReferenceViewState".');
880
- }
881
- return getSpatialFiltersResolution(source, referenceViewState);
862
+ if (spatialIndex === exports.SpatialIndex.H3) {
863
+ return h3Js.getResolution(data[0].id);
882
864
  }
883
865
  }
884
- WidgetSource.defaultProps = {
885
- apiVersion: exports.ApiVersion.V3,
886
- apiBaseUrl: DEFAULT_API_BASE_URL,
887
- clientId: getClient(),
888
- filters: {},
889
- filtersLogicalOperator: 'and'
890
- };
891
-
892
- /**
893
- * Return more descriptive error from API
894
- * @internalRemarks Source: @carto/react-api
895
- */
896
-
897
- /** @internalRemarks Source: @carto/react-api */
898
-
899
- function _catch(body, recover) {
900
- try {
901
- var result = body();
902
- } catch (e) {
903
- return recover(e);
866
+ const bboxWest = [-180, -90, 0, 90];
867
+ const bboxEast = [0, -90, 180, 90];
868
+ function getCellsCoverGeometry(geometry, spatialIndex, resolution) {
869
+ if (spatialIndex === exports.SpatialIndex.QUADBIN) {
870
+ // @ts-expect-error TODO: Probably ought to be stricter about number vs. bigint types in this file.
871
+ return quadbin.geometryToCells(geometry, resolution);
904
872
  }
905
- if (result && result.then) {
906
- return result.then(void 0, recover);
873
+ if (spatialIndex === exports.SpatialIndex.H3) {
874
+ // The current H3 polyfill algorithm can't deal with polygon segments of greater than 180 degrees longitude
875
+ // so we clip the geometry to be sure that none of them is greater than 180 degrees
876
+ // https://github.com/uber/h3-js/issues/24#issuecomment-431893796
877
+ return h3Js.polygonToCells(bboxClip(geometry, bboxWest).geometry.coordinates, resolution, true).concat(h3Js.polygonToCells(bboxClip(geometry, bboxEast).geometry.coordinates, resolution, true));
907
878
  }
908
- return result;
909
879
  }
910
- const makeCall = function (_ref2) {
911
- let {
912
- url,
913
- accessToken,
914
- opts
915
- } = _ref2;
916
- try {
917
- let _exit;
918
- function _temp2(_result) {
919
- if (_exit) ;
920
- if (!response.ok) {
921
- dealWithApiError({
922
- response,
923
- data
924
- });
925
- }
926
- return data;
927
- }
928
- let response;
929
- let data;
930
- const isPost = opts?.method === 'POST';
931
- const _temp = _catch(function () {
932
- return Promise.resolve(fetch(url.toString(), {
933
- headers: {
934
- Authorization: `Bearer ${accessToken}`,
935
- ...(isPost && {
936
- 'Content-Type': 'application/json'
937
- })
938
- },
939
- ...(isPost && {
940
- method: opts?.method,
941
- body: opts?.body
942
- }),
943
- signal: opts?.abortController?.signal,
944
- ...opts?.otherOptions
945
- })).then(function (_fetch) {
946
- response = _fetch;
947
- return Promise.resolve(response.json()).then(function (_response$json) {
948
- data = _response$json;
949
- });
950
- });
951
- }, function (error) {
952
- if (error.name === 'AbortError') throw error;
953
- throw new Error(`Failed request: ${error}`);
954
- });
955
- return Promise.resolve(_temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp));
956
- } catch (e) {
957
- return Promise.reject(e);
958
- }
959
- };
960
- function dealWithApiError(_ref) {
961
- let {
962
- response,
963
- data
964
- } = _ref;
965
- if (data.error === 'Column not found') {
966
- throw new InvalidColumnError(`${data.error} ${data.column_name}`);
967
- }
968
- if (typeof data.error === 'string' && data.error?.includes('Missing columns')) {
969
- throw new InvalidColumnError(data.error);
970
- }
971
- switch (response.status) {
972
- case 401:
973
- throw new Error('Unauthorized access. Invalid credentials');
974
- case 403:
975
- throw new Error('Forbidden access to the requested data');
880
+ function getSpatialIndex(spatialDataType) {
881
+ switch (spatialDataType) {
882
+ case 'h3':
883
+ return exports.SpatialIndex.H3;
884
+ case 'quadbin':
885
+ return exports.SpatialIndex.QUADBIN;
976
886
  default:
977
- const msg = data && data.error && typeof data.error === 'string' ? data.error : JSON.stringify(data?.hint || data.error?.[0]);
978
- throw new Error(msg);
887
+ throw new Error('Unexpected spatial data type');
979
888
  }
980
889
  }
981
890
 
982
- /** @internalRemarks Source: @carto/react-api */
983
- const AVAILABLE_MODELS = ['category', 'histogram', 'formula', 'pick', 'timeseries', 'range', 'scatterplot', 'table'];
984
- const {
985
- V3
986
- } = exports.ApiVersion;
987
- const REQUEST_GET_MAX_URL_LENGTH = 2048;
988
891
  /**
989
- * Execute a SQL model request.
990
- * @internalRemarks Source: @carto/react-api
892
+ * Current version of @carto/api-client.
893
+ * @internal
991
894
  */
992
- function executeModel(props) {
993
- assert$1(props.source, 'executeModel: missing source');
994
- assert$1(props.model, 'executeModel: missing model');
995
- assert$1(props.params, 'executeModel: missing params');
996
- assert$1(AVAILABLE_MODELS.includes(props.model), `executeModel: model provided isn't valid. Available models: ${AVAILABLE_MODELS.join(', ')}`);
997
- const {
998
- model,
999
- source,
1000
- params,
1001
- opts
1002
- } = props;
1003
- const {
1004
- type,
1005
- apiVersion,
1006
- apiBaseUrl,
1007
- accessToken,
1008
- connectionName,
1009
- clientId
1010
- } = source;
1011
- assert$1(apiBaseUrl, 'executeModel: missing apiBaseUrl');
1012
- assert$1(accessToken, 'executeModel: missing accessToken');
1013
- assert$1(apiVersion === V3, 'executeModel: SQL Model API requires CARTO 3+');
1014
- assert$1(type !== 'tileset', 'executeModel: Tilesets not supported');
1015
- let url = `${apiBaseUrl}/v3/sql/${connectionName}/model/${model}`;
1016
- const {
1017
- data,
1018
- filters,
1019
- filtersLogicalOperator = 'and',
1020
- spatialDataType = 'geo',
1021
- spatialFiltersMode = 'intersects',
1022
- spatialFiltersResolution = 0
1023
- } = source;
1024
- const queryParams = {
1025
- type,
1026
- client: clientId,
1027
- source: data,
1028
- params,
1029
- queryParameters: source.queryParameters || '',
1030
- filters,
1031
- filtersLogicalOperator
1032
- };
1033
- const spatialDataColumn = source.spatialDataColumn || DEFAULT_GEO_COLUMN;
1034
- // Picking Model API requires 'spatialDataColumn'.
1035
- if (model === 'pick') {
1036
- queryParams.spatialDataColumn = spatialDataColumn;
895
+ /** @internal */
896
+ const V3_MINOR_VERSION = '3.4';
897
+ /** @privateRemarks Source: @carto/constants, @deck.gl/carto */
898
+ const DEFAULT_GEO_COLUMN = 'geom';
899
+ /**
900
+ * Fastly default limit is 8192; leave some padding.
901
+ * @privateRemarks Source: @deck.gl/carto
902
+ */
903
+ const DEFAULT_MAX_LENGTH_URL = 7000;
904
+ /** @privateRemarks Source: @deck.gl/carto */
905
+ const DEFAULT_TILE_RESOLUTION = 0.5;
906
+ /**
907
+ * @privateRemarks Source: @deck.gl/carto
908
+ * @internal
909
+ */
910
+ const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4;
911
+ /**
912
+ * @privateRemarks Source: @deck.gl/carto
913
+ * @internal
914
+ */
915
+ const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6;
916
+
917
+ function tileFeaturesRaster(_ref) {
918
+ let {
919
+ tiles,
920
+ ...options
921
+ } = _ref;
922
+ // Cache band metadata for faster lookup while iterating over pixels.
923
+ const bandMetadataByName = {};
924
+ for (const band of options.rasterMetadata.bands) {
925
+ bandMetadataByName[band.name] = band;
1037
926
  }
1038
- // API supports multiple filters, we apply it only to spatialDataColumn
1039
- const spatialFilters = source.spatialFilter ? {
1040
- [spatialDataColumn]: source.spatialFilter
1041
- } : undefined;
1042
- if (spatialFilters) {
1043
- queryParams.spatialFilters = spatialFilters;
1044
- queryParams.spatialDataColumn = spatialDataColumn;
1045
- queryParams.spatialDataType = spatialDataType;
1046
- }
1047
- if (spatialDataType !== 'geo') {
1048
- if (spatialFiltersResolution > 0) {
1049
- queryParams.spatialFiltersResolution = spatialFiltersResolution;
1050
- }
1051
- queryParams.spatialFiltersMode = spatialFiltersMode;
1052
- }
1053
- const urlWithSearchParams = url + '?' + objectToURLSearchParams(queryParams).toString();
1054
- const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
1055
- if (isGet) {
1056
- url = urlWithSearchParams;
1057
- }
1058
- return makeCall({
1059
- url,
1060
- accessToken: source.accessToken,
1061
- opts: {
1062
- ...opts,
1063
- method: isGet ? 'GET' : 'POST',
1064
- ...(!isGet && {
1065
- body: JSON.stringify(queryParams)
1066
- })
1067
- }
1068
- });
1069
- }
1070
- function objectToURLSearchParams(object) {
1071
- const params = new URLSearchParams();
1072
- for (const key in object) {
1073
- if (isPureObject(object[key])) {
1074
- params.append(key, JSON.stringify(object[key]));
1075
- } else if (Array.isArray(object[key])) {
1076
- params.append(key, JSON.stringify(object[key]));
1077
- } else if (object[key] === null) {
1078
- params.append(key, 'null');
1079
- } else if (object[key] !== undefined) {
1080
- params.append(key, String(object[key]));
1081
- }
1082
- }
1083
- return params;
1084
- }
1085
-
1086
- /**
1087
- * Source for Widget API requests.
1088
- *
1089
- * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
1090
- */
1091
- class WidgetRemoteSource extends WidgetSource {
1092
- getCategories(options) {
1093
- try {
1094
- const _this = this;
1095
- const {
1096
- filterOwner,
1097
- spatialFilter,
1098
- spatialFiltersMode,
1099
- spatialIndexReferenceViewState,
1100
- abortController,
1101
- ...params
1102
- } = options;
1103
- const {
1104
- column,
1105
- operation,
1106
- operationColumn
1107
- } = params;
1108
- const source = _this.getModelSource(filterOwner);
1109
- const spatialFiltersResolution = _this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1110
- return Promise.resolve(executeModel({
1111
- model: 'category',
1112
- source: {
1113
- ...source,
1114
- spatialFiltersResolution,
1115
- spatialFiltersMode,
1116
- spatialFilter
1117
- },
1118
- params: {
1119
- column,
1120
- operation,
1121
- operationColumn: operationColumn || column
1122
- },
1123
- opts: {
1124
- abortController
1125
- }
1126
- }).then(res => normalizeObjectKeys(res.rows)));
1127
- } catch (e) {
1128
- return Promise.reject(e);
1129
- }
1130
- }
1131
- getFeatures(options) {
1132
- try {
1133
- const _this2 = this;
1134
- const {
1135
- filterOwner,
1136
- spatialFilter,
1137
- spatialFiltersMode,
1138
- spatialIndexReferenceViewState,
1139
- abortController,
1140
- ...params
1141
- } = options;
1142
- const {
1143
- columns,
1144
- dataType,
1145
- featureIds,
1146
- z,
1147
- limit,
1148
- tileResolution
1149
- } = params;
1150
- const source = _this2.getModelSource(filterOwner);
1151
- const spatialFiltersResolution = _this2._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1152
- return Promise.resolve(executeModel({
1153
- model: 'pick',
1154
- source: {
1155
- ...source,
1156
- spatialFiltersResolution,
1157
- spatialFiltersMode,
1158
- spatialFilter
1159
- },
1160
- params: {
1161
- columns,
1162
- dataType,
1163
- featureIds,
1164
- z,
1165
- limit: limit || 1000,
1166
- tileResolution: tileResolution || DEFAULT_TILE_RESOLUTION
1167
- },
1168
- opts: {
1169
- abortController
1170
- }
1171
- // Avoid `normalizeObjectKeys()`, which changes column names.
1172
- }).then(_ref => {
1173
- let {
1174
- rows
1175
- } = _ref;
1176
- return {
1177
- rows
1178
- };
1179
- }));
1180
- } catch (e) {
1181
- return Promise.reject(e);
1182
- }
1183
- }
1184
- getFormula(options) {
1185
- try {
1186
- const _this3 = this;
1187
- const {
1188
- filterOwner,
1189
- spatialFilter,
1190
- spatialFiltersMode,
1191
- spatialIndexReferenceViewState,
1192
- abortController,
1193
- operationExp,
1194
- ...params
1195
- } = options;
1196
- const {
1197
- column,
1198
- operation
1199
- } = params;
1200
- const source = _this3.getModelSource(filterOwner);
1201
- const spatialFiltersResolution = _this3._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1202
- return Promise.resolve(executeModel({
1203
- model: 'formula',
1204
- source: {
1205
- ...source,
1206
- spatialFiltersResolution,
1207
- spatialFiltersMode,
1208
- spatialFilter
1209
- },
1210
- params: {
1211
- column: column ?? '*',
1212
- operation: operation ?? 'count',
1213
- operationExp
1214
- },
1215
- opts: {
1216
- abortController
1217
- }
1218
- }).then(res => normalizeObjectKeys(res.rows[0])));
1219
- } catch (e) {
1220
- return Promise.reject(e);
1221
- }
1222
- }
1223
- getHistogram(options) {
1224
- try {
1225
- const _this4 = this;
1226
- const {
1227
- filterOwner,
1228
- spatialFilter,
1229
- spatialFiltersMode,
1230
- spatialIndexReferenceViewState,
1231
- abortController,
1232
- ...params
1233
- } = options;
1234
- const {
1235
- column,
1236
- operation,
1237
- ticks
1238
- } = params;
1239
- const source = _this4.getModelSource(filterOwner);
1240
- const spatialFiltersResolution = _this4._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1241
- return Promise.resolve(executeModel({
1242
- model: 'histogram',
1243
- source: {
1244
- ...source,
1245
- spatialFiltersResolution,
1246
- spatialFiltersMode,
1247
- spatialFilter
1248
- },
1249
- params: {
1250
- column,
1251
- operation,
1252
- ticks
1253
- },
1254
- opts: {
1255
- abortController
1256
- }
1257
- }).then(res => normalizeObjectKeys(res.rows))).then(function (data) {
1258
- if (data.length) {
1259
- // Given N ticks the API returns up to N+1 bins, omitting any empty bins. Bins
1260
- // include 1 bin below the lowest tick, N-1 between ticks, and 1 bin above the highest tick.
1261
- const result = Array(ticks.length + 1).fill(0);
1262
- data.forEach(_ref2 => {
1263
- let {
1264
- tick,
1265
- value
1266
- } = _ref2;
1267
- return result[tick] = value;
1268
- });
1269
- return result;
1270
- }
1271
- return [];
1272
- });
1273
- } catch (e) {
1274
- return Promise.reject(e);
1275
- }
1276
- }
1277
- getRange(options) {
1278
- try {
1279
- const _this5 = this;
1280
- const {
1281
- filterOwner,
1282
- spatialFilter,
1283
- spatialFiltersMode,
1284
- spatialIndexReferenceViewState,
1285
- abortController,
1286
- ...params
1287
- } = options;
1288
- const {
1289
- column
1290
- } = params;
1291
- const source = _this5.getModelSource(filterOwner);
1292
- const spatialFiltersResolution = _this5._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1293
- return Promise.resolve(executeModel({
1294
- model: 'range',
1295
- source: {
1296
- ...source,
1297
- spatialFiltersResolution,
1298
- spatialFiltersMode,
1299
- spatialFilter
1300
- },
1301
- params: {
1302
- column
1303
- },
1304
- opts: {
1305
- abortController
1306
- }
1307
- }).then(res => normalizeObjectKeys(res.rows[0])));
1308
- } catch (e) {
1309
- return Promise.reject(e);
1310
- }
1311
- }
1312
- getScatter(options) {
1313
- try {
1314
- const _this6 = this;
1315
- const {
1316
- filterOwner,
1317
- spatialFilter,
1318
- spatialFiltersMode,
1319
- spatialIndexReferenceViewState,
1320
- abortController,
1321
- ...params
1322
- } = options;
1323
- const {
1324
- xAxisColumn,
1325
- xAxisJoinOperation,
1326
- yAxisColumn,
1327
- yAxisJoinOperation
1328
- } = params;
1329
- const source = _this6.getModelSource(filterOwner);
1330
- const spatialFiltersResolution = _this6._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1331
- // Make sure this is sync with the same constant in cloud-native/maps-api
1332
- const HARD_LIMIT = 500;
1333
- return Promise.resolve(executeModel({
1334
- model: 'scatterplot',
1335
- source: {
1336
- ...source,
1337
- spatialFiltersResolution,
1338
- spatialFiltersMode,
1339
- spatialFilter
1340
- },
1341
- params: {
1342
- xAxisColumn,
1343
- xAxisJoinOperation,
1344
- yAxisColumn,
1345
- yAxisJoinOperation,
1346
- limit: HARD_LIMIT
1347
- },
1348
- opts: {
1349
- abortController
927
+ // Omit empty and invisible tiles for simpler processing and types.
928
+ tiles = tiles.filter(isRasterTileVisible);
929
+ if (tiles.length === 0) return [];
930
+ // Raster tiles, and all pixels, are quadbin cells. Resolution of a pixel is
931
+ // the resolution of the tile, plus the number of subdivisions. Block size
932
+ // must be square, N x N, where N is a power of two.
933
+ const tileResolution = quadbin.getResolution(tiles[0].index.q);
934
+ const tileBlockSize = tiles[0].data.blockSize;
935
+ const cellResolution = tileResolution + BigInt(Math.log2(tileBlockSize));
936
+ // Compute covering cells for the spatial filter, at same resolution as the
937
+ // raster pixels, to be used as a mask.
938
+ const spatialFilterCells = new Set(quadbin.geometryToCells(options.spatialFilter, cellResolution));
939
+ const data = new Map();
940
+ for (const tile of tiles) {
941
+ const parent = tile.index.q;
942
+ const children = cellToChildrenSorted(parent, cellResolution);
943
+ // For each pixel/cell within the spatial filter, create a FeatureData.
944
+ // Order is row-major, starting from NW and ending at SE.
945
+ for (let i = 0; i < children.length; i++) {
946
+ if (!spatialFilterCells.has(children[i])) continue;
947
+ const cellData = {};
948
+ let cellDataExists = false;
949
+ for (const band in tile.data.cells.numericProps) {
950
+ const value = tile.data.cells.numericProps[band].value[i];
951
+ // TODO(cleanup): nodata should be a number, not a string.
952
+ if (Number(bandMetadataByName[band].nodata) !== value) {
953
+ cellData[band] = tile.data.cells.numericProps[band].value[i];
954
+ cellDataExists = true;
1350
955
  }
1351
- }).then(res => normalizeObjectKeys(res.rows)).then(res => res.map(_ref3 => {
1352
- let {
1353
- x,
1354
- y
1355
- } = _ref3;
1356
- return [x, y];
1357
- })));
1358
- } catch (e) {
1359
- return Promise.reject(e);
956
+ }
957
+ if (cellDataExists) {
958
+ data.set(children[i], cellData);
959
+ }
1360
960
  }
1361
961
  }
1362
- getTable(options) {
1363
- try {
1364
- const _this7 = this;
1365
- const {
1366
- filterOwner,
1367
- spatialFilter,
1368
- spatialFiltersMode,
1369
- spatialIndexReferenceViewState,
1370
- abortController,
1371
- ...params
1372
- } = options;
1373
- const {
1374
- columns,
1375
- sortBy,
1376
- sortDirection,
1377
- offset = 0,
1378
- limit = 10
1379
- } = params;
1380
- const source = _this7.getModelSource(filterOwner);
1381
- const spatialFiltersResolution = _this7._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1382
- return Promise.resolve(executeModel({
1383
- model: 'table',
1384
- source: {
1385
- ...source,
1386
- spatialFiltersResolution,
1387
- spatialFiltersMode,
1388
- spatialFilter
1389
- },
1390
- params: {
1391
- column: columns,
1392
- sortBy,
1393
- sortDirection,
1394
- limit,
1395
- offset
1396
- },
1397
- opts: {
1398
- abortController
1399
- }
1400
- }).then(res => ({
1401
- // Avoid `normalizeObjectKeys()`, which changes column names.
1402
- rows: res.rows ?? res.ROWS,
1403
- totalCount: res.metadata?.total ?? res.METADATA?.TOTAL
1404
- })));
1405
- } catch (e) {
1406
- return Promise.reject(e);
962
+ return Array.from(data.values());
963
+ }
964
+ /**
965
+ * Detects whether a given {@link Tile} is a {@link RasterTile}.
966
+ * @privateRemarks Method of detection is arbitrary, and may be changed.
967
+ */
968
+ function isRasterTile(tile) {
969
+ return !!tile.data?.cells;
970
+ }
971
+ function isRasterTileVisible(tile) {
972
+ return !!(tile.isVisible && tile.data?.cells?.numericProps);
973
+ }
974
+ /**
975
+ * For the raster format, children are sorted in row-major order, starting from
976
+ * NW and ending at SE. Order returned by quadbin's cellToChildren() is not
977
+ * defined (and not related to the raster format), so sort explicitly here.
978
+ */
979
+ function cellToChildrenSorted(parent, resolution) {
980
+ return quadbin.cellToChildren(parent, resolution).sort((cellA, cellB) => {
981
+ const tileA = quadbin.cellToTile(cellA);
982
+ const tileB = quadbin.cellToTile(cellB);
983
+ if (tileA.y !== tileB.y) {
984
+ return tileA.y > tileB.y ? 1 : -1;
1407
985
  }
1408
- }
1409
- getTimeSeries(options) {
1410
- try {
1411
- const _this8 = this;
1412
- const {
1413
- filterOwner,
1414
- abortController,
1415
- spatialFilter,
1416
- spatialFiltersMode,
1417
- spatialIndexReferenceViewState,
1418
- ...params
1419
- } = options;
1420
- const {
1421
- column,
1422
- operationColumn,
1423
- joinOperation,
1424
- operation,
1425
- stepSize,
1426
- stepMultiplier,
1427
- splitByCategory,
1428
- splitByCategoryLimit,
1429
- splitByCategoryValues
1430
- } = params;
1431
- const source = _this8.getModelSource(filterOwner);
1432
- const spatialFiltersResolution = _this8._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
1433
- return Promise.resolve(executeModel({
1434
- model: 'timeseries',
1435
- source: {
1436
- ...source,
1437
- spatialFiltersResolution,
1438
- spatialFiltersMode,
1439
- spatialFilter
1440
- },
1441
- params: {
1442
- column,
1443
- stepSize,
1444
- stepMultiplier,
1445
- operationColumn: operationColumn || column,
1446
- joinOperation,
1447
- operation,
1448
- splitByCategory,
1449
- splitByCategoryLimit,
1450
- splitByCategoryValues
1451
- },
1452
- opts: {
1453
- abortController
1454
- }
1455
- }).then(res => ({
1456
- rows: normalizeObjectKeys(res.rows),
1457
- categories: res.metadata?.categories
1458
- })));
1459
- } catch (e) {
1460
- return Promise.reject(e);
986
+ return tileA.x > tileB.x ? 1 : -1;
987
+ });
988
+ }
989
+
990
+ const FILTER_TYPES = new Set(Object.values(exports.FilterType));
991
+ const isFilterType = type => FILTER_TYPES.has(type);
992
+ /**
993
+ * @privateRemarks Source: @carto/react-widgets
994
+ * @internal
995
+ */
996
+ function getApplicableFilters(owner, filters) {
997
+ if (!filters) return {};
998
+ const applicableFilters = {};
999
+ for (const column in filters) {
1000
+ for (const type in filters[column]) {
1001
+ if (!isFilterType(type)) continue;
1002
+ const filter = filters[column][type];
1003
+ const isApplicable = !owner || !filter?.owner || filter?.owner !== owner;
1004
+ if (filter && isApplicable) {
1005
+ applicableFilters[column] ||= {};
1006
+ applicableFilters[column][type] = filter;
1007
+ }
1461
1008
  }
1462
1009
  }
1010
+ return applicableFilters;
1463
1011
  }
1464
-
1465
1012
  /**
1466
- * Source for Widget API requests on a data source defined by a SQL query.
1467
- *
1468
- * Generally not intended to be constructed directly. Instead, call
1469
- * {@link vectorQuerySource}, {@link h3QuerySource}, or {@link quadbinQuerySource},
1470
- * which can be shared with map layers. Sources contain a `widgetSource` property,
1471
- * for use by widget implementations.
1472
- *
1473
- * Example:
1474
- *
1475
- * ```javascript
1476
- * import { vectorQuerySource } from '@carto/api-client';
1477
- *
1478
- * const data = vectorQuerySource({
1479
- * accessToken: '••••',
1480
- * connectionName: 'carto_dw',
1481
- * sqlQuery: 'SELECT * FROM carto-demo-data.demo_tables.retail_stores'
1482
- * });
1013
+ * Due to each data warehouse having its own behavior with columns,
1014
+ * we need to normalize them and transform every key to lowercase.
1483
1015
  *
1484
- * const { widgetSource } = await data;
1485
- * ```
1016
+ * @privateRemarks Source: @carto/react-widgets
1017
+ * @internal
1486
1018
  */
1487
- class WidgetQuerySource extends WidgetRemoteSource {
1488
- getModelSource(owner) {
1489
- return {
1490
- ...super._getModelSource(owner),
1491
- type: 'query',
1492
- data: this.props.sqlQuery,
1493
- queryParameters: this.props.queryParameters
1494
- };
1019
+ function normalizeObjectKeys(el) {
1020
+ if (Array.isArray(el)) {
1021
+ return el.map(value => normalizeObjectKeys(value));
1022
+ } else if (typeof el !== 'object') {
1023
+ return el;
1024
+ }
1025
+ return Object.entries(el).reduce((acc, _ref) => {
1026
+ let [key, value] = _ref;
1027
+ acc[key.toLowerCase()] = typeof value === 'object' && value ? normalizeObjectKeys(value) : value;
1028
+ return acc;
1029
+ }, {});
1030
+ }
1031
+ /** @privateRemarks Source: @carto/react-core */
1032
+ function assert(condition, message) {
1033
+ if (!condition) {
1034
+ throw new Error(message);
1035
+ }
1036
+ }
1037
+ /**
1038
+ * @privateRemarks Source: @carto/react-core
1039
+ * @internal
1040
+ */
1041
+ class InvalidColumnError extends Error {
1042
+ constructor(message) {
1043
+ super(`${InvalidColumnError.NAME}: ${message}`);
1044
+ this.name = InvalidColumnError.NAME;
1045
+ }
1046
+ static is(error) {
1047
+ return error instanceof InvalidColumnError || error.message?.includes(InvalidColumnError.NAME);
1048
+ }
1049
+ }
1050
+ InvalidColumnError.NAME = 'InvalidColumnError';
1051
+ function isEmptyObject(object) {
1052
+ for (const _ in object) {
1053
+ return false;
1054
+ }
1055
+ return true;
1056
+ }
1057
+ /** @internal */
1058
+ const isObject = x => x !== null && typeof x === 'object';
1059
+ /** @internal */
1060
+ const isPureObject = x => isObject(x) && x.constructor === {}.constructor;
1061
+
1062
+ /** @privateRemarks Source: @carto/react-core */
1063
+ function tileFeatures(_ref) {
1064
+ let {
1065
+ tiles,
1066
+ spatialFilter,
1067
+ uniqueIdProperty,
1068
+ tileFormat,
1069
+ spatialDataColumn = DEFAULT_GEO_COLUMN,
1070
+ spatialDataType,
1071
+ rasterMetadata,
1072
+ options = {}
1073
+ } = _ref;
1074
+ if (spatialDataType === 'geo') {
1075
+ return tileFeaturesGeometries({
1076
+ tiles,
1077
+ tileFormat,
1078
+ spatialFilter,
1079
+ uniqueIdProperty,
1080
+ options
1081
+ });
1082
+ }
1083
+ if (tiles.some(isRasterTile)) {
1084
+ assert(rasterMetadata, 'Missing raster metadata');
1085
+ return tileFeaturesRaster({
1086
+ tiles: tiles,
1087
+ spatialFilter,
1088
+ spatialDataColumn,
1089
+ spatialDataType,
1090
+ rasterMetadata
1091
+ });
1495
1092
  }
1496
- }
1497
-
1498
- function makeIntervalComplete(intervals) {
1499
- return intervals.map(val => {
1500
- if (val[0] === undefined || val[0] === null) {
1501
- return [Number.MIN_SAFE_INTEGER, val[1]];
1502
- }
1503
- if (val[1] === undefined || val[1] === null) {
1504
- return [val[0], Number.MAX_SAFE_INTEGER];
1505
- }
1506
- return val;
1093
+ return tileFeaturesSpatialIndex({
1094
+ tiles: tiles,
1095
+ spatialFilter,
1096
+ spatialDataColumn,
1097
+ spatialDataType
1507
1098
  });
1508
1099
  }
1509
1100
 
1510
- const filterFunctions = {
1511
- [exports.FilterType.IN]: filterIn,
1512
- [exports.FilterType.BETWEEN]: filterBetween,
1513
- [exports.FilterType.TIME]: filterTime,
1514
- [exports.FilterType.CLOSED_OPEN]: filterClosedOpen,
1515
- [exports.FilterType.STRING_SEARCH]: filterStringSearch
1516
- };
1517
- function filterIn(filterValues, featureValue) {
1518
- return filterValues.includes(featureValue);
1519
- }
1520
- // FilterTypes.BETWEEN
1521
- function filterBetween(filterValues, featureValue) {
1522
- const checkRange = range => {
1523
- const [lowerBound, upperBound] = range;
1524
- return featureValue >= lowerBound && featureValue <= upperBound;
1101
+ /**
1102
+ * Creates props for DataFilterExtension, from `@deck.gl/extensions`, given
1103
+ * a set of filters.
1104
+ *
1105
+ * @privateRemarks DataFilterExtension accepts up to 4 values to filter. This
1106
+ * implementation uses the 1st for all filters except the time filter, and the
1107
+ * 2nd for the time filter.
1108
+ */
1109
+ function getDataFilterExtensionProps(filters, filtersLogicalOperator, filterSize) {
1110
+ filterSize ??= 4;
1111
+ const {
1112
+ filtersWithoutTimeType,
1113
+ timeColumn,
1114
+ timeFilter
1115
+ } = getFiltersByType(filters);
1116
+ return {
1117
+ filterRange: getFilterRange(timeFilter, filterSize),
1118
+ updateTriggers: getUpdateTriggers(filtersWithoutTimeType, timeColumn, timeFilter),
1119
+ getFilterValue: getFilterValue(filtersWithoutTimeType, timeColumn, timeFilter, filterSize, filtersLogicalOperator)
1525
1120
  };
1526
- return makeIntervalComplete(filterValues).some(checkRange);
1527
1121
  }
1528
- function filterTime(filterValues, featureValue) {
1529
- const featureValueAsTimestamp = new Date(featureValue).getTime();
1530
- if (isFinite(featureValueAsTimestamp)) {
1531
- return filterBetween(filterValues, featureValueAsTimestamp);
1532
- } else {
1533
- throw new Error(`Column used to filter by time isn't well formatted.`);
1122
+ /** @internal */
1123
+ function getFiltersByType(filters) {
1124
+ const filtersWithoutTimeType = {};
1125
+ let timeColumn = null;
1126
+ let timeFilter = null;
1127
+ for (const [column, columnData] of Object.entries(filters)) {
1128
+ for (const [type, typeData] of Object.entries(columnData)) {
1129
+ if (type === exports.FilterType.TIME) {
1130
+ timeColumn = column;
1131
+ timeFilter = typeData;
1132
+ } else {
1133
+ filtersWithoutTimeType[column] = {
1134
+ [type]: typeData
1135
+ };
1136
+ }
1137
+ }
1534
1138
  }
1535
- }
1536
- // FilterTypes.CLOSED_OPEN
1537
- function filterClosedOpen(filterValues, featureValue) {
1538
- const checkRange = range => {
1539
- const [lowerBound, upperBound] = range;
1540
- return featureValue >= lowerBound && featureValue < upperBound;
1139
+ return {
1140
+ filtersWithoutTimeType,
1141
+ timeColumn,
1142
+ timeFilter
1541
1143
  };
1542
- return makeIntervalComplete(filterValues).some(checkRange);
1543
1144
  }
1544
- // FilterTypes.STRING_SEARCH
1545
- function filterStringSearch(filterValues, featureValue, params) {
1546
- if (params === void 0) {
1547
- params = {};
1145
+ /** @internal */
1146
+ function getFilterRange(timeFilter, filterSize) {
1147
+ const result = Array(filterSize).fill([0, 0]);
1148
+ // According to getFilterValue all filters are resolved as 0 or 1 in the first position of the array
1149
+ // except the time filter value that is resolved with the real value of the feature in the second position of the array
1150
+ result[0] = [1, 1];
1151
+ if (timeFilter) {
1152
+ const offsetBy = timeFilter.params?.offsetBy || 0;
1153
+ result[1] = timeFilter.values[0].map(v => v - offsetBy);
1548
1154
  }
1549
- const normalizedFeatureValue = normalize(featureValue, params);
1550
- const stringRegExp = params.useRegExp ? filterValues : filterValues.map(filterValue => {
1551
- let stringRegExp = escapeRegExp(normalize(filterValue, params));
1552
- if (params.mustStart) stringRegExp = `^${stringRegExp}`;
1553
- if (params.mustEnd) stringRegExp = `${stringRegExp}$`;
1554
- return stringRegExp;
1555
- });
1556
- const regex = new RegExp(stringRegExp.join('|'), params.caseSensitive ? 'g' : 'gi');
1557
- return !!normalizedFeatureValue.match(regex);
1558
- }
1559
- // Aux
1560
- const specialCharRegExp = /[.*+?^${}()|[\]\\]/g;
1561
- const normalizeRegExp = /\p{Diacritic}/gu;
1562
- function escapeRegExp(value) {
1563
- return value.replace(specialCharRegExp, '\\$&');
1564
- }
1565
- function normalize(data, params) {
1566
- let normalizedData = String(data);
1567
- if (!params.keepSpecialCharacters) normalizedData = normalizedData.normalize('NFD').replace(normalizeRegExp, '');
1568
- return normalizedData;
1569
- }
1570
-
1571
- const LOGICAL_OPERATOR_METHODS = {
1572
- and: 'every',
1573
- or: 'some'
1574
- };
1575
- function passesFilter(columns, filters, feature, filtersLogicalOperator) {
1576
- const method = LOGICAL_OPERATOR_METHODS[filtersLogicalOperator];
1577
- return columns[method](column => {
1578
- const columnFilters = filters[column];
1579
- const columnFilterTypes = Object.keys(columnFilters);
1580
- if (!feature || feature[column] === null || feature[column] === undefined) {
1581
- return false;
1582
- }
1583
- return columnFilterTypes.every(filter => {
1584
- const filterFunction = filterFunctions[filter];
1585
- if (!filterFunction) {
1586
- throw new Error(`"${filter}" filter is not implemented.`);
1587
- }
1588
- return filterFunction(columnFilters[filter].values, feature[column], columnFilters[filter].params);
1589
- });
1590
- });
1155
+ return result;
1591
1156
  }
1592
- function buildFeatureFilter(_ref) {
1593
- let {
1594
- filters = {},
1595
- type = 'boolean',
1596
- filtersLogicalOperator = 'and'
1597
- } = _ref;
1598
- const columns = Object.keys(filters);
1599
- if (!columns.length) {
1600
- return () => type === 'number' ? 1 : true;
1157
+ /** @internal */
1158
+ function getUpdateTriggers(filtersWithoutTimeType, timeColumn, timeFilter) {
1159
+ const result = {
1160
+ ...filtersWithoutTimeType
1161
+ };
1162
+ // We don't want to change the layer UpdateTriggers every time that the time filter changes
1163
+ // because this filter is changed by the time series widget during its animation
1164
+ // so we remove the time filter value from the `updateTriggers`
1165
+ if (timeColumn && timeFilter) {
1166
+ result[timeColumn] = {
1167
+ ...result[timeColumn],
1168
+ offsetBy: timeFilter.params?.offsetBy,
1169
+ [exports.FilterType.TIME]: {} // Allows working with other filters, without an impact on performance.
1170
+ };
1601
1171
  }
1602
- return feature => {
1603
- const f = feature.properties || feature;
1604
- const featurePassesFilter = passesFilter(columns, filters, f, filtersLogicalOperator);
1605
- return type === 'number' ? Number(featurePassesFilter) : featurePassesFilter;
1172
+ return {
1173
+ getFilterValue: JSON.stringify(result)
1606
1174
  };
1607
1175
  }
1608
- // Apply certain filters to a collection of features
1609
- function applyFilters(features, filters, filtersLogicalOperator) {
1610
- return Object.keys(filters).length ? features.filter(buildFeatureFilter({
1611
- filters,
1176
+ /** @internal */
1177
+ function getFilterValue(filtersWithoutTimeType, timeColumn, timeFilter, filterSize, filtersLogicalOperator) {
1178
+ const result = Array(filterSize).fill(0);
1179
+ const featureFilter = _buildFeatureFilter({
1180
+ filters: filtersWithoutTimeType,
1181
+ type: 'number',
1612
1182
  filtersLogicalOperator
1613
- })) : features;
1614
- }
1615
- // Binary
1616
- function buildBinaryFeatureFilter(_ref2) {
1617
- let {
1618
- filters = {}
1619
- } = _ref2;
1620
- const columns = Object.keys(filters);
1621
- if (!columns.length) {
1622
- return () => 1;
1623
- }
1624
- return (featureIdIdx, binaryData) => passesFilterUsingBinary(columns, filters, featureIdIdx, binaryData);
1625
- }
1626
- function getValueFromNumericProps(featureIdIdx, binaryData, _ref3) {
1627
- let {
1628
- column
1629
- } = _ref3;
1630
- return binaryData.numericProps?.[column]?.value[featureIdIdx];
1631
- }
1632
- function getValueFromProperties(featureIdIdx, binaryData, _ref4) {
1633
- let {
1634
- column
1635
- } = _ref4;
1636
- const propertyIdx = binaryData.featureIds.value[featureIdIdx];
1637
- return binaryData.properties[propertyIdx]?.[column];
1638
- }
1639
- const GET_VALUE_BY_BINARY_PROP = {
1640
- properties: getValueFromProperties,
1641
- numericProps: getValueFromNumericProps
1642
- };
1643
- function getBinaryPropertyByFilterValues(filterValues) {
1644
- return typeof filterValues.flat()[0] === 'string' ? 'properties' : 'numericProps';
1645
- }
1646
- function getFeatureValue(featureIdIdx, binaryData, filter) {
1647
- const {
1648
- column,
1649
- values
1650
- } = filter;
1651
- const binaryProp = getBinaryPropertyByFilterValues(values);
1652
- const getFeatureValueFn = GET_VALUE_BY_BINARY_PROP[binaryProp];
1653
- return getFeatureValueFn(featureIdIdx, binaryData, {
1654
- column
1655
- });
1656
- }
1657
- function passesFilterUsingBinary(columns, filters, featureIdIdx, binaryData) {
1658
- return columns.every(column => {
1659
- const columnFilters = filters[column];
1660
- return Object.entries(columnFilters).every(_ref5 => {
1661
- let [type, {
1662
- values
1663
- }] = _ref5;
1664
- const filterFn = filterFunctions[type];
1665
- if (!filterFn) {
1666
- throw new Error(`"${type}" filter is not implemented.`);
1667
- }
1668
- if (!values) return 0;
1669
- const featureValue = getFeatureValue(featureIdIdx, binaryData, {
1670
- type: type,
1671
- column,
1672
- values
1673
- });
1674
- if (featureValue === undefined || featureValue === null) return 0;
1675
- return filterFn(values, featureValue);
1676
- });
1677
1183
  });
1184
+ // We evaluate all filters except the time filter using _buildFeatureFilter function.
1185
+ // For the time filter, we return the value of the feature and we will change the getFilterRange result
1186
+ // every time this filter changes
1187
+ return feature => {
1188
+ result[0] = featureFilter(feature);
1189
+ if (timeColumn && timeFilter) {
1190
+ const offsetBy = timeFilter.params?.offsetBy || 0;
1191
+ const f = feature.properties || feature;
1192
+ result[1] = f[timeColumn] - offsetBy;
1193
+ }
1194
+ return result;
1195
+ };
1678
1196
  }
1679
1197
 
1680
- function geojsonFeatures(_ref) {
1198
+ /**
1199
+ * Adds a {@link Filter} to the filter set. Any previous filters with the same
1200
+ * `column` and `type` will be replaced.
1201
+ */
1202
+ function addFilter(filters, _ref) {
1681
1203
  let {
1682
- geojson,
1683
- spatialFilter,
1684
- uniqueIdProperty
1204
+ column,
1205
+ type,
1206
+ values,
1207
+ owner
1685
1208
  } = _ref;
1686
- let uniqueIdx = 0;
1687
- const map = new Map();
1688
- if (!spatialFilter) {
1689
- return [];
1690
- }
1691
- for (const feature of geojson.features) {
1692
- const uniqueId = uniqueIdProperty ? feature.properties[uniqueIdProperty] : ++uniqueIdx;
1693
- if (!map.has(uniqueId) && intersects(spatialFilter, feature)) {
1694
- map.set(uniqueId, feature.properties);
1695
- }
1209
+ if (!filters[column]) {
1210
+ filters[column] = {};
1696
1211
  }
1697
- return Array.from(map.values());
1212
+ const filter = {
1213
+ values,
1214
+ owner
1215
+ };
1216
+ filters[column][type] = filter;
1217
+ return filters;
1698
1218
  }
1699
-
1700
- // math.gl
1701
- // SPDX-License-Identifier: MIT
1702
- // Copyright (c) vis.gl contributors
1703
- const DEFAULT_CONFIG = {
1704
- EPSILON: 1e-12,
1705
- debug: false,
1706
- precision: 4,
1707
- printTypes: false,
1708
- printDegrees: false,
1709
- printRowMajor: true,
1710
- _cartographicRadians: false
1711
- };
1712
- // Configuration is truly global as of v3.6 to ensure single config even if multiple copies of math.gl
1713
- // Multiple copies of config can be quite tricky to debug...
1714
- globalThis.mathgl = globalThis.mathgl || {
1715
- config: {
1716
- ...DEFAULT_CONFIG
1219
+ /**
1220
+ * Removes one or more {@link Filter filters} from the filter set. If only
1221
+ * `column` is specified, then all filters on that column are removed. If both
1222
+ * `column` and `owner` are specified, then only filters for that column
1223
+ * associated with the owner are removed.
1224
+ */
1225
+ function removeFilter(filters, _ref2) {
1226
+ let {
1227
+ column,
1228
+ owner
1229
+ } = _ref2;
1230
+ const filter = filters[column];
1231
+ if (!filter) {
1232
+ return filters;
1717
1233
  }
1718
- };
1234
+ if (owner) {
1235
+ for (const type of Object.values(exports.FilterType)) {
1236
+ if (owner === filter[type]?.owner) {
1237
+ delete filter[type];
1238
+ }
1239
+ }
1240
+ }
1241
+ if (!owner || isEmptyObject(filter)) {
1242
+ delete filters[column];
1243
+ }
1244
+ return filters;
1245
+ }
1719
1246
  /**
1720
- * Check if value is an "array"
1721
- * Returns `true` if value is either an array or a typed array
1722
- * Note: returns `false` for `ArrayBuffer` and `DataView` instances
1723
- * @note isTypedArray and isNumericArray are often more useful in TypeScript
1247
+ * Clears all {@link Filter filters} from the filter set.
1724
1248
  */
1725
- function isArray(value) {
1726
- return Array.isArray(value) || ArrayBuffer.isView(value) && !(value instanceof DataView);
1249
+ function clearFilters(filters) {
1250
+ for (const column of Object.keys(filters)) {
1251
+ delete filters[column];
1252
+ }
1253
+ return filters;
1727
1254
  }
1728
- function lerp(a, b, t) {
1729
- if (isArray(a)) {
1730
- return a.map((ai, i) => lerp(ai, b[i], t));
1255
+ function hasFilter(filters, _ref3) {
1256
+ let {
1257
+ column,
1258
+ owner
1259
+ } = _ref3;
1260
+ const filter = filters[column];
1261
+ if (!filter) {
1262
+ return false;
1731
1263
  }
1732
- return t * b + (1 - t) * a;
1264
+ if (!owner) {
1265
+ return true;
1266
+ }
1267
+ for (const type of Object.values(exports.FilterType)) {
1268
+ if (owner === filter[type]?.owner) {
1269
+ return true;
1270
+ }
1271
+ }
1272
+ return false;
1733
1273
  }
1734
-
1735
- // Replacement for the external assert method to reduce bundle size
1736
- // Note: We don't use the second "message" argument in calling code,
1737
- // so no need to support it here
1738
- function assert(condition, message) {
1739
- if (!condition) {
1740
- throw new Error(message || '@math.gl/web-mercator: assertion failed.');
1274
+ function getFilter(filters, _ref4) {
1275
+ let {
1276
+ column,
1277
+ type,
1278
+ owner
1279
+ } = _ref4;
1280
+ const filter = filters[column];
1281
+ if (!filter) {
1282
+ return null;
1741
1283
  }
1284
+ if (!owner || owner === filter[type]?.owner) {
1285
+ return filter[type] || null;
1286
+ }
1287
+ return null;
1742
1288
  }
1743
1289
 
1744
- // TODO - THE UTILITIES IN THIS FILE SHOULD BE IMPORTED FROM WEB-MERCATOR-VIEWPORT MODULE
1745
- // CONSTANTS
1746
- const PI = Math.PI;
1747
- const PI_4 = PI / 4;
1748
- const DEGREES_TO_RADIANS = PI / 180;
1749
- const RADIANS_TO_DEGREES = 180 / PI;
1750
- const TILE_SIZE = 512;
1751
1290
  /**
1752
- * Project [lng,lat] on sphere onto [x,y] on 512*512 Mercator Zoom 0 tile.
1753
- * Performs the nonlinear part of the web mercator projection.
1754
- * Remaining projection is done with 4x4 matrices which also handles
1755
- * perspective.
1291
+ * Returns a {@link SpatialFilter} for a given viewport, typically obtained
1292
+ * from deck.gl's `viewport.getBounds()` method ([west, south, east, north]).
1293
+ * If the viewport covers the entire world (to some margin of error in Web
1294
+ * Mercator space), `undefined` is returned instead.
1756
1295
  *
1757
- * @param lngLat - [lng, lat] coordinates
1758
- * Specifies a point on the sphere to project onto the map.
1759
- * @return [x,y] coordinates.
1296
+ * If the viewport extends beyond longitude range [-180, +180], the polygon
1297
+ * may be reformatted for compatibility with CARTO APIs.
1760
1298
  */
1761
- function lngLatToWorld(lngLat) {
1762
- const [lng, lat] = lngLat;
1763
- assert(Number.isFinite(lng));
1764
- assert(Number.isFinite(lat) && lat >= -90 && lat <= 90, 'invalid latitude');
1765
- const lambda2 = lng * DEGREES_TO_RADIANS;
1766
- const phi2 = lat * DEGREES_TO_RADIANS;
1767
- const x = TILE_SIZE * (lambda2 + PI) / (2 * PI);
1768
- const y = TILE_SIZE * (PI + Math.log(Math.tan(PI_4 + phi2 * 0.5))) / (2 * PI);
1769
- return [x, y];
1299
+ function createViewportSpatialFilter(viewport) {
1300
+ if (_isGlobalViewport(viewport)) {
1301
+ return;
1302
+ }
1303
+ return createPolygonSpatialFilter(bboxPolygon(viewport).geometry);
1770
1304
  }
1771
1305
  /**
1772
- * Unproject world point [x,y] on map onto {lat, lon} on sphere
1306
+ * Returns a {@link SpatialFilter} for a given {@link Polygon} or
1307
+ * {@link MultiPolygon}. If the polygon(s) extend outside longitude
1308
+ * range [-180, +180], the result may be reformatted for compatibility
1309
+ * with CARTO APIs.
1310
+ */
1311
+ function createPolygonSpatialFilter(spatialFilter) {
1312
+ return spatialFilter && _normalizeGeometry(spatialFilter) || undefined;
1313
+ }
1314
+ /**
1315
+ * Check if a viewport is large enough to represent a global coverage.
1316
+ * In this case the spatial filter parameter for widget calculation is removed.
1773
1317
  *
1774
- * @param xy - array with [x,y] members
1775
- * representing point on projected map plane
1776
- * @return - array with [x,y] of point on sphere.
1777
- * Has toArray method if you need a GeoJSON Array.
1778
- * Per cartographic tradition, lat and lon are specified as degrees.
1318
+ * @privateRemarks Source: @carto/react-core
1779
1319
  */
1780
- function worldToLngLat(xy) {
1781
- const [x, y] = xy;
1782
- const lambda2 = x / TILE_SIZE * (2 * PI) - PI;
1783
- const phi2 = 2 * (Math.atan(Math.exp(y / TILE_SIZE * (2 * PI) - PI)) - PI_4);
1784
- return [lambda2 * RADIANS_TO_DEGREES, phi2 * RADIANS_TO_DEGREES];
1320
+ function _isGlobalViewport(viewport) {
1321
+ const [minx, miny, maxx, maxy] = viewport;
1322
+ return maxx - minx > 179.5 * 2 && maxy - miny > 85.05 * 2;
1785
1323
  }
1786
-
1787
- const TRANSFORM_FN$1 = {
1788
- Point: transformPoint$1,
1789
- MultiPoint: transformMultiPoint$1,
1790
- LineString: transformLineString$1,
1791
- MultiLineString: transformMultiLineString$1,
1792
- Polygon: transformPolygon$1,
1793
- MultiPolygon: transformMultiPolygon$1
1794
- };
1795
1324
  /**
1796
- * Transform WGS84 coordinates to tile coords.
1797
- * 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)
1325
+ * Normalized a geometry, coming from a mask or a viewport. The parts
1326
+ * spanning outside longitude range [-180, +180] are clipped and "folded"
1327
+ * back to the valid range and unioned to the polygons inide that range.
1798
1328
  *
1799
- * @param geometry - any valid geojson geometry
1800
- * @param bbox - geojson bbox
1329
+ * It results in a Polygon or MultiPolygon strictly inside the validity range.
1330
+ *
1331
+ * @privateRemarks Source: @carto/react-core
1801
1332
  */
1802
- function transformToTileCoords(geometry, bbox) {
1803
- const [west, south, east, north] = bbox;
1804
- const nw = projectFlat([west, north]);
1805
- const se = projectFlat([east, south]);
1806
- const projectedBbox = [nw, se];
1807
- if (geometry.type === 'GeometryCollection') {
1808
- throw new Error('Unsupported geometry type GeometryCollection');
1333
+ function _normalizeGeometry(geometry) {
1334
+ const WORLD = [-180, -90, +180, +90];
1335
+ const worldClip = _clean(bboxClip(geometry, WORLD).geometry);
1336
+ const geometryTxWest = _tx(geometry, 360);
1337
+ const geometryTxEast = _tx(geometry, -360);
1338
+ let result = worldClip;
1339
+ if (result && geometryTxWest) {
1340
+ const worldWestClip = _clean(bboxClip(geometryTxWest, WORLD).geometry);
1341
+ if (worldWestClip) {
1342
+ const collection = helpers.featureCollection([helpers.feature(result), helpers.feature(worldWestClip)]);
1343
+ const merged = union(collection);
1344
+ result = merged ? _clean(merged.geometry) : result;
1345
+ }
1809
1346
  }
1810
- const transformFn = TRANSFORM_FN$1[geometry.type];
1811
- const coordinates = transformFn(geometry.coordinates, projectedBbox);
1812
- return {
1813
- ...geometry,
1814
- coordinates
1815
- };
1347
+ if (result && geometryTxEast) {
1348
+ const worldEastClip = _clean(bboxClip(geometryTxEast, WORLD).geometry);
1349
+ if (worldEastClip) {
1350
+ const collection = helpers.featureCollection([helpers.feature(result), helpers.feature(worldEastClip)]);
1351
+ const merged = union(collection);
1352
+ result = merged ? _clean(merged.geometry) : result;
1353
+ }
1354
+ }
1355
+ return result;
1816
1356
  }
1817
- function transformPoint$1(_ref, _ref2) {
1818
- let [pointX, pointY] = _ref;
1819
- let [nw, se] = _ref2;
1820
- const x = inverseLerp(nw[0], se[0], pointX);
1821
- const y = inverseLerp(nw[1], se[1], pointY);
1822
- return [x, y];
1357
+ /** @privateRemarks Source: @carto/react-core */
1358
+ function _cleanPolygonCoords(cc) {
1359
+ const coords = cc.filter(c => c.length > 0);
1360
+ return coords.length > 0 ? coords : null;
1823
1361
  }
1824
- function getPoints$1(geometry, bbox) {
1825
- return geometry.map(g => transformPoint$1(projectFlat(g), bbox));
1362
+ /** @privateRemarks Source: @carto/react-core */
1363
+ function _cleanMultiPolygonCoords(ccc) {
1364
+ const coords = ccc.map(_cleanPolygonCoords).filter(cc => cc);
1365
+ return coords.length > 0 ? coords : null;
1826
1366
  }
1827
- function transformMultiPoint$1(multiPoint, bbox) {
1828
- return getPoints$1(multiPoint, bbox);
1367
+ /** @privateRemarks Source: @carto/react-core */
1368
+ function _clean(geometry) {
1369
+ if (!geometry) {
1370
+ return null;
1371
+ }
1372
+ if (_isPolygon(geometry)) {
1373
+ const coords = _cleanPolygonCoords(geometry.coordinates);
1374
+ return coords ? helpers.polygon(coords).geometry : null;
1375
+ }
1376
+ if (_isMultiPolygon(geometry)) {
1377
+ const coords = _cleanMultiPolygonCoords(geometry.coordinates);
1378
+ return coords ? helpers.multiPolygon(coords).geometry : null;
1379
+ }
1380
+ return null;
1829
1381
  }
1830
- function transformLineString$1(line, bbox) {
1831
- return getPoints$1(line, bbox);
1382
+ /** @privateRemarks Source: @carto/react-core */
1383
+ function _txContourCoords(cc, distance) {
1384
+ return cc.map(c => [c[0] + distance, c[1]]);
1832
1385
  }
1833
- function transformMultiLineString$1(multiLineString, bbox) {
1834
- return multiLineString.map(lineString => transformLineString$1(lineString, bbox));
1386
+ /** @privateRemarks Source: @carto/react-core */
1387
+ function _txPolygonCoords(ccc, distance) {
1388
+ return ccc.map(cc => _txContourCoords(cc, distance));
1835
1389
  }
1836
- function transformPolygon$1(polygon, bbox) {
1837
- return polygon.map(polygonRing => getPoints$1(polygonRing, bbox));
1390
+ /** @privateRemarks Source: @carto/react-core */
1391
+ function _txMultiPolygonCoords(cccc, distance) {
1392
+ return cccc.map(ccc => _txPolygonCoords(ccc, distance));
1393
+ }
1394
+ /** @privateRemarks Source: @carto/react-core */
1395
+ function _tx(geometry, distance) {
1396
+ if (geometry && invariant.getType(geometry) === 'Polygon') {
1397
+ const coords = _txPolygonCoords(geometry.coordinates, distance);
1398
+ return helpers.polygon(coords).geometry;
1399
+ } else if (geometry && invariant.getType(geometry) === 'MultiPolygon') {
1400
+ const coords = _txMultiPolygonCoords(geometry.coordinates, distance);
1401
+ return helpers.multiPolygon(coords).geometry;
1402
+ } else {
1403
+ return null;
1404
+ }
1405
+ }
1406
+ function _isPolygon(geometry) {
1407
+ return invariant.getType(geometry) === 'Polygon';
1408
+ }
1409
+ function _isMultiPolygon(geometry) {
1410
+ return invariant.getType(geometry) === 'MultiPolygon';
1411
+ }
1412
+
1413
+ // deck.gl
1414
+ // SPDX-License-Identifier: MIT
1415
+ // Copyright (c) vis.gl contributors
1416
+ function joinPath() {
1417
+ return [].slice.call(arguments).map(part => part.endsWith('/') ? part.slice(0, -1) : part).join('/');
1418
+ }
1419
+ function buildV3Path(apiBaseUrl, version, endpoint) {
1420
+ return joinPath(apiBaseUrl, version, endpoint, ...[].slice.call(arguments, 3));
1421
+ }
1422
+ /** @internal Required by fetchMap(). */
1423
+ function buildPublicMapUrl(_ref) {
1424
+ let {
1425
+ apiBaseUrl,
1426
+ cartoMapId
1427
+ } = _ref;
1428
+ return buildV3Path(apiBaseUrl, 'v3', 'maps', 'public', cartoMapId);
1838
1429
  }
1839
- function transformMultiPolygon$1(multiPolygon, bbox) {
1840
- return multiPolygon.map(polygon => transformPolygon$1(polygon, bbox));
1430
+ /** @internal Required by fetchMap(). */
1431
+ function buildStatsUrl(_ref2) {
1432
+ let {
1433
+ attribute,
1434
+ apiBaseUrl,
1435
+ connectionName,
1436
+ source,
1437
+ type
1438
+ } = _ref2;
1439
+ if (type === 'query') {
1440
+ return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, attribute);
1441
+ }
1442
+ // type === 'table'
1443
+ return buildV3Path(apiBaseUrl, 'v3', 'stats', connectionName, source, attribute);
1841
1444
  }
1842
- function projectFlat(xyz) {
1843
- return lngLatToWorld(xyz);
1445
+ function buildSourceUrl(_ref3) {
1446
+ let {
1447
+ apiBaseUrl,
1448
+ connectionName,
1449
+ endpoint
1450
+ } = _ref3;
1451
+ return buildV3Path(apiBaseUrl, 'v3', 'maps', connectionName, endpoint);
1844
1452
  }
1845
- function inverseLerp(a, b, x) {
1846
- return (x - a) / (b - a);
1453
+ function buildQueryUrl(_ref4) {
1454
+ let {
1455
+ apiBaseUrl,
1456
+ connectionName
1457
+ } = _ref4;
1458
+ return buildV3Path(apiBaseUrl, 'v3', 'sql', connectionName, 'query');
1847
1459
  }
1848
1460
 
1849
- const TRANSFORM_FN = {
1850
- Point: transformPoint,
1851
- MultiPoint: transformMultiPoint,
1852
- LineString: transformLineString,
1853
- MultiLineString: transformMultiLineString,
1854
- Polygon: transformPolygon,
1855
- MultiPolygon: transformMultiPolygon
1856
- };
1461
+ // deck.gl
1462
+ // SPDX-License-Identifier: MIT
1463
+ // Copyright (c) vis.gl contributors
1857
1464
  /**
1858
- * Transform tile coords to WGS84 coordinates.
1859
1465
  *
1860
- * @param geometry - any valid geojson geometry
1861
- * @param bbox - geojson bbox
1466
+ * Custom error for reported errors in CARTO Maps API.
1467
+ * Provides useful debugging information in console and context for applications.
1468
+ *
1862
1469
  */
1863
- function transformTileCoordsToWGS84(geometry, bbox) {
1864
- const [west, south, east, north] = bbox;
1865
- const nw = lngLatToWorld([west, north]);
1866
- const se = lngLatToWorld([east, south]);
1867
- const projectedBbox = [nw, se];
1868
- if (geometry.type === 'GeometryCollection') {
1869
- throw new Error('Unsupported geometry type GeometryCollection');
1470
+ class CartoAPIError extends Error {
1471
+ constructor(error, errorContext, response, responseJson) {
1472
+ let responseString = 'Failed to connect';
1473
+ if (response) {
1474
+ responseString = 'Server returned: ';
1475
+ if (response.status === 400) {
1476
+ responseString += 'Bad request';
1477
+ } else if (response.status === 401 || response.status === 403) {
1478
+ responseString += 'Unauthorized access';
1479
+ } else if (response.status === 404) {
1480
+ responseString += 'Not found';
1481
+ } else {
1482
+ responseString += 'Error';
1483
+ }
1484
+ responseString += ` (${response.status}):`;
1485
+ }
1486
+ responseString += ` ${error.message || error}`;
1487
+ let message = `${errorContext.requestType} API request failed`;
1488
+ message += `\n${responseString}`;
1489
+ for (const key of Object.keys(errorContext)) {
1490
+ if (key === 'requestType') continue;
1491
+ message += `\n${formatErrorKey(key)}: ${errorContext[key]}`;
1492
+ }
1493
+ message += '\n';
1494
+ super(message);
1495
+ /** Source error from server */
1496
+ this.error = void 0;
1497
+ /** Context (API call & parameters) in which error occured */
1498
+ this.errorContext = void 0;
1499
+ /** Response from server */
1500
+ this.response = void 0;
1501
+ /** JSON Response from server */
1502
+ this.responseJson = void 0;
1503
+ this.name = 'CartoAPIError';
1504
+ this.response = response;
1505
+ this.responseJson = responseJson;
1506
+ this.error = error;
1507
+ this.errorContext = errorContext;
1870
1508
  }
1871
- const transformFn = TRANSFORM_FN[geometry.type];
1872
- const coordinates = transformFn(geometry.coordinates, projectedBbox);
1873
- return {
1874
- ...geometry,
1875
- coordinates
1876
- };
1877
- }
1878
- function transformPoint(_ref, _ref2) {
1879
- let [pointX, pointY] = _ref;
1880
- let [nw, se] = _ref2;
1881
- const x = lerp(nw[0], se[0], pointX);
1882
- const y = lerp(nw[1], se[1], pointY);
1883
- return worldToLngLat([x, y]);
1884
- }
1885
- function getPoints(geometry, bbox) {
1886
- return geometry.map(g => transformPoint(g, bbox));
1887
1509
  }
1888
- function transformMultiPoint(multiPoint, bbox) {
1889
- return getPoints(multiPoint, bbox);
1510
+ /**
1511
+ * Converts camelCase to Camel Case
1512
+ */
1513
+ function formatErrorKey(key) {
1514
+ return key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
1890
1515
  }
1891
- function transformLineString(line, bbox) {
1892
- return getPoints(line, bbox);
1516
+
1517
+ // deck.gl
1518
+ // SPDX-License-Identifier: MIT
1519
+ // Copyright (c) vis.gl contributors
1520
+ const requestWithParameters = function (_ref) {
1521
+ let {
1522
+ baseUrl,
1523
+ parameters = {},
1524
+ headers: customHeaders = {},
1525
+ errorContext,
1526
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL,
1527
+ localCache
1528
+ } = _ref;
1529
+ try {
1530
+ // Parameters added to all requests issued with `requestWithParameters()`.
1531
+ // These parameters override parameters already in the base URL, but not
1532
+ // user-provided parameters.
1533
+ parameters = {
1534
+ v: V3_MINOR_VERSION,
1535
+ client: getClient(),
1536
+ ...(typeof deck !== 'undefined' && deck.VERSION && {
1537
+ deckglVersion: deck.VERSION
1538
+ }),
1539
+ ...parameters
1540
+ };
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 Promise.resolve(REQUEST_CACHE.get(key));
1550
+ }
1551
+ const url = createURLWithParameters(baseUrl, parameters);
1552
+ const headers = {
1553
+ ...DEFAULT_HEADERS,
1554
+ ...customHeaders
1555
+ };
1556
+ /* global fetch */
1557
+ const fetchPromise = url.length > maxLengthURL ? fetch(baseUrl, {
1558
+ method: 'POST',
1559
+ body: JSON.stringify(parameters),
1560
+ headers
1561
+ }) : fetch(url, {
1562
+ headers
1563
+ });
1564
+ let response;
1565
+ let responseJson;
1566
+ const jsonPromise = fetchPromise.then(_response => {
1567
+ response = _response;
1568
+ return response.json();
1569
+ }).then(json => {
1570
+ responseJson = json;
1571
+ if (!response || !response.ok) {
1572
+ throw new Error(json.error);
1573
+ }
1574
+ return json;
1575
+ }).catch(error => {
1576
+ if (canStoreInCache) {
1577
+ REQUEST_CACHE.delete(key);
1578
+ }
1579
+ throw new CartoAPIError(error, errorContext, response, responseJson);
1580
+ });
1581
+ if (canStoreInCache) {
1582
+ REQUEST_CACHE.set(key, jsonPromise);
1583
+ }
1584
+ return Promise.resolve(jsonPromise);
1585
+ } catch (e) {
1586
+ return Promise.reject(e);
1587
+ }
1588
+ };
1589
+ const DEFAULT_HEADERS = {
1590
+ Accept: 'application/json',
1591
+ 'Content-Type': 'application/json'
1592
+ };
1593
+ const DEFAULT_REQUEST_CACHE = new Map();
1594
+ function getCacheSettings(localCache) {
1595
+ const canReadCache = localCache?.cacheControl?.includes('no-cache') ? false : true;
1596
+ const canStoreInCache = localCache?.cacheControl?.includes('no-store') ? false : true;
1597
+ const cache = localCache?.cache || DEFAULT_REQUEST_CACHE;
1598
+ return {
1599
+ cache,
1600
+ canReadCache,
1601
+ canStoreInCache
1602
+ };
1893
1603
  }
1894
- function transformMultiLineString(multiLineString, bbox) {
1895
- return multiLineString.map(lineString => transformLineString(lineString, bbox));
1604
+ function createCacheKey(baseUrl, parameters, headers) {
1605
+ const parameterEntries = Object.entries(parameters).sort((_ref2, _ref3) => {
1606
+ let [a] = _ref2;
1607
+ let [b] = _ref3;
1608
+ return a > b ? 1 : -1;
1609
+ });
1610
+ const headerEntries = Object.entries(headers).sort((_ref4, _ref5) => {
1611
+ let [a] = _ref4;
1612
+ let [b] = _ref5;
1613
+ return a > b ? 1 : -1;
1614
+ });
1615
+ return JSON.stringify({
1616
+ baseUrl,
1617
+ parameters: parameterEntries,
1618
+ headers: headerEntries
1619
+ });
1896
1620
  }
1897
- function transformPolygon(polygon, bbox) {
1898
- return polygon.map(polygonRing => getPoints(polygonRing, bbox));
1621
+ /**
1622
+ * Appends query string parameters to a URL. Existing URL parameters are kept,
1623
+ * unless there is a conflict, in which case the new parameters override
1624
+ * those already in the URL.
1625
+ */
1626
+ function createURLWithParameters(baseUrlString, parameters) {
1627
+ const baseUrl = new URL(baseUrlString);
1628
+ for (const [key, value] of Object.entries(parameters)) {
1629
+ if (isPureObject(value) || Array.isArray(value)) {
1630
+ baseUrl.searchParams.set(key, JSON.stringify(value));
1631
+ } else {
1632
+ baseUrl.searchParams.set(key, value.toString());
1633
+ }
1634
+ }
1635
+ return baseUrl.toString();
1899
1636
  }
1900
- function transformMultiPolygon(multiPolygon, bbox) {
1901
- return multiPolygon.map(polygon => transformPolygon(polygon, bbox));
1637
+ /**
1638
+ * Deletes query string parameters from a URL.
1639
+ */
1640
+ function excludeURLParameters(baseUrlString, parameters) {
1641
+ const baseUrl = new URL(baseUrlString);
1642
+ for (const param of parameters) {
1643
+ if (baseUrl.searchParams.has(param)) {
1644
+ baseUrl.searchParams.delete(param);
1645
+ }
1646
+ }
1647
+ return baseUrl.toString();
1902
1648
  }
1903
1649
 
1904
- const FEATURE_GEOM_PROPERTY = '__geomValue';
1905
- function tileFeaturesGeometries(_ref) {
1906
- let {
1907
- tiles,
1908
- tileFormat,
1909
- spatialFilter,
1910
- uniqueIdProperty,
1911
- options
1912
- } = _ref;
1913
- const map = new Map();
1914
- for (const tile of tiles) {
1915
- // Discard if it's not a visible tile (only check false value, not undefined)
1916
- // or tile has not data
1917
- if (tile.isVisible === false || !tile.data) {
1918
- continue;
1919
- }
1920
- const bbox = [tile.bbox.west, tile.bbox.south, tile.bbox.east, tile.bbox.north];
1921
- const bboxToGeom = bboxPolygon(bbox);
1922
- const tileIsFullyVisible = booleanWithin(bboxToGeom, spatialFilter);
1923
- // Clip the geometry to intersect with the tile
1924
- const spatialFilterFeature = {
1925
- type: 'Feature',
1926
- geometry: spatialFilter,
1927
- properties: {}
1650
+ // deck.gl
1651
+ // SPDX-License-Identifier: MIT
1652
+ // Copyright (c) vis.gl contributors
1653
+ const baseSource = function (endpoint, options, urlParameters) {
1654
+ try {
1655
+ const {
1656
+ accessToken,
1657
+ connectionName,
1658
+ cache,
1659
+ ...optionalOptions
1660
+ } = options;
1661
+ const mergedOptions = {
1662
+ ...SOURCE_DEFAULTS,
1663
+ accessToken,
1664
+ connectionName,
1665
+ endpoint
1928
1666
  };
1929
- const clippedGeometryToIntersect = intersect(helpers.featureCollection([bboxToGeom, spatialFilterFeature]));
1930
- if (!clippedGeometryToIntersect) {
1931
- continue;
1667
+ for (const key in optionalOptions) {
1668
+ if (optionalOptions[key]) {
1669
+ mergedOptions[key] = optionalOptions[key];
1670
+ }
1932
1671
  }
1933
- // We assume that MVT tileFormat uses local coordinates so we transform the geometry to intersect to tile coordinates [0..1],
1934
- // while in the case of 'geojson' or binary, the geometries are already in WGS84
1935
- const transformedGeometryToIntersect = tileFormat === exports.TileFormat.MVT ? transformToTileCoords(clippedGeometryToIntersect.geometry, bbox) : clippedGeometryToIntersect.geometry;
1936
- createIndicesForPoints(tile.data.points);
1937
- calculateFeatures({
1938
- map,
1939
- tileIsFullyVisible,
1940
- geometryIntersection: transformedGeometryToIntersect,
1941
- data: tile.data.points,
1942
- type: 'Point',
1943
- bbox,
1944
- tileFormat,
1945
- uniqueIdProperty,
1946
- options
1947
- });
1948
- calculateFeatures({
1949
- map,
1950
- tileIsFullyVisible,
1951
- geometryIntersection: transformedGeometryToIntersect,
1952
- data: tile.data.lines,
1953
- type: 'LineString',
1954
- bbox,
1955
- tileFormat,
1956
- uniqueIdProperty,
1957
- options
1958
- });
1959
- calculateFeatures({
1960
- map,
1961
- tileIsFullyVisible,
1962
- geometryIntersection: transformedGeometryToIntersect,
1963
- data: tile.data.polygons,
1964
- type: 'Polygon',
1965
- bbox,
1966
- tileFormat,
1967
- uniqueIdProperty,
1968
- options
1672
+ const baseUrl = buildSourceUrl(mergedOptions);
1673
+ const {
1674
+ clientId,
1675
+ maxLengthURL,
1676
+ format,
1677
+ localCache
1678
+ } = mergedOptions;
1679
+ const headers = {
1680
+ Authorization: `Bearer ${options.accessToken}`,
1681
+ ...options.headers
1682
+ };
1683
+ const parameters = {
1684
+ client: clientId,
1685
+ ...urlParameters
1686
+ };
1687
+ const errorContext = {
1688
+ requestType: 'Map instantiation',
1689
+ connection: options.connectionName,
1690
+ type: endpoint,
1691
+ source: JSON.stringify(parameters, undefined, 2)
1692
+ };
1693
+ return Promise.resolve(requestWithParameters({
1694
+ baseUrl,
1695
+ parameters,
1696
+ headers,
1697
+ errorContext,
1698
+ maxLengthURL,
1699
+ localCache
1700
+ })).then(function (mapInstantiation) {
1701
+ let _exit;
1702
+ function _temp2(_result) {
1703
+ return _exit ? _result : Promise.resolve(requestWithParameters({
1704
+ baseUrl: dataUrl,
1705
+ headers,
1706
+ errorContext,
1707
+ maxLengthURL,
1708
+ localCache
1709
+ }));
1710
+ }
1711
+ const dataUrl = mapInstantiation[format].url[0];
1712
+ if (cache) {
1713
+ cache.value = parseInt(new URL(dataUrl).searchParams.get('cache') || '', 10);
1714
+ }
1715
+ errorContext.requestType = 'Map data';
1716
+ const _temp = function () {
1717
+ if (format === 'tilejson') {
1718
+ return Promise.resolve(requestWithParameters({
1719
+ baseUrl: dataUrl,
1720
+ headers,
1721
+ errorContext,
1722
+ maxLengthURL,
1723
+ localCache
1724
+ })).then(function (json) {
1725
+ if (accessToken) {
1726
+ json.accessToken = accessToken;
1727
+ }
1728
+ _exit = 1;
1729
+ return json;
1730
+ });
1731
+ }
1732
+ }();
1733
+ return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
1969
1734
  });
1735
+ } catch (e) {
1736
+ return Promise.reject(e);
1970
1737
  }
1971
- return Array.from(map.values());
1972
- }
1973
- function processTileFeatureProperties(_ref2) {
1974
- let {
1975
- map,
1976
- data,
1977
- startIndex,
1978
- endIndex,
1979
- type,
1980
- bbox,
1981
- tileFormat,
1982
- uniqueIdProperty,
1983
- storeGeometry,
1984
- geometryIntersection
1985
- } = _ref2;
1986
- const tileProps = getPropertiesFromTile(data, startIndex);
1987
- const uniquePropertyValue = getUniquePropertyValue(tileProps, uniqueIdProperty, map);
1988
- if (!uniquePropertyValue || map.has(uniquePropertyValue)) {
1989
- return;
1738
+ };
1739
+ const SOURCE_DEFAULTS = {
1740
+ apiBaseUrl: DEFAULT_API_BASE_URL,
1741
+ clientId: getClient(),
1742
+ format: 'tilejson',
1743
+ headers: {},
1744
+ maxLengthURL: DEFAULT_MAX_LENGTH_URL
1745
+ };
1746
+
1747
+ // deck.gl
1748
+ // SPDX-License-Identifier: MIT
1749
+ // Copyright (c) vis.gl contributors
1750
+ const boundaryQuerySource = function (options) {
1751
+ try {
1752
+ const {
1753
+ columns,
1754
+ filters,
1755
+ tilesetTableName,
1756
+ propertiesSqlQuery,
1757
+ queryParameters
1758
+ } = options;
1759
+ const urlParameters = {
1760
+ tilesetTableName,
1761
+ propertiesSqlQuery
1762
+ };
1763
+ if (columns) {
1764
+ urlParameters.columns = columns.join(',');
1765
+ }
1766
+ if (filters) {
1767
+ urlParameters.filters = filters;
1768
+ }
1769
+ if (queryParameters) {
1770
+ urlParameters.queryParameters = queryParameters;
1771
+ }
1772
+ return Promise.resolve(baseSource('boundary', options, urlParameters));
1773
+ } catch (e) {
1774
+ return Promise.reject(e);
1990
1775
  }
1991
- let geometry = null;
1992
- // Only calculate geometry if necessary
1993
- if (storeGeometry || geometryIntersection) {
1776
+ };
1777
+
1778
+ // deck.gl
1779
+ // SPDX-License-Identifier: MIT
1780
+ // Copyright (c) vis.gl contributors
1781
+ const boundaryTableSource = function (options) {
1782
+ try {
1994
1783
  const {
1995
- positions
1996
- } = data;
1997
- const ringCoordinates = getRingCoordinatesFor(startIndex, endIndex, positions);
1998
- geometry = getFeatureByType(ringCoordinates, type);
1784
+ filters,
1785
+ tilesetTableName,
1786
+ columns,
1787
+ propertiesTableName
1788
+ } = options;
1789
+ const urlParameters = {
1790
+ tilesetTableName,
1791
+ propertiesTableName
1792
+ };
1793
+ if (columns) {
1794
+ urlParameters.columns = columns.join(',');
1795
+ }
1796
+ if (filters) {
1797
+ urlParameters.filters = filters;
1798
+ }
1799
+ return Promise.resolve(baseSource('boundary', options, urlParameters));
1800
+ } catch (e) {
1801
+ return Promise.reject(e);
1999
1802
  }
2000
- // If intersection is required, check before proceeding
2001
- if (geometry && geometryIntersection && !intersects(geometry, geometryIntersection)) {
2002
- return;
1803
+ };
1804
+
1805
+ const DEFAULT_TILE_SIZE = 512;
1806
+ const QUADBIN_ZOOM_MAX_OFFSET = 4;
1807
+ function getSpatialFiltersResolution(source, viewState) {
1808
+ const dataResolution = source.dataResolution ?? Number.MAX_VALUE;
1809
+ const aggregationResLevel = source.aggregationResLevel ?? (source.spatialDataType === 'h3' ? DEFAULT_AGGREGATION_RES_LEVEL_H3 : DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN);
1810
+ const aggregationResLevelOffset = Math.max(0, Math.floor(aggregationResLevel));
1811
+ const currentZoomInt = Math.ceil(viewState.zoom);
1812
+ if (source.spatialDataType === 'h3') {
1813
+ const tileSize = DEFAULT_TILE_SIZE;
1814
+ const maxResolutionForZoom = maxH3SpatialFiltersResolutions.find(_ref => {
1815
+ let [zoom] = _ref;
1816
+ return zoom === currentZoomInt;
1817
+ })?.[1] ?? Math.max(0, currentZoomInt - 3);
1818
+ const maxSpatialFiltersResolution = maxResolutionForZoom ? Math.min(dataResolution, maxResolutionForZoom) : dataResolution;
1819
+ const hexagonResolution = _getHexagonResolution(viewState, tileSize) + aggregationResLevelOffset;
1820
+ return Math.min(hexagonResolution, maxSpatialFiltersResolution);
2003
1821
  }
2004
- const properties = parseProperties(tileProps);
2005
- // Only save geometry if necessary
2006
- if (storeGeometry && geometry) {
2007
- properties[FEATURE_GEOM_PROPERTY] = tileFormat === exports.TileFormat.MVT ? transformTileCoordsToWGS84(geometry, bbox) : geometry;
1822
+ if (source.spatialDataType === 'quadbin') {
1823
+ const maxResolutionForZoom = currentZoomInt + QUADBIN_ZOOM_MAX_OFFSET;
1824
+ const maxSpatialFiltersResolution = Math.min(dataResolution, maxResolutionForZoom);
1825
+ const quadsResolution = Math.floor(viewState.zoom) + aggregationResLevelOffset;
1826
+ return Math.min(quadsResolution, maxSpatialFiltersResolution);
2008
1827
  }
2009
- map.set(uniquePropertyValue, properties);
1828
+ return undefined;
2010
1829
  }
2011
- function addIntersectedFeaturesInTile(_ref3) {
2012
- let {
2013
- map,
2014
- data,
2015
- geometryIntersection,
2016
- type,
2017
- bbox,
2018
- tileFormat,
2019
- uniqueIdProperty,
2020
- options
2021
- } = _ref3;
2022
- const indices = getIndices(data);
2023
- const storeGeometry = options?.storeGeometry || false;
2024
- for (let i = 0; i < indices.length - 1; i++) {
2025
- const startIndex = indices[i];
2026
- const endIndex = indices[i + 1];
2027
- processTileFeatureProperties({
2028
- map,
2029
- data,
2030
- startIndex,
2031
- endIndex,
2032
- type,
2033
- bbox,
2034
- tileFormat,
2035
- uniqueIdProperty,
2036
- storeGeometry,
2037
- geometryIntersection
2038
- });
2039
- }
1830
+ 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]];
1831
+ // stolen from https://github.com/visgl/deck.gl/blob/master/modules/carto/src/layers/h3-tileset-2d.ts
1832
+ // Relative scale factor (0 = no biasing, 2 = a few hexagons cover view)
1833
+ const BIAS = 2;
1834
+ /**
1835
+ * Resolution conversion function. Takes a WebMercatorViewport and returns
1836
+ * a H3 resolution such that the screen space size of the hexagons is
1837
+ * "similar" to the given tileSize on screen. Intended for use with deck.gl.
1838
+ * @internal
1839
+ */
1840
+ function _getHexagonResolution(viewport, tileSize) {
1841
+ // Difference in given tile size compared to deck's internal 512px tile size,
1842
+ // expressed as an offset to the viewport zoom.
1843
+ const zoomOffset = Math.log2(tileSize / DEFAULT_TILE_SIZE);
1844
+ const hexagonScaleFactor = 2 / 3 * (viewport.zoom - zoomOffset);
1845
+ const latitudeScaleFactor = Math.log(1 / Math.cos(Math.PI * viewport.latitude / 180));
1846
+ // Clip and bias
1847
+ return Math.max(0, Math.floor(hexagonScaleFactor + latitudeScaleFactor - BIAS));
2040
1848
  }
2041
- function getIndices(data) {
2042
- let indices;
2043
- switch (data.type) {
2044
- case 'Point':
2045
- // @ts-expect-error Missing or changed types?
2046
- indices = data.pointIndices;
2047
- break;
2048
- case 'LineString':
2049
- indices = data.pathIndices;
2050
- break;
2051
- case 'Polygon':
2052
- indices = data.primitivePolygonIndices;
2053
- break;
2054
- default:
2055
- throw new Error(`Unexpected type, "${data.type}"`);
1849
+
1850
+ /**
1851
+ * Source for Widget API requests on a data source defined by a SQL query.
1852
+ *
1853
+ * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
1854
+ */
1855
+ class WidgetSource {
1856
+ constructor(props) {
1857
+ this.props = void 0;
1858
+ this.props = {
1859
+ ...WidgetSource.defaultProps,
1860
+ ...props
1861
+ };
1862
+ }
1863
+ _getModelSource(filters, filterOwner) {
1864
+ const props = this.props;
1865
+ return {
1866
+ apiVersion: props.apiVersion,
1867
+ apiBaseUrl: props.apiBaseUrl,
1868
+ clientId: props.clientId,
1869
+ accessToken: props.accessToken,
1870
+ connectionName: props.connectionName,
1871
+ filters: getApplicableFilters(filterOwner, filters || props.filters),
1872
+ filtersLogicalOperator: props.filtersLogicalOperator,
1873
+ spatialDataType: props.spatialDataType,
1874
+ spatialDataColumn: props.spatialDataColumn,
1875
+ dataResolution: props.dataResolution
1876
+ };
1877
+ }
1878
+ _getSpatialFiltersResolution(source, spatialFilter, referenceViewState) {
1879
+ // spatialFiltersResolution applies only to spatial index sources.
1880
+ if (!spatialFilter || source.spatialDataType === 'geo') {
1881
+ return;
1882
+ }
1883
+ if (!referenceViewState) {
1884
+ throw new Error('Missing required option, "spatialIndexReferenceViewState".');
1885
+ }
1886
+ return getSpatialFiltersResolution(source, referenceViewState);
2056
1887
  }
2057
- return indices.value;
2058
- }
2059
- function getFeatureId(data, startIndex) {
2060
- return data.featureIds.value[startIndex];
2061
1888
  }
2062
- function getPropertiesFromTile(data, startIndex) {
2063
- const featureId = getFeatureId(data, startIndex);
2064
- const {
2065
- properties,
2066
- numericProps,
2067
- fields
2068
- } = data;
2069
- const result = {
2070
- uniqueId: fields?.[featureId]?.id,
2071
- properties: properties[featureId],
2072
- numericProps: {}
2073
- };
2074
- for (const key in numericProps) {
2075
- result.numericProps[key] = numericProps[key].value[startIndex];
1889
+ WidgetSource.defaultProps = {
1890
+ apiVersion: exports.ApiVersion.V3,
1891
+ apiBaseUrl: DEFAULT_API_BASE_URL,
1892
+ clientId: getClient(),
1893
+ filters: {},
1894
+ filtersLogicalOperator: 'and'
1895
+ };
1896
+
1897
+ /**
1898
+ * Return more descriptive error from API
1899
+ * @privateRemarks Source: @carto/react-api
1900
+ */
1901
+
1902
+ /** @privateRemarks Source: @carto/react-api */
1903
+
1904
+ function _catch(body, recover) {
1905
+ try {
1906
+ var result = body();
1907
+ } catch (e) {
1908
+ return recover(e);
1909
+ }
1910
+ if (result && result.then) {
1911
+ return result.then(void 0, recover);
2076
1912
  }
2077
1913
  return result;
2078
1914
  }
2079
- function parseProperties(tileProps) {
2080
- const {
2081
- properties,
2082
- numericProps
2083
- } = tileProps;
2084
- return Object.assign({}, properties, numericProps);
2085
- }
2086
- function getUniquePropertyValue(tileProps, uniqueIdProperty, map) {
2087
- if (uniqueIdProperty) {
2088
- return getValueFromTileProps(tileProps, uniqueIdProperty);
1915
+ const makeCall = function (_ref2) {
1916
+ let {
1917
+ url,
1918
+ accessToken,
1919
+ opts
1920
+ } = _ref2;
1921
+ try {
1922
+ let _exit;
1923
+ function _temp2(_result) {
1924
+ if (_exit) ;
1925
+ if (!response.ok) {
1926
+ dealWithApiError({
1927
+ response,
1928
+ data
1929
+ });
1930
+ }
1931
+ return data;
1932
+ }
1933
+ let response;
1934
+ let data;
1935
+ const isPost = opts?.method === 'POST';
1936
+ const _temp = _catch(function () {
1937
+ return Promise.resolve(fetch(url.toString(), {
1938
+ headers: {
1939
+ Authorization: `Bearer ${accessToken}`,
1940
+ ...(isPost && {
1941
+ 'Content-Type': 'application/json'
1942
+ }),
1943
+ ...opts.headers
1944
+ },
1945
+ ...(isPost && {
1946
+ method: opts?.method,
1947
+ body: opts?.body
1948
+ }),
1949
+ signal: opts?.abortController?.signal,
1950
+ ...opts?.otherOptions
1951
+ })).then(function (_fetch) {
1952
+ response = _fetch;
1953
+ return Promise.resolve(response.json()).then(function (_response$json) {
1954
+ data = _response$json;
1955
+ });
1956
+ });
1957
+ }, function (error) {
1958
+ if (error.name === 'AbortError') throw error;
1959
+ throw new Error(`Failed request: ${error}`);
1960
+ });
1961
+ return Promise.resolve(_temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp));
1962
+ } catch (e) {
1963
+ return Promise.reject(e);
2089
1964
  }
2090
- if (tileProps.uniqueId) {
2091
- return tileProps.uniqueId;
1965
+ };
1966
+ function dealWithApiError(_ref) {
1967
+ let {
1968
+ response,
1969
+ data
1970
+ } = _ref;
1971
+ if (data.error === 'Column not found') {
1972
+ throw new InvalidColumnError(`${data.error} ${data.column_name}`);
2092
1973
  }
2093
- const artificialId = map.size + 1; // a counter, assumed as a valid new id
2094
- return getValueFromTileProps(tileProps, 'cartodb_id') || getValueFromTileProps(tileProps, 'geoid') || artificialId;
2095
- }
2096
- function getValueFromTileProps(tileProps, propertyName) {
2097
- const {
2098
- properties,
2099
- numericProps
2100
- } = tileProps;
2101
- return numericProps[propertyName] || properties[propertyName];
2102
- }
2103
- function getFeatureByType(coordinates, type) {
2104
- switch (type) {
2105
- case 'Polygon':
2106
- return {
2107
- type: 'Polygon',
2108
- coordinates: [coordinates]
2109
- };
2110
- case 'LineString':
2111
- return {
2112
- type: 'LineString',
2113
- coordinates
2114
- };
2115
- case 'Point':
2116
- return {
2117
- type: 'Point',
2118
- coordinates: coordinates[0]
2119
- };
2120
- default:
2121
- throw new Error('Invalid geometry type');
1974
+ if (typeof data.error === 'string' && data.error?.includes('Missing columns')) {
1975
+ throw new InvalidColumnError(data.error);
2122
1976
  }
2123
- }
2124
- function getRingCoordinatesFor(startIndex, endIndex, positions) {
2125
- const ringCoordinates = [];
2126
- for (let j = startIndex; j < endIndex; j++) {
2127
- ringCoordinates.push(Array.from(positions.value.subarray(j * positions.size, (j + 1) * positions.size)));
1977
+ switch (response.status) {
1978
+ case 401:
1979
+ throw new Error('Unauthorized access. Invalid credentials');
1980
+ case 403:
1981
+ throw new Error('Forbidden access to the requested data');
1982
+ default:
1983
+ throw new Error(data && data.error && typeof data.error === 'string' ? data.error : JSON.stringify(data?.hint || data.error?.[0]));
2128
1984
  }
2129
- return ringCoordinates;
2130
1985
  }
2131
- function calculateFeatures(_ref4) {
2132
- let {
2133
- map,
2134
- tileIsFullyVisible,
2135
- geometryIntersection,
1986
+
1987
+ /** @privateRemarks Source: @carto/react-api */
1988
+ const AVAILABLE_MODELS = ['category', 'histogram', 'formula', 'pick', 'timeseries', 'range', 'scatterplot', 'table'];
1989
+ const {
1990
+ V3
1991
+ } = exports.ApiVersion;
1992
+ const REQUEST_GET_MAX_URL_LENGTH = 2048;
1993
+ /**
1994
+ * Execute a SQL model request.
1995
+ * @privateRemarks Source: @carto/react-api
1996
+ */
1997
+ function executeModel(props) {
1998
+ assert(props.source, 'executeModel: missing source');
1999
+ assert(props.model, 'executeModel: missing model');
2000
+ assert(props.params, 'executeModel: missing params');
2001
+ assert(AVAILABLE_MODELS.includes(props.model), `executeModel: model provided isn't valid. Available models: ${AVAILABLE_MODELS.join(', ')}`);
2002
+ const {
2003
+ model,
2004
+ source,
2005
+ params,
2006
+ opts
2007
+ } = props;
2008
+ const {
2009
+ type,
2010
+ apiVersion,
2011
+ apiBaseUrl,
2012
+ accessToken,
2013
+ connectionName,
2014
+ clientId
2015
+ } = source;
2016
+ assert(apiBaseUrl, 'executeModel: missing apiBaseUrl');
2017
+ assert(accessToken, 'executeModel: missing accessToken');
2018
+ assert(apiVersion === V3, 'executeModel: SQL Model API requires CARTO 3+');
2019
+ assert(type !== 'tileset', 'executeModel: Tilesets not supported');
2020
+ let url = `${apiBaseUrl}/v3/sql/${connectionName}/model/${model}`;
2021
+ const {
2136
2022
  data,
2023
+ filters,
2024
+ filtersLogicalOperator = 'and',
2025
+ spatialDataType = 'geo',
2026
+ spatialFiltersMode = 'intersects',
2027
+ spatialFiltersResolution = 0
2028
+ } = source;
2029
+ const queryParams = {
2137
2030
  type,
2138
- bbox,
2139
- tileFormat,
2140
- uniqueIdProperty,
2141
- options
2142
- } = _ref4;
2143
- if (!data?.properties.length) {
2144
- return;
2031
+ client: clientId,
2032
+ source: data,
2033
+ params,
2034
+ queryParameters: source.queryParameters || '',
2035
+ filters,
2036
+ filtersLogicalOperator
2037
+ };
2038
+ const spatialDataColumn = source.spatialDataColumn || DEFAULT_GEO_COLUMN;
2039
+ // Picking Model API requires 'spatialDataColumn'.
2040
+ if (model === 'pick') {
2041
+ queryParams.spatialDataColumn = spatialDataColumn;
2145
2042
  }
2146
- if (tileIsFullyVisible) {
2147
- addAllFeaturesInTile({
2148
- map,
2149
- data,
2150
- type,
2151
- bbox,
2152
- tileFormat,
2153
- uniqueIdProperty,
2154
- options
2155
- });
2156
- } else {
2157
- addIntersectedFeaturesInTile({
2158
- map,
2159
- data,
2160
- geometryIntersection,
2161
- type,
2162
- bbox,
2163
- tileFormat,
2164
- uniqueIdProperty,
2165
- options
2166
- });
2043
+ // API supports multiple filters, we apply it only to spatialDataColumn
2044
+ const spatialFilters = source.spatialFilter ? {
2045
+ [spatialDataColumn]: source.spatialFilter
2046
+ } : undefined;
2047
+ if (spatialFilters) {
2048
+ queryParams.spatialFilters = spatialFilters;
2049
+ queryParams.spatialDataColumn = spatialDataColumn;
2050
+ queryParams.spatialDataType = spatialDataType;
2167
2051
  }
2168
- }
2169
- function addAllFeaturesInTile(_ref5) {
2170
- let {
2171
- map,
2172
- data,
2173
- type,
2174
- bbox,
2175
- tileFormat,
2176
- uniqueIdProperty,
2177
- options
2178
- } = _ref5;
2179
- const indices = getIndices(data);
2180
- const storeGeometry = options?.storeGeometry || false;
2181
- for (let i = 0; i < indices.length - 1; i++) {
2182
- const startIndex = indices[i];
2183
- const endIndex = indices[i + 1];
2184
- processTileFeatureProperties({
2185
- map,
2186
- data,
2187
- startIndex,
2188
- endIndex,
2189
- type,
2190
- bbox,
2191
- tileFormat,
2192
- uniqueIdProperty,
2193
- storeGeometry
2194
- });
2052
+ if (spatialDataType !== 'geo') {
2053
+ if (spatialFiltersResolution > 0) {
2054
+ queryParams.spatialFiltersResolution = spatialFiltersResolution;
2055
+ }
2056
+ queryParams.spatialFiltersMode = spatialFiltersMode;
2195
2057
  }
2058
+ const urlWithSearchParams = url + '?' + objectToURLSearchParams(queryParams).toString();
2059
+ const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
2060
+ if (isGet) {
2061
+ url = urlWithSearchParams;
2062
+ }
2063
+ return makeCall({
2064
+ url,
2065
+ accessToken: source.accessToken,
2066
+ opts: {
2067
+ ...opts,
2068
+ method: isGet ? 'GET' : 'POST',
2069
+ ...(!isGet && {
2070
+ body: JSON.stringify(queryParams)
2071
+ })
2072
+ }
2073
+ });
2196
2074
  }
2197
- function createIndicesForPoints(data) {
2198
- const featureIds = data.featureIds.value;
2199
- const lastFeatureId = featureIds[featureIds.length - 1];
2200
- const PointIndicesArray = featureIds.constructor;
2201
- const pointIndices = {
2202
- value: new PointIndicesArray(featureIds.length + 1),
2203
- size: 1
2204
- };
2205
- pointIndices.value.set(featureIds);
2206
- pointIndices.value.set([lastFeatureId + 1], featureIds.length);
2207
- // @ts-expect-error Missing or changed types?
2208
- data.pointIndices = pointIndices;
2075
+ function objectToURLSearchParams(object) {
2076
+ const params = new URLSearchParams();
2077
+ for (const key in object) {
2078
+ if (isPureObject(object[key])) {
2079
+ params.append(key, JSON.stringify(object[key]));
2080
+ } else if (Array.isArray(object[key])) {
2081
+ params.append(key, JSON.stringify(object[key]));
2082
+ } else if (object[key] === null) {
2083
+ params.append(key, 'null');
2084
+ } else if (object[key] !== undefined) {
2085
+ params.append(key, String(object[key]));
2086
+ }
2087
+ }
2088
+ return params;
2209
2089
  }
2210
2090
 
2211
- function tileFeaturesSpatialIndex(_ref) {
2212
- let {
2213
- tiles,
2214
- spatialFilter,
2215
- spatialDataColumn,
2216
- spatialDataType
2217
- } = _ref;
2218
- const map = new Map();
2219
- const spatialIndex = getSpatialIndex(spatialDataType);
2220
- const resolution = getResolution(tiles, spatialIndex);
2221
- const spatialIndexIDName = spatialDataColumn ? spatialDataColumn : spatialIndex;
2222
- if (!resolution) {
2223
- return [];
2091
+ /**
2092
+ * Source for Widget API requests.
2093
+ *
2094
+ * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
2095
+ */
2096
+ class WidgetRemoteSource extends WidgetSource {
2097
+ constructor() {
2098
+ super(...arguments);
2099
+ this._headers = {};
2224
2100
  }
2225
- const cells = getCellsCoverGeometry(spatialFilter, spatialIndex, resolution);
2226
- if (!cells?.length) {
2227
- return [];
2101
+ /** Assigns HTTP headers to be included on API requests from this source. */
2102
+ setRequestHeaders(headers) {
2103
+ this._headers = headers;
2228
2104
  }
2229
- // We transform cells to Set to improve the performace
2230
- const cellsSet = new Set(cells);
2231
- for (const tile of tiles) {
2232
- if (tile.isVisible === false || !tile.data) {
2233
- continue;
2105
+ getCategories(options) {
2106
+ try {
2107
+ const _this = this;
2108
+ const {
2109
+ filters = _this.props.filters,
2110
+ filterOwner,
2111
+ spatialFilter,
2112
+ spatialFiltersMode,
2113
+ spatialIndexReferenceViewState,
2114
+ abortController,
2115
+ ...params
2116
+ } = options;
2117
+ const {
2118
+ column,
2119
+ operation,
2120
+ operationColumn
2121
+ } = params;
2122
+ const source = _this.getModelSource(filters, filterOwner);
2123
+ const spatialFiltersResolution = _this._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2124
+ return Promise.resolve(executeModel({
2125
+ model: 'category',
2126
+ source: {
2127
+ ...source,
2128
+ spatialFiltersResolution,
2129
+ spatialFiltersMode,
2130
+ spatialFilter
2131
+ },
2132
+ params: {
2133
+ column,
2134
+ operation,
2135
+ operationColumn: operationColumn || column
2136
+ },
2137
+ opts: {
2138
+ abortController,
2139
+ headers: _this._headers
2140
+ }
2141
+ }).then(res => normalizeObjectKeys(res.rows)));
2142
+ } catch (e) {
2143
+ return Promise.reject(e);
2144
+ }
2145
+ }
2146
+ getFeatures(options) {
2147
+ try {
2148
+ const _this2 = this;
2149
+ const {
2150
+ filters = _this2.props.filters,
2151
+ filterOwner,
2152
+ spatialFilter,
2153
+ spatialFiltersMode,
2154
+ spatialIndexReferenceViewState,
2155
+ abortController,
2156
+ ...params
2157
+ } = options;
2158
+ const {
2159
+ columns,
2160
+ dataType,
2161
+ featureIds,
2162
+ z,
2163
+ limit,
2164
+ tileResolution
2165
+ } = params;
2166
+ const source = _this2.getModelSource(filters, filterOwner);
2167
+ const spatialFiltersResolution = _this2._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2168
+ return Promise.resolve(executeModel({
2169
+ model: 'pick',
2170
+ source: {
2171
+ ...source,
2172
+ spatialFiltersResolution,
2173
+ spatialFiltersMode,
2174
+ spatialFilter
2175
+ },
2176
+ params: {
2177
+ columns,
2178
+ dataType,
2179
+ featureIds,
2180
+ z,
2181
+ limit: limit || 1000,
2182
+ tileResolution: tileResolution || DEFAULT_TILE_RESOLUTION
2183
+ },
2184
+ opts: {
2185
+ abortController,
2186
+ headers: _this2._headers
2187
+ }
2188
+ // Avoid `normalizeObjectKeys()`, which changes column names.
2189
+ }).then(_ref => {
2190
+ let {
2191
+ rows
2192
+ } = _ref;
2193
+ return {
2194
+ rows
2195
+ };
2196
+ }));
2197
+ } catch (e) {
2198
+ return Promise.reject(e);
2234
2199
  }
2235
- tile.data.forEach(d => {
2236
- if (cellsSet.has(d.id)) {
2237
- map.set(d.id, {
2238
- ...d.properties,
2239
- [spatialIndexIDName]: d.id
2240
- });
2241
- }
2242
- });
2243
- }
2244
- return Array.from(map.values());
2245
- }
2246
- function getResolution(tiles, spatialIndex) {
2247
- const data = tiles.find(tile => tile.data?.length)?.data;
2248
- if (!data) {
2249
- return;
2250
2200
  }
2251
- if (spatialIndex === exports.SpatialIndex.QUADBIN) {
2252
- return Number(quadbin.getResolution(data[0].id));
2253
- }
2254
- if (spatialIndex === exports.SpatialIndex.H3) {
2255
- return h3Js.getResolution(data[0].id);
2201
+ getFormula(options) {
2202
+ try {
2203
+ const _this3 = this;
2204
+ const {
2205
+ filters = _this3.props.filters,
2206
+ filterOwner,
2207
+ spatialFilter,
2208
+ spatialFiltersMode,
2209
+ spatialIndexReferenceViewState,
2210
+ abortController,
2211
+ operationExp,
2212
+ ...params
2213
+ } = options;
2214
+ const {
2215
+ column,
2216
+ operation
2217
+ } = params;
2218
+ const source = _this3.getModelSource(filters, filterOwner);
2219
+ const spatialFiltersResolution = _this3._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2220
+ return Promise.resolve(executeModel({
2221
+ model: 'formula',
2222
+ source: {
2223
+ ...source,
2224
+ spatialFiltersResolution,
2225
+ spatialFiltersMode,
2226
+ spatialFilter
2227
+ },
2228
+ params: {
2229
+ column: column ?? '*',
2230
+ operation: operation ?? 'count',
2231
+ operationExp
2232
+ },
2233
+ opts: {
2234
+ abortController,
2235
+ headers: _this3._headers
2236
+ }
2237
+ }).then(res => normalizeObjectKeys(res.rows[0])));
2238
+ } catch (e) {
2239
+ return Promise.reject(e);
2240
+ }
2256
2241
  }
2257
- }
2258
- const bboxWest = [-180, -90, 0, 90];
2259
- const bboxEast = [0, -90, 180, 90];
2260
- function getCellsCoverGeometry(geometry, spatialIndex, resolution) {
2261
- if (spatialIndex === exports.SpatialIndex.QUADBIN) {
2262
- // @ts-expect-error TODO: Probably ought to be stricter about number vs. bigint types in this file.
2263
- return quadbin.geometryToCells(geometry, resolution);
2242
+ getHistogram(options) {
2243
+ try {
2244
+ const _this4 = this;
2245
+ const {
2246
+ filters = _this4.props.filters,
2247
+ filterOwner,
2248
+ spatialFilter,
2249
+ spatialFiltersMode,
2250
+ spatialIndexReferenceViewState,
2251
+ abortController,
2252
+ ...params
2253
+ } = options;
2254
+ const {
2255
+ column,
2256
+ operation,
2257
+ ticks
2258
+ } = params;
2259
+ const source = _this4.getModelSource(filters, filterOwner);
2260
+ const spatialFiltersResolution = _this4._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2261
+ return Promise.resolve(executeModel({
2262
+ model: 'histogram',
2263
+ source: {
2264
+ ...source,
2265
+ spatialFiltersResolution,
2266
+ spatialFiltersMode,
2267
+ spatialFilter
2268
+ },
2269
+ params: {
2270
+ column,
2271
+ operation,
2272
+ ticks
2273
+ },
2274
+ opts: {
2275
+ abortController,
2276
+ headers: _this4._headers
2277
+ }
2278
+ }).then(res => normalizeObjectKeys(res.rows))).then(function (data) {
2279
+ if (data.length) {
2280
+ // Given N ticks the API returns up to N+1 bins, omitting any empty bins. Bins
2281
+ // include 1 bin below the lowest tick, N-1 between ticks, and 1 bin above the highest tick.
2282
+ const result = Array(ticks.length + 1).fill(0);
2283
+ data.forEach(_ref2 => {
2284
+ let {
2285
+ tick,
2286
+ value
2287
+ } = _ref2;
2288
+ return result[tick] = value;
2289
+ });
2290
+ return result;
2291
+ }
2292
+ return [];
2293
+ });
2294
+ } catch (e) {
2295
+ return Promise.reject(e);
2296
+ }
2264
2297
  }
2265
- if (spatialIndex === exports.SpatialIndex.H3) {
2266
- // The current H3 polyfill algorithm can't deal with polygon segments of greater than 180 degrees longitude
2267
- // so we clip the geometry to be sure that none of them is greater than 180 degrees
2268
- // https://github.com/uber/h3-js/issues/24#issuecomment-431893796
2269
- return h3Js.polygonToCells(bboxClip(geometry, bboxWest).geometry.coordinates, resolution, true).concat(h3Js.polygonToCells(bboxClip(geometry, bboxEast).geometry.coordinates, resolution, true));
2298
+ getRange(options) {
2299
+ try {
2300
+ const _this5 = this;
2301
+ const {
2302
+ filters = _this5.props.filters,
2303
+ filterOwner,
2304
+ spatialFilter,
2305
+ spatialFiltersMode,
2306
+ spatialIndexReferenceViewState,
2307
+ abortController,
2308
+ ...params
2309
+ } = options;
2310
+ const {
2311
+ column
2312
+ } = params;
2313
+ const source = _this5.getModelSource(filters, filterOwner);
2314
+ const spatialFiltersResolution = _this5._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2315
+ return Promise.resolve(executeModel({
2316
+ model: 'range',
2317
+ source: {
2318
+ ...source,
2319
+ spatialFiltersResolution,
2320
+ spatialFiltersMode,
2321
+ spatialFilter
2322
+ },
2323
+ params: {
2324
+ column
2325
+ },
2326
+ opts: {
2327
+ abortController,
2328
+ headers: _this5._headers
2329
+ }
2330
+ }).then(res => normalizeObjectKeys(res.rows[0])));
2331
+ } catch (e) {
2332
+ return Promise.reject(e);
2333
+ }
2270
2334
  }
2271
- }
2272
- function getSpatialIndex(spatialDataType) {
2273
- switch (spatialDataType) {
2274
- case 'h3':
2275
- return exports.SpatialIndex.H3;
2276
- case 'quadbin':
2277
- return exports.SpatialIndex.QUADBIN;
2278
- default:
2279
- throw new Error('Unexpected spatial data type');
2335
+ getScatter(options) {
2336
+ try {
2337
+ const _this6 = this;
2338
+ const {
2339
+ filters = _this6.props.filters,
2340
+ filterOwner,
2341
+ spatialFilter,
2342
+ spatialFiltersMode,
2343
+ spatialIndexReferenceViewState,
2344
+ abortController,
2345
+ ...params
2346
+ } = options;
2347
+ const {
2348
+ xAxisColumn,
2349
+ xAxisJoinOperation,
2350
+ yAxisColumn,
2351
+ yAxisJoinOperation
2352
+ } = params;
2353
+ const source = _this6.getModelSource(filters, filterOwner);
2354
+ const spatialFiltersResolution = _this6._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2355
+ // Make sure this is sync with the same constant in cloud-native/maps-api
2356
+ const HARD_LIMIT = 500;
2357
+ return Promise.resolve(executeModel({
2358
+ model: 'scatterplot',
2359
+ source: {
2360
+ ...source,
2361
+ spatialFiltersResolution,
2362
+ spatialFiltersMode,
2363
+ spatialFilter
2364
+ },
2365
+ params: {
2366
+ xAxisColumn,
2367
+ xAxisJoinOperation,
2368
+ yAxisColumn,
2369
+ yAxisJoinOperation,
2370
+ limit: HARD_LIMIT
2371
+ },
2372
+ opts: {
2373
+ abortController,
2374
+ headers: _this6._headers
2375
+ }
2376
+ }).then(res => normalizeObjectKeys(res.rows)).then(res => res.map(_ref3 => {
2377
+ let {
2378
+ x,
2379
+ y
2380
+ } = _ref3;
2381
+ return [x, y];
2382
+ })));
2383
+ } catch (e) {
2384
+ return Promise.reject(e);
2385
+ }
2280
2386
  }
2281
- }
2282
-
2283
- function tileFeaturesRaster(_ref) {
2284
- let {
2285
- tiles,
2286
- ...options
2287
- } = _ref;
2288
- // Cache band metadata for faster lookup while iterating over pixels.
2289
- const bandMetadataByName = {};
2290
- for (const band of options.rasterMetadata.bands) {
2291
- bandMetadataByName[band.name] = band;
2387
+ getTable(options) {
2388
+ try {
2389
+ const _this7 = this;
2390
+ const {
2391
+ filters = _this7.props.filters,
2392
+ filterOwner,
2393
+ spatialFilter,
2394
+ spatialFiltersMode,
2395
+ spatialIndexReferenceViewState,
2396
+ abortController,
2397
+ ...params
2398
+ } = options;
2399
+ const {
2400
+ columns,
2401
+ sortBy,
2402
+ sortDirection,
2403
+ offset = 0,
2404
+ limit = 10
2405
+ } = params;
2406
+ const source = _this7.getModelSource(filters, filterOwner);
2407
+ const spatialFiltersResolution = _this7._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2408
+ return Promise.resolve(executeModel({
2409
+ model: 'table',
2410
+ source: {
2411
+ ...source,
2412
+ spatialFiltersResolution,
2413
+ spatialFiltersMode,
2414
+ spatialFilter
2415
+ },
2416
+ params: {
2417
+ column: columns,
2418
+ sortBy,
2419
+ sortDirection,
2420
+ limit,
2421
+ offset
2422
+ },
2423
+ opts: {
2424
+ abortController,
2425
+ headers: _this7._headers
2426
+ }
2427
+ }).then(res => ({
2428
+ // Avoid `normalizeObjectKeys()`, which changes column names.
2429
+ rows: res.rows ?? res.ROWS,
2430
+ totalCount: res.metadata?.total ?? res.METADATA?.TOTAL
2431
+ })));
2432
+ } catch (e) {
2433
+ return Promise.reject(e);
2434
+ }
2292
2435
  }
2293
- // Omit empty and invisible tiles for simpler processing and types.
2294
- tiles = tiles.filter(isRasterTileVisible);
2295
- if (tiles.length === 0) return [];
2296
- // Raster tiles, and all pixels, are quadbin cells. Resolution of a pixel is
2297
- // the resolution of the tile, plus the number of subdivisions. Block size
2298
- // must be square, N x N, where N is a power of two.
2299
- const tileResolution = quadbin.getResolution(tiles[0].index.q);
2300
- const tileBlockSize = tiles[0].data.blockSize;
2301
- const cellResolution = tileResolution + BigInt(Math.log2(tileBlockSize));
2302
- // Compute covering cells for the spatial filter, at same resolution as the
2303
- // raster pixels, to be used as a mask.
2304
- const spatialFilterCells = new Set(quadbin.geometryToCells(options.spatialFilter, cellResolution));
2305
- const data = new Map();
2306
- for (const tile of tiles) {
2307
- const parent = tile.index.q;
2308
- const children = cellToChildrenSorted(parent, cellResolution);
2309
- // For each pixel/cell within the spatial filter, create a FeatureData.
2310
- // Order is row-major, starting from NW and ending at SE.
2311
- for (let i = 0; i < children.length; i++) {
2312
- if (!spatialFilterCells.has(children[i])) continue;
2313
- const cellData = {};
2314
- let cellDataExists = false;
2315
- for (const band in tile.data.cells.numericProps) {
2316
- const value = tile.data.cells.numericProps[band].value[i];
2317
- // TODO(cleanup): nodata should be a number, not a string.
2318
- if (Number(bandMetadataByName[band].nodata) !== value) {
2319
- cellData[band] = tile.data.cells.numericProps[band].value[i];
2320
- cellDataExists = true;
2436
+ getTimeSeries(options) {
2437
+ try {
2438
+ const _this8 = this;
2439
+ const {
2440
+ filters = _this8.props.filters,
2441
+ filterOwner,
2442
+ abortController,
2443
+ spatialFilter,
2444
+ spatialFiltersMode,
2445
+ spatialIndexReferenceViewState,
2446
+ ...params
2447
+ } = options;
2448
+ const {
2449
+ column,
2450
+ operationColumn,
2451
+ joinOperation,
2452
+ operation,
2453
+ stepSize,
2454
+ stepMultiplier,
2455
+ splitByCategory,
2456
+ splitByCategoryLimit,
2457
+ splitByCategoryValues
2458
+ } = params;
2459
+ const source = _this8.getModelSource(filters, filterOwner);
2460
+ const spatialFiltersResolution = _this8._getSpatialFiltersResolution(source, spatialFilter, spatialIndexReferenceViewState);
2461
+ return Promise.resolve(executeModel({
2462
+ model: 'timeseries',
2463
+ source: {
2464
+ ...source,
2465
+ spatialFiltersResolution,
2466
+ spatialFiltersMode,
2467
+ spatialFilter
2468
+ },
2469
+ params: {
2470
+ column,
2471
+ stepSize,
2472
+ stepMultiplier,
2473
+ operationColumn: operationColumn || column,
2474
+ joinOperation,
2475
+ operation,
2476
+ splitByCategory,
2477
+ splitByCategoryLimit,
2478
+ splitByCategoryValues
2479
+ },
2480
+ opts: {
2481
+ abortController,
2482
+ headers: _this8._headers
2321
2483
  }
2322
- }
2323
- if (cellDataExists) {
2324
- data.set(children[i], cellData);
2325
- }
2484
+ }).then(res => ({
2485
+ rows: normalizeObjectKeys(res.rows),
2486
+ categories: res.metadata?.categories
2487
+ })));
2488
+ } catch (e) {
2489
+ return Promise.reject(e);
2326
2490
  }
2327
2491
  }
2328
- return Array.from(data.values());
2329
- }
2330
- /**
2331
- * Detects whether a given {@link Tile} is a {@link RasterTile}.
2332
- * @privateRemarks Method of detection is arbitrary, and may be changed.
2333
- */
2334
- function isRasterTile(tile) {
2335
- return tile.data ? tile.data.hasOwnProperty('cells') : false;
2336
- }
2337
- function isRasterTileVisible(tile) {
2338
- return !!(tile.isVisible && tile.data?.cells?.numericProps);
2339
2492
  }
2493
+
2340
2494
  /**
2341
- * For the raster format, children are sorted in row-major order, starting from
2342
- * NW and ending at SE. Order returned by quadbin's cellToChildren() is not
2343
- * defined (and not related to the raster format), so sort explicitly here.
2495
+ * Source for Widget API requests on a data source defined by a SQL query.
2496
+ *
2497
+ * Generally not intended to be constructed directly. Instead, call
2498
+ * {@link vectorQuerySource}, {@link h3QuerySource}, or {@link quadbinQuerySource},
2499
+ * which can be shared with map layers. Sources contain a `widgetSource` property,
2500
+ * for use by widget implementations.
2501
+ *
2502
+ * Example:
2503
+ *
2504
+ * ```javascript
2505
+ * import { vectorQuerySource } from '@carto/api-client';
2506
+ *
2507
+ * const data = vectorQuerySource({
2508
+ * accessToken: '••••',
2509
+ * connectionName: 'carto_dw',
2510
+ * sqlQuery: 'SELECT * FROM carto-demo-data.demo_tables.retail_stores'
2511
+ * });
2512
+ *
2513
+ * const { widgetSource } = await data;
2514
+ * ```
2344
2515
  */
2345
- function cellToChildrenSorted(parent, resolution) {
2346
- return quadbin.cellToChildren(parent, resolution).sort((cellA, cellB) => {
2347
- const tileA = quadbin.cellToTile(cellA);
2348
- const tileB = quadbin.cellToTile(cellB);
2349
- if (tileA.y !== tileB.y) {
2350
- return tileA.y > tileB.y ? 1 : -1;
2351
- }
2352
- return tileA.x > tileB.x ? 1 : -1;
2353
- });
2354
- }
2355
-
2356
- /** @internalRemarks Source: @carto/react-core */
2357
- function tileFeatures(_ref) {
2358
- let {
2359
- tiles,
2360
- spatialFilter,
2361
- uniqueIdProperty,
2362
- tileFormat,
2363
- spatialDataColumn = DEFAULT_GEO_COLUMN,
2364
- spatialDataType,
2365
- rasterMetadata,
2366
- options = {}
2367
- } = _ref;
2368
- // TODO(cleanup): Is an empty response the expected result when spatialFilter
2369
- // is omitted? Why not make the parameter required, or return the full input?
2370
- if (!spatialFilter) {
2371
- return [];
2372
- }
2373
- if (spatialDataType === 'geo') {
2374
- return tileFeaturesGeometries({
2375
- tiles,
2376
- tileFormat,
2377
- spatialFilter,
2378
- uniqueIdProperty,
2379
- options
2380
- });
2381
- }
2382
- if (tiles.some(isRasterTile)) {
2383
- assert$1(rasterMetadata, 'Missing raster metadata');
2384
- return tileFeaturesRaster({
2385
- tiles: tiles,
2386
- spatialFilter,
2387
- spatialDataColumn,
2388
- spatialDataType,
2389
- rasterMetadata
2390
- });
2516
+ class WidgetQuerySource extends WidgetRemoteSource {
2517
+ getModelSource(filters, filterOwner) {
2518
+ return {
2519
+ ...super._getModelSource(filters, filterOwner),
2520
+ type: 'query',
2521
+ data: this.props.sqlQuery,
2522
+ queryParameters: this.props.queryParameters
2523
+ };
2391
2524
  }
2392
- return tileFeaturesSpatialIndex({
2393
- tiles: tiles,
2394
- spatialFilter,
2395
- spatialDataColumn,
2396
- spatialDataType
2397
- });
2398
2525
  }
2399
2526
 
2400
- /** @internalRemarks Source: @carto/react-core */
2527
+ /** @privateRemarks Source: @carto/react-core */
2401
2528
  const aggregationFunctions = {
2402
2529
  count: values => values.length,
2403
2530
  min: function () {
@@ -2413,7 +2540,7 @@ const aggregationFunctions = {
2413
2540
  return applyAggregationFunction(avg, ...[].slice.call(arguments));
2414
2541
  }
2415
2542
  };
2416
- /** @internalRemarks Source: @carto/react-core */
2543
+ /** @privateRemarks Source: @carto/react-core */
2417
2544
  function aggregate(feature, keys, operation) {
2418
2545
  if (!keys?.length) {
2419
2546
  throw new Error('Cannot aggregate a feature without having keys');
@@ -2562,7 +2689,7 @@ var thenBy_module = function () {
2562
2689
  * @param [sortOptions.sortByDirection] - Direction by the columns will be sorted
2563
2690
  * @param [sortOptions.sortByColumnType] - Column type
2564
2691
  * @internal
2565
- * @internalRemarks Source: @carto/react-core
2692
+ * @privateRemarks Source: @carto/react-core
2566
2693
  */
2567
2694
  function applySorting(features, _temp) {
2568
2695
  let {
@@ -2601,7 +2728,7 @@ function createSortFn(_ref) {
2601
2728
  sortByColumnType
2602
2729
  });
2603
2730
  let sortFn = thenBy_module.firstBy(...firstSortOption);
2604
- for (let sortOptions of othersSortOptions) {
2731
+ for (const sortOptions of othersSortOptions) {
2605
2732
  sortFn = sortFn.thenBy(...sortOptions);
2606
2733
  }
2607
2734
  return sortFn;
@@ -2650,7 +2777,7 @@ function normalizeSortByOptions(_ref2) {
2650
2777
  });
2651
2778
  }
2652
2779
 
2653
- /** @internalRemarks Source: @carto/react-core */
2780
+ /** @privateRemarks Source: @carto/react-core */
2654
2781
  function groupValuesByColumn(_ref) {
2655
2782
  let {
2656
2783
  data,
@@ -2712,7 +2839,7 @@ const GROUP_KEY_FN_MAPPING = {
2712
2839
  minute: date => Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes()),
2713
2840
  second: date => Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds())
2714
2841
  };
2715
- /** @internalRemarks Source: @carto/react-core */
2842
+ /** @privateRemarks Source: @carto/react-core */
2716
2843
  function groupValuesByDateColumn(_ref) {
2717
2844
  let {
2718
2845
  data,
@@ -2760,7 +2887,7 @@ function groupValuesByDateColumn(_ref) {
2760
2887
 
2761
2888
  /**
2762
2889
  * Histogram computation.
2763
- * @internalRemarks Source: @carto/react-core
2890
+ * @privateRemarks Source: @carto/react-core
2764
2891
  */
2765
2892
  function histogram(_ref) {
2766
2893
  let {
@@ -2798,7 +2925,7 @@ function histogram(_ref) {
2798
2925
 
2799
2926
  /**
2800
2927
  * Filters invalid features and formats data.
2801
- * @internalRemarks Source: @carto/react-core
2928
+ * @privateRemarks Source: @carto/react-core
2802
2929
  */
2803
2930
  function scatterPlot(_ref) {
2804
2931
  let {
@@ -2847,10 +2974,12 @@ class WidgetTilesetSource extends WidgetSource {
2847
2974
  super(...arguments);
2848
2975
  this._tiles = [];
2849
2976
  this._features = [];
2977
+ this._tileFeatureExtractOptions = {};
2978
+ this._tileFeatureExtractPreviousInputs = {};
2850
2979
  }
2851
- getModelSource(owner) {
2980
+ getModelSource(filters, filterOwner) {
2852
2981
  return {
2853
- ...super._getModelSource(owner),
2982
+ ...super._getModelSource(filters, filterOwner),
2854
2983
  type: 'tileset',
2855
2984
  data: this.props.tableName
2856
2985
  };
@@ -2862,39 +2991,46 @@ class WidgetTilesetSource extends WidgetSource {
2862
2991
  */
2863
2992
  loadTiles(tiles) {
2864
2993
  this._tiles = tiles;
2865
- }
2866
- /**
2867
- * Extracts feature data from tiles previously loaded with {@link loadTiles}.
2868
- * Must be called before computing statistics on tiles.
2869
- */
2870
- extractTileFeatures(_ref) {
2871
- let {
2872
- spatialFilter,
2873
- uniqueIdProperty,
2874
- options
2875
- } = _ref;
2994
+ this._features.length = 0;
2995
+ }
2996
+ /** Configures options used to extract features from tiles. */
2997
+ setTileFeatureExtractOptions(options) {
2998
+ this._tileFeatureExtractOptions = options;
2999
+ this._features.length = 0;
3000
+ }
3001
+ _extractTileFeatures(spatialFilter) {
3002
+ // When spatial filter has not changed, don't redo extraction. If tiles or
3003
+ // tile extract options change, features will have been cleared already.
3004
+ const prevInputs = this._tileFeatureExtractPreviousInputs;
3005
+ if (this._features.length && prevInputs.spatialFilter && booleanEqual.booleanEqual(prevInputs.spatialFilter, spatialFilter)) {
3006
+ return;
3007
+ }
2876
3008
  this._features = tileFeatures({
3009
+ ...this.props,
3010
+ ...this._tileFeatureExtractOptions,
2877
3011
  tiles: this._tiles,
2878
- options,
2879
- spatialFilter,
2880
- uniqueIdProperty,
2881
- ...this.props
3012
+ spatialFilter
2882
3013
  });
3014
+ prevInputs.spatialFilter = spatialFilter;
2883
3015
  }
2884
- /** Loads features as GeoJSON (used for testing). */
2885
- loadGeoJSON(_ref2) {
3016
+ /**
3017
+ * Loads features as GeoJSON (used for testing).
3018
+ * @experimental
3019
+ * @internal Not for public use. Spatial filters in other method calls will be ignored.
3020
+ */
3021
+ loadGeoJSON(_ref) {
2886
3022
  let {
2887
3023
  geojson,
2888
- spatialFilter,
2889
- uniqueIdProperty
2890
- } = _ref2;
3024
+ spatialFilter
3025
+ } = _ref;
2891
3026
  this._features = geojsonFeatures({
2892
3027
  geojson,
2893
3028
  spatialFilter,
2894
- uniqueIdProperty
3029
+ ...this._tileFeatureExtractOptions
2895
3030
  });
3031
+ this._tileFeatureExtractPreviousInputs.spatialFilter = spatialFilter;
2896
3032
  }
2897
- getFeatures(options) {
3033
+ getFeatures() {
2898
3034
  try {
2899
3035
  throw new Error('getFeatures not supported for tilesets');
2900
3036
  return Promise.resolve();
@@ -2902,28 +3038,25 @@ class WidgetTilesetSource extends WidgetSource {
2902
3038
  return Promise.reject(e);
2903
3039
  }
2904
3040
  }
2905
- getFormula(_ref3) {
3041
+ getFormula(_ref2) {
2906
3042
  let {
2907
3043
  column = '*',
2908
3044
  operation = 'count',
2909
3045
  joinOperation,
2910
- filterOwner
2911
- } = _ref3;
3046
+ filters,
3047
+ filterOwner,
3048
+ spatialFilter
3049
+ } = _ref2;
2912
3050
  try {
2913
3051
  const _this = this;
2914
3052
  if (operation === 'custom') {
2915
3053
  throw new Error('Custom aggregation not supported for tilesets');
2916
3054
  }
2917
- if (!_this._features.length) {
2918
- return Promise.resolve({
2919
- value: null
2920
- });
2921
- }
2922
3055
  // Column is required except when operation is 'count'.
2923
3056
  if (column && column !== '*' || operation !== 'count') {
2924
3057
  assertColumn(_this._features, column);
2925
3058
  }
2926
- const filteredFeatures = _this._getFilteredFeatures(filterOwner);
3059
+ const filteredFeatures = _this._getFilteredFeatures(spatialFilter, filters, filterOwner);
2927
3060
  if (filteredFeatures.length === 0 && operation !== 'count') {
2928
3061
  return Promise.resolve({
2929
3062
  value: null
@@ -2937,21 +3070,23 @@ class WidgetTilesetSource extends WidgetSource {
2937
3070
  return Promise.reject(e);
2938
3071
  }
2939
3072
  }
2940
- getHistogram(_ref4) {
3073
+ getHistogram(_ref3) {
2941
3074
  let {
2942
3075
  operation = 'count',
2943
3076
  ticks,
2944
3077
  column,
2945
3078
  joinOperation,
2946
- filterOwner
2947
- } = _ref4;
3079
+ filters,
3080
+ filterOwner,
3081
+ spatialFilter
3082
+ } = _ref3;
2948
3083
  try {
2949
3084
  const _this2 = this;
3085
+ const filteredFeatures = _this2._getFilteredFeatures(spatialFilter, filters, filterOwner);
3086
+ assertColumn(_this2._features, column);
2950
3087
  if (!_this2._features.length) {
2951
3088
  return Promise.resolve([]);
2952
3089
  }
2953
- const filteredFeatures = _this2._getFilteredFeatures(filterOwner);
2954
- assertColumn(_this2._features, column);
2955
3090
  return Promise.resolve(histogram({
2956
3091
  data: filteredFeatures,
2957
3092
  valuesColumns: normalizeColumns(column),
@@ -2963,20 +3098,22 @@ class WidgetTilesetSource extends WidgetSource {
2963
3098
  return Promise.reject(e);
2964
3099
  }
2965
3100
  }
2966
- getCategories(_ref5) {
3101
+ getCategories(_ref4) {
2967
3102
  let {
2968
3103
  column,
2969
3104
  operation = 'count',
2970
3105
  operationColumn,
2971
3106
  joinOperation,
2972
- filterOwner
2973
- } = _ref5;
3107
+ filters,
3108
+ filterOwner,
3109
+ spatialFilter
3110
+ } = _ref4;
2974
3111
  try {
2975
3112
  const _this3 = this;
2976
- if (!_this3._features.length) {
3113
+ const filteredFeatures = _this3._getFilteredFeatures(spatialFilter, filters, filterOwner);
3114
+ if (!filteredFeatures.length) {
2977
3115
  return Promise.resolve([]);
2978
3116
  }
2979
- const filteredFeatures = _this3._getFilteredFeatures(filterOwner);
2980
3117
  assertColumn(_this3._features, column, operationColumn);
2981
3118
  const groups = groupValuesByColumn({
2982
3119
  data: filteredFeatures,
@@ -2990,20 +3127,22 @@ class WidgetTilesetSource extends WidgetSource {
2990
3127
  return Promise.reject(e);
2991
3128
  }
2992
3129
  }
2993
- getScatter(_ref6) {
3130
+ getScatter(_ref5) {
2994
3131
  let {
2995
3132
  xAxisColumn,
2996
3133
  yAxisColumn,
2997
3134
  xAxisJoinOperation,
2998
3135
  yAxisJoinOperation,
2999
- filterOwner
3000
- } = _ref6;
3136
+ filters,
3137
+ filterOwner,
3138
+ spatialFilter
3139
+ } = _ref5;
3001
3140
  try {
3002
3141
  const _this4 = this;
3003
- if (!_this4._features.length) {
3142
+ const filteredFeatures = _this4._getFilteredFeatures(spatialFilter, filters, filterOwner);
3143
+ if (!filteredFeatures.length) {
3004
3144
  return Promise.resolve([]);
3005
3145
  }
3006
- const filteredFeatures = _this4._getFilteredFeatures(filterOwner);
3007
3146
  assertColumn(_this4._features, xAxisColumn, yAxisColumn);
3008
3147
  return Promise.resolve(scatterPlot({
3009
3148
  data: filteredFeatures,
@@ -3016,35 +3155,31 @@ class WidgetTilesetSource extends WidgetSource {
3016
3155
  return Promise.reject(e);
3017
3156
  }
3018
3157
  }
3019
- getTable(options) {
3158
+ getTable(_ref6) {
3159
+ let {
3160
+ columns,
3161
+ searchFilterColumn,
3162
+ searchFilterText,
3163
+ sortBy,
3164
+ sortDirection,
3165
+ sortByColumnType,
3166
+ offset = 0,
3167
+ limit = 10,
3168
+ filters,
3169
+ filterOwner,
3170
+ spatialFilter
3171
+ } = _ref6;
3020
3172
  try {
3021
3173
  const _this5 = this;
3022
- const {
3023
- filterOwner,
3024
- spatialFilter,
3025
- abortController,
3026
- ...params
3027
- } = options;
3028
- const {
3029
- columns,
3030
- searchFilterColumn,
3031
- searchFilterText,
3032
- sortBy,
3033
- sortDirection,
3034
- sortByColumnType,
3035
- offset = 0,
3036
- limit = 10
3037
- } = params;
3038
- if (!_this5._features.length) {
3174
+ // Filter.
3175
+ let filteredFeatures = _this5._getFilteredFeatures(spatialFilter, filters, filterOwner);
3176
+ if (!filteredFeatures.length) {
3039
3177
  return Promise.resolve({
3040
3178
  rows: [],
3041
3179
  totalCount: 0
3042
3180
  });
3043
3181
  }
3044
- // Filter.
3045
- let filteredFeatures = _this5._getFilteredFeatures(filterOwner);
3046
3182
  // Search.
3047
- // TODO: Could we get the same behavior by applying filters in loadTiles()?
3048
3183
  if (searchFilterColumn && searchFilterText) {
3049
3184
  filteredFeatures = filteredFeatures.filter(row => row[searchFilterColumn] && String(row[searchFilterColumn]).toLowerCase().includes(String(searchFilterText).toLowerCase()));
3050
3185
  }
@@ -3080,16 +3215,18 @@ class WidgetTilesetSource extends WidgetSource {
3080
3215
  operation,
3081
3216
  operationColumn,
3082
3217
  joinOperation,
3083
- filterOwner
3218
+ filters,
3219
+ filterOwner,
3220
+ spatialFilter
3084
3221
  } = _ref7;
3085
3222
  try {
3086
3223
  const _this6 = this;
3087
- if (!_this6._features.length) {
3224
+ const filteredFeatures = _this6._getFilteredFeatures(spatialFilter, filters, filterOwner);
3225
+ if (!filteredFeatures.length) {
3088
3226
  return Promise.resolve({
3089
3227
  rows: []
3090
3228
  });
3091
3229
  }
3092
- const filteredFeatures = _this6._getFilteredFeatures(filterOwner);
3093
3230
  assertColumn(_this6._features, column, operationColumn);
3094
3231
  const rows = groupValuesByDateColumn({
3095
3232
  data: filteredFeatures,
@@ -3109,17 +3246,19 @@ class WidgetTilesetSource extends WidgetSource {
3109
3246
  getRange(_ref8) {
3110
3247
  let {
3111
3248
  column,
3112
- filterOwner
3249
+ filters,
3250
+ filterOwner,
3251
+ spatialFilter
3113
3252
  } = _ref8;
3114
3253
  try {
3115
3254
  const _this7 = this;
3255
+ assertColumn(_this7._features, column);
3256
+ const filteredFeatures = _this7._getFilteredFeatures(spatialFilter, filters, filterOwner);
3116
3257
  if (!_this7._features.length) {
3117
3258
  // TODO: Is this the only nullable response in the Widgets API? If so,
3118
3259
  // can we do something more consistent?
3119
3260
  return Promise.resolve(null);
3120
3261
  }
3121
- assertColumn(_this7._features, column);
3122
- const filteredFeatures = _this7._getFilteredFeatures(filterOwner);
3123
3262
  return Promise.resolve({
3124
3263
  min: aggregationFunctions.min(filteredFeatures, column),
3125
3264
  max: aggregationFunctions.max(filteredFeatures, column)
@@ -3131,8 +3270,10 @@ class WidgetTilesetSource extends WidgetSource {
3131
3270
  /****************************************************************************
3132
3271
  * INTERNAL
3133
3272
  */
3134
- _getFilteredFeatures(filterOwner) {
3135
- return applyFilters(this._features, getApplicableFilters(filterOwner, this.props.filters), this.props.filtersLogicalOperator || 'and');
3273
+ _getFilteredFeatures(spatialFilter, filters, filterOwner) {
3274
+ assert(spatialFilter, 'spatialFilter required for tilesets');
3275
+ this._extractTileFeatures(spatialFilter);
3276
+ return applyFilters(this._features, getApplicableFilters(filterOwner, filters || this.props.filters), this.props.filtersLogicalOperator || 'and');
3136
3277
  }
3137
3278
  }
3138
3279
  function assertColumn(features) {
@@ -3174,9 +3315,9 @@ class WidgetRasterSource extends WidgetTilesetSource {}
3174
3315
  * ```
3175
3316
  */
3176
3317
  class WidgetTableSource extends WidgetRemoteSource {
3177
- getModelSource(owner) {
3318
+ getModelSource(filters, filterOwner) {
3178
3319
  return {
3179
- ...super._getModelSource(owner),
3320
+ ...super._getModelSource(filters, filterOwner),
3180
3321
  type: 'table',
3181
3322
  data: this.props.tableName
3182
3323
  };
@@ -3186,7 +3327,6 @@ class WidgetTableSource extends WidgetRemoteSource {
3186
3327
  // deck.gl
3187
3328
  // SPDX-License-Identifier: MIT
3188
3329
  // Copyright (c) vis.gl contributors
3189
- /* eslint-disable camelcase */
3190
3330
  const h3QuerySource = function (options) {
3191
3331
  try {
3192
3332
  const {
@@ -3230,7 +3370,6 @@ const h3QuerySource = function (options) {
3230
3370
  // deck.gl
3231
3371
  // SPDX-License-Identifier: MIT
3232
3372
  // Copyright (c) vis.gl contributors
3233
- /* eslint-disable camelcase */
3234
3373
  const h3TableSource = function (options) {
3235
3374
  try {
3236
3375
  const {
@@ -3331,7 +3470,6 @@ const rasterSource = function (options) {
3331
3470
  // deck.gl
3332
3471
  // SPDX-License-Identifier: MIT
3333
3472
  // Copyright (c) vis.gl contributors
3334
- /* eslint-disable camelcase */
3335
3473
  const quadbinQuerySource = function (options) {
3336
3474
  try {
3337
3475
  const {
@@ -3375,7 +3513,6 @@ const quadbinQuerySource = function (options) {
3375
3513
  // deck.gl
3376
3514
  // SPDX-License-Identifier: MIT
3377
3515
  // Copyright (c) vis.gl contributors
3378
- /* eslint-disable camelcase */
3379
3516
  const quadbinTableSource = function (options) {
3380
3517
  try {
3381
3518
  const {
@@ -3441,7 +3578,6 @@ const quadbinTilesetSource = function (options) {
3441
3578
  // deck.gl
3442
3579
  // SPDX-License-Identifier: MIT
3443
3580
  // Copyright (c) vis.gl contributors
3444
- /* eslint-disable camelcase */
3445
3581
  const vectorQuerySource = function (options) {
3446
3582
  try {
3447
3583
  const {
@@ -3490,7 +3626,6 @@ const vectorQuerySource = function (options) {
3490
3626
  // deck.gl
3491
3627
  // SPDX-License-Identifier: MIT
3492
3628
  // Copyright (c) vis.gl contributors
3493
- /* eslint-disable camelcase */
3494
3629
  const vectorTableSource = function (options) {
3495
3630
  try {
3496
3631
  const {
@@ -3619,6 +3754,7 @@ exports.WidgetRemoteSource = WidgetRemoteSource;
3619
3754
  exports.WidgetSource = WidgetSource;
3620
3755
  exports.WidgetTableSource = WidgetTableSource;
3621
3756
  exports.WidgetTilesetSource = WidgetTilesetSource;
3757
+ exports._buildFeatureFilter = _buildFeatureFilter;
3622
3758
  exports._getHexagonResolution = _getHexagonResolution;
3623
3759
  exports.addFilter = addFilter;
3624
3760
  exports.aggregate = aggregate;
@@ -3628,7 +3764,6 @@ exports.applySorting = applySorting;
3628
3764
  exports.boundaryQuerySource = boundaryQuerySource;
3629
3765
  exports.boundaryTableSource = boundaryTableSource;
3630
3766
  exports.buildBinaryFeatureFilter = buildBinaryFeatureFilter;
3631
- exports.buildFeatureFilter = buildFeatureFilter;
3632
3767
  exports.buildPublicMapUrl = buildPublicMapUrl;
3633
3768
  exports.buildStatsUrl = buildStatsUrl;
3634
3769
  exports.clearFilters = clearFilters;
@@ -3637,6 +3772,7 @@ exports.createViewportSpatialFilter = createViewportSpatialFilter;
3637
3772
  exports.filterFunctions = filterFunctions;
3638
3773
  exports.geojsonFeatures = geojsonFeatures;
3639
3774
  exports.getClient = getClient;
3775
+ exports.getDataFilterExtensionProps = getDataFilterExtensionProps;
3640
3776
  exports.getFilter = getFilter;
3641
3777
  exports.groupValuesByColumn = groupValuesByColumn;
3642
3778
  exports.groupValuesByDateColumn = groupValuesByDateColumn;