mapshaper 0.5.67 → 0.5.71

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.
package/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.66";
3
+ var VERSION = "0.5.69";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -1167,6 +1167,10 @@
1167
1167
  throw new UserError(formatLogArgs(arguments));
1168
1168
  };
1169
1169
 
1170
+ var _interrupt = function() {
1171
+ throw new NonFatalError(formatLogArgs(arguments));
1172
+ };
1173
+
1170
1174
  var _message = function() {
1171
1175
  logArgs(arguments);
1172
1176
  };
@@ -1185,10 +1189,14 @@
1185
1189
  }
1186
1190
 
1187
1191
  // Handle an error caused by invalid input or misuse of API
1188
- function stop () {
1192
+ function stop() {
1189
1193
  _stop.apply(null, utils.toArray(arguments));
1190
1194
  }
1191
1195
 
1196
+ function interrupt() {
1197
+ _interrupt.apply(null, utils.toArray(arguments));
1198
+ }
1199
+
1192
1200
  // Print a status message
1193
1201
  function message() {
1194
1202
  _message.apply(null, messageArgs(arguments));
@@ -1228,7 +1236,9 @@
1228
1236
  if (utils.isString(err)) {
1229
1237
  err = new UserError(err);
1230
1238
  }
1231
- if (err.name == 'UserError') {
1239
+ if (err.name == 'NonFatalError') {
1240
+ console.error(messageArgs([err.message]).join(' '));
1241
+ } else if (err.name == 'UserError') {
1232
1242
  msg = err.message;
1233
1243
  if (!/Error/.test(msg)) {
1234
1244
  msg = "Error: " + msg;
@@ -1248,6 +1258,12 @@
1248
1258
  return err;
1249
1259
  }
1250
1260
 
1261
+ function NonFatalError(msg) {
1262
+ var err = new Error(msg);
1263
+ err.name = 'NonFatalError';
1264
+ return err;
1265
+ }
1266
+
1251
1267
  function formatColumns(arr, alignments) {
1252
1268
  var widths = arr.reduce(function(memo, line) {
1253
1269
  return line.map(function(str, i) {
@@ -1308,6 +1324,7 @@
1308
1324
  loggingEnabled: loggingEnabled,
1309
1325
  error: error,
1310
1326
  stop: stop,
1327
+ interrupt: interrupt,
1311
1328
  message: message,
1312
1329
  setLoggingFunctions: setLoggingFunctions,
1313
1330
  print: print,
@@ -1315,6 +1332,7 @@
1315
1332
  debug: debug,
1316
1333
  printError: printError,
1317
1334
  UserError: UserError,
1335
+ NonFatalError: NonFatalError,
1318
1336
  formatColumns: formatColumns,
1319
1337
  formatStringsAsGrid: formatStringsAsGrid,
1320
1338
  formatLogArgs: formatLogArgs,
@@ -14306,7 +14324,7 @@
14306
14324
  joinStr = obj.tag == 'text' || obj.tag == 'tspan' ? '' : '\n';
14307
14325
  svg += '>' + joinStr;
14308
14326
  if (obj.value) {
14309
- svg += obj.value;
14327
+ svg += stringEscape(obj.value);
14310
14328
  }
14311
14329
  if (obj.children) {
14312
14330
  svg += obj.children.map(stringify).join(joinStr);
@@ -14329,8 +14347,13 @@
14329
14347
  "'": '''
14330
14348
  };
14331
14349
  function stringEscape(s) {
14332
- return String(s).replace(rxp, function(s) {
14333
- return map[s];
14350
+ return String(s).replace(rxp, function(match, i) {
14351
+ var entity = map[match];
14352
+ // don't replace & with &
14353
+ if (match == '&' && s.substr(i, entity.length) == entity) {
14354
+ return '&';
14355
+ }
14356
+ return entity;
14334
14357
  });
14335
14358
  }
14336
14359
 
@@ -18416,6 +18439,9 @@ ${svg}
18416
18439
  type: 'strings',
18417
18440
  describe: '[CSV] comma-sep. list of fields to import'
18418
18441
  })
18442
+ // .option('csv-comment', {
18443
+ // describe: '[CSV] comment line character(s)'
18444
+ // })
18419
18445
  .option('decimal-comma', {
18420
18446
  type: 'flag',
18421
18447
  describe: '[CSV] import numbers formatted like 1.000,01 or 1 000,01'
@@ -18652,7 +18678,8 @@ ${svg}
18652
18678
  .option('no-replace', noReplaceOpt);
18653
18679
 
18654
18680
  parser.command('classify')
18655
- .describe('apply sequential or categorical classification')
18681
+ // .describe('apply sequential or categorical classification')
18682
+ .describe('assign colors or values using one of several methods')
18656
18683
  .option('field', {
18657
18684
  describe: 'name of field to classify',
18658
18685
  DEFAULT: true
@@ -18672,6 +18699,10 @@ ${svg}
18672
18699
  // deprecated in favor of colors=
18673
18700
  // describe: 'name of a predefined color scheme (see -colors command)'
18674
18701
  })
18702
+ .option('non-adjacent', {
18703
+ describe: 'assign non-adjacent colors to a polygon layer',
18704
+ assign_to: 'method'
18705
+ })
18675
18706
  .option('stops', {
18676
18707
  describe: 'a pair of values (0-100) for limiting a color ramp',
18677
18708
  type: 'numbers'
@@ -18756,7 +18787,6 @@ ${svg}
18756
18787
  .option('key-last-suffix', {
18757
18788
  describe: 'string to append to last label'
18758
18789
  })
18759
-
18760
18790
  .option('target', targetOpt);
18761
18791
 
18762
18792
  parser.command('clean')
@@ -19165,6 +19195,14 @@ ${svg}
19165
19195
  .option('target', targetOpt)
19166
19196
  .option('no-replace', noReplaceOpt);
19167
19197
 
19198
+ parser.command('ignore')
19199
+ // .describe('stop processing if a condition is met')
19200
+ .option('empty', {
19201
+ describe: 'ignore empty files',
19202
+ type: 'flag'
19203
+ })
19204
+ .option('target', targetOpt);
19205
+
19168
19206
  parser.command('inlay')
19169
19207
  .describe('inscribe a polygon layer inside another polygon layer')
19170
19208
  .option('source', {
@@ -19236,6 +19274,10 @@ ${svg}
19236
19274
  // describe: 'use planar geometry when interpolating by area' // useful for testing
19237
19275
  type: 'flag'
19238
19276
  })
19277
+ .option('duplication', {
19278
+ describe: 'duplicate target features on many-to-one joins',
19279
+ type: 'flag'
19280
+ })
19239
19281
  .option('string-fields', stringFieldsOpt)
19240
19282
  .option('field-types', fieldTypesOpt)
19241
19283
  .option('sum-fields', {
@@ -28025,25 +28067,241 @@ ${svg}
28025
28067
  return maxId + 1;
28026
28068
  }
28027
28069
 
28028
- cmd.classify = function(lyr, optsArg) {
28070
+ // Returns a function for constructing a query function that accepts an arc id and
28071
+ // returns information about the polygon or polygons that use the given arc.
28072
+ // TODO: explain this better.
28073
+ //
28074
+ // options:
28075
+ // filter: optional filter function; signature: function(idA, idB or -1) : boolean
28076
+ // reusable: flag that lets an arc be queried multiple times.
28077
+ function getArcClassifier(shapes, arcs) {
28078
+ var opts = arguments[2] || {},
28079
+ useOnce = !opts.reusable,
28080
+ n = arcs.size(),
28081
+ a = new Int32Array(n),
28082
+ b = new Int32Array(n);
28083
+
28084
+ utils.initializeArray(a, -1);
28085
+ utils.initializeArray(b, -1);
28086
+
28087
+ traversePaths(shapes, function(o) {
28088
+ var i = absArcId(o.arcId);
28089
+ var shpId = o.shapeId;
28090
+ var aval = a[i];
28091
+ if (aval == -1) {
28092
+ a[i] = shpId;
28093
+ } else if (shpId < aval) {
28094
+ b[i] = aval;
28095
+ a[i] = shpId;
28096
+ } else {
28097
+ b[i] = shpId;
28098
+ }
28099
+ });
28100
+
28101
+ function classify(arcId, getKey) {
28102
+ var i = absArcId(arcId);
28103
+ var shpA = a[i];
28104
+ var shpB = b[i];
28105
+ var key;
28106
+ if (shpA == -1) return null;
28107
+ key = getKey(shpA, shpB);
28108
+ if (key === null || key === false) return null;
28109
+ if (useOnce) {
28110
+ // arc can only be queried once
28111
+ a[i] = -1;
28112
+ b[i] = -1;
28113
+ }
28114
+ // use optional filter to exclude some arcs
28115
+ if (opts.filter && !opts.filter(shpA, shpB)) return null;
28116
+ return key;
28117
+ }
28118
+
28119
+ return function(getKey) {
28120
+ return function(arcId) {
28121
+ return classify(arcId, getKey);
28122
+ };
28123
+ };
28124
+ }
28125
+
28126
+ var ArcClassifier = /*#__PURE__*/Object.freeze({
28127
+ __proto__: null,
28128
+ getArcClassifier: getArcClassifier
28129
+ });
28130
+
28131
+ // Returns a function for querying the neighbors of a given shape. The function
28132
+ // can be called in either of two ways:
28133
+ //
28134
+ // 1. function(shapeId, callback)
28135
+ // Callback signature: function(adjacentShapeId, arcId)
28136
+ // The callback function is called once for each arc that the given feature
28137
+ // shares with another feature.
28138
+ //
28139
+ // 2. function(shapeId)
28140
+ // The function returns an array of unique ids of neighboring shapes, or
28141
+ // an empty array if a shape has no neighbors.
28142
+ //
28143
+ function getNeighborLookupFunction(lyr, arcs) {
28144
+ var classifier = getArcClassifier(lyr.shapes, arcs, {reusable: true});
28145
+ var classify = classifier(onShapes);
28146
+ var currShapeId;
28147
+ var neighbors;
28148
+ var callback;
28149
+
28150
+ function onShapes(a, b) {
28151
+ if (b == -1) return -1; // outer edges are b == -1
28152
+ return a == currShapeId ? b : a;
28153
+ }
28154
+
28155
+ function onArc(arcId) {
28156
+ var nabeId = classify(arcId);
28157
+ if (nabeId == -1) return;
28158
+ if (callback) {
28159
+ callback(nabeId, arcId);
28160
+ } else if (neighbors.indexOf(nabeId) == -1) {
28161
+ neighbors.push(nabeId);
28162
+ }
28163
+ }
28164
+
28165
+ return function(shpId, cb) {
28166
+ currShapeId = shpId;
28167
+ if (cb) {
28168
+ callback = cb;
28169
+ forEachArcId(lyr.shapes[shpId], onArc);
28170
+ callback = null;
28171
+ } else {
28172
+ neighbors = [];
28173
+ forEachArcId(lyr.shapes[shpId], onArc);
28174
+ return neighbors;
28175
+ }
28176
+ };
28177
+ }
28178
+
28179
+
28180
+ // Returns an array containing all pairs of adjacent shapes
28181
+ // in a collection of polygon shapes. A pair of shapes is represented as
28182
+ // an array of two shape indexes [a, b].
28183
+ function findPairsOfNeighbors(shapes, arcs) {
28184
+ var getKey = function(a, b) {
28185
+ return b > -1 && a > -1 ? [a, b] : null;
28186
+ };
28187
+ var classify = getArcClassifier(shapes, arcs)(getKey);
28188
+ var arr = [];
28189
+ var index = {};
28190
+ var onArc = function(arcId) {
28191
+ var obj = classify(arcId);
28192
+ var key;
28193
+ if (obj) {
28194
+ key = obj.join('~');
28195
+ if (key in index === false) {
28196
+ arr.push(obj);
28197
+ index[key] = true;
28198
+ }
28199
+ }
28200
+ };
28201
+ forEachArcId(shapes, onArc);
28202
+ return arr;
28203
+ }
28204
+
28205
+ var PolygonNeighbors = /*#__PURE__*/Object.freeze({
28206
+ __proto__: null,
28207
+ getNeighborLookupFunction: getNeighborLookupFunction,
28208
+ findPairsOfNeighbors: findPairsOfNeighbors
28209
+ });
28210
+
28211
+ function getNonAdjacentClassifier(lyr, dataset, colors) {
28212
+ requirePolygonLayer(lyr);
28213
+ var getNeighbors = getNeighborLookupFunction(lyr, dataset.arcs);
28214
+ var errorCount = 0;
28215
+ var data = utils.range(getFeatureCount(lyr)).map(function(shpId) {
28216
+ var nabes = getNeighbors(shpId) || [];
28217
+ return {
28218
+ nabes: nabes,
28219
+ n: nabes.length,
28220
+ colorId: -1
28221
+ };
28222
+ });
28223
+ var getSortedColorIds = getUpdateFunction(colors.length);
28224
+ var colorIds = getSortedColorIds();
28225
+ // Sort adjacency data by number of neighbors in descending order
28226
+ var sorted = data.concat();
28227
+ utils.sortOn(sorted, 'n', false);
28228
+ // Assign colors, starting with polygons with the largest number of neighbors
28229
+ sorted.forEach(function(d) {
28230
+ var colorId = pickColor(d, data, colorIds);
28231
+ if (colorId == -1) {
28232
+ errorCount++;
28233
+ colorId = colorIds[0];
28234
+ }
28235
+ d.colorId = colorId;
28236
+ colorIds = getSortedColorIds(colorId);
28237
+ });
28238
+
28239
+ if (errorCount > 0) {
28240
+ message(`Unable to find non-adjacent colors for ${errorCount} ${errorCount == 1 ? 'polygon' : 'polygons'}`);
28241
+ }
28242
+ return function(shpId) {
28243
+ return colors[data[shpId].colorId];
28244
+ };
28245
+ }
28246
+
28247
+ // Pick the id of a color that is not shared with a neighboring polygon
28248
+ function pickColor(d, data, colorIds) {
28249
+ var candidateId;
28250
+ for (var i=0; i<colorIds.length; i++) {
28251
+ candidateId = colorIds[i];
28252
+ if (isAvailableColor(d, data, candidateId)) {
28253
+ return candidateId;
28254
+ }
28255
+ }
28256
+ return -1; // no colors are available
28257
+ }
28258
+
28259
+ function isAvailableColor(d, data, colorId) {
28260
+ var nabes = d.nabes;
28261
+ for (var i=0; i<nabes.length; i++) {
28262
+ if (data[nabes[i]].colorId === colorId) return false;
28263
+ }
28264
+ return true;
28265
+ }
28266
+
28267
+ // Update function returns an array of ids, sorted in descending order of preference
28268
+ // (less-used ids are preferred).
28269
+ // Function recieves an (optional) id that was just used.
28270
+ function getUpdateFunction(n) {
28271
+ var ids = utils.range(n);
28272
+ var counts = new Uint32Array(n);
28273
+ return function(i) {
28274
+ if (i >= 0 && i < n) {
28275
+ counts[i]++;
28276
+ utils.sortArrayIndex(ids, counts, true);
28277
+ } else if (i !== undefined) {
28278
+ error('Unexpected color index:', i);
28279
+ }
28280
+ return ids;
28281
+ };
28282
+ }
28283
+
28284
+ cmd.classify = function(lyr, dataset, optsArg) {
28285
+ if (!lyr.data) {
28286
+ initDataTable(lyr);
28287
+ }
28029
28288
  var opts = optsArg || {};
28030
28289
  var records = lyr.data && lyr.data.getRecords();
28031
28290
  var nullValue = opts.null_value || null;
28032
28291
  var looksLikeColors = !!opts.colors || !!opts.color_scheme;
28033
28292
  var colorScheme;
28034
- var classValues, classify;
28293
+ var classValues, classifyByValue, classifyById;
28035
28294
  var numBuckets, numValues;
28036
28295
  var dataField, outputField;
28037
28296
 
28038
28297
  // validate explicitly set classes
28039
28298
  if (opts.classes) {
28040
28299
  if (!utils.isInteger(opts.classes) || opts.classes > 1 === false) {
28041
- stop('Invalid classes= value:', opts.classes);
28300
+ stop('Invalid number of classes:', opts.classes, '(expected a value greater than 1)');
28042
28301
  }
28043
28302
  numBuckets = opts.classes;
28044
28303
  }
28045
28304
 
28046
-
28047
28305
  // TODO: better validation of breaks values
28048
28306
  if (opts.breaks) {
28049
28307
  numBuckets = opts.breaks.length + 1;
@@ -28061,9 +28319,6 @@ ${svg}
28061
28319
 
28062
28320
  } else if (opts.field) {
28063
28321
  dataField = opts.field;
28064
-
28065
- } else {
28066
- stop('Missing a data field to classify');
28067
28322
  }
28068
28323
 
28069
28324
  // expand categories if value is '*'
@@ -28071,7 +28326,18 @@ ${svg}
28071
28326
  opts.categories = getUniqFieldValues(records, dataField);
28072
28327
  }
28073
28328
 
28074
- requireDataField(lyr.data, dataField);
28329
+ if (opts.method == 'non-adjacent') {
28330
+ if (lyr.geometry_type != 'polygon') {
28331
+ stop('The non-adjacent option requires a polygon layer');
28332
+ }
28333
+ if (dataField) {
28334
+ stop('The non-adjacent option does not accept a data field argument');
28335
+ }
28336
+ } else if (!dataField) {
28337
+ stop('Missing a data field to classify');
28338
+ } else {
28339
+ requireDataField(lyr.data, dataField);
28340
+ }
28075
28341
 
28076
28342
  if (numBuckets) {
28077
28343
  numValues = opts.continuous ? numBuckets + 1 : numBuckets;
@@ -28139,15 +28405,25 @@ ${svg}
28139
28405
 
28140
28406
  // get a function to convert input data to class indexes
28141
28407
  //
28142
- if (opts.index_field) {
28408
+ if (opts.method == 'non-adjacent') {
28409
+ classifyById = getNonAdjacentClassifier(lyr, dataset, classValues);
28410
+ } else if (opts.index_field) {
28143
28411
  // data is pre-classified... just read the index from a field
28144
- classify = getIndexedClassifier(classValues, nullValue, opts);
28412
+ classifyByValue = getIndexedClassifier(classValues, nullValue, opts);
28145
28413
  } else if (opts.categories) {
28146
- classify = getCategoricalClassifier(classValues, nullValue, opts);
28414
+ classifyByValue = getCategoricalClassifier(classValues, nullValue, opts);
28147
28415
  } else {
28148
- classify = getSequentialClassifier(classValues, nullValue, getFieldValues(records, dataField), opts);
28416
+ classifyByValue = getSequentialClassifier(classValues, nullValue, getFieldValues(records, dataField), opts);
28417
+ }
28418
+
28419
+ if (classifyByValue) {
28420
+ classifyById = function(id) {
28421
+ var d = records[id] || {};
28422
+ return classifyByValue(d[dataField]);
28423
+ };
28149
28424
  }
28150
28425
 
28426
+
28151
28427
  // get the name of the output field
28152
28428
  //
28153
28429
  if (looksLikeColors) {
@@ -28163,8 +28439,7 @@ ${svg}
28163
28439
  }
28164
28440
 
28165
28441
  records.forEach(function(d, i) {
28166
- d = d || {};
28167
- d[outputField] = classify(d[dataField]);
28442
+ d[outputField] = classifyById(i);
28168
28443
  });
28169
28444
  };
28170
28445
 
@@ -28976,147 +29251,6 @@ ${svg}
28976
29251
  getClipMessage: getClipMessage
28977
29252
  });
28978
29253
 
28979
- // Returns a function for constructing a query function that accepts an arc id and
28980
- // returns information about the polygon or polygons that use the given arc.
28981
- // TODO: explain this better.
28982
- //
28983
- // options:
28984
- // filter: optional filter function; signature: function(idA, idB or -1) : boolean
28985
- // reusable: flag that lets an arc be queried multiple times.
28986
- function getArcClassifier(shapes, arcs) {
28987
- var opts = arguments[2] || {},
28988
- useOnce = !opts.reusable,
28989
- n = arcs.size(),
28990
- a = new Int32Array(n),
28991
- b = new Int32Array(n);
28992
-
28993
- utils.initializeArray(a, -1);
28994
- utils.initializeArray(b, -1);
28995
-
28996
- traversePaths(shapes, function(o) {
28997
- var i = absArcId(o.arcId);
28998
- var shpId = o.shapeId;
28999
- var aval = a[i];
29000
- if (aval == -1) {
29001
- a[i] = shpId;
29002
- } else if (shpId < aval) {
29003
- b[i] = aval;
29004
- a[i] = shpId;
29005
- } else {
29006
- b[i] = shpId;
29007
- }
29008
- });
29009
-
29010
- function classify(arcId, getKey) {
29011
- var i = absArcId(arcId);
29012
- var shpA = a[i];
29013
- var shpB = b[i];
29014
- var key;
29015
- if (shpA == -1) return null;
29016
- key = getKey(shpA, shpB);
29017
- if (key === null || key === false) return null;
29018
- if (useOnce) {
29019
- // arc can only be queried once
29020
- a[i] = -1;
29021
- b[i] = -1;
29022
- }
29023
- // use optional filter to exclude some arcs
29024
- if (opts.filter && !opts.filter(shpA, shpB)) return null;
29025
- return key;
29026
- }
29027
-
29028
- return function(getKey) {
29029
- return function(arcId) {
29030
- return classify(arcId, getKey);
29031
- };
29032
- };
29033
- }
29034
-
29035
- var ArcClassifier = /*#__PURE__*/Object.freeze({
29036
- __proto__: null,
29037
- getArcClassifier: getArcClassifier
29038
- });
29039
-
29040
- // Returns a function for querying the neighbors of a given shape. The function
29041
- // can be called in either of two ways:
29042
- //
29043
- // 1. function(shapeId, callback)
29044
- // Callback signature: function(adjacentShapeId, arcId)
29045
- // The callback function is called once for each arc that the given feature
29046
- // shares with another feature.
29047
- //
29048
- // 2. function(shapeId)
29049
- // The function returns an array of unique ids of neighboring shapes, or
29050
- // an empty array if a shape has no neighbors.
29051
- //
29052
- function getNeighborLookupFunction(lyr, arcs) {
29053
- var classifier = getArcClassifier(lyr.shapes, arcs, {reusable: true});
29054
- var classify = classifier(onShapes);
29055
- var currShapeId;
29056
- var neighbors;
29057
- var callback;
29058
-
29059
- function onShapes(a, b) {
29060
- if (b == -1) return -1; // outer edges are b == -1
29061
- return a == currShapeId ? b : a;
29062
- }
29063
-
29064
- function onArc(arcId) {
29065
- var nabeId = classify(arcId);
29066
- if (nabeId == -1) return;
29067
- if (callback) {
29068
- callback(nabeId, arcId);
29069
- } else if (neighbors.indexOf(nabeId) == -1) {
29070
- neighbors.push(nabeId);
29071
- }
29072
- }
29073
-
29074
- return function(shpId, cb) {
29075
- currShapeId = shpId;
29076
- if (cb) {
29077
- callback = cb;
29078
- forEachArcId(lyr.shapes[shpId], onArc);
29079
- callback = null;
29080
- } else {
29081
- neighbors = [];
29082
- forEachArcId(lyr.shapes[shpId], onArc);
29083
- return neighbors;
29084
- }
29085
- };
29086
- }
29087
-
29088
-
29089
- // Returns an array containing all pairs of adjacent shapes
29090
- // in a collection of polygon shapes. A pair of shapes is represented as
29091
- // an array of two shape indexes [a, b].
29092
- function findPairsOfNeighbors(shapes, arcs) {
29093
- var getKey = function(a, b) {
29094
- return b > -1 && a > -1 ? [a, b] : null;
29095
- };
29096
- var classify = getArcClassifier(shapes, arcs)(getKey);
29097
- var arr = [];
29098
- var index = {};
29099
- var onArc = function(arcId) {
29100
- var obj = classify(arcId);
29101
- var key;
29102
- if (obj) {
29103
- key = obj.join('~');
29104
- if (key in index === false) {
29105
- arr.push(obj);
29106
- index[key] = true;
29107
- }
29108
- }
29109
- };
29110
- forEachArcId(shapes, onArc);
29111
- return arr;
29112
- }
29113
-
29114
- var PolygonNeighbors = /*#__PURE__*/Object.freeze({
29115
- __proto__: null,
29116
- getNeighborLookupFunction: getNeighborLookupFunction,
29117
- findPairsOfNeighbors: findPairsOfNeighbors
29118
- });
29119
-
29120
29254
  // Assign a cluster id to each polygon in a dataset, which can be used with
29121
29255
  // one of the dissolve commands to dissolve the clusters
29122
29256
  // Works by iteratively grouping pairs of polygons with the smallest distance
@@ -29803,12 +29937,19 @@ ${svg}
29803
29937
  });
29804
29938
 
29805
29939
  // Join data from @src table to records in @dest table
29940
+ function joinTables(dest, src, join, opts) {
29941
+ return joinTableToLayer({data: dest}, src, join, opts);
29942
+ }
29943
+
29944
+ // Join data from @src table to records in @destLyr layer.
29806
29945
  // @join function
29807
29946
  // Receives index of record in the dest table
29808
29947
  // Returns array of matching records in src table, or null if no matches
29809
29948
  //
29810
- function joinTables(dest, src, join, opts) {
29811
- var srcRecords = src.getRecords(),
29949
+ function joinTableToLayer(destLyr, src, join, opts) {
29950
+ var dest = destLyr.data,
29951
+ useDuplication = !!opts.duplication,
29952
+ srcRecords = src.getRecords(),
29812
29953
  destRecords = dest.getRecords(),
29813
29954
  prefix = opts.prefix || '',
29814
29955
  unmatchedRecords = [],
@@ -29823,6 +29964,14 @@ ${svg}
29823
29964
  retn = {},
29824
29965
  srcRec, srcId, destRec, joins, count, filter, calc, i, j, n, m;
29825
29966
 
29967
+ // support for duplication of destination records
29968
+ var duplicateRecords, destShapes;
29969
+ if (useDuplication) {
29970
+ if (opts.calc) stop('duplication and calc options cannot be used together');
29971
+ duplicateRecords = dest.clone().getRecords();
29972
+ destShapes = destLyr.shapes || [];
29973
+ }
29974
+
29826
29975
  if (opts.where) {
29827
29976
  filter = getJoinFilter(src, opts.where);
29828
29977
  }
@@ -29832,7 +29981,8 @@ ${svg}
29832
29981
  }
29833
29982
 
29834
29983
  // join source records to target records
29835
- for (i=0, n=destRecords.length; i<n; i++) {
29984
+ n = destRecords.length;
29985
+ for (i=0; i<n; i++) {
29836
29986
  destRec = destRecords[i];
29837
29987
  joins = join(i);
29838
29988
  if (joins && filter) {
@@ -29843,7 +29993,13 @@ ${svg}
29843
29993
  for (j=0, count=0, m=joins ? joins.length : 0; j<m; j++) {
29844
29994
  srcId = joins[j];
29845
29995
  srcRec = srcRecords[srcId];
29846
- if (count === 0) {
29996
+ // duplication mode: many-to-one joins add new features to the target layer.
29997
+ if (count > 0 && useDuplication) {
29998
+ destRec = copyRecord(duplicateRecords[i]);
29999
+ destRecords.push(destRec);
30000
+ destShapes.push(cloneShape(destShapes[i]));
30001
+ }
30002
+ if (count === 0 || useDuplication) {
29847
30003
  if (copyFields.length > 0) {
29848
30004
  // only copying the first match
29849
30005
  joinByCopy(destRec, srcRec, copyFields, prefix);
@@ -29875,7 +30031,7 @@ ${svg}
29875
30031
  }
29876
30032
  }
29877
30033
 
29878
- printJoinMessage(matchCount, destRecords.length,
30034
+ printJoinMessage(matchCount, n,
29879
30035
  countJoins(joinCounts), srcRecords.length, skipCount, collisionCount, collisionFields);
29880
30036
 
29881
30037
  if (opts.unjoined) {
@@ -29903,6 +30059,7 @@ ${svg}
29903
30059
  });
29904
30060
  }
29905
30061
 
30062
+
29906
30063
  function countJoins(counts) {
29907
30064
  var joinCount = 0;
29908
30065
  for (var i=0, n=counts.length; i<n; i++) {
@@ -29965,14 +30122,16 @@ ${svg}
29965
30122
 
29966
30123
  function printJoinMessage(matches, n, joins, m, skipped, collisions, collisionFields) {
29967
30124
  // TODO: add tip for troubleshooting join problems, if join is less than perfect.
30125
+ var unmatched = n - matches;
29968
30126
  if (matches > 0 === false) {
29969
30127
  message("No records could be joined");
29970
30128
  return;
29971
30129
  }
29972
30130
  message(utils.format("Joined data from %'d source record%s to %'d target record%s",
29973
30131
  joins, utils.pluralSuffix(joins), matches, utils.pluralSuffix(matches)));
29974
- if (matches < n) {
29975
- message(utils.format('%d/%d target records received no data', n-matches, n));
30132
+ if (unmatched > 0) {
30133
+ message(utils.format('%d target record%s received no data', unmatched, utils.pluralSuffix(unmatched)));
30134
+ // message(utils.format('%d target records received no data', n-matches));
29976
30135
  }
29977
30136
  if (joins < m) {
29978
30137
  message(utils.format("%d/%d source records could not be joined", m-joins, m));
@@ -30021,6 +30180,7 @@ ${svg}
30021
30180
  var JoinTables = /*#__PURE__*/Object.freeze({
30022
30181
  __proto__: null,
30023
30182
  joinTables: joinTables,
30183
+ joinTableToLayer: joinTableToLayer,
30024
30184
  validateFieldNames: validateFieldNames,
30025
30185
  updateUnmatchedRecord: updateUnmatchedRecord,
30026
30186
  findCollisionFields: findCollisionFields,
@@ -31487,13 +31647,13 @@ ${svg}
31487
31647
  // TODO: option to copy points that can't be joined to a new layer
31488
31648
  var joinFunction = getPolygonToPointsFunction(targetLyr, arcs, pointLyr, opts);
31489
31649
  prepJoinLayers(targetLyr, pointLyr);
31490
- return joinTables(targetLyr.data, pointLyr.data, joinFunction, opts);
31650
+ return joinTableToLayer(targetLyr, pointLyr.data, joinFunction, opts);
31491
31651
  }
31492
31652
 
31493
31653
  function joinPolygonsToPoints(targetLyr, polygonLyr, arcs, opts) {
31494
31654
  var joinFunction = getPointToPolygonsFunction(targetLyr, polygonLyr, arcs, opts);
31495
31655
  prepJoinLayers(targetLyr, polygonLyr);
31496
- return joinTables(targetLyr.data, polygonLyr.data, joinFunction, opts);
31656
+ return joinTableToLayer(targetLyr, polygonLyr.data, joinFunction, opts);
31497
31657
  }
31498
31658
 
31499
31659
 
@@ -33279,6 +33439,12 @@ ${svg}
33279
33439
  };
33280
33440
  }
33281
33441
 
33442
+ cmd.ignore = function(targetLayer, dataset, opts) {
33443
+ if (opts.empty && layerIsEmpty(targetLayer)) {
33444
+ interrupt('Layer is empty, stopping processing');
33445
+ }
33446
+ };
33447
+
33282
33448
  cmd.include = function(opts) {
33283
33449
  var content, obj, context;
33284
33450
  // TODO: handle web context
@@ -33654,9 +33820,10 @@ ${svg}
33654
33820
 
33655
33821
  var joinOpts = utils.extend({}, opts);
33656
33822
  var joinFunction = getPolygonToPolygonFunction(targetLyr, sourceLyr, mosaicIndex, opts);
33657
- var retn = joinTables(targetLyr.data, sourceLyr.data, joinFunction, joinOpts);
33823
+ var retn = joinTableToLayer(targetLyr, sourceLyr.data, joinFunction, joinOpts);
33658
33824
 
33659
33825
  if (opts.interpolate) {
33826
+ if (opts.duplication) stop('duplication and interpolate options cannot be used together');
33660
33827
  interpolateFieldsByArea(targetLyr, sourceLyr, mosaicIndex, opts);
33661
33828
  }
33662
33829
  return retn;
@@ -34571,7 +34738,7 @@ ${svg}
34571
34738
  function joinPointsToPoints(targetLyr, srcLyr, crs, opts) {
34572
34739
  var joinFunction = getPointToPointFunction(targetLyr, srcLyr, crs, opts);
34573
34740
  prepJoinLayers(targetLyr, srcLyr);
34574
- return joinTables(targetLyr.data, srcLyr.data, joinFunction, opts);
34741
+ return joinTableToLayer(targetLyr, srcLyr.data, joinFunction, opts);
34575
34742
  }
34576
34743
 
34577
34744
  function getPointToPointFunction(targetLyr, srcLyr, crs, opts) {
@@ -34621,14 +34788,14 @@ ${svg}
34621
34788
  }
34622
34789
  };
34623
34790
 
34624
- function joinAttributesToFeatures(lyr, srcTable, opts) {
34791
+ function joinAttributesToFeatures(destLyr, srcTable, opts) {
34625
34792
  var keys = opts.keys,
34626
34793
  destKey = keys[0],
34627
34794
  srcKey = keys[1],
34628
- destTable = lyr.data,
34795
+ destTable = destLyr.data,
34629
34796
  joinFunction = getJoinByKey(destTable, destKey, srcTable, srcKey);
34630
34797
  validateFieldNames(keys);
34631
- return joinTables(destTable, srcTable, joinFunction, opts);
34798
+ return joinTableToLayer(destLyr, srcTable, joinFunction, opts);
34632
34799
  }
34633
34800
 
34634
34801
  // Return a function for translating a target id to an array of source ids based on values
@@ -37620,7 +37787,7 @@ ${svg}
37620
37787
  applyCommandToEachLayer(cmd.calc, targetLayers, arcs, opts);
37621
37788
 
37622
37789
  } else if (name == 'classify') {
37623
- applyCommandToEachLayer(cmd.classify, targetLayers, opts);
37790
+ applyCommandToEachLayer(cmd.classify, targetLayers, targetDataset, opts);
37624
37791
 
37625
37792
  } else if (name == 'clean') {
37626
37793
  cmd.cleanLayers(targetLayers, targetDataset, opts);
@@ -37704,6 +37871,9 @@ ${svg}
37704
37871
  outputLayers = targetDataset.layers; // kludge to allow layer naming below
37705
37872
  }
37706
37873
 
37874
+ } else if (name == 'ignore') {
37875
+ applyCommandToEachLayer(cmd.ignore, targetLayers, targetDataset, opts);
37876
+
37707
37877
  } else if (name == 'include') {
37708
37878
  cmd.include(opts);
37709
37879
 
@@ -38074,6 +38244,7 @@ ${svg}
38074
38244
  cmd.options.output = outputArr;
38075
38245
  }
38076
38246
  });
38247
+
38077
38248
  runParsedCommands(commands, null, callback);
38078
38249
  }
38079
38250
 
@@ -38134,10 +38305,6 @@ ${svg}
38134
38305
  error("Changed in v0.4: runParsedCommands() takes a Catalog object");
38135
38306
  }
38136
38307
 
38137
- if (!utils.isFunction(done)) {
38138
- error("Missing a callback function");
38139
- }
38140
-
38141
38308
  if (!utils.isArray(commands)) {
38142
38309
  error("Expected an array of parsed commands");
38143
38310
  }
@@ -38158,18 +38325,25 @@ ${svg}
38158
38325
  // that it can modify the output dataset in-place instead of making a copy.
38159
38326
  commands[commands.length-1].options.final = true;
38160
38327
  }
38161
- commands = divideImportCommand(commands);
38162
- utils.reduceAsync(commands, catalog, nextCommand, done);
38163
38328
 
38164
- function nextCommand(catalog, cmd, next) {
38165
- setStateVar('current_command', cmd.name); // for log msgs
38166
- setStateVar('verbose', !!cmd.options.verbose);
38167
- setStateVar('debug', !!cmd.options.debug);
38168
- runCommand(cmd, catalog, next);
38329
+ var groups = divideImportCommand(commands);
38330
+ if (groups.length == 1) {
38331
+ // run a simple sequence of commands (input files are not batched)
38332
+ return runParsedCommands2(commands, catalog, done);
38333
+ }
38334
+
38335
+ // run duplicated commands (i.e. batch mode)
38336
+ utils.reduceAsync(groups, catalog, nextGroup, done);
38337
+
38338
+ function nextGroup(catalog, commands, next) {
38339
+ runParsedCommands2(commands, catalog, function(err, catalog) {
38340
+ err = filterError(err);
38341
+ next(err, catalog);
38342
+ });
38169
38343
  }
38170
38344
 
38171
38345
  function done(err, catalog) {
38172
- if (err) printError(err);
38346
+ err = filterError(err);
38173
38347
  cb(err, catalog);
38174
38348
  setStateVar('current_command', null);
38175
38349
  setStateVar('verbose', false);
@@ -38177,11 +38351,31 @@ ${svg}
38177
38351
  }
38178
38352
  }
38179
38353
 
38354
+ function filterError(err) {
38355
+ if (err) printError(err);
38356
+ if (err && err.name == 'NonFatalError') {
38357
+ return null;
38358
+ }
38359
+ return err;
38360
+ }
38361
+
38362
+ function runParsedCommands2(commands, catalog, cb) {
38363
+ utils.reduceAsync(commands, catalog, nextCommand, cb);
38364
+
38365
+ function nextCommand(catalog, cmd, next) {
38366
+ setStateVar('current_command', cmd.name); // for log msgs
38367
+ setStateVar('verbose', !!cmd.options.verbose);
38368
+ setStateVar('debug', !!cmd.options.debug);
38369
+ runCommand(cmd, catalog, next);
38370
+ }
38371
+ }
38372
+
38373
+
38180
38374
  // If an initial import command indicates that several input files should be
38181
38375
  // processed separately, then duplicate the sequence of commands to run
38182
38376
  // once for each input file
38183
38377
  // @commands Array of parsed commands
38184
- // Returns: either original command array or array of duplicated commands.
38378
+ // Returns: Array of one or more sequences of parsed commands
38185
38379
  //
38186
38380
  function divideImportCommand(commands) {
38187
38381
  var firstCmd = commands[0],
@@ -38189,23 +38383,23 @@ ${svg}
38189
38383
 
38190
38384
  if (firstCmd.name != 'i' || opts.stdin || opts.merge_files ||
38191
38385
  opts.combine_files || !opts.files || opts.files.length < 2) {
38192
- return commands;
38386
+ return [commands];
38193
38387
  }
38194
38388
 
38195
- return (opts.files).reduce(function(memo, file) {
38196
- var importCmd = {
38389
+ return opts.files.map(function(file) {
38390
+ var group = [{
38197
38391
  name: 'i',
38198
38392
  options: utils.defaults({
38199
38393
  files:[file],
38200
38394
  replace: true // kludge to replace data catalog
38201
38395
  }, opts)
38202
- };
38203
- memo.push(importCmd);
38204
- memo.push.apply(memo, commands.slice(1));
38205
- return memo;
38206
- }, []);
38396
+ }];
38397
+ group.push.apply(group, commands.slice(1));
38398
+ return group;
38399
+ });
38207
38400
  }
38208
38401
 
38402
+
38209
38403
  function printStartupMessages() {
38210
38404
  // print heap memory message if running with a custom amount
38211
38405
  var rxp = /^--max-old-space-size=([0-9]+)$/;