@carto/api-client 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,15 @@
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
+
7
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
+
9
+ var bboxClip__default = /*#__PURE__*/_interopDefaultLegacy(bboxClip);
10
+ var bboxPolygon__default = /*#__PURE__*/_interopDefaultLegacy(bboxPolygon);
11
+ var union__default = /*#__PURE__*/_interopDefaultLegacy(union);
12
+
1
13
  /**
2
14
  * @internal
3
15
  * @internalRemarks Source: @carto/react-core
@@ -207,6 +219,129 @@ function getFilter(filters, _ref4) {
207
219
  return null;
208
220
  }
209
221
 
222
+ /**
223
+ * Returns a {@link SpatialFilter} for a given viewport, typically obtained
224
+ * from deck.gl's `viewport.getBounds()` method ([west, south, east, north]).
225
+ * If the viewport covers the entire world (to some margin of error in Web
226
+ * Mercator space), `undefined` is returned instead.
227
+ *
228
+ * If the viewport extends beyond longitude range [-180, +180], the polygon
229
+ * may be reformatted for compatibility with CARTO APIs.
230
+ */
231
+ function createViewportSpatialFilter(viewport) {
232
+ if (_isGlobalViewport(viewport)) {
233
+ return;
234
+ }
235
+ return createPolygonSpatialFilter(bboxPolygon__default["default"](viewport).geometry);
236
+ }
237
+ /**
238
+ * Returns a {@link SpatialFilter} for a given {@link Polygon} or
239
+ * {@link MultiPolygon}. If the polygon(s) extend outside longitude
240
+ * range [-180, +180], the result may be reformatted for compatibility
241
+ * with CARTO APIs.
242
+ */
243
+ function createPolygonSpatialFilter(spatialFilter) {
244
+ return spatialFilter && _normalizeGeometry(spatialFilter) || undefined;
245
+ }
246
+ /**
247
+ * Check if a viewport is large enough to represent a global coverage.
248
+ * In this case the spatial filter parameter for widget calculation is removed.
249
+ *
250
+ * @internalRemarks Source: @carto/react-core
251
+ */
252
+ function _isGlobalViewport(viewport) {
253
+ const [minx, miny, maxx, maxy] = viewport;
254
+ return maxx - minx > 179.5 * 2 && maxy - miny > 85.05 * 2;
255
+ }
256
+ /**
257
+ * Normalized a geometry, coming from a mask or a viewport. The parts
258
+ * spanning outside longitude range [-180, +180] are clipped and "folded"
259
+ * back to the valid range and unioned to the polygons inide that range.
260
+ *
261
+ * It results in a Polygon or MultiPolygon strictly inside the validity range.
262
+ *
263
+ * @internalRemarks Source: @carto/react-core
264
+ */
265
+ function _normalizeGeometry(geometry) {
266
+ const WORLD = [-180, -90, +180, +90];
267
+ const worldClip = _clean(bboxClip__default["default"](geometry, WORLD).geometry);
268
+ const geometryTxWest = _tx(geometry, 360);
269
+ const geometryTxEast = _tx(geometry, -360);
270
+ let result = worldClip;
271
+ if (result && geometryTxWest) {
272
+ const worldWestClip = _clean(bboxClip__default["default"](geometryTxWest, WORLD).geometry);
273
+ if (worldWestClip) {
274
+ const collection = helpers.featureCollection([helpers.feature(result), helpers.feature(worldWestClip)]);
275
+ const merged = union__default["default"](collection);
276
+ result = merged ? _clean(merged.geometry) : result;
277
+ }
278
+ }
279
+ if (result && geometryTxEast) {
280
+ const worldEastClip = _clean(bboxClip__default["default"](geometryTxEast, WORLD).geometry);
281
+ if (worldEastClip) {
282
+ const collection = helpers.featureCollection([helpers.feature(result), helpers.feature(worldEastClip)]);
283
+ const merged = union__default["default"](collection);
284
+ result = merged ? _clean(merged.geometry) : result;
285
+ }
286
+ }
287
+ return result;
288
+ }
289
+ /** @internalRemarks Source: @carto/react-core */
290
+ function _cleanPolygonCoords(cc) {
291
+ const coords = cc.filter(c => c.length > 0);
292
+ return coords.length > 0 ? coords : null;
293
+ }
294
+ /** @internalRemarks Source: @carto/react-core */
295
+ function _cleanMultiPolygonCoords(ccc) {
296
+ const coords = ccc.map(_cleanPolygonCoords).filter(cc => cc);
297
+ return coords.length > 0 ? coords : null;
298
+ }
299
+ /** @internalRemarks Source: @carto/react-core */
300
+ function _clean(geometry) {
301
+ if (!geometry) {
302
+ return null;
303
+ }
304
+ if (_isPolygon(geometry)) {
305
+ const coords = _cleanPolygonCoords(geometry.coordinates);
306
+ return coords ? helpers.polygon(coords).geometry : null;
307
+ }
308
+ if (_isMultiPolygon(geometry)) {
309
+ const coords = _cleanMultiPolygonCoords(geometry.coordinates);
310
+ return coords ? helpers.multiPolygon(coords).geometry : null;
311
+ }
312
+ return null;
313
+ }
314
+ /** @internalRemarks Source: @carto/react-core */
315
+ function _txContourCoords(cc, distance) {
316
+ return cc.map(c => [c[0] + distance, c[1]]);
317
+ }
318
+ /** @internalRemarks Source: @carto/react-core */
319
+ function _txPolygonCoords(ccc, distance) {
320
+ return ccc.map(cc => _txContourCoords(cc, distance));
321
+ }
322
+ /** @internalRemarks Source: @carto/react-core */
323
+ function _txMultiPolygonCoords(cccc, distance) {
324
+ return cccc.map(ccc => _txPolygonCoords(ccc, distance));
325
+ }
326
+ /** @internalRemarks Source: @carto/react-core */
327
+ function _tx(geometry, distance) {
328
+ if (geometry && invariant.getType(geometry) === 'Polygon') {
329
+ const coords = _txPolygonCoords(geometry.coordinates, distance);
330
+ return helpers.polygon(coords).geometry;
331
+ } else if (geometry && invariant.getType(geometry) === 'MultiPolygon') {
332
+ const coords = _txMultiPolygonCoords(geometry.coordinates, distance);
333
+ return helpers.multiPolygon(coords).geometry;
334
+ } else {
335
+ return null;
336
+ }
337
+ }
338
+ function _isPolygon(geometry) {
339
+ return invariant.getType(geometry) === 'Polygon';
340
+ }
341
+ function _isMultiPolygon(geometry) {
342
+ return invariant.getType(geometry) === 'MultiPolygon';
343
+ }
344
+
210
345
  /******************************************************************************
211
346
  * DEFAULTS
212
347
  */
