mapshaper 0.6.42 → 0.6.44

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/bin/mapshaper-gui CHANGED
@@ -61,7 +61,10 @@ function startServer(port) {
61
61
  http.createServer(function(request, response) {
62
62
  var uri = url.parse(request.url).pathname;
63
63
  clearTimeout(timeout);
64
- if (uri == '/close') {
64
+ if (uri.includes('..')) {
65
+ // block attempts to load files outside webroot
66
+ serve404(response);
67
+ } else if (uri == '/close') {
65
68
  // end process when page closes, unless page is immediately refreshed
66
69
  timeout = setTimeout(function() {
67
70
  process.exit(0);
@@ -116,10 +119,14 @@ function serveError(text, code, response) {
116
119
  response.end();
117
120
  }
118
121
 
122
+ function serve404(response) {
123
+ serveError("404 Not Found\n", 404, response);
124
+ }
125
+
119
126
  function serveFile(filename, response) {
120
127
  fs.readFile(filename, function(err, content) {
121
128
  if (err) {
122
- serveError("404 Not Found\n", 404, response);
129
+ serve404(response);
123
130
  } else {
124
131
  serveContent(content, response, getMimeType(filename));
125
132
  }
package/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.6.42";
3
+ var VERSION = "0.6.44";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -8,6 +8,7 @@
8
8
  get default () { return utils; },
9
9
  get getUniqueName () { return getUniqueName; },
10
10
  get isFunction () { return isFunction; },
11
+ get isPromise () { return isPromise; },
11
12
  get isObject () { return isObject; },
12
13
  get clamp () { return clamp; },
13
14
  get isArray () { return isArray; },
@@ -147,6 +148,10 @@
147
148
  return typeof obj == 'function';
148
149
  }
149
150
 
151
+ function isPromise(arg) {
152
+ return arg ? isFunction(arg.then) : false;
153
+ }
154
+
150
155
  function isObject(obj) {
151
156
  return obj === Object(obj); // via underscore
152
157
  }
@@ -12488,7 +12493,6 @@
12488
12493
  return +n.toPrecision(d);
12489
12494
  }
12490
12495
 
12491
-
12492
12496
  function roundToDigits(n, d) {
12493
12497
  return +n.toFixed(d); // string conversion makes this slow
12494
12498
  }
@@ -12552,6 +12556,37 @@
12552
12556
  });
12553
12557
  }
12554
12558
 
12559
+ const fround2 = (function() {
12560
+ var arr = new Float32Array(1);
12561
+ return function(x) {
12562
+ arr[0] = x;
12563
+ return arr[0];
12564
+ };
12565
+ })();
12566
+
12567
+ // This function rounds towards 0 (i.e. floor). TODO: round properly
12568
+ // @bits: number of bits to round
12569
+ // performance: about 3x slower than Math.fround()
12570
+ function getBinaryRoundingFunction(bits) {
12571
+ // double: sign (1) exponent (11) fraction (52)
12572
+ // single: sign (1) exponent (8) fraction (23)
12573
+ if ((bits >= 1 && bits <= 32) === false) {
12574
+ error('Invalid bits argument:', bits);
12575
+ }
12576
+ var isLE = require('os').endianness() == 'LE';
12577
+ var fp = new Float64Array(1);
12578
+ var leastBits = new Uint32Array(fp.buffer, isLE ? 0 : 4, 1);
12579
+ var mask = 2 ** 32 - 2 ** bits; // e.g. bits = 4 -> 0b11110000
12580
+ return function(x) {
12581
+ fp[0] = x;
12582
+ leastBits[0] = leastBits[0] & mask;
12583
+ return fp[0];
12584
+ };
12585
+ }
12586
+
12587
+ // "round to even" on the 23rd bit of the mantissa
12588
+ const fround = Math.fround || fround2;
12589
+
12555
12590
  function setCoordinatePrecision(dataset, precision) {
12556
12591
  var round = getRoundingFunction(precision);
12557
12592
  // var dissolvePolygon, nodes;
@@ -12586,6 +12621,9 @@
12586
12621
  getRoundedCoordString: getRoundedCoordString,
12587
12622
  getRoundedCoords: getRoundedCoords,
12588
12623
  roundPoints: roundPoints,
12624
+ fround2: fround2,
12625
+ getBinaryRoundingFunction: getBinaryRoundingFunction,
12626
+ fround: fround,
12589
12627
  setCoordinatePrecision: setCoordinatePrecision
12590
12628
  });
12591
12629
 
@@ -16438,19 +16476,41 @@
16438
16476
  buildPolygonMosaic: buildPolygonMosaic
16439
16477
  });
16440
16478
 
16479
+ // Map non-negative integers to non-negative integer ids
16480
+ function IdLookupIndex(n) {
16481
+ var index = new Uint32Array(n);
16482
+
16483
+ this.setId = function(id, val) {
16484
+ if (id >= 0 && val >= 0 && val < n - 1) {
16485
+ index[id] = val + 1;
16486
+ } else {
16487
+ error('Invalid value');
16488
+ }
16489
+ };
16490
+
16491
+ this.hasId = function(id) {
16492
+ return this.getId(id) > -1;
16493
+ };
16494
+
16495
+ this.getId = function(id) {
16496
+ if (id >= 0 && id < n) {
16497
+ return index[id] - 1;
16498
+ } else {
16499
+ return -1;
16500
+ // error('Invalid index');
16501
+ }
16502
+ };
16503
+ }
16504
+
16505
+
16441
16506
  // Map positive or negative integer ids to non-negative integer ids
16442
- function IdLookupIndex(n, clearable) {
16507
+ function ArcLookupIndex(n) {
16443
16508
  var fwdIndex = new Int32Array(n);
16444
16509
  var revIndex = new Int32Array(n);
16445
- var index = this;
16446
- var setList = [];
16447
16510
  utils.initializeArray(fwdIndex, -1);
16448
16511
  utils.initializeArray(revIndex, -1);
16449
16512
 
16450
16513
  this.setId = function(id, val) {
16451
- if (clearable && !index.hasId(id)) {
16452
- setList.push(id);
16453
- }
16454
16514
  if (id < 0) {
16455
16515
  revIndex[~id] = val;
16456
16516
  } else {
@@ -16458,25 +16518,8 @@
16458
16518
  }
16459
16519
  };
16460
16520
 
16461
- this.clear = function() {
16462
- if (!clearable) {
16463
- error('Index is not clearable');
16464
- }
16465
- setList.forEach(function(id) {
16466
- index.setId(id, -1);
16467
- });
16468
- setList = [];
16469
- };
16470
-
16471
- this.clearId = function(id) {
16472
- if (!index.hasId(id)) {
16473
- error('Tried to clear an unset id');
16474
- }
16475
- index.setId(id, -1);
16476
- };
16477
-
16478
16521
  this.hasId = function(id) {
16479
- var val = index.getId(id);
16522
+ var val = this.getId(id);
16480
16523
  return val > -1;
16481
16524
  };
16482
16525
 
@@ -16489,6 +16532,36 @@
16489
16532
  };
16490
16533
  }
16491
16534
 
16535
+ // Support clearing the index (for efficient reuse)
16536
+ function ClearableArcLookupIndex(n) {
16537
+ var setList = [];
16538
+ var idx = new ArcLookupIndex(n);
16539
+ var _setId = idx.setId;
16540
+
16541
+ idx.setId = function(id, val) {
16542
+ if (!idx.hasId(id)) {
16543
+ setList.push(id);
16544
+ }
16545
+ _setId(id, val);
16546
+ };
16547
+
16548
+ idx.clear = function() {
16549
+ setList.forEach(function(id) {
16550
+ _setId(id, -1);
16551
+ });
16552
+ setList = [];
16553
+ };
16554
+
16555
+ this.clearId = function(id) {
16556
+ if (!idx.hasId(id)) {
16557
+ error('Tried to clear an unset id');
16558
+ }
16559
+ _setId(id, -1);
16560
+ };
16561
+
16562
+ return idx;
16563
+ }
16564
+
16492
16565
  // Associate mosaic tiles with shapes (i.e. identify the groups of tiles that
16493
16566
  // belong to each shape)
16494
16567
  //
@@ -16740,7 +16813,7 @@
16740
16813
  // Supports looking up a shape id using an arc id.
16741
16814
  function ShapeArcIndex(shapes, arcs) {
16742
16815
  var n = arcs.size();
16743
- var index = new IdLookupIndex(n);
16816
+ var index = new ArcLookupIndex(n);
16744
16817
  var shapeId;
16745
16818
  shapes.forEach(onShape);
16746
16819
 
@@ -16892,7 +16965,7 @@
16892
16965
  var arcs = dataset.arcs;
16893
16966
  var filter = getArcPresenceTest(lyr.shapes, arcs);
16894
16967
  var nodes = new NodeCollection(arcs, filter);
16895
- var arcIndex = new IdLookupIndex(arcs.size(), true);
16968
+ var arcIndex = new ClearableArcLookupIndex(arcs.size());
16896
16969
  lyr.shapes = lyr.shapes.map(function(shp, i) {
16897
16970
  if (!shp) return null;
16898
16971
  // split parts at nodes (where multiple arcs intersect)
@@ -18378,7 +18451,8 @@
18378
18451
  // null values indicate the lack of a function for parsing/identifying this property
18379
18452
  // (in which case a heuristic is used for distinguishing a string literal from an expression)
18380
18453
  var stylePropertyTypes = {
18381
- css: null,
18454
+ // css: null,
18455
+ css: 'inlinecss',
18382
18456
  class: 'classname',
18383
18457
  dx: 'measure',
18384
18458
  dy: 'measure',
@@ -18565,6 +18639,8 @@
18565
18639
  val = isPattern(strVal) ? strVal : null;
18566
18640
  } else if (type == 'boolean') {
18567
18641
  val = parseBoolean(strVal);
18642
+ } else if (type == 'inlinecss') {
18643
+ val = strVal; // TODO: validate
18568
18644
  }
18569
18645
  // else {
18570
18646
  // // unknown type -- assume literal value
@@ -24662,9 +24738,9 @@ ${svg}
24662
24738
  .option('class', {
24663
24739
  describe: 'name of CSS class or classes (space-separated)'
24664
24740
  })
24665
- // .option('css', {
24666
- // describe: 'inline css style'
24667
- // })
24741
+ .option('css', {
24742
+ describe: 'inline css style'
24743
+ })
24668
24744
  .option('fill', {
24669
24745
  describe: 'fill color; examples: #eee pink rgba(0, 0, 0, 0.2)'
24670
24746
  })
@@ -38262,7 +38338,7 @@ ${svg}
38262
38338
  // polygon layer, @clipData). This avoids performing unnecessary intersection
38263
38339
  // tests on each line segment.
38264
38340
  var layers = dataset.layers.filter(function(lyr) {
38265
- return !layerIsFullyEnclosed(lyr, dataset, clipData);
38341
+ return layerHasGeometry(lyr) && !layerIsFullyEnclosed(lyr, dataset, clipData);
38266
38342
  });
38267
38343
  if (layers.length > 0) {
38268
38344
  clipLayersInPlace(layers, clipData, dataset, 'clip');
@@ -41240,21 +41316,24 @@ ${svg}
41240
41316
  parseConsoleCommands: parseConsoleCommands
41241
41317
  });
41242
41318
 
41243
- cmd.run = function(job, targets, opts, cb) {
41319
+ cmd.run = async function(job, targets, opts) {
41244
41320
  var commandStr, commands;
41245
41321
  if (!opts.expression) {
41246
41322
  stop("Missing expression parameter");
41247
41323
  }
41248
41324
  commandStr = runGlobalExpression(opts.expression, targets);
41325
+ // Support async functions as expressions
41326
+ if (utils.isPromise(commandStr)) {
41327
+ commandStr = await commandStr;
41328
+ }
41249
41329
  if (commandStr) {
41250
41330
  message(`command: [${commandStr}]`);
41251
41331
  commands = parseCommands(commandStr);
41252
- runParsedCommands(commands, job, cb);
41253
- } else {
41254
- cb(null);
41332
+ await utils.promisify(runParsedCommands)(commands, job);
41255
41333
  }
41256
41334
  };
41257
41335
 
41336
+ // This could return a Promise or a value or nothing
41258
41337
  function runGlobalExpression(expression, targets) {
41259
41338
  var ctx = getBaseContext();
41260
41339
  var output, targetData;
@@ -42381,6 +42460,7 @@ ${svg}
42381
42460
  shapes = lyr0.shapes,
42382
42461
  index = {},
42383
42462
  splitLayers = [],
42463
+ n = getFeatureCount(lyr0),
42384
42464
  namer;
42385
42465
 
42386
42466
  if (opts.ids) {
@@ -42389,11 +42469,18 @@ ${svg}
42389
42469
  namer = getSplitNameFunction(lyr0, expression);
42390
42470
  }
42391
42471
 
42472
+ // // halt if split field is missing
42392
42473
  // if (splitField) {
42393
42474
  // internal.requireDataField(lyr0, splitField);
42394
42475
  // }
42395
42476
 
42396
- utils.repeat(getFeatureCount(lyr0), function(i) {
42477
+ // if input layer is empty, return original layer
42478
+ // TODO: consider halting
42479
+ if (n === 0) {
42480
+ return [lyr0];
42481
+ }
42482
+
42483
+ utils.repeat(n, function(i) {
42397
42484
  var name = namer(i),
42398
42485
  lyr;
42399
42486
 
@@ -42416,6 +42503,7 @@ ${svg}
42416
42503
  lyr.data.getRecords().push(properties[i]);
42417
42504
  }
42418
42505
  });
42506
+
42419
42507
  return splitLayers;
42420
42508
  };
42421
42509
 
@@ -42426,26 +42514,34 @@ ${svg}
42426
42514
  };
42427
42515
  }
42428
42516
 
42429
- function getDefaultSplitFunction(lyr) {
42430
- // if not splitting on an expression and layer is unnamed, name split-apart layers
42431
- // like: split-1, split-2, ...
42432
- return function(i) {
42433
- return (lyr && lyr.name || 'split') + '-' + (i + 1);
42434
- };
42435
- }
42436
-
42437
- function getSplitNameFunction(lyr, exp) {
42517
+ function getSplitNameFunction(lyr, arg) {
42438
42518
  var compiled;
42439
- if (!exp) return getDefaultSplitFunction(lyr);
42519
+ if (!arg) {
42520
+ // if not splitting on an expression and layer is unnamed, name split-apart layers
42521
+ // like: split-1, split-2, ...
42522
+ return function(i) {
42523
+ return (lyr && lyr.name || 'split') + '-' + (i + 1);
42524
+ };
42525
+ }
42526
+ if (lyr.data && lyr.data.fieldExists(arg)) {
42527
+ // Argument is a field name
42528
+ return function(i) {
42529
+ var rec = lyr.data.getRecords()[i];
42530
+ return rec ? valueToLayerName(rec[arg]) : '';
42531
+ };
42532
+ }
42533
+ // Assume: argument is an expression
42440
42534
  lyr = {name: lyr.name, data: lyr.data}; // remove shape info
42441
- compiled = compileValueExpression(exp, lyr, null);
42535
+ compiled = compileValueExpression(arg, lyr, null);
42442
42536
  return function(i) {
42443
42537
  var val = compiled(i);
42444
- return String(val);
42445
- // return val || val === 0 ? String(val) : '';
42538
+ return valueToLayerName(val);
42446
42539
  };
42447
42540
  }
42448
42541
 
42542
+ function valueToLayerName(val) {
42543
+ return String(val);
42544
+ }
42449
42545
 
42450
42546
  // internal.getSplitKey = function(i, field, properties) {
42451
42547
  // var rec = field && properties ? properties[i] : null;
@@ -42481,6 +42577,9 @@ ${svg}
42481
42577
 
42482
42578
  cmd.svgStyle = function(lyr, dataset, opts) {
42483
42579
  var filter;
42580
+ if (getFeatureCount(lyr) === 0) {
42581
+ return;
42582
+ }
42484
42583
  if (!lyr.data) {
42485
42584
  initDataTable(lyr);
42486
42585
  }
@@ -43786,7 +43885,7 @@ ${svg}
43786
43885
  });
43787
43886
 
43788
43887
  } else if (name == 'run') {
43789
- await utils.promisify(cmd.run)(job, targets, opts);
43888
+ await cmd.run(job, targets, opts);
43790
43889
 
43791
43890
  } else if (name == 'scalebar') {
43792
43891
  cmd.scalebar(job.catalog, opts);
@@ -43809,7 +43908,7 @@ ${svg}
43809
43908
 
43810
43909
  } else if (name == 'snap') {
43811
43910
  // cmd.snap(targetDataset, opts);
43812
- applyCommandToEachTarget(targets, opts);
43911
+ applyCommandToEachTarget(cmd.snap, targets, opts);
43813
43912
 
43814
43913
  } else if (name == 'sort') {
43815
43914
  applyCommandToEachLayer(cmd.sortFeatures, targetLayers, arcs, opts);
@@ -44063,6 +44162,13 @@ ${svg}
44063
44162
  }
44064
44163
  });
44065
44164
 
44165
+ var lastCmd = commands[commands.length - 1];
44166
+ if (!runningInBrowser() && lastCmd.name == 'o') {
44167
+ // in CLI, set 'final' flag on final -o command, so the export function knows
44168
+ // that it can modify the output dataset in-place instead of making a copy.
44169
+ lastCmd.options.final = true;
44170
+ }
44171
+
44066
44172
  var batches = divideImportCommand(commands);
44067
44173
  utils.reduceAsync(batches, null, nextGroup, done);
44068
44174
 
@@ -44131,11 +44237,6 @@ ${svg}
44131
44237
  // @done: function([error], [job])
44132
44238
  //
44133
44239
  function runParsedCommands(commands, job, done) {
44134
- if (!runningInBrowser() && commands[commands.length-1].name == 'o') {
44135
- // in CLI, set 'final' flag on final -o command, so the export function knows
44136
- // that it can modify the output dataset in-place instead of making a copy.
44137
- commands[commands.length-1].options.final = true;
44138
- }
44139
44240
  if (!job) job = new Job();
44140
44241
  commands = readAndRemoveSettings(job, commands);
44141
44242
  if (!runningInBrowser()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.6.42",
3
+ "version": "0.6.44",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
@@ -5,6 +5,7 @@
5
5
  get default () { return utils; },
6
6
  get getUniqueName () { return getUniqueName; },
7
7
  get isFunction () { return isFunction; },
8
+ get isPromise () { return isPromise; },
8
9
  get isObject () { return isObject; },
9
10
  get clamp () { return clamp; },
10
11
  get isArray () { return isArray; },
@@ -2157,11 +2158,18 @@
2157
2158
  }
2158
2159
 
2159
2160
  function downloadNextFile(memo, item, next) {
2160
- var blob, err;
2161
- fetch(item.url).then(resp => resp.blob()).then(b => {
2162
- blob = b;
2163
- blob.name = item.basename;
2164
- memo.push(blob);
2161
+ var err;
2162
+ fetch(item.url).then(resp => {
2163
+ if (resp.status != 200) {
2164
+ // e.g. 404 because a URL listed in the GUI query string does not exist
2165
+ throw Error();
2166
+ }
2167
+ return resp.blob();
2168
+ }).then(blob => {
2169
+ if (blob) {
2170
+ blob.name = item.basename;
2171
+ memo.push(blob);
2172
+ }
2165
2173
  }).catch(e => {
2166
2174
  err = "Error&nbsp;loading&nbsp;" + item.name + ". Possible causes include: wrong URL, no network connection, server not configured for cross-domain sharing (CORS).";
2167
2175
  }).finally(() => {
@@ -5209,6 +5217,10 @@
5209
5217
  return typeof obj == 'function';
5210
5218
  }
5211
5219
 
5220
+ function isPromise(arg) {
5221
+ return arg ? isFunction(arg.then) : false;
5222
+ }
5223
+
5212
5224
  function isObject(obj) {
5213
5225
  return obj === Object(obj); // via underscore
5214
5226
  }
@@ -8112,7 +8124,7 @@
8112
8124
  el.addClass('option-menu');
8113
8125
  var html = `<input type="text" class="field-name text-input" placeholder="field name"><br>
8114
8126
  <input type="text" class="field-value text-input" placeholder="value"><br>
8115
- <div class="btn dialog-btn">Apply</div> <span class="inline-checkbox"><input type="checkbox" class="all" />assign value to all records</span>`;
8127
+ <div tabindex="0" class="btn dialog-btn">Apply</div> <span class="inline-checkbox"><input type="checkbox" class="all" />assign value to all records</span>`;
8116
8128
  el.html(html);
8117
8129
 
8118
8130
  var name = el.findChild('.field-name');
package/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.6.42";
3
+ var VERSION = "0.6.44";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -8,6 +8,7 @@
8
8
  get default () { return utils; },
9
9
  get getUniqueName () { return getUniqueName; },
10
10
  get isFunction () { return isFunction; },
11
+ get isPromise () { return isPromise; },
11
12
  get isObject () { return isObject; },
12
13
  get clamp () { return clamp; },
13
14
  get isArray () { return isArray; },
@@ -147,6 +148,10 @@
147
148
  return typeof obj == 'function';
148
149
  }
149
150
 
151
+ function isPromise(arg) {
152
+ return arg ? isFunction(arg.then) : false;
153
+ }
154
+
150
155
  function isObject(obj) {
151
156
  return obj === Object(obj); // via underscore
152
157
  }
@@ -12488,7 +12493,6 @@
12488
12493
  return +n.toPrecision(d);
12489
12494
  }
12490
12495
 
12491
-
12492
12496
  function roundToDigits(n, d) {
12493
12497
  return +n.toFixed(d); // string conversion makes this slow
12494
12498
  }
@@ -12552,6 +12556,37 @@
12552
12556
  });
12553
12557
  }
12554
12558
 
12559
+ const fround2 = (function() {
12560
+ var arr = new Float32Array(1);
12561
+ return function(x) {
12562
+ arr[0] = x;
12563
+ return arr[0];
12564
+ };
12565
+ })();
12566
+
12567
+ // This function rounds towards 0 (i.e. floor). TODO: round properly
12568
+ // @bits: number of bits to round
12569
+ // performance: about 3x slower than Math.fround()
12570
+ function getBinaryRoundingFunction(bits) {
12571
+ // double: sign (1) exponent (11) fraction (52)
12572
+ // single: sign (1) exponent (8) fraction (23)
12573
+ if ((bits >= 1 && bits <= 32) === false) {
12574
+ error('Invalid bits argument:', bits);
12575
+ }
12576
+ var isLE = require('os').endianness() == 'LE';
12577
+ var fp = new Float64Array(1);
12578
+ var leastBits = new Uint32Array(fp.buffer, isLE ? 0 : 4, 1);
12579
+ var mask = 2 ** 32 - 2 ** bits; // e.g. bits = 4 -> 0b11110000
12580
+ return function(x) {
12581
+ fp[0] = x;
12582
+ leastBits[0] = leastBits[0] & mask;
12583
+ return fp[0];
12584
+ };
12585
+ }
12586
+
12587
+ // "round to even" on the 23rd bit of the mantissa
12588
+ const fround = Math.fround || fround2;
12589
+
12555
12590
  function setCoordinatePrecision(dataset, precision) {
12556
12591
  var round = getRoundingFunction(precision);
12557
12592
  // var dissolvePolygon, nodes;
@@ -12586,6 +12621,9 @@
12586
12621
  getRoundedCoordString: getRoundedCoordString,
12587
12622
  getRoundedCoords: getRoundedCoords,
12588
12623
  roundPoints: roundPoints,
12624
+ fround2: fround2,
12625
+ getBinaryRoundingFunction: getBinaryRoundingFunction,
12626
+ fround: fround,
12589
12627
  setCoordinatePrecision: setCoordinatePrecision
12590
12628
  });
12591
12629
 
@@ -16438,19 +16476,41 @@
16438
16476
  buildPolygonMosaic: buildPolygonMosaic
16439
16477
  });
16440
16478
 
16479
+ // Map non-negative integers to non-negative integer ids
16480
+ function IdLookupIndex(n) {
16481
+ var index = new Uint32Array(n);
16482
+
16483
+ this.setId = function(id, val) {
16484
+ if (id >= 0 && val >= 0 && val < n - 1) {
16485
+ index[id] = val + 1;
16486
+ } else {
16487
+ error('Invalid value');
16488
+ }
16489
+ };
16490
+
16491
+ this.hasId = function(id) {
16492
+ return this.getId(id) > -1;
16493
+ };
16494
+
16495
+ this.getId = function(id) {
16496
+ if (id >= 0 && id < n) {
16497
+ return index[id] - 1;
16498
+ } else {
16499
+ return -1;
16500
+ // error('Invalid index');
16501
+ }
16502
+ };
16503
+ }
16504
+
16505
+
16441
16506
  // Map positive or negative integer ids to non-negative integer ids
16442
- function IdLookupIndex(n, clearable) {
16507
+ function ArcLookupIndex(n) {
16443
16508
  var fwdIndex = new Int32Array(n);
16444
16509
  var revIndex = new Int32Array(n);
16445
- var index = this;
16446
- var setList = [];
16447
16510
  utils.initializeArray(fwdIndex, -1);
16448
16511
  utils.initializeArray(revIndex, -1);
16449
16512
 
16450
16513
  this.setId = function(id, val) {
16451
- if (clearable && !index.hasId(id)) {
16452
- setList.push(id);
16453
- }
16454
16514
  if (id < 0) {
16455
16515
  revIndex[~id] = val;
16456
16516
  } else {
@@ -16458,25 +16518,8 @@
16458
16518
  }
16459
16519
  };
16460
16520
 
16461
- this.clear = function() {
16462
- if (!clearable) {
16463
- error('Index is not clearable');
16464
- }
16465
- setList.forEach(function(id) {
16466
- index.setId(id, -1);
16467
- });
16468
- setList = [];
16469
- };
16470
-
16471
- this.clearId = function(id) {
16472
- if (!index.hasId(id)) {
16473
- error('Tried to clear an unset id');
16474
- }
16475
- index.setId(id, -1);
16476
- };
16477
-
16478
16521
  this.hasId = function(id) {
16479
- var val = index.getId(id);
16522
+ var val = this.getId(id);
16480
16523
  return val > -1;
16481
16524
  };
16482
16525
 
@@ -16489,6 +16532,36 @@
16489
16532
  };
16490
16533
  }
16491
16534
 
16535
+ // Support clearing the index (for efficient reuse)
16536
+ function ClearableArcLookupIndex(n) {
16537
+ var setList = [];
16538
+ var idx = new ArcLookupIndex(n);
16539
+ var _setId = idx.setId;
16540
+
16541
+ idx.setId = function(id, val) {
16542
+ if (!idx.hasId(id)) {
16543
+ setList.push(id);
16544
+ }
16545
+ _setId(id, val);
16546
+ };
16547
+
16548
+ idx.clear = function() {
16549
+ setList.forEach(function(id) {
16550
+ _setId(id, -1);
16551
+ });
16552
+ setList = [];
16553
+ };
16554
+
16555
+ this.clearId = function(id) {
16556
+ if (!idx.hasId(id)) {
16557
+ error('Tried to clear an unset id');
16558
+ }
16559
+ _setId(id, -1);
16560
+ };
16561
+
16562
+ return idx;
16563
+ }
16564
+
16492
16565
  // Associate mosaic tiles with shapes (i.e. identify the groups of tiles that
16493
16566
  // belong to each shape)
16494
16567
  //
@@ -16740,7 +16813,7 @@
16740
16813
  // Supports looking up a shape id using an arc id.
16741
16814
  function ShapeArcIndex(shapes, arcs) {
16742
16815
  var n = arcs.size();
16743
- var index = new IdLookupIndex(n);
16816
+ var index = new ArcLookupIndex(n);
16744
16817
  var shapeId;
16745
16818
  shapes.forEach(onShape);
16746
16819
 
@@ -16892,7 +16965,7 @@
16892
16965
  var arcs = dataset.arcs;
16893
16966
  var filter = getArcPresenceTest(lyr.shapes, arcs);
16894
16967
  var nodes = new NodeCollection(arcs, filter);
16895
- var arcIndex = new IdLookupIndex(arcs.size(), true);
16968
+ var arcIndex = new ClearableArcLookupIndex(arcs.size());
16896
16969
  lyr.shapes = lyr.shapes.map(function(shp, i) {
16897
16970
  if (!shp) return null;
16898
16971
  // split parts at nodes (where multiple arcs intersect)
@@ -18378,7 +18451,8 @@
18378
18451
  // null values indicate the lack of a function for parsing/identifying this property
18379
18452
  // (in which case a heuristic is used for distinguishing a string literal from an expression)
18380
18453
  var stylePropertyTypes = {
18381
- css: null,
18454
+ // css: null,
18455
+ css: 'inlinecss',
18382
18456
  class: 'classname',
18383
18457
  dx: 'measure',
18384
18458
  dy: 'measure',
@@ -18565,6 +18639,8 @@
18565
18639
  val = isPattern(strVal) ? strVal : null;
18566
18640
  } else if (type == 'boolean') {
18567
18641
  val = parseBoolean(strVal);
18642
+ } else if (type == 'inlinecss') {
18643
+ val = strVal; // TODO: validate
18568
18644
  }
18569
18645
  // else {
18570
18646
  // // unknown type -- assume literal value
@@ -24662,9 +24738,9 @@ ${svg}
24662
24738
  .option('class', {
24663
24739
  describe: 'name of CSS class or classes (space-separated)'
24664
24740
  })
24665
- // .option('css', {
24666
- // describe: 'inline css style'
24667
- // })
24741
+ .option('css', {
24742
+ describe: 'inline css style'
24743
+ })
24668
24744
  .option('fill', {
24669
24745
  describe: 'fill color; examples: #eee pink rgba(0, 0, 0, 0.2)'
24670
24746
  })
@@ -38262,7 +38338,7 @@ ${svg}
38262
38338
  // polygon layer, @clipData). This avoids performing unnecessary intersection
38263
38339
  // tests on each line segment.
38264
38340
  var layers = dataset.layers.filter(function(lyr) {
38265
- return !layerIsFullyEnclosed(lyr, dataset, clipData);
38341
+ return layerHasGeometry(lyr) && !layerIsFullyEnclosed(lyr, dataset, clipData);
38266
38342
  });
38267
38343
  if (layers.length > 0) {
38268
38344
  clipLayersInPlace(layers, clipData, dataset, 'clip');
@@ -41240,21 +41316,24 @@ ${svg}
41240
41316
  parseConsoleCommands: parseConsoleCommands
41241
41317
  });
41242
41318
 
41243
- cmd.run = function(job, targets, opts, cb) {
41319
+ cmd.run = async function(job, targets, opts) {
41244
41320
  var commandStr, commands;
41245
41321
  if (!opts.expression) {
41246
41322
  stop("Missing expression parameter");
41247
41323
  }
41248
41324
  commandStr = runGlobalExpression(opts.expression, targets);
41325
+ // Support async functions as expressions
41326
+ if (utils.isPromise(commandStr)) {
41327
+ commandStr = await commandStr;
41328
+ }
41249
41329
  if (commandStr) {
41250
41330
  message(`command: [${commandStr}]`);
41251
41331
  commands = parseCommands(commandStr);
41252
- runParsedCommands(commands, job, cb);
41253
- } else {
41254
- cb(null);
41332
+ await utils.promisify(runParsedCommands)(commands, job);
41255
41333
  }
41256
41334
  };
41257
41335
 
41336
+ // This could return a Promise or a value or nothing
41258
41337
  function runGlobalExpression(expression, targets) {
41259
41338
  var ctx = getBaseContext();
41260
41339
  var output, targetData;
@@ -42381,6 +42460,7 @@ ${svg}
42381
42460
  shapes = lyr0.shapes,
42382
42461
  index = {},
42383
42462
  splitLayers = [],
42463
+ n = getFeatureCount(lyr0),
42384
42464
  namer;
42385
42465
 
42386
42466
  if (opts.ids) {
@@ -42389,11 +42469,18 @@ ${svg}
42389
42469
  namer = getSplitNameFunction(lyr0, expression);
42390
42470
  }
42391
42471
 
42472
+ // // halt if split field is missing
42392
42473
  // if (splitField) {
42393
42474
  // internal.requireDataField(lyr0, splitField);
42394
42475
  // }
42395
42476
 
42396
- utils.repeat(getFeatureCount(lyr0), function(i) {
42477
+ // if input layer is empty, return original layer
42478
+ // TODO: consider halting
42479
+ if (n === 0) {
42480
+ return [lyr0];
42481
+ }
42482
+
42483
+ utils.repeat(n, function(i) {
42397
42484
  var name = namer(i),
42398
42485
  lyr;
42399
42486
 
@@ -42416,6 +42503,7 @@ ${svg}
42416
42503
  lyr.data.getRecords().push(properties[i]);
42417
42504
  }
42418
42505
  });
42506
+
42419
42507
  return splitLayers;
42420
42508
  };
42421
42509
 
@@ -42426,26 +42514,34 @@ ${svg}
42426
42514
  };
42427
42515
  }
42428
42516
 
42429
- function getDefaultSplitFunction(lyr) {
42430
- // if not splitting on an expression and layer is unnamed, name split-apart layers
42431
- // like: split-1, split-2, ...
42432
- return function(i) {
42433
- return (lyr && lyr.name || 'split') + '-' + (i + 1);
42434
- };
42435
- }
42436
-
42437
- function getSplitNameFunction(lyr, exp) {
42517
+ function getSplitNameFunction(lyr, arg) {
42438
42518
  var compiled;
42439
- if (!exp) return getDefaultSplitFunction(lyr);
42519
+ if (!arg) {
42520
+ // if not splitting on an expression and layer is unnamed, name split-apart layers
42521
+ // like: split-1, split-2, ...
42522
+ return function(i) {
42523
+ return (lyr && lyr.name || 'split') + '-' + (i + 1);
42524
+ };
42525
+ }
42526
+ if (lyr.data && lyr.data.fieldExists(arg)) {
42527
+ // Argument is a field name
42528
+ return function(i) {
42529
+ var rec = lyr.data.getRecords()[i];
42530
+ return rec ? valueToLayerName(rec[arg]) : '';
42531
+ };
42532
+ }
42533
+ // Assume: argument is an expression
42440
42534
  lyr = {name: lyr.name, data: lyr.data}; // remove shape info
42441
- compiled = compileValueExpression(exp, lyr, null);
42535
+ compiled = compileValueExpression(arg, lyr, null);
42442
42536
  return function(i) {
42443
42537
  var val = compiled(i);
42444
- return String(val);
42445
- // return val || val === 0 ? String(val) : '';
42538
+ return valueToLayerName(val);
42446
42539
  };
42447
42540
  }
42448
42541
 
42542
+ function valueToLayerName(val) {
42543
+ return String(val);
42544
+ }
42449
42545
 
42450
42546
  // internal.getSplitKey = function(i, field, properties) {
42451
42547
  // var rec = field && properties ? properties[i] : null;
@@ -42481,6 +42577,9 @@ ${svg}
42481
42577
 
42482
42578
  cmd.svgStyle = function(lyr, dataset, opts) {
42483
42579
  var filter;
42580
+ if (getFeatureCount(lyr) === 0) {
42581
+ return;
42582
+ }
42484
42583
  if (!lyr.data) {
42485
42584
  initDataTable(lyr);
42486
42585
  }
@@ -43786,7 +43885,7 @@ ${svg}
43786
43885
  });
43787
43886
 
43788
43887
  } else if (name == 'run') {
43789
- await utils.promisify(cmd.run)(job, targets, opts);
43888
+ await cmd.run(job, targets, opts);
43790
43889
 
43791
43890
  } else if (name == 'scalebar') {
43792
43891
  cmd.scalebar(job.catalog, opts);
@@ -43809,7 +43908,7 @@ ${svg}
43809
43908
 
43810
43909
  } else if (name == 'snap') {
43811
43910
  // cmd.snap(targetDataset, opts);
43812
- applyCommandToEachTarget(targets, opts);
43911
+ applyCommandToEachTarget(cmd.snap, targets, opts);
43813
43912
 
43814
43913
  } else if (name == 'sort') {
43815
43914
  applyCommandToEachLayer(cmd.sortFeatures, targetLayers, arcs, opts);
@@ -44063,6 +44162,13 @@ ${svg}
44063
44162
  }
44064
44163
  });
44065
44164
 
44165
+ var lastCmd = commands[commands.length - 1];
44166
+ if (!runningInBrowser() && lastCmd.name == 'o') {
44167
+ // in CLI, set 'final' flag on final -o command, so the export function knows
44168
+ // that it can modify the output dataset in-place instead of making a copy.
44169
+ lastCmd.options.final = true;
44170
+ }
44171
+
44066
44172
  var batches = divideImportCommand(commands);
44067
44173
  utils.reduceAsync(batches, null, nextGroup, done);
44068
44174
 
@@ -44131,11 +44237,6 @@ ${svg}
44131
44237
  // @done: function([error], [job])
44132
44238
  //
44133
44239
  function runParsedCommands(commands, job, done) {
44134
- if (!runningInBrowser() && commands[commands.length-1].name == 'o') {
44135
- // in CLI, set 'final' flag on final -o command, so the export function knows
44136
- // that it can modify the output dataset in-place instead of making a copy.
44137
- commands[commands.length-1].options.final = true;
44138
- }
44139
44240
  if (!job) job = new Job();
44140
44241
  commands = readAndRemoveSettings(job, commands);
44141
44242
  if (!runningInBrowser()) {