@carto/api-client 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,9 @@
1
+ import bboxClip from '@turf/bbox-clip';
2
+ import bboxPolygon from '@turf/bbox-polygon';
3
+ import union from '@turf/union';
4
+ import { getType } from '@turf/invariant';
5
+ import { featureCollection, feature, polygon, multiPolygon } from '@turf/helpers';
6
+
1
7
  /**
2
8
  * @internal
3
9
  * @internalRemarks Source: @carto/react-core
@@ -206,6 +212,129 @@ function getFilter(filters, {
206
212
  return null;
207
213
  }
208
214
 
215
+ /**
216
+ * Returns a {@link SpatialFilter} for a given viewport, typically obtained
217
+ * from deck.gl's `viewport.getBounds()` method ([west, south, east, north]).
218
+ * If the viewport covers the entire world (to some margin of error in Web
219
+ * Mercator space), `undefined` is returned instead.
220
+ *
221
+ * If the viewport extends beyond longitude range [-180, +180], the polygon
222
+ * may be reformatted for compatibility with CARTO APIs.
223
+ */
224
+ function createViewportSpatialFilter(viewport) {
225
+ if (_isGlobalViewport(viewport)) {
226
+ return;
227
+ }
228
+ return createPolygonSpatialFilter(bboxPolygon(viewport).geometry);
229
+ }
230
+ /**
231
+ * Returns a {@link SpatialFilter} for a given {@link Polygon} or
232
+ * {@link MultiPolygon}. If the polygon(s) extend outside longitude
233
+ * range [-180, +180], the result may be reformatted for compatibility
234
+ * with CARTO APIs.
235
+ */
236
+ function createPolygonSpatialFilter(spatialFilter) {
237
+ return spatialFilter && _normalizeGeometry(spatialFilter) || undefined;
238
+ }
239
+ /**
240
+ * Check if a viewport is large enough to represent a global coverage.
241
+ * In this case the spatial filter parameter for widget calculation is removed.
242
+ *
243
+ * @internalRemarks Source: @carto/react-core
244
+ */
245
+ function _isGlobalViewport(viewport) {
246
+ const [minx, miny, maxx, maxy] = viewport;
247
+ return maxx - minx > 179.5 * 2 && maxy - miny > 85.05 * 2;
248
+ }
249
+ /**
250
+ * Normalized a geometry, coming from a mask or a viewport. The parts
251
+ * spanning outside longitude range [-180, +180] are clipped and "folded"
252
+ * back to the valid range and unioned to the polygons inide that range.
253
+ *
254
+ * It results in a Polygon or MultiPolygon strictly inside the validity range.
255
+ *
256
+ * @internalRemarks Source: @carto/react-core
257
+ */
258
+ function _normalizeGeometry(geometry) {
259
+ const WORLD = [-180, -90, +180, +90];
260
+ const worldClip = _clean(bboxClip(geometry, WORLD).geometry);
261
+ const geometryTxWest = _tx(geometry, 360);
262
+ const geometryTxEast = _tx(geometry, -360);
263
+ let result = worldClip;
264
+ if (result && geometryTxWest) {
265
+ const worldWestClip = _clean(bboxClip(geometryTxWest, WORLD).geometry);
266
+ if (worldWestClip) {
267
+ const collection = featureCollection([feature(result), feature(worldWestClip)]);
268
+ const merged = union(collection);
269
+ result = merged ? _clean(merged.geometry) : result;
270
+ }
271
+ }
272
+ if (result && geometryTxEast) {
273
+ const worldEastClip = _clean(bboxClip(geometryTxEast, WORLD).geometry);
274
+ if (worldEastClip) {
275
+ const collection = featureCollection([feature(result), feature(worldEastClip)]);
276
+ const merged = union(collection);
277
+ result = merged ? _clean(merged.geometry) : result;
278
+ }
279
+ }
280
+ return result;
281
+ }
282
+ /** @internalRemarks Source: @carto/react-core */
283
+ function _cleanPolygonCoords(cc) {
284
+ const coords = cc.filter(c => c.length > 0);
285
+ return coords.length > 0 ? coords : null;
286
+ }
287
+ /** @internalRemarks Source: @carto/react-core */
288
+ function _cleanMultiPolygonCoords(ccc) {
289
+ const coords = ccc.map(_cleanPolygonCoords).filter(cc => cc);
290
+ return coords.length > 0 ? coords : null;
291
+ }
292
+ /** @internalRemarks Source: @carto/react-core */
293
+ function _clean(geometry) {
294
+ if (!geometry) {
295
+ return null;
296
+ }
297
+ if (_isPolygon(geometry)) {
298
+ const coords = _cleanPolygonCoords(geometry.coordinates);
299
+ return coords ? polygon(coords).geometry : null;
300
+ }
301
+ if (_isMultiPolygon(geometry)) {
302
+ const coords = _cleanMultiPolygonCoords(geometry.coordinates);
303
+ return coords ? multiPolygon(coords).geometry : null;
304
+ }
305
+ return null;
306
+ }
307
+ /** @internalRemarks Source: @carto/react-core */
308
+ function _txContourCoords(cc, distance) {
309
+ return cc.map(c => [c[0] + distance, c[1]]);
310
+ }
311
+ /** @internalRemarks Source: @carto/react-core */
312
+ function _txPolygonCoords(ccc, distance) {
313
+ return ccc.map(cc => _txContourCoords(cc, distance));
314
+ }
315
+ /** @internalRemarks Source: @carto/react-core */
316
+ function _txMultiPolygonCoords(cccc, distance) {
317
+ return cccc.map(ccc => _txPolygonCoords(ccc, distance));
318
+ }
319
+ /** @internalRemarks Source: @carto/react-core */
320
+ function _tx(geometry, distance) {
321
+ if (geometry && getType(geometry) === 'Polygon') {
322
+ const coords = _txPolygonCoords(geometry.coordinates, distance);
323
+ return polygon(coords).geometry;
324
+ } else if (geometry && getType(geometry) === 'MultiPolygon') {
325
+ const coords = _txMultiPolygonCoords(geometry.coordinates, distance);
326
+ return multiPolygon(coords).geometry;
327
+ } else {
328
+ return null;
329
+ }
330
+ }
331
+ function _isPolygon(geometry) {
332
+ return getType(geometry) === 'Polygon';
333
+ }
334
+ function _isMultiPolygon(geometry) {
335
+ return getType(geometry) === 'MultiPolygon';
336
+ }
337
+
209
338
  function _extends() {
210
339
  return _extends = Object.assign ? Object.assign.bind() : function (n) {
211
340
  for (var e = 1; e < arguments.length; e++) {
@@ -219,7 +348,7 @@ function _objectWithoutPropertiesLoose(r, e) {
219
348
  if (null == r) return {};
220
349
  var t = {};
221
350
  for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
222
- if (e.indexOf(n) >= 0) continue;
351
+ if (e.includes(n)) continue;
223
352
  t[n] = r[n];
224
353
  }
225
354
  return t;
@@ -786,14 +915,13 @@ class WidgetTableSource extends WidgetBaseSource {
786
915
  }
787
916
  }
788
917
 
789
- // loaders.gl
790
- const isObject = x => x !== null && typeof x === 'object';
791
- const isPureObject = x => isObject(x) && x.constructor === {}.constructor;
792
-
793
918
  const DEFAULT_TILE_RESOLUTION = 0.5;
794
919
  const DEFAULT_AGGREGATION_RES_LEVEL_H3 = 4;
795
920
  const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6;
796
921
 
922
+ const isObject = x => x !== null && typeof x === 'object';
923
+ const isPureObject = x => isObject(x) && x.constructor === {}.constructor;
924
+
797
925
  /**
798
926
  *
799
927
  * Custom error for reported errors in CARTO Maps API.
@@ -801,7 +929,7 @@ const DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN = 6;
801
929
  *
802
930
  */
803
931
  class CartoAPIError extends Error {
804
- constructor(error, errorContext, response) {
932
+ constructor(error, errorContext, response, responseJson) {
805
933
  let responseString = 'Failed to connect';
806
934
  if (response) {
807
935
  responseString = 'Server returned: ';
@@ -827,6 +955,7 @@ class CartoAPIError extends Error {
827
955
  super(message);
828
956
  this.name = 'CartoAPIError';
829
957
  this.response = response;
958
+ this.responseJson = responseJson;
830
959
  this.error = error;
831
960
  this.errorContext = errorContext;
832
961
  }
@@ -841,15 +970,8 @@ function formatErrorKey(key) {
841
970
  const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com';
842
971
  const DEFAULT_CLIENT = 'deck-gl-carto';
843
972
  const V3_MINOR_VERSION = '3.4';
844
- const MAX_GET_LENGTH = 8192;
845
- const DEFAULT_PARAMETERS = {
846
- v: V3_MINOR_VERSION,
847
- deckglVersion: "0.1.0"
848
- };
849
- const DEFAULT_HEADERS = {
850
- Accept: 'application/json',
851
- 'Content-Type': 'application/json'
852
- };
973
+ // Fastly default limit is 8192; leave some padding.
974
+ const DEFAULT_MAX_LENGTH_URL = 7000;
853
975
 
854
976
  function joinPath(...args) {
855
977
  return args.map(part => part.endsWith('/') ? part.slice(0, -1) : part).join('/');
@@ -866,36 +988,42 @@ function buildSourceUrl({
866
988
  }
867
989
 
868
990
  /**
869
- * Simple encode parameter
991
+ * Parameters added to all requests issued with `requestWithParameters()`.
992
+ * These parameters override parameters already in the base URL, but not
993
+ * user-provided parameters.
870
994
  */
871
- function encodeParameter(name, value) {
872
- if (isPureObject(value) || Array.isArray(value)) {
873
- return `${name}=${encodeURIComponent(JSON.stringify(value))}`;
874
- }
875
- return `${name}=${encodeURIComponent(value)}`;
876
- }
995
+ const DEFAULT_PARAMETERS = {
996
+ v: V3_MINOR_VERSION,
997
+ deckglVersion: "0.2.0"
998
+ };
999
+ const DEFAULT_HEADERS = {
1000
+ Accept: 'application/json',
1001
+ 'Content-Type': 'application/json'
1002
+ };
877
1003
  const REQUEST_CACHE = new Map();
878
1004
  async function requestWithParameters({
879
1005
  baseUrl,
880
- parameters,
881
- headers: customHeaders,
882
- errorContext
1006
+ parameters = {},
1007
+ headers: customHeaders = {},
1008
+ errorContext,
1009
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
883
1010
  }) {
884
1011
  parameters = {
885
1012
  ...DEFAULT_PARAMETERS,
886
1013
  ...parameters
887
1014
  };
888
- const key = createCacheKey(baseUrl, parameters || {}, customHeaders || {});
1015
+ baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters));
1016
+ const key = createCacheKey(baseUrl, parameters, customHeaders);
889
1017
  if (REQUEST_CACHE.has(key)) {
890
1018
  return REQUEST_CACHE.get(key);
891
1019
  }
892
- const url = parameters ? createURLWithParameters(baseUrl, parameters) : baseUrl;
1020
+ const url = createURLWithParameters(baseUrl, parameters);
893
1021
  const headers = {
894
1022
  ...DEFAULT_HEADERS,
895
1023
  ...customHeaders
896
1024
  };
897
1025
  /* global fetch */
898
- const fetchPromise = url.length > MAX_GET_LENGTH ? fetch(baseUrl, {
1026
+ const fetchPromise = url.length > maxLengthURL ? fetch(baseUrl, {
899
1027
  method: 'POST',
900
1028
  body: JSON.stringify(parameters),
901
1029
  headers
@@ -903,17 +1031,19 @@ async function requestWithParameters({
903
1031
  headers
904
1032
  });
905
1033
  let response;
1034
+ let responseJson;
906
1035
  const jsonPromise = fetchPromise.then(_response => {
907
1036
  response = _response;
908
1037
  return response.json();
909
1038
  }).then(json => {
1039
+ responseJson = json;
910
1040
  if (!response || !response.ok) {
911
1041
  throw new Error(json.error);
912
1042
  }
913
1043
  return json;
914
1044
  }).catch(error => {
915
1045
  REQUEST_CACHE.delete(key);
916
- throw new CartoAPIError(error, errorContext, response);
1046
+ throw new CartoAPIError(error, errorContext, response, responseJson);
917
1047
  });
918
1048
  REQUEST_CACHE.set(key, jsonPromise);
919
1049
  return jsonPromise;
@@ -927,9 +1057,33 @@ function createCacheKey(baseUrl, parameters, headers) {
927
1057
  headers: headerEntries
928
1058
  });
929
1059
  }
930
- function createURLWithParameters(baseUrl, parameters) {
931
- const encodedParameters = Object.entries(parameters).map(([key, value]) => encodeParameter(key, value));
932
- return `${baseUrl}?${encodedParameters.join('&')}`;
1060
+ /**
1061
+ * Appends query string parameters to a URL. Existing URL parameters are kept,
1062
+ * unless there is a conflict, in which case the new parameters override
1063
+ * those already in the URL.
1064
+ */
1065
+ function createURLWithParameters(baseUrlString, parameters) {
1066
+ const baseUrl = new URL(baseUrlString);
1067
+ for (const [key, value] of Object.entries(parameters)) {
1068
+ if (isPureObject(value) || Array.isArray(value)) {
1069
+ baseUrl.searchParams.set(key, JSON.stringify(value));
1070
+ } else {
1071
+ baseUrl.searchParams.set(key, value.toString());
1072
+ }
1073
+ }
1074
+ return baseUrl.toString();
1075
+ }
1076
+ /**
1077
+ * Deletes query string parameters from a URL.
1078
+ */
1079
+ function excludeURLParameters(baseUrlString, parameters) {
1080
+ const baseUrl = new URL(baseUrlString);
1081
+ for (const param of parameters) {
1082
+ if (baseUrl.searchParams.has(param)) {
1083
+ baseUrl.searchParams.delete(param);
1084
+ }
1085
+ }
1086
+ return baseUrl.toString();
933
1087
  }
934
1088
 
935
1089
  /* eslint-disable camelcase */
@@ -937,7 +1091,8 @@ const SOURCE_DEFAULTS = {
937
1091
  apiBaseUrl: DEFAULT_API_BASE_URL,
938
1092
  clientId: DEFAULT_CLIENT,
939
1093
  format: 'tilejson',
940
- headers: {}
1094
+ headers: {},
1095
+ maxLengthURL: DEFAULT_MAX_LENGTH_URL
941
1096
  };
942
1097
  async function baseSource(endpoint, options, urlParameters) {
943
1098
  const {
@@ -960,6 +1115,7 @@ async function baseSource(endpoint, options, urlParameters) {
960
1115
  const baseUrl = buildSourceUrl(mergedOptions);
961
1116
  const {
962
1117
  clientId,
1118
+ maxLengthURL,
963
1119
  format
964
1120
  } = mergedOptions;
965
1121
  const headers = {
@@ -980,7 +1136,8 @@ async function baseSource(endpoint, options, urlParameters) {
980
1136
  baseUrl,
981
1137
  parameters,
982
1138
  headers,
983
- errorContext
1139
+ errorContext,
1140
+ maxLengthURL
984
1141
  });
985
1142
  const dataUrl = mapInstantiation[format].url[0];
986
1143
  if (cache) {
@@ -991,7 +1148,8 @@ async function baseSource(endpoint, options, urlParameters) {
991
1148
  const json = await requestWithParameters({
992
1149
  baseUrl: dataUrl,
993
1150
  headers,
994
- errorContext
1151
+ errorContext,
1152
+ maxLengthURL
995
1153
  });
996
1154
  if (accessToken) {
997
1155
  json.accessToken = accessToken;
@@ -1001,7 +1159,8 @@ async function baseSource(endpoint, options, urlParameters) {
1001
1159
  return await requestWithParameters({
1002
1160
  baseUrl: dataUrl,
1003
1161
  headers,
1004
- errorContext
1162
+ errorContext,
1163
+ maxLengthURL
1005
1164
  });
1006
1165
  }
1007
1166
 
@@ -1010,13 +1169,11 @@ const boundaryQuerySource = async function (options) {
1010
1169
  columns,
1011
1170
  filters,
1012
1171
  tilesetTableName,
1013
- matchingColumn = 'id',
1014
1172
  propertiesSqlQuery,
1015
1173
  queryParameters
1016
1174
  } = options;
1017
1175
  const urlParameters = {
1018
1176
  tilesetTableName,
1019
- matchingColumn,
1020
1177
  propertiesSqlQuery
1021
1178
  };
1022
1179
  if (columns) {
@@ -1036,12 +1193,10 @@ const boundaryTableSource = async function (options) {
1036
1193
  filters,
1037
1194
  tilesetTableName,
1038
1195
  columns,
1039
- matchingColumn = 'id',
1040
1196
  propertiesTableName
1041
1197
  } = options;
1042
1198
  const urlParameters = {
1043
1199
  tilesetTableName,
1044
- matchingColumn,
1045
1200
  propertiesTableName
1046
1201
  };
1047
1202
  if (columns) {
@@ -1060,7 +1215,8 @@ const h3QuerySource$1 = async function (options) {
1060
1215
  aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_H3,
1061
1216
  sqlQuery,
1062
1217
  spatialDataColumn = 'h3',
1063
- queryParameters
1218
+ queryParameters,
1219
+ filters
1064
1220
  } = options;
1065
1221
  const urlParameters = {
1066
1222
  aggregationExp,
@@ -1074,6 +1230,9 @@ const h3QuerySource$1 = async function (options) {
1074
1230
  if (queryParameters) {
1075
1231
  urlParameters.queryParameters = queryParameters;
1076
1232
  }
1233
+ if (filters) {
1234
+ urlParameters.filters = filters;
1235
+ }
1077
1236
  return baseSource('query', options, urlParameters);
1078
1237
  };
1079
1238
 
@@ -1083,7 +1242,8 @@ const h3TableSource$1 = async function (options) {
1083
1242
  aggregationExp,
1084
1243
  aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_H3,
1085
1244
  spatialDataColumn = 'h3',
1086
- tableName
1245
+ tableName,
1246
+ filters
1087
1247
  } = options;
1088
1248
  const urlParameters = {
1089
1249
  aggregationExp,
@@ -1094,6 +1254,9 @@ const h3TableSource$1 = async function (options) {
1094
1254
  if (aggregationResLevel) {
1095
1255
  urlParameters.aggregationResLevel = String(aggregationResLevel);
1096
1256
  }
1257
+ if (filters) {
1258
+ urlParameters.filters = filters;
1259
+ }
1097
1260
  return baseSource('table', options, urlParameters);
1098
1261
  };
1099
1262
 
@@ -1109,11 +1272,15 @@ const h3TilesetSource = async function (options) {
1109
1272
 
1110
1273
  const rasterSource = async function (options) {
1111
1274
  const {
1112
- tableName
1275
+ tableName,
1276
+ filters
1113
1277
  } = options;
1114
1278
  const urlParameters = {
1115
1279
  name: tableName
1116
1280
  };
1281
+ if (filters) {
1282
+ urlParameters.filters = filters;
1283
+ }
1117
1284
  return baseSource('raster', options, urlParameters);
1118
1285
  };
1119
1286
 
@@ -1124,7 +1291,8 @@ const quadbinQuerySource$1 = async function (options) {
1124
1291
  aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN,
1125
1292
  sqlQuery,
1126
1293
  spatialDataColumn = 'quadbin',
1127
- queryParameters
1294
+ queryParameters,
1295
+ filters
1128
1296
  } = options;
1129
1297
  const urlParameters = {
1130
1298
  aggregationExp,
@@ -1138,6 +1306,9 @@ const quadbinQuerySource$1 = async function (options) {
1138
1306
  if (queryParameters) {
1139
1307
  urlParameters.queryParameters = queryParameters;
1140
1308
  }
1309
+ if (filters) {
1310
+ urlParameters.filters = filters;
1311
+ }
1141
1312
  return baseSource('query', options, urlParameters);
1142
1313
  };
1143
1314
 
@@ -1147,7 +1318,8 @@ const quadbinTableSource$1 = async function (options) {
1147
1318
  aggregationExp,
1148
1319
  aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN,
1149
1320
  spatialDataColumn = 'quadbin',
1150
- tableName
1321
+ tableName,
1322
+ filters
1151
1323
  } = options;
1152
1324
  const urlParameters = {
1153
1325
  aggregationExp,
@@ -1158,6 +1330,9 @@ const quadbinTableSource$1 = async function (options) {
1158
1330
  if (aggregationResLevel) {
1159
1331
  urlParameters.aggregationResLevel = String(aggregationResLevel);
1160
1332
  }
1333
+ if (filters) {
1334
+ urlParameters.filters = filters;
1335
+ }
1161
1336
  return baseSource('table', options, urlParameters);
1162
1337
  };
1163
1338
 
@@ -1305,5 +1480,5 @@ function assignDefaultProps(props) {
1305
1480
  }
1306
1481
  }
1307
1482
 
1308
- export { FilterType, WidgetBaseSource, WidgetQuerySource, WidgetTableSource, addFilter, clearFilters, getClient, getFilter, h3QuerySource, h3TableSource, hasFilter, quadbinQuerySource, quadbinTableSource, removeFilter, setClient, vectorQuerySource, vectorTableSource };
1483
+ export { FilterType, WidgetBaseSource, WidgetQuerySource, WidgetTableSource, addFilter, clearFilters, createPolygonSpatialFilter, createViewportSpatialFilter, getClient, getFilter, h3QuerySource, h3TableSource, hasFilter, quadbinQuerySource, quadbinTableSource, removeFilter, setClient, vectorQuerySource, vectorTableSource };
1309
1484
  //# sourceMappingURL=api-client.modern.js.map