@@ -842,14 +977,13 @@ class WidgetTableSource extends WidgetBaseSource {
842
977
  }
843
978
  }
844
979
 
845
- // loaders.gl
846
- const isObject = x => x !== null && typeof x === 'object';
847
- const isPureObject = x => isObject(x) && x.constructor === {}.constructor;
848
-
849
980
  const DEFAULT_TILE_RESOLUTION = 0.5;
850
981
  const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4;
851
982
  const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6;
852
983
 
984
+ const isObject = x => x !== null && typeof x === 'object';
985
+ const isPureObject = x => isObject(x) && x.constructor === {}.constructor;
986
+
853
987
  /**
854
988
  *
855
989
  * Custom error for reported errors in CARTO Maps API.
@@ -857,7 +991,7 @@ const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6;
857
991
  *
858
992
  */
859
993
  class CartoAPIError extends Error {
860
- constructor(error, errorContext, response) {
994
+ constructor(error, errorContext, response, responseJson) {
861
995
  let responseString = 'Failed to connect';
862
996
  if (response) {
863
997
  responseString = 'Server returned: ';
@@ -883,6 +1017,7 @@ class CartoAPIError extends Error {
883
1017
  super(message);
884
1018
  this.name = 'CartoAPIError';
885
1019
  this.response = response;
1020
+ this.responseJson = responseJson;
886
1021
  this.error = error;
887
1022
  this.errorContext = errorContext;
888
1023
  }
@@ -897,15 +1032,8 @@ function formatErrorKey(key) {
897
1032
  const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com';
898
1033
  const DEFAULT_CLIENT = 'deck-gl-carto';
899
1034
  const V3_MINOR_VERSION = '3.4';
900
- const MAX_GET_LENGTH = 8192;
901
- const DEFAULT_PARAMETERS = {
902
- v: V3_MINOR_VERSION,
903
- deckglVersion: "0.1.0"
904
- };
905
- const DEFAULT_HEADERS = {
906
- Accept: 'application/json',
907
- 'Content-Type': 'application/json'
908
- };
1035
+ // Fastly default limit is 8192; leave some padding.
1036
+ const DEFAULT_MAX_LENGTH_URL = 7000;
909
1037
 
910
1038
  function joinPath(...args) {
911
1039
  return args.map(part => part.endsWith('/') ? part.slice(0, -1) : part).join('/');
@@ -922,36 +1050,42 @@ function buildSourceUrl({
922
1050
  }
923
1051
 
924
1052
  /**
925
- * Simple encode parameter
1053
+ * Parameters added to all requests issued with `requestWithParameters()`.
1054
+ * These parameters override parameters already in the base URL, but not
1055
+ * user-provided parameters.
926
1056
  */
927
- function encodeParameter(name, value) {
928
- if (isPureObject(value) || Array.isArray(value)) {
929
- return `${name}=${encodeURIComponent(JSON.stringify(value))}`;
930
- }
931
- return `${name}=${encodeURIComponent(value)}`;
932
- }
1057
+ const DEFAULT_PARAMETERS = {
1058
+ v: V3_MINOR_VERSION,
1059
+ deckglVersion: "0.1.1"
1060
+ };
1061
+ const DEFAULT_HEADERS = {
1062
+ Accept: 'application/json',
1063
+ 'Content-Type': 'application/json'
1064
+ };
933
1065
  const REQUEST_CACHE = new Map();
934
1066
  async function requestWithParameters({
935
1067
  baseUrl,
936
- parameters,
937
- headers: customHeaders,
938
- errorContext
1068
+ parameters = {},
1069
+ headers: customHeaders = {},
1070
+ errorContext,
1071
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
939
1072
  }) {
940
1073
  parameters = {
941
1074
  ...DEFAULT_PARAMETERS,
942
1075
  ...parameters
943
1076
  };
944
- const key = createCacheKey(baseUrl, parameters || {}, customHeaders || {});
1077
+ baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters));
1078
+ const key = createCacheKey(baseUrl, parameters, customHeaders);
945
1079
  if (REQUEST_CACHE.has(key)) {
946
1080
  return REQUEST_CACHE.get(key);
947
1081
  }
948
- const url = parameters ? createURLWithParameters(baseUrl, parameters) : baseUrl;
1082
+ const url = createURLWithParameters(baseUrl, parameters);
949
1083
  const headers = {
950
1084
  ...DEFAULT_HEADERS,
951
1085
  ...customHeaders
952
1086
  };
953
1087
  /* global fetch */
954
- const fetchPromise = url.length > MAX_GET_LENGTH ? fetch(baseUrl, {
1088
+ const fetchPromise = url.length > maxLengthURL ? fetch(baseUrl, {
955
1089
  method: 'POST',
956
1090
  body: JSON.stringify(parameters),
957
1091
  headers
@@ -959,17 +1093,19 @@ async function requestWithParameters({
959
1093
  headers
960
1094
  });
961
1095
  let response;
1096
+ let responseJson;
962
1097
  const jsonPromise = fetchPromise.then(_response => {
963
1098
  response = _response;
964
1099
  return response.json();
965
1100
  }).then(json => {
1101
+ responseJson = json;
966
1102
  if (!response || !response.ok) {
967
1103
  throw new Error(json.error);
968
1104
  }
969
1105
  return json;
970
1106
  }).catch(error => {
971
1107
  REQUEST_CACHE.delete(key);
972
- throw new CartoAPIError(error, errorContext, response);
1108
+ throw new CartoAPIError(error, errorContext, response, responseJson);
973
1109
  });
974
1110
  REQUEST_CACHE.set(key, jsonPromise);
975
1111
  return jsonPromise;
@@ -983,9 +1119,33 @@ function createCacheKey(baseUrl, parameters, headers) {
983
1119
  headers: headerEntries
984
1120
  });
985
1121
  }
986
- function createURLWithParameters(baseUrl, parameters) {
987
- const encodedParameters = Object.entries(parameters).map(([key, value]) => encodeParameter(key, value));
988
- return `${baseUrl}?${encodedParameters.join('&')}`;
1122
+ /**
1123
+ * Appends query string parameters to a URL. Existing URL parameters are kept,
1124
+ * unless there is a conflict, in which case the new parameters override
1125
+ * those already in the URL.
1126
+ */
1127
+ function createURLWithParameters(baseUrlString, parameters) {
1128
+ const baseUrl = new URL(baseUrlString);
1129
+ for (const [key, value] of Object.entries(parameters)) {
1130
+ if (isPureObject(value) || Array.isArray(value)) {
1131
+ baseUrl.searchParams.set(key, JSON.stringify(value));
1132
+ } else {
1133
+ baseUrl.searchParams.set(key, value.toString());
1134
+ }
1135
+ }
1136
+ return baseUrl.toString();
1137
+ }
1138
+ /**
1139
+ * Deletes query string parameters from a URL.
1140
+ */
1141
+ function excludeURLParameters(baseUrlString, parameters) {
1142
+ const baseUrl = new URL(baseUrlString);
1143
+ for (const param of parameters) {
1144
+ if (baseUrl.searchParams.has(param)) {
1145
+ baseUrl.searchParams.delete(param);
1146
+ }
1147
+ }
1148
+ return baseUrl.toString();
989
1149
  }
990
1150
 
991
1151
  /* eslint-disable camelcase */
@@ -993,7 +1153,8 @@ const SOURCE_DEFAULTS = {
993
1153
  apiBaseUrl: DEFAULT_API_BASE_URL,
994
1154
  clientId: DEFAULT_CLIENT,
995
1155
  format: 'tilejson',
996
- headers: {}
1156
+ headers: {},
1157
+ maxLengthURL: DEFAULT_MAX_LENGTH_URL
997
1158
  };
998
1159
  async function baseSource(endpoint, options, urlParameters) {
999
1160
  const {
@@ -1016,6 +1177,7 @@ async function baseSource(endpoint, options, urlParameters) {
1016
1177
  const baseUrl = buildSourceUrl(mergedOptions);
1017
1178
  const {
1018
1179
  clientId,
1180
+ maxLengthURL,
1019
1181
  format
1020
1182
  } = mergedOptions;
1021
1183
  const headers = {
@@ -1036,7 +1198,8 @@ async function baseSource(endpoint, options, urlParameters) {
1036
1198
  baseUrl,
1037
1199
  parameters,
1038
1200
  headers,
1039
- errorContext
1201
+ errorContext,
1202
+ maxLengthURL
1040
1203
  });
1041
1204
  const dataUrl = mapInstantiation[format].url[0];
1042
1205
  if (cache) {
@@ -1047,7 +1210,8 @@ async function baseSource(endpoint, options, urlParameters) {
1047
1210
  const json = await requestWithParameters({
1048
1211
  baseUrl: dataUrl,
1049
1212
  headers,
1050
- errorContext
1213
+ errorContext,
1214
+ maxLengthURL
1051
1215
  });
1052
1216
  if (accessToken) {
1053
1217
  json.accessToken = accessToken;
@@ -1057,7 +1221,8 @@ async function baseSource(endpoint, options, urlParameters) {
1057
1221
  return await requestWithParameters({
1058
1222
  baseUrl: dataUrl,
1059
1223
  headers,
1060
- errorContext
1224
+ errorContext,
1225
+ maxLengthURL
1061
1226
  });
1062
1227
  }
1063
1228
 
@@ -1066,13 +1231,11 @@ const boundaryQuerySource = async function (options) {
1066
1231
  columns,
1067
1232
  filters,
1068
1233
  tilesetTableName,
1069
- matchingColumn = 'id',
1070
1234
  propertiesSqlQuery,
1071
1235
  queryParameters
1072
1236
  } = options;
1073
1237
  const urlParameters = {
1074
1238
  tilesetTableName,
1075
- matchingColumn,
1076
1239
  propertiesSqlQuery
1077
1240
  };
1078
1241
  if (columns) {
@@ -1092,12 +1255,10 @@ const boundaryTableSource = async function (options) {
1092
1255
  filters,
1093
1256
  tilesetTableName,
1094
1257
  columns,
1095
- matchingColumn = 'id',
1096
1258
  propertiesTableName
1097
1259
  } = options;
1098
1260
  const urlParameters = {
1099
1261
  tilesetTableName,
1100
- matchingColumn,
1101
1262
  propertiesTableName
1102
1263
  };
1103
1264
  if (columns) {
@@ -1116,7 +1277,8 @@ const h3QuerySource$1 = async function (options) {
1116
1277
  aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_H3,
1117
1278
  sqlQuery,
1118
1279
  spatialDataColumn = 'h3',
1119
- queryParameters
1280
+ queryParameters,
1281
+ filters
1120
1282
  } = options;
1121
1283
  const urlParameters = {
1122
1284
  aggregationExp,
@@ -1130,6 +1292,9 @@ const h3QuerySource$1 = async function (options) {
1130
1292
  if (queryParameters) {
1131
1293
  urlParameters.queryParameters = queryParameters;
1132
1294
  }
1295
+ if (filters) {
1296
+ urlParameters.filters = filters;
1297
+ }
1133
1298
  return baseSource('query', options, urlParameters);
1134
1299
  };
1135
1300
 
@@ -1139,7 +1304,8 @@ const h3TableSource$1 = async function (options) {
1139
1304
  aggregationExp,
1140
1305
  aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_H3,
1141
1306
  spatialDataColumn = 'h3',
1142
- tableName
1307
+ tableName,
1308
+ filters
1143
1309
  } = options;
1144
1310
  const urlParameters = {
1145
1311
  aggregationExp,
@@ -1150,6 +1316,9 @@ const h3TableSource$1 = async function (options) {
1150
1316
  if (aggregationResLevel) {
1151
1317
  urlParameters.aggregationResLevel = String(aggregationResLevel);
1152
1318
  }
1319
+ if (filters) {
1320
+ urlParameters.filters = filters;
1321
+ }
1153
1322
  return baseSource('table', options, urlParameters);
1154
1323
  };
1155
1324
 
@@ -1165,11 +1334,15 @@ const h3TilesetSource = async function (options) {
1165
1334
 
1166
1335
  const rasterSource = async function (options) {
1167
1336
  const {
1168
- tableName
1337
+ tableName,
1338
+ filters
1169
1339
  } = options;
1170
1340
  const urlParameters = {
1171
1341
  name: tableName
1172
1342
  };
1343
+ if (filters) {
1344
+ urlParameters.filters = filters;
1345
+ }
1173
1346
  return baseSource('raster', options, urlParameters);
1174
1347
  };
1175
1348
 
@@ -1180,7 +1353,8 @@ const quadbinQuerySource$1 = async function (options) {
1180
1353
  aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN,
1181
1354
  sqlQuery,
1182
1355
  spatialDataColumn = 'quadbin',
1183
- queryParameters
1356
+ queryParameters,
1357
+ filters
1184
1358
  } = options;
1185
1359
  const urlParameters = {
1186
1360
  aggregationExp,
@@ -1194,6 +1368,9 @@ const quadbinQuerySource$1 = async function (options) {
1194
1368
  if (queryParameters) {
1195
1369
  urlParameters.queryParameters = queryParameters;
1196
1370
  }
1371
+ if (filters) {
1372
+ urlParameters.filters = filters;
1373
+ }
1197
1374
  return baseSource('query', options, urlParameters);
1198
1375
  };
1199
1376
 
@@ -1203,7 +1380,8 @@ const quadbinTableSource$1 = async function (options) {
1203
1380
  aggregationExp,
1204
1381
  aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN,
1205
1382
  spatialDataColumn = 'quadbin',
1206
- tableName
1383
+ tableName,
1384
+ filters
1207
1385
  } = options;
1208
1386
  const urlParameters = {
1209
1387
  aggregationExp,
@@ -1214,6 +1392,9 @@ const quadbinTableSource$1 = async function (options) {
1214
1392
  if (aggregationResLevel) {
1215
1393
  urlParameters.aggregationResLevel = String(aggregationResLevel);
1216
1394
  }
1395
+ if (filters) {
1396
+ urlParameters.filters = filters;
1397
+ }
1217
1398
  return baseSource('table', options, urlParameters);
1218
1399
  };
1219
1400
 
@@ -1403,6 +1584,8 @@ exports.WidgetQuerySource = WidgetQuerySource;
1403
1584
  exports.WidgetTableSource = WidgetTableSource;
1404
1585
  exports.addFilter = addFilter;
1405
1586
  exports.clearFilters = clearFilters;
1587
+ exports.createPolygonSpatialFilter = createPolygonSpatialFilter;
1588
+ exports.createViewportSpatialFilter = createViewportSpatialFilter;
1406
1589
  exports.getClient = getClient;
1407
1590
  exports.getFilter = getFilter;
1408
1591
  exports.h3QuerySource = h3QuerySource;