mapshaper 0.5.108 → 0.5.111

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.108";
3
+ var VERSION = "0.5.111";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -101,6 +101,37 @@
101
101
  get trimQuotes () { return trimQuotes; }
102
102
  });
103
103
 
104
+ // This module provides a way for multiple jobs to run together asynchronously
105
+ // while keeping job-level context variables (like "defs") separate.
106
+
107
+ var stash = {};
108
+
109
+ function stashVar(key, val) {
110
+ if (key in stash) {
111
+ error('Tried to replace a stashed variable:', key);
112
+ }
113
+ stash[key] = val;
114
+ }
115
+
116
+ function getStashedVar(key) {
117
+ if (key in stash === false) {
118
+ return undefined; // to support running commands in tests
119
+ // error('Tried to read a nonexistent variable from the stash:', key);
120
+ }
121
+ return stash[key];
122
+ }
123
+
124
+ function clearStash() {
125
+ stash = {};
126
+ }
127
+
128
+ var Stash = /*#__PURE__*/Object.freeze({
129
+ __proto__: null,
130
+ stashVar: stashVar,
131
+ getStashedVar: getStashedVar,
132
+ clearStash: clearStash
133
+ });
134
+
104
135
  var Buffer = require('buffer').Buffer; // works with browserify
105
136
 
106
137
  var uniqCount = 0;
@@ -1133,62 +1164,6 @@
1133
1164
  return raw;
1134
1165
  }
1135
1166
 
1136
- var context = createContext(); // command context (persist for the current command cycle)
1137
-
1138
- function runningInBrowser() {
1139
- return typeof window !== 'undefined' && typeof window.document !== 'undefined';
1140
- }
1141
-
1142
- function getStateVar(key) {
1143
- return context[key];
1144
- }
1145
-
1146
- function setStateVar(key, val) {
1147
- context[key] = val;
1148
- }
1149
-
1150
- function createContext() {
1151
- return {
1152
- DEBUG: false,
1153
- QUIET: false,
1154
- VERBOSE: false,
1155
- defs: {},
1156
- input_files: []
1157
- };
1158
- }
1159
-
1160
- // Install a new set of context variables, clear them when an async callback is called.
1161
- // @cb callback function to wrap
1162
- // returns wrapped callback function
1163
- function createAsyncContext(cb) {
1164
- context = createContext();
1165
- return function() {
1166
- cb.apply(null, utils.toArray(arguments));
1167
- // clear context after cb(), so output/errors can be handled in current context
1168
- context = createContext();
1169
- };
1170
- }
1171
-
1172
- // Save the current context, restore it when an async callback is called
1173
- // @cb callback function to wrap
1174
- // returns wrapped callback function
1175
- function preserveContext(cb) {
1176
- var ctx = context;
1177
- return function() {
1178
- context = ctx;
1179
- cb.apply(null, utils.toArray(arguments));
1180
- };
1181
- }
1182
-
1183
- var State = /*#__PURE__*/Object.freeze({
1184
- __proto__: null,
1185
- runningInBrowser: runningInBrowser,
1186
- getStateVar: getStateVar,
1187
- setStateVar: setStateVar,
1188
- createAsyncContext: createAsyncContext,
1189
- preserveContext: preserveContext
1190
- });
1191
-
1192
1167
  var LOGGING = false;
1193
1168
  var STDOUT = false; // use stdout for status messages
1194
1169
  var _error, _stop, _message;
@@ -1268,13 +1243,13 @@
1268
1243
 
1269
1244
  function verbose() {
1270
1245
  // verbose can be set globally with the -verbose command or separately for each command
1271
- if (getStateVar('VERBOSE') || getStateVar('verbose')) {
1246
+ if (getStashedVar('VERBOSE')) {
1272
1247
  message.apply(null, arguments);
1273
1248
  }
1274
1249
  }
1275
1250
 
1276
1251
  function debug() {
1277
- if (getStateVar('DEBUG') || getStateVar('debug')) {
1252
+ if (getStashedVar('DEBUG')) {
1278
1253
  logArgs(arguments);
1279
1254
  }
1280
1255
  }
@@ -1354,7 +1329,7 @@
1354
1329
 
1355
1330
  function messageArgs(args) {
1356
1331
  var arr = utils.toArray(args);
1357
- var cmd = getStateVar('current_command');
1332
+ var cmd = getStashedVar('current_command');
1358
1333
  if (cmd && cmd != 'help') {
1359
1334
  arr.unshift('[' + cmd + ']');
1360
1335
  }
@@ -1362,7 +1337,7 @@
1362
1337
  }
1363
1338
 
1364
1339
  function logArgs(args) {
1365
- if (!LOGGING || getStateVar('QUIET') || !utils.isArrayLike(args)) return;
1340
+ if (!LOGGING || getStashedVar('QUIET') || !utils.isArrayLike(args)) return;
1366
1341
  var msg = formatLogArgs(args);
1367
1342
  if (STDOUT) console.log(msg);
1368
1343
  else console.error(msg);
@@ -2750,21 +2725,30 @@
2750
2725
  message(utils.format("Snapped %s point%s", snapCount, utils.pluralSuffix(snapCount)));
2751
2726
  }
2752
2727
 
2728
+ function snapCoordsByInterval(arcs, snapDist) {
2729
+ if (snapDist > 0 === false) return 0;
2730
+ var ids = getCoordinateIds(arcs);
2731
+ return snapCoordsInternal(ids, arcs, snapDist);
2732
+ }
2733
+
2734
+ function snapEndpointsByInterval(arcs, snapDist) {
2735
+ if (snapDist > 0 === false) return 0;
2736
+ var ids = getEndpointIds(arcs);
2737
+ return snapCoordsInternal(ids, arcs, snapDist);
2738
+ }
2739
+
2753
2740
  // Snap together points within a small threshold
2754
2741
  //
2755
- function snapCoordsByInterval(arcs, snapDist) {
2742
+ function snapCoordsInternal(ids, arcs, snapDist) {
2756
2743
  var snapCount = 0,
2757
- data = arcs.getVertexData(),
2758
- ids;
2744
+ n = ids.length,
2745
+ data = arcs.getVertexData();
2759
2746
 
2760
- if (snapDist > 0) {
2761
- // Get sorted coordinate ids
2762
- // Consider: speed up sorting -- try bucket sort as first pass.
2763
- //
2764
- ids = sortCoordinateIds(data.xx);
2765
- for (var i=0, n=ids.length; i<n; i++) {
2766
- snapCount += snapPoint(i, snapDist, ids, data.xx, data.yy);
2767
- }
2747
+ quicksortIds(data.xx, ids, 0, n-1);
2748
+
2749
+ // Consider: speed up sorting -- try bucket sort as first pass.
2750
+ for (var i=0; i<n; i++) {
2751
+ snapCount += snapPoint(i, snapDist, ids, data.xx, data.yy);
2768
2752
  }
2769
2753
  return snapCount;
2770
2754
 
@@ -2790,13 +2774,25 @@
2790
2774
  }
2791
2775
  }
2792
2776
 
2793
- function sortCoordinateIds(a) {
2794
- var n = a.length,
2777
+ function getCoordinateIds(arcs) {
2778
+ var data = arcs.getVertexData(),
2779
+ n = data.xx.length,
2795
2780
  ids = new Uint32Array(n);
2796
2781
  for (var i=0; i<n; i++) {
2797
2782
  ids[i] = i;
2798
2783
  }
2799
- quicksortIds(a, ids, 0, ids.length-1);
2784
+ return ids;
2785
+ }
2786
+
2787
+ function getEndpointIds(arcs) {
2788
+ var i = 0;
2789
+ var ids = [];
2790
+ var data = arcs.getVertexData();
2791
+ data.nn.forEach(function(n) {
2792
+ if (n > 0 === false) return;
2793
+ ids.push(i, i+n-1);
2794
+ i += n;
2795
+ });
2800
2796
  return ids;
2801
2797
  }
2802
2798
 
@@ -2896,7 +2892,9 @@
2896
2892
  getHighPrecisionSnapInterval: getHighPrecisionSnapInterval,
2897
2893
  snapCoords: snapCoords,
2898
2894
  snapCoordsByInterval: snapCoordsByInterval,
2899
- sortCoordinateIds: sortCoordinateIds
2895
+ snapEndpointsByInterval: snapEndpointsByInterval,
2896
+ getCoordinateIds: getCoordinateIds,
2897
+ getEndpointIds: getEndpointIds
2900
2898
  });
2901
2899
 
2902
2900
  // Find the intersection between two 2D segments
@@ -4122,6 +4120,7 @@
4122
4120
  s = [],
4123
4121
  dataNulls = 0,
4124
4122
  rec;
4123
+
4125
4124
  for (var i=0, n=shapes.length; i<n; i++) {
4126
4125
  if (types[i] != geoType) continue;
4127
4126
  if (geoType) s.push(shapes[i]);
@@ -4132,7 +4131,7 @@
4132
4131
  return {
4133
4132
  geometry_type: geoType,
4134
4133
  shapes: s,
4135
- data: dataNulls < s.length ? new DataTable(p) : null
4134
+ data: dataNulls < p.length ? new DataTable(p) : null
4136
4135
  };
4137
4136
  });
4138
4137
  return layers;
@@ -4742,9 +4741,9 @@
4742
4741
  defn = projectionAliases[str]; // defn is a function
4743
4742
  } else if (looksLikeInitString(str)) {
4744
4743
  defn = '+init=' + str.toLowerCase();
4745
- } else if (str in getStateVar('defs')) {
4744
+ } else if (str in (getStashedVar('defs') || {})) {
4746
4745
  // a proj4 alias could be dynamically created in a -calc expression
4747
- defn = getStateVar('defs')[str];
4746
+ defn = getStashedVar('defs')[str];
4748
4747
  } else {
4749
4748
  defn = parseCustomProjection(str);
4750
4749
  }
@@ -7031,6 +7030,15 @@
7031
7030
  transformPoints: transformPoints
7032
7031
  });
7033
7032
 
7033
+ function runningInBrowser() {
7034
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined';
7035
+ }
7036
+
7037
+ var State = /*#__PURE__*/Object.freeze({
7038
+ __proto__: null,
7039
+ runningInBrowser: runningInBrowser
7040
+ });
7041
+
7034
7042
  function getPathSep(path) {
7035
7043
  // TODO: improve
7036
7044
  return path.indexOf('/') == -1 && path.indexOf('\\') != -1 ? '\\' : '/';
@@ -7139,7 +7147,8 @@
7139
7147
  } else if (fname == '/dev/stdin') {
7140
7148
  content = require('rw').readFileSync(fname);
7141
7149
  } else {
7142
- getStateVar('input_files').push(fname);
7150
+ // kludge to prevent overwriting of input files
7151
+ (getStashedVar('input_files') || []).push(fname);
7143
7152
  content = require('fs').readFileSync(fname);
7144
7153
  }
7145
7154
  if (encoding && Buffer.isBuffer(content)) {
@@ -7164,7 +7173,7 @@
7164
7173
  var fs = require('rw');
7165
7174
  cli.createDirIfNeeded(fname);
7166
7175
  if (cb) {
7167
- fs.writeFile(fname, content, preserveContext(cb));
7176
+ fs.writeFile(fname, content, cb);
7168
7177
  } else {
7169
7178
  fs.writeFileSync(fname, content);
7170
7179
  }
@@ -7259,7 +7268,7 @@
7259
7268
  return cli.writeFile('/dev/stdout', exports[0].content, cb);
7260
7269
  } else {
7261
7270
  var paths = getOutputPaths(utils.pluck(exports, 'filename'), opts);
7262
- var inputFiles = getStateVar('input_files');
7271
+ var inputFiles = getStashedVar('input_files');
7263
7272
  exports.forEach(function(obj, i) {
7264
7273
  var path = paths[i];
7265
7274
  if (obj.content instanceof ArrayBuffer) {
@@ -9514,6 +9523,140 @@
9514
9523
  };
9515
9524
  }
9516
9525
 
9526
+ // convert targets from [{layers: [...], dataset: <>}, ...] format to
9527
+ // [{layer: <>, dataset: <>}, ...] format
9528
+ function expandCommandTargets(targets) {
9529
+ return targets.reduce(function(memo, target) {
9530
+ target.layers.forEach(function(lyr) {
9531
+ memo.push({layer: lyr, dataset: target.dataset});
9532
+ });
9533
+ return memo;
9534
+ }, []);
9535
+ }
9536
+
9537
+
9538
+ function findCommandTargets(layers, pattern, type) {
9539
+ var targets = [];
9540
+ var matches = findMatchingLayers(layers, pattern, true);
9541
+ if (type) {
9542
+ matches = matches.filter(function(o) {return o.layer.geometry_type == type;});
9543
+ }
9544
+ // assign target_id to matched layers
9545
+ // (kludge so layers can be sorted in the order that they match; used by -o command)
9546
+ layers.forEach(function(o) {o.layer.target_id = -1;});
9547
+ matches.forEach(function(o, i) {o.layer.target_id = i;});
9548
+ return groupLayersByDataset(matches);
9549
+ }
9550
+
9551
+ // arr: array of {layer: <>, dataset: <>} objects
9552
+ function groupLayersByDataset(arr) {
9553
+ var datasets = [];
9554
+ var targets = [];
9555
+ arr.forEach(function(o) {
9556
+ var i = datasets.indexOf(o.dataset);
9557
+ if (i == -1) {
9558
+ datasets.push(o.dataset);
9559
+ targets.push({layers: [o.layer], dataset: o.dataset});
9560
+ } else {
9561
+ targets[i].layers.push(o.layer);
9562
+ }
9563
+ });
9564
+ return targets;
9565
+ }
9566
+
9567
+ // layers: array of {layer: <>, dataset: <>} objects
9568
+ // pattern: is a layer identifier or a comma-sep. list of identifiers.
9569
+ // An identifier is a literal name, a pattern containing "*" wildcard or
9570
+ // a 1-based index (1..n)
9571
+ function findMatchingLayers(layers, pattern, throws) {
9572
+ var matchedLayers = [];
9573
+ var unmatchedIds = [];
9574
+ var index = {};
9575
+ pattern.split(',').forEach(function(subpattern, i) {
9576
+ var test = getLayerMatch(subpattern);
9577
+ var matched = false;
9578
+ layers.forEach(function(o, layerId) {
9579
+ // if (matchedLayers.indexOf(lyr) > -1) return; // performance bottleneck with 1000s of layers
9580
+ if (layerId in index) {
9581
+ matched = true;
9582
+ } else if (test(o.layer, layerId + 1)) { // layers are 1-indexed
9583
+ matchedLayers.push(o);
9584
+ index[layerId] = true;
9585
+ matched = true;
9586
+ }
9587
+ });
9588
+ if (matched == false) {
9589
+ unmatchedIds.push(subpattern);
9590
+ }
9591
+ });
9592
+ if (throws && unmatchedIds.length) {
9593
+ stop(utils.format('Missing layer%s: %s', unmatchedIds.length == 1 ? '' : 's', unmatchedIds.join(',')));
9594
+ }
9595
+ return matchedLayers;
9596
+ }
9597
+
9598
+ function getLayerMatch(pattern) {
9599
+ var isIndex = utils.isInteger(Number(pattern));
9600
+ var nameRxp = isIndex ? null : utils.wildcardToRegExp(pattern);
9601
+ return function(lyr, i) {
9602
+ return isIndex ? String(i) == pattern : nameRxp.test(lyr.name || '');
9603
+ };
9604
+ }
9605
+
9606
+ function countTargetLayers(targets) {
9607
+ return targets.reduce(function(memo, target) {
9608
+ return memo + target.layers.length;
9609
+ }, 0);
9610
+ }
9611
+
9612
+ // get an identifier for a layer that can be used in a target= option
9613
+ // (returns name if layer has a unique name, or a numerical id)
9614
+ function getLayerTargetId(catalog, lyr) {
9615
+ var nameCount = 0,
9616
+ name = lyr.name,
9617
+ id;
9618
+ catalog.getLayers().forEach(function(o, i) {
9619
+ if (lyr.name && o.layer.name == lyr.name) nameCount++;
9620
+ if (lyr == o.layer) id = String(i + 1);
9621
+ });
9622
+ if (!id) error('Layer not found');
9623
+ return nameCount == 1 ? lyr.name : id;
9624
+ }
9625
+
9626
+ var TargetUtils = /*#__PURE__*/Object.freeze({
9627
+ __proto__: null,
9628
+ expandCommandTargets: expandCommandTargets,
9629
+ findCommandTargets: findCommandTargets,
9630
+ groupLayersByDataset: groupLayersByDataset,
9631
+ findMatchingLayers: findMatchingLayers,
9632
+ getLayerMatch: getLayerMatch,
9633
+ countTargetLayers: countTargetLayers,
9634
+ getLayerTargetId: getLayerTargetId
9635
+ });
9636
+
9637
+ function getNullLayerProxy(targets) {
9638
+ var obj = {};
9639
+ var n = countTargetLayers(targets);
9640
+ var getters = {
9641
+ name: error,
9642
+ data: error,
9643
+ type: error,
9644
+ size: error,
9645
+ empty: error,
9646
+ bbox: error
9647
+ };
9648
+ addGetters(obj, getters);
9649
+ obj.field_exists = error;
9650
+ obj.field_type = error;
9651
+ obj.field_includes = error;
9652
+ return obj;
9653
+ function error() {
9654
+ throw Error(`This expression requires a single target layer; Received ${n} layers.`);
9655
+ }
9656
+ }
9657
+
9658
+
9659
+
9517
9660
  // Returns an object representing a layer in a JS expression
9518
9661
  function getLayerProxy(lyr, arcs) {
9519
9662
  var obj = {};
@@ -9523,10 +9666,10 @@
9523
9666
  data: records,
9524
9667
  type: lyr.geometry_type,
9525
9668
  size: getFeatureCount(lyr),
9526
- empty: getFeatureCount(lyr) === 0
9669
+ empty: getFeatureCount(lyr) === 0,
9670
+ bbox: getBBoxGetter(obj, lyr, arcs)
9527
9671
  };
9528
9672
  addGetters(obj, getters);
9529
- addBBoxGetter(obj, lyr, arcs);
9530
9673
  obj.field_exists = function(name) {
9531
9674
  return lyr.data && lyr.data.fieldExists(name) ? true : false;
9532
9675
  };
@@ -9555,16 +9698,14 @@
9555
9698
  return ctx;
9556
9699
  }
9557
9700
 
9558
- function addBBoxGetter(obj, lyr, arcs) {
9701
+ function getBBoxGetter(obj, lyr, arcs) {
9559
9702
  var bbox;
9560
- addGetters(obj, {
9561
- bbox: function() {
9562
- if (!bbox) {
9563
- bbox = getBBox$1(lyr, arcs);
9564
- }
9565
- return bbox;
9703
+ return function() {
9704
+ if (!bbox) {
9705
+ bbox = getBBox$1(lyr, arcs);
9566
9706
  }
9567
- });
9707
+ return bbox;
9708
+ };
9568
9709
  }
9569
9710
 
9570
9711
  function getBBox$1(lyr, arcs) {
@@ -9965,7 +10106,7 @@
9965
10106
  }
9966
10107
 
9967
10108
  function getExpressionContext(lyr, mixins, opts) {
9968
- var defs = getStateVar('defs');
10109
+ var defs = getStashedVar('defs');
9969
10110
  var env = getBaseContext();
9970
10111
  var ctx = {};
9971
10112
  var fields = lyr.data ? lyr.data.getFields() : [];
@@ -10154,7 +10295,7 @@
10154
10295
  }
10155
10296
  // Save any assigned variables to the defs object, so they will be available
10156
10297
  // for later -each expressions to use.
10157
- defs = getStateVar('defs');
10298
+ defs = getStashedVar('defs');
10158
10299
  compiled = compileCalcExpression(lyr, arcs, opts.expression);
10159
10300
  result = compiled(null, defs);
10160
10301
  message(msg + ": " + result);
@@ -13934,7 +14075,7 @@
13934
14075
 
13935
14076
  var propertiesBySymbolType = {
13936
14077
  polygon: utils.arrayToIndex(commonProperties.concat('fill', 'fill-pattern')),
13937
- polyline: utils.arrayToIndex(commonProperties),
14078
+ polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin')),
13938
14079
  point: utils.arrayToIndex(commonProperties.concat('fill', 'r')),
13939
14080
  label: utils.arrayToIndex(commonProperties.concat(
13940
14081
  'fill,r,font-family,font-size,text-anchor,font-weight,font-style,letter-spacing,dominant-baseline'.split(',')))
@@ -14022,7 +14163,6 @@
14022
14163
  function parseStyleExpression(strVal, lyr) {
14023
14164
  var func;
14024
14165
  try {
14025
- // func = compileValueExpression(strVal, lyr, null, {context: getStateVar('defs'), no_warn: true});
14026
14166
  func = compileValueExpression(strVal, lyr, null, {no_warn: true});
14027
14167
  func(0); // check for runtime errors (e.g. undefined variables)
14028
14168
  } catch(e) {
@@ -16804,7 +16944,7 @@ ${svg}
16804
16944
  function guessInputFileType(file) {
16805
16945
  var ext = getFileExtension(file || '').toLowerCase(),
16806
16946
  type = null;
16807
- if (ext == 'dbf' || ext == 'shp' || ext == 'prj' || ext == 'shx') {
16947
+ if (ext == 'dbf' || ext == 'shp' || ext == 'prj' || ext == 'shx' || ext == 'kml') {
16808
16948
  type = ext;
16809
16949
  } else if (/json$/.test(ext)) {
16810
16950
  type = 'json';
@@ -16817,7 +16957,8 @@ ${svg}
16817
16957
  function guessInputContentType(content) {
16818
16958
  var type = null;
16819
16959
  if (utils.isString(content)) {
16820
- type = stringLooksLikeJSON(content) ? 'json' : 'text';
16960
+ type = stringLooksLikeJSON(content) && 'json' ||
16961
+ stringLooksLikeKML(content) && 'kml' || 'text';
16821
16962
  } else if (utils.isObject(content) && content.type || utils.isArray(content)) {
16822
16963
  type = 'json';
16823
16964
  }
@@ -16828,11 +16969,15 @@ ${svg}
16828
16969
  return guessInputFileType(file) || guessInputContentType(content);
16829
16970
  }
16830
16971
 
16831
- //
16832
16972
  function stringLooksLikeJSON(str) {
16833
16973
  return /^\s*[{[]/.test(String(str));
16834
16974
  }
16835
16975
 
16976
+ function stringLooksLikeKML(str) {
16977
+ str = String(str);
16978
+ return str.includes('<kml ') && str.includes('xmlns="http://www.opengis.net/kml/');
16979
+ }
16980
+
16836
16981
  function couldBeDsvFile(name) {
16837
16982
  var ext = getFileExtension(name).toLowerCase();
16838
16983
  return /csv|tsv|txt$/.test(ext);
@@ -16877,6 +17022,7 @@ ${svg}
16877
17022
  guessInputContentType: guessInputContentType,
16878
17023
  guessInputType: guessInputType,
16879
17024
  stringLooksLikeJSON: stringLooksLikeJSON,
17025
+ stringLooksLikeKML: stringLooksLikeKML,
16880
17026
  couldBeDsvFile: couldBeDsvFile,
16881
17027
  isZipFile: isZipFile,
16882
17028
  isSupportedOutputFormat: isSupportedOutputFormat,
@@ -17178,116 +17324,6 @@ ${svg}
17178
17324
  formatVersionedFileName: formatVersionedFileName
17179
17325
  });
17180
17326
 
17181
- // convert targets from [{layers: [...], dataset: <>}, ...] format to
17182
- // [{layer: <>, dataset: <>}, ...] format
17183
- function expandCommandTargets(targets) {
17184
- return targets.reduce(function(memo, target) {
17185
- target.layers.forEach(function(lyr) {
17186
- memo.push({layer: lyr, dataset: target.dataset});
17187
- });
17188
- return memo;
17189
- }, []);
17190
- }
17191
-
17192
- function findCommandTargets(layers, pattern, type) {
17193
- var targets = [];
17194
- var matches = findMatchingLayers(layers, pattern, true);
17195
- if (type) {
17196
- matches = matches.filter(function(o) {return o.layer.geometry_type == type;});
17197
- }
17198
- // assign target_id to matched layers
17199
- // (kludge so layers can be sorted in the order that they match; used by -o command)
17200
- layers.forEach(function(o) {o.layer.target_id = -1;});
17201
- matches.forEach(function(o, i) {o.layer.target_id = i;});
17202
- return groupLayersByDataset(matches);
17203
- }
17204
-
17205
- // arr: array of {layer: <>, dataset: <>} objects
17206
- function groupLayersByDataset(arr) {
17207
- var datasets = [];
17208
- var targets = [];
17209
- arr.forEach(function(o) {
17210
- var i = datasets.indexOf(o.dataset);
17211
- if (i == -1) {
17212
- datasets.push(o.dataset);
17213
- targets.push({layers: [o.layer], dataset: o.dataset});
17214
- } else {
17215
- targets[i].layers.push(o.layer);
17216
- }
17217
- });
17218
- return targets;
17219
- }
17220
-
17221
- // layers: array of {layer: <>, dataset: <>} objects
17222
- // pattern: is a layer identifier or a comma-sep. list of identifiers.
17223
- // An identifier is a literal name, a pattern containing "*" wildcard or
17224
- // a 1-based index (1..n)
17225
- function findMatchingLayers(layers, pattern, throws) {
17226
- var matchedLayers = [];
17227
- var unmatchedIds = [];
17228
- var index = {};
17229
- pattern.split(',').forEach(function(subpattern, i) {
17230
- var test = getLayerMatch(subpattern);
17231
- var matched = false;
17232
- layers.forEach(function(o, layerId) {
17233
- // if (matchedLayers.indexOf(lyr) > -1) return; // performance bottleneck with 1000s of layers
17234
- if (layerId in index) {
17235
- matched = true;
17236
- } else if (test(o.layer, layerId + 1)) { // layers are 1-indexed
17237
- matchedLayers.push(o);
17238
- index[layerId] = true;
17239
- matched = true;
17240
- }
17241
- });
17242
- if (matched == false) {
17243
- unmatchedIds.push(subpattern);
17244
- }
17245
- });
17246
- if (throws && unmatchedIds.length) {
17247
- stop(utils.format('Missing layer%s: %s', unmatchedIds.length == 1 ? '' : 's', unmatchedIds.join(',')));
17248
- }
17249
- return matchedLayers;
17250
- }
17251
-
17252
- function getLayerMatch(pattern) {
17253
- var isIndex = utils.isInteger(Number(pattern));
17254
- var nameRxp = isIndex ? null : utils.wildcardToRegExp(pattern);
17255
- return function(lyr, i) {
17256
- return isIndex ? String(i) == pattern : nameRxp.test(lyr.name || '');
17257
- };
17258
- }
17259
-
17260
- function countTargetLayers(targets) {
17261
- return targets.reduce(function(memo, target) {
17262
- return memo + target.layers.length;
17263
- }, 0);
17264
- }
17265
-
17266
- // get an identifier for a layer that can be used in a target= option
17267
- // (returns name if layer has a unique name, or a numerical id)
17268
- function getLayerTargetId(catalog, lyr) {
17269
- var nameCount = 0,
17270
- name = lyr.name,
17271
- id;
17272
- catalog.getLayers().forEach(function(o, i) {
17273
- if (lyr.name && o.layer.name == lyr.name) nameCount++;
17274
- if (lyr == o.layer) id = String(i + 1);
17275
- });
17276
- if (!id) error('Layer not found');
17277
- return nameCount == 1 ? lyr.name : id;
17278
- }
17279
-
17280
- var TargetUtils = /*#__PURE__*/Object.freeze({
17281
- __proto__: null,
17282
- expandCommandTargets: expandCommandTargets,
17283
- findCommandTargets: findCommandTargets,
17284
- groupLayersByDataset: groupLayersByDataset,
17285
- findMatchingLayers: findMatchingLayers,
17286
- getLayerMatch: getLayerMatch,
17287
- countTargetLayers: countTargetLayers,
17288
- getLayerTargetId: getLayerTargetId
17289
- });
17290
-
17291
17327
  function readFirstChars(reader, n) {
17292
17328
  return bufferToString(reader.readSync(0, Math.min(n || 1000, reader.size())));
17293
17329
  }
@@ -17355,8 +17391,8 @@ ${svg}
17355
17391
  DEFAULT_CACHE_LEN = opts && opts.cacheSize || 0x1000000, // 16MB
17356
17392
  DEFAULT_BUFFER_LEN = opts && opts.bufferSize || 0x40000, // 256K
17357
17393
  fd, cacheOffs, cache, binArr;
17358
-
17359
- getStateVar('input_files').push(path); // bit of a kludge
17394
+ // kludge to let us check if input files are being overwritten
17395
+ (getStashedVar('input_files') || []).push(path);
17360
17396
 
17361
17397
  // Double the default size of the Buffer returned by readSync()
17362
17398
  this.expandBuffer = function() {
@@ -20163,6 +20199,10 @@ ${svg}
20163
20199
  DEFAULT: true,
20164
20200
  type: 'distance'
20165
20201
  })
20202
+ .option('endpoints', {
20203
+ describe: 'only snap together the endpoints of lines',
20204
+ type: 'flag'
20205
+ })
20166
20206
  .option('precision', {
20167
20207
  describe: 'round all coordinates to a given decimal precision (e.g. 0.000001)',
20168
20208
  type: 'number'
@@ -20704,16 +20744,16 @@ ${svg}
20704
20744
  var ifOpts = {
20705
20745
  expression: {
20706
20746
  DEFAULT: true,
20707
- describe: 'JS expression targeting a single layer'
20708
- },
20709
- empty: {
20710
- describe: 'run if layer is empty',
20711
- type: 'flag'
20712
- },
20713
- 'not-empty': {
20714
- describe: 'run if layer is not empty',
20715
- type: 'flag'
20747
+ describe: 'JS expression'
20716
20748
  },
20749
+ // empty: {
20750
+ // describe: 'run if layer is empty',
20751
+ // type: 'flag'
20752
+ // },
20753
+ // 'not-empty': {
20754
+ // describe: 'run if layer is not empty',
20755
+ // type: 'flag'
20756
+ // },
20717
20757
  layer: {
20718
20758
  describe: 'name or id of layer to test (default is current target)'
20719
20759
  },
@@ -23158,6 +23198,13 @@ ${svg}
23158
23198
  importJSON: importJSON
23159
23199
  });
23160
23200
 
23201
+ function importKML(str, opts) {
23202
+ var togeojson = require("@tmcw/togeojson");
23203
+ var Parser = typeof DOMParser == 'undefined' ? require("@xmldom/xmldom").DOMParser : DOMParser;
23204
+ var geojson = togeojson.kml(new Parser().parseFromString(str, "text/xml"));
23205
+ return importGeoJSON(geojson, opts || {});
23206
+ }
23207
+
23161
23208
  // Parse content of one or more input files and return a dataset
23162
23209
  // @obj: file data, indexed by file type
23163
23210
  // File data objects have two properties:
@@ -23194,6 +23241,11 @@ ${svg}
23194
23241
  fileFmt = 'prj';
23195
23242
  data = obj.prj;
23196
23243
  dataset = {layers: [], info: {prj: data.content}};
23244
+
23245
+ } else if (obj.kml) {
23246
+ fileFmt = 'kml';
23247
+ data = obj.kml;
23248
+ dataset = importKML(data.content, opts);
23197
23249
  }
23198
23250
 
23199
23251
  if (!dataset) {
@@ -23375,7 +23427,7 @@ ${svg}
23375
23427
  stop('Expected binary content, received a string');
23376
23428
  }
23377
23429
 
23378
- } else if (fileType) { // string type
23430
+ } else if (fileType) { // string type, e.g. kml, geojson
23379
23431
  content = cli.readFile(path, encoding || 'utf-8', cache);
23380
23432
 
23381
23433
  } else { // type can't be inferred from filename -- try reading as text
@@ -23664,6 +23716,49 @@ ${svg}
23664
23716
  getFormattedLayerList: getFormattedLayerList
23665
23717
  });
23666
23718
 
23719
+ function Job(catalog) {
23720
+ var currentCmd;
23721
+
23722
+ var job = {
23723
+ catalog: catalog || new Catalog(),
23724
+ defs: {},
23725
+ settings: {},
23726
+ input_files: []
23727
+ };
23728
+
23729
+ job.initSettings = function(o) {
23730
+ job.settings = o;
23731
+ stashVars(job, {});
23732
+ };
23733
+
23734
+ job.startCommand = function(cmd) {
23735
+ currentCmd = cmd;
23736
+ stashVars(job, cmd);
23737
+ };
23738
+
23739
+ // Rejected the idea of passing a command reference to compare with the initial command
23740
+ // (for error checking) ... the "-run" command inserts other commands before this call
23741
+ job.endCommand = function() {
23742
+ currentCmd = null;
23743
+ clearStash();
23744
+ };
23745
+
23746
+ job.resumeCommand = function() {
23747
+ stashVars(job, currentCmd);
23748
+ };
23749
+
23750
+ return job;
23751
+ }
23752
+
23753
+ function stashVars(job, cmd) {
23754
+ clearStash(); // prevent errors from overwriting stash
23755
+ stashVar('current_command', cmd.name);
23756
+ stashVar('DEBUG', job.settings.DEBUG || cmd.debug);
23757
+ stashVar('QUIET', job.settings.QUIET || cmd.quiet);
23758
+ stashVar('defs', job.defs);
23759
+ stashVar('input_files', job.input_files);
23760
+ }
23761
+
23667
23762
  const epsilon = 1.1102230246251565e-16;
23668
23763
  const splitter = 134217729;
23669
23764
  const resulterrbound = (3 + 8 * epsilon) * epsilon;
@@ -30407,7 +30502,7 @@ ${svg}
30407
30502
  if (isReservedName(opts.name)) {
30408
30503
  stop('"' + opts.name + '" is a reserved name');
30409
30504
  }
30410
- getStateVar('defs')[opts.name] = getColorizerFunction(opts);
30505
+ getStashedVar('defs')[opts.name] = getColorizerFunction(opts);
30411
30506
  };
30412
30507
 
30413
30508
  function isReservedName(name) {
@@ -30526,7 +30621,7 @@ ${svg}
30526
30621
 
30527
30622
  cmd.dashlines = function(lyr, dataset, opts) {
30528
30623
  var crs = getDatasetCRS(dataset);
30529
- var defs = getStateVar('defs');
30624
+ var defs = getStashedVar('defs');
30530
30625
  var exp = `this.geojson = splitFeature(this.geojson)`;
30531
30626
  requirePolylineLayer(lyr);
30532
30627
  defs.splitFeature = getSplitFeatureFunction(crs, opts);
@@ -30930,7 +31025,7 @@ ${svg}
30930
31025
  if (!opts.expression) {
30931
31026
  stop('Missing an assignment expression');
30932
31027
  }
30933
- var defs = getStateVar('defs');
31028
+ var defs = getStashedVar('defs');
30934
31029
  var compiled = compileFeatureExpression(opts.expression, {}, null, {no_warn: true});
30935
31030
  var result = compiled(null, defs);
30936
31031
  };
@@ -32202,8 +32297,6 @@ ${svg}
32202
32297
  if (opts && opts.where) {
32203
32298
  filter = compileValueExpression(opts.where, lyr, arcs);
32204
32299
  }
32205
- // 'defs' are now added to the context of all expressions
32206
- // compiled = compileFeatureExpression(exp, lyr, arcs, {context: getStateVar('defs')});
32207
32300
  compiled = compileFeatureExpression(exp, lyr, arcs, exprOpts);
32208
32301
  // call compiled expression with id of each record
32209
32302
  for (var i=0; i<n; i++) {
@@ -34612,48 +34705,60 @@ ${svg}
34612
34705
  };
34613
34706
  }
34614
34707
 
34615
- function resetControlFlow() {
34616
- setStateVar('control', null);
34708
+ function resetControlFlow(job) {
34709
+ job.control = null;
34617
34710
  }
34618
34711
 
34619
- function inControlBlock() {
34620
- var state = getState();
34621
- return !!state.inControlBlock;
34712
+ function inControlBlock(job) {
34713
+ return !!getState(job).inControlBlock;
34622
34714
  }
34623
34715
 
34624
- function enterActiveBranch() {
34625
- var state = getState();
34716
+ function enterActiveBranch(job) {
34717
+ var state = getState(job);
34626
34718
  state.inControlBlock = true;
34627
34719
  state.active = true;
34628
34720
  state.complete = true;
34629
34721
  }
34630
34722
 
34631
- function enterInactiveBranch() {
34632
- var state = getState();
34723
+ function enterInactiveBranch(job) {
34724
+ var state = getState(job);
34633
34725
  state.inControlBlock = true;
34634
34726
  state.active = false;
34635
34727
  }
34636
34728
 
34637
- function blockWasActive() {
34638
- return !!getState().complete;
34729
+ function blockWasActive(job) {
34730
+ return !!getState(job).complete;
34639
34731
  }
34640
34732
 
34641
- function inActiveBranch() {
34642
- return !!getState().active;
34733
+ function inActiveBranch(job) {
34734
+ return !!getState(job).active;
34643
34735
  }
34644
34736
 
34645
- function getState() {
34646
- var o = getStateVar('control') || setStateVar('control', {}) || getStateVar('control');
34647
- return o;
34737
+ function getState(job) {
34738
+ return job.control || (job.control = {});
34648
34739
  }
34649
34740
 
34650
- function compileIfCommandExpression(expr, catalog, targ, opts) {
34651
- var ctx = getLayerProxy(targ.layer, targ.dataset.arcs, opts);
34741
+ function compileIfCommandExpression(expr, catalog, opts) {
34742
+ var targetId = opts.layer || opts.target || null;
34743
+ var targets = catalog.findCommandTargets(targetId);
34744
+ var isSingle = targets.length == 1 && targets[0].layers.length == 1;
34745
+ if (targets.length === 0 && targetId) {
34746
+ stop('Layer not found:', targetId);
34747
+ }
34748
+ var ctx;
34749
+ if (isSingle) {
34750
+ ctx = getLayerProxy(targets[0].layers[0], targets[0].dataset.arcs, opts);
34751
+ } else {
34752
+ ctx = getNullLayerProxy(targets);
34753
+ }
34652
34754
  var exprOpts = Object.assign({returns: true}, opts);
34653
34755
  var func = compileExpressionToFunction(expr, exprOpts);
34654
34756
 
34655
- ctx.layer_exists = function(name) {
34656
- return !!catalog.findSingleLayer(name);
34757
+ // @geoType: optional geometry type (polygon, polyline, point, null);
34758
+ ctx.layer_exists = function(name, geoType) {
34759
+ var targets = catalog.findCommandTargets(name, geoType);
34760
+ if (targets.length === 0) return false;
34761
+ return true;
34657
34762
  };
34658
34763
 
34659
34764
  ctx.file_exists = function(file) {
@@ -34670,89 +34775,68 @@ ${svg}
34670
34775
  };
34671
34776
  }
34672
34777
 
34673
- function skipCommand(cmdName) {
34778
+ function skipCommand(cmdName, job) {
34674
34779
  // allow all control commands to run
34675
34780
  if (isControlFlowCommand(cmdName)) return false;
34676
- return inControlBlock() && !inActiveBranch();
34781
+ return inControlBlock(job) && !inActiveBranch(job);
34677
34782
  }
34678
34783
 
34679
- cmd.if = function(catalog, opts) {
34680
- if (inControlBlock()) {
34784
+ cmd.if = function(job, opts) {
34785
+ if (inControlBlock(job)) {
34681
34786
  stop('Nested -if commands are not supported.');
34682
34787
  }
34683
- evaluateIf(catalog, opts);
34788
+ evaluateIf(job, opts);
34684
34789
  };
34685
34790
 
34686
- cmd.elif = function(catalog, opts) {
34687
- if (!inControlBlock()) {
34791
+ cmd.elif = function(job, opts) {
34792
+ if (!inControlBlock(job)) {
34688
34793
  stop('-elif command must be preceded by an -if command.');
34689
34794
  }
34690
- evaluateIf(catalog, opts);
34795
+ evaluateIf(job, opts);
34691
34796
  };
34692
34797
 
34693
- cmd.else = function() {
34694
- if (!inControlBlock()) {
34798
+ cmd.else = function(job) {
34799
+ if (!inControlBlock(job)) {
34695
34800
  stop('-else command must be preceded by an -if command.');
34696
34801
  }
34697
- if (blockWasActive()) {
34698
- enterInactiveBranch();
34802
+ if (blockWasActive(job)) {
34803
+ enterInactiveBranch(job);
34699
34804
  } else {
34700
- enterActiveBranch();
34805
+ enterActiveBranch(job);
34701
34806
  }
34702
34807
  };
34703
34808
 
34704
- cmd.endif = function() {
34705
- if (!inControlBlock()) {
34809
+ cmd.endif = function(job) {
34810
+ if (!inControlBlock(job)) {
34706
34811
  stop('-endif command must be preceded by an -if command.');
34707
34812
  }
34708
- resetControlFlow();
34813
+ resetControlFlow(job);
34709
34814
  };
34710
34815
 
34711
34816
  function isControlFlowCommand(cmd) {
34712
34817
  return ['if','elif','else','endif'].includes(cmd);
34713
34818
  }
34714
34819
 
34715
- function testLayer(catalog, opts) {
34716
- var targ = getTargetLayer(catalog, opts);
34820
+ function test(catalog, opts) {
34821
+ // var targ = getTargetLayer(catalog, opts);
34717
34822
  if (opts.expression) {
34718
- return compileIfCommandExpression(opts.expression, catalog, targ, opts)();
34719
- }
34720
- if (opts.empty) {
34721
- return layerIsEmpty(targ.layer);
34722
- }
34723
- if (opts.not_empty) {
34724
- return !layerIsEmpty(targ.layer);
34823
+ return compileIfCommandExpression(opts.expression, catalog, opts)();
34725
34824
  }
34825
+ // if (opts.empty) {
34826
+ // return layerIsEmpty(targ.layer);
34827
+ // }
34828
+ // if (opts.not_empty) {
34829
+ // return !layerIsEmpty(targ.layer);
34830
+ // }
34726
34831
  return true;
34727
34832
  }
34728
34833
 
34729
- function evaluateIf(catalog, opts) {
34730
- if (!blockWasActive() && testLayer(catalog, opts)) {
34731
- enterActiveBranch();
34834
+ function evaluateIf(job, opts) {
34835
+ if (!blockWasActive(job) && test(job.catalog, opts)) {
34836
+ enterActiveBranch(job);
34732
34837
  } else {
34733
- enterInactiveBranch();
34734
- }
34735
- }
34736
-
34737
- // layerId: optional layer identifier
34738
- //
34739
- function getTargetLayer(catalog, opts) {
34740
- var layerId = opts.layer || opts.target;
34741
- var targets = catalog.findCommandTargets(layerId);
34742
- if (targets.length === 0) {
34743
- if (layerId) {
34744
- stop('Layer not found:', layerId);
34745
- } else {
34746
- stop('Missing a target layer.');
34747
- }
34748
- }
34749
- if (targets.length > 1 || targets[0].layers.length > 1) {
34750
- stop('Command requires a single target layer.');
34838
+ enterInactiveBranch(job);
34751
34839
  }
34752
- return {
34753
- layer: targets[0].layers[0],
34754
- dataset: targets[0].dataset
34755
- };
34756
34840
  }
34757
34841
 
34758
34842
  cmd.ignore = function(targetLayer, dataset, opts) {
@@ -34790,7 +34874,7 @@ ${svg}
34790
34874
  obj = content;
34791
34875
  }
34792
34876
 
34793
- utils.extend(getStateVar('defs'), obj);
34877
+ utils.extend(getStashedVar('defs'), obj);
34794
34878
  };
34795
34879
 
34796
34880
  var MAX_RULE_LEN = 50;
@@ -34849,7 +34933,7 @@ ${svg}
34849
34933
  null_shape_count: 0,
34850
34934
  null_data_count: lyr.data ? countNullRecords(lyr.data.getRecords()) : n
34851
34935
  };
34852
- if (lyr.shapes) {
34936
+ if (lyr.shapes && lyr.shapes.length > 0) {
34853
34937
  o.null_shape_count = countNullShapes(lyr.shapes);
34854
34938
  o.bbox =getLayerBounds(lyr, dataset.arcs).toArray();
34855
34939
  o.proj4 = getProjInfo(dataset);
@@ -37122,7 +37206,7 @@ ${svg}
37122
37206
  parseConsoleCommands: parseConsoleCommands
37123
37207
  });
37124
37208
 
37125
- cmd.run = function(targets, catalog, opts, cb) {
37209
+ cmd.run = function(job, targets, opts, cb) {
37126
37210
  var commandStr, commands;
37127
37211
  if (opts.include) {
37128
37212
  cmd.include({file: opts.include});
@@ -37133,7 +37217,7 @@ ${svg}
37133
37217
  commandStr = runGlobalExpression(opts.commands, targets);
37134
37218
  if (commandStr) {
37135
37219
  commands = parseCommands(commandStr);
37136
- runParsedCommands(commands, catalog, cb);
37220
+ runParsedCommands(commands, job, cb);
37137
37221
  } else {
37138
37222
  cb(null);
37139
37223
  }
@@ -37147,7 +37231,7 @@ ${svg}
37147
37231
  targetData = getRunCommandData(targets[0]);
37148
37232
  Object.defineProperty(ctx, 'target', {value: targetData});
37149
37233
  }
37150
- utils.extend(ctx, getStateVar('defs'));
37234
+ utils.extend(ctx, getStashedVar('defs'));
37151
37235
  try {
37152
37236
  output = Function('ctx', 'with(ctx) {return (' + expression + ');}').call({}, ctx);
37153
37237
  } catch(e) {
@@ -37166,7 +37250,7 @@ ${svg}
37166
37250
  }
37167
37251
 
37168
37252
  cmd.require = function(targets, opts) {
37169
- var defs = getStateVar('defs');
37253
+ var defs = getStashedVar('defs');
37170
37254
  var moduleFile, moduleName, mod;
37171
37255
  if (!opts.module) {
37172
37256
  stop("Missing module name or path to module");
@@ -38448,7 +38532,12 @@ ${svg}
38448
38532
  interval = getHighPrecisionSnapInterval(arcBounds.toArray());
38449
38533
  }
38450
38534
  arcs.flatten(); // bake in any simplification
38451
- if (interval > 0) {
38535
+ if (interval > 0 && opts.endpoints) {
38536
+ // snaps line endpoints together
38537
+ // TODO: also snap endpoints to line segments to remove undershoots and overshoots
38538
+ snapCount = snapEndpointsByInterval(arcs, interval);
38539
+ message(utils.format("Snapped %s endpoint%s", snapCount, utils.pluralSuffix(snapCount)));
38540
+ } else if (interval > 0) {
38452
38541
  snapCount = snapCoordsByInterval(arcs, interval);
38453
38542
  message(utils.format("Snapped %s point%s", snapCount, utils.pluralSuffix(snapCount)));
38454
38543
  }
@@ -39468,7 +39557,20 @@ ${svg}
39468
39557
  return [lyr1, lyr2];
39469
39558
  }
39470
39559
 
39471
- function runCommand(command, catalog, cb) {
39560
+ function commandAcceptsMultipleTargetDatasets(name) {
39561
+ return name == 'rotate' || name == 'info' || name == 'proj' ||
39562
+ name == 'drop' || name == 'target' || name == 'if' || name == 'elif' ||
39563
+ name == 'else' || name == 'endif';
39564
+ }
39565
+
39566
+ function commandAcceptsEmptyTarget(name) {
39567
+ return name == 'graticule' || name == 'i' || name == 'help' ||
39568
+ name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
39569
+ name == 'include' || name == 'print' || name == 'if' || name == 'elif' ||
39570
+ name == 'else' || name == 'endif';
39571
+ }
39572
+
39573
+ function runCommand(command, job, cb) {
39472
39574
  var name = command.name,
39473
39575
  opts = command.options,
39474
39576
  source,
@@ -39481,18 +39583,19 @@ ${svg}
39481
39583
  target,
39482
39584
  arcs;
39483
39585
 
39484
- if (skipCommand(name)) {
39586
+ if (skipCommand(name, job)) {
39485
39587
  return done(null);
39486
39588
  }
39487
39589
 
39488
- try { // catch errors from synchronous functions
39590
+ if (!job) job = new Job();
39591
+ job.startCommand(command);
39489
39592
 
39593
+ try { // catch errors from synchronous functions
39490
39594
  T$1.start();
39491
- if (!catalog) catalog = new Catalog();
39492
39595
 
39493
39596
  if (name == 'rename-layers') {
39494
39597
  // default target is all layers
39495
- targets = catalog.findCommandTargets(opts.target || '*');
39598
+ targets = job.catalog.findCommandTargets(opts.target || '*');
39496
39599
  targetLayers = targets.reduce(function(memo, obj) {
39497
39600
  return memo.concat(obj.layers);
39498
39601
  }, []);
@@ -39500,19 +39603,17 @@ ${svg}
39500
39603
  } else if (name == 'o') {
39501
39604
  // when combining GeoJSON layers, default is all layers
39502
39605
  // TODO: check that combine_layers is only used w/ GeoJSON output
39503
- targets = catalog.findCommandTargets(opts.target || opts.combine_layers && '*');
39606
+ targets = job.catalog.findCommandTargets(opts.target || opts.combine_layers && '*');
39504
39607
 
39505
- } else if (name == 'rotate' || name == 'info' || name == 'proj' || name == 'drop' || name == 'target') {
39506
- // these commands accept multiple target datasets
39507
- targets = catalog.findCommandTargets(opts.target);
39608
+ } else if (commandAcceptsMultipleTargetDatasets(name)) {
39609
+ targets = job.catalog.findCommandTargets(opts.target);
39508
39610
 
39509
39611
  } else {
39510
- targets = catalog.findCommandTargets(opts.target);
39511
-
39612
+ targets = job.catalog.findCommandTargets(opts.target);
39512
39613
  // special case to allow -merge-layers and -union to combine layers from multiple datasets
39513
39614
  // TODO: support multi-dataset targets for other commands
39514
39615
  if (targets.length > 1 && (name == 'merge-layers' || name == 'union')) {
39515
- targets = mergeCommandTargets(targets, catalog);
39616
+ targets = mergeCommandTargets(targets, job.catalog);
39516
39617
  }
39517
39618
 
39518
39619
  if (targets.length == 1) {
@@ -39520,7 +39621,7 @@ ${svg}
39520
39621
  arcs = targetDataset.arcs;
39521
39622
  targetLayers = targets[0].layers;
39522
39623
  // target= option sets default target
39523
- catalog.setDefaultTarget(targetLayers, targetDataset);
39624
+ job.catalog.setDefaultTarget(targetLayers, targetDataset);
39524
39625
 
39525
39626
  } else if (targets.length > 1) {
39526
39627
  stop("This command does not support targetting layers from different datasets");
@@ -39530,17 +39631,15 @@ ${svg}
39530
39631
  if (targets.length === 0) {
39531
39632
  if (opts.target) {
39532
39633
  stop(utils.format('Missing target: %s\nAvailable layers: %s',
39533
- opts.target, getFormattedLayerList(catalog)));
39634
+ opts.target, getFormattedLayerList(job.catalog)));
39534
39635
  }
39535
- if (!(name == 'graticule' || name == 'i' || name == 'help' ||
39536
- name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
39537
- name == 'include' || name == 'print')) {
39636
+ if (!commandAcceptsEmptyTarget(name)) {
39538
39637
  throw new UserError("No data is available");
39539
39638
  }
39540
39639
  }
39541
39640
 
39542
39641
  if (opts.source) {
39543
- source = findCommandSource(convertSourceName(opts.source, targets), catalog, opts);
39642
+ source = findCommandSource(convertSourceName(opts.source, targets), job.catalog, opts);
39544
39643
  }
39545
39644
 
39546
39645
  if (name == 'affine') {
@@ -39594,7 +39693,7 @@ ${svg}
39594
39693
  outputLayers = applyCommandToEachLayer(cmd.dots, targetLayers, arcs, opts);
39595
39694
 
39596
39695
  } else if (name == 'drop') {
39597
- cmd.drop2(catalog, targets, opts);
39696
+ cmd.drop2(job.catalog, targets, opts);
39598
39697
  // cmd.drop(catalog, targetLayers, targetDataset, opts);
39599
39698
 
39600
39699
  } else if (name == 'each') {
@@ -39631,33 +39730,33 @@ ${svg}
39631
39730
  applyCommandToEachLayer(cmd.filterSlivers, targetLayers, targetDataset, opts);
39632
39731
 
39633
39732
  } else if (name == 'frame') {
39634
- cmd.frame(catalog, source, opts);
39733
+ cmd.frame(job.catalog, source, opts);
39635
39734
 
39636
39735
  } else if (name == 'fuzzy-join') {
39637
39736
  applyCommandToEachLayer(cmd.fuzzyJoin, targetLayers, arcs, source, opts);
39638
39737
 
39639
39738
  } else if (name == 'graticule') {
39640
- catalog.addDataset(cmd.graticule(targetDataset, opts));
39739
+ job.catalog.addDataset(cmd.graticule(targetDataset, opts));
39641
39740
 
39642
39741
  } else if (name == 'help') {
39643
39742
  // placing this here to handle errors from invalid command names
39644
39743
  getOptionParser().printHelp(command.options.command);
39645
39744
 
39646
39745
  } else if (name == 'i') {
39647
- if (opts.replace) catalog = new Catalog();
39746
+ if (opts.replace) job.catalog = new Catalog(); // is this what we want?
39648
39747
  targetDataset = cmd.importFiles(command.options);
39649
39748
  if (targetDataset) {
39650
- catalog.addDataset(targetDataset);
39749
+ job.catalog.addDataset(targetDataset);
39651
39750
  outputLayers = targetDataset.layers; // kludge to allow layer naming below
39652
39751
  }
39653
39752
 
39654
39753
  } else if (name == 'if' || name == 'elif') {
39655
39754
  // target = findSingleTargetLayer(opts.layer, targets[0], catalog);
39656
39755
  // cmd[name](target.layer, target.dataset, opts);
39657
- cmd[name](catalog, opts);
39756
+ cmd[name](job, opts);
39658
39757
 
39659
39758
  } else if (name == 'else' || name == 'endif') {
39660
- cmd[name]();
39759
+ cmd[name](job);
39661
39760
 
39662
39761
  } else if (name == 'ignore') {
39663
39762
  applyCommandToEachLayer(cmd.ignore, targetLayers, targetDataset, opts);
@@ -39697,14 +39796,16 @@ ${svg}
39697
39796
  outputFiles = exportTargetLayers(targets, opts);
39698
39797
  if (opts.final) {
39699
39798
  // don't propagate data if output is final
39700
- catalog = null;
39799
+ //// catalog = null;
39800
+ job.catalog = new Catalog();
39701
39801
  }
39702
- return writeFiles(outputFiles, opts, done);
39802
+ writeFiles(outputFiles, opts, done);
39803
+ return; // async command
39703
39804
 
39704
39805
  } else if (name == 'point-grid') {
39705
39806
  outputLayers = [cmd.pointGrid(targetDataset, opts)];
39706
39807
  if (!targetDataset) {
39707
- catalog.addDataset({layers: outputLayers});
39808
+ job.catalog.addDataset({layers: outputLayers});
39708
39809
  }
39709
39810
 
39710
39811
  } else if (name == 'point-to-grid') {
@@ -39724,10 +39825,11 @@ ${svg}
39724
39825
 
39725
39826
  } else if (name == 'proj') {
39726
39827
  initProjLibrary(opts, function() {
39828
+ job.resumeCommand();
39727
39829
  var err = null;
39728
39830
  try {
39729
39831
  targets.forEach(function(targ) {
39730
- cmd.proj(targ.dataset, catalog, opts);
39832
+ cmd.proj(targ.dataset, job.catalog, opts);
39731
39833
  });
39732
39834
  } catch(e) {
39733
39835
  err = e;
@@ -39738,7 +39840,7 @@ ${svg}
39738
39840
 
39739
39841
  } else if (name == 'rectangle') {
39740
39842
  if (source || opts.bbox || targets.length === 0) {
39741
- catalog.addDataset(cmd.rectangle(source, opts));
39843
+ job.catalog.addDataset(cmd.rectangle(source, opts));
39742
39844
  } else {
39743
39845
  outputLayers = cmd.rectangle2(targets[0], opts);
39744
39846
  }
@@ -39750,7 +39852,7 @@ ${svg}
39750
39852
  applyCommandToEachLayer(cmd.renameFields, targetLayers, opts.fields);
39751
39853
 
39752
39854
  } else if (name == 'rename-layers') {
39753
- cmd.renameLayers(targetLayers, opts.names, catalog);
39855
+ cmd.renameLayers(targetLayers, opts.names, job.catalog);
39754
39856
 
39755
39857
  } else if (name == 'require') {
39756
39858
  cmd.require(targets, opts);
@@ -39761,14 +39863,14 @@ ${svg}
39761
39863
  });
39762
39864
 
39763
39865
  } else if (name == 'run') {
39764
- cmd.run(targets, catalog, opts, done);
39765
- return;
39866
+ cmd.run(job, targets, opts, done);
39867
+ return; // async command
39766
39868
 
39767
39869
  } else if (name == 'scalebar') {
39768
- cmd.scalebar(catalog, opts);
39870
+ cmd.scalebar(job.catalog, opts);
39769
39871
 
39770
39872
  } else if (name == 'shape') {
39771
- catalog.addDataset(cmd.shape(targetDataset, opts));
39873
+ job.catalog.addDataset(cmd.shape(targetDataset, opts));
39772
39874
 
39773
39875
  } else if (name == 'shapes') {
39774
39876
  outputLayers = applyCommandToEachLayer(cmd.shapes, targetLayers, targetDataset, opts);
@@ -39808,7 +39910,7 @@ ${svg}
39808
39910
  outputLayers = applyCommandToEachLayer(cmd.subdivideLayer, targetLayers, arcs, opts.expression);
39809
39911
 
39810
39912
  } else if (name == 'target') {
39811
- cmd.target(catalog, opts);
39913
+ cmd.target(job.catalog, opts);
39812
39914
 
39813
39915
  } else if (name == 'union') {
39814
39916
  outputLayers = cmd.union(targetLayers, targetDataset, opts);
@@ -39818,7 +39920,7 @@ ${svg}
39818
39920
 
39819
39921
  } else {
39820
39922
  // throws error if command is not registered
39821
- cmd.runExternalCommand(command, catalog);
39923
+ cmd.runExternalCommand(command, job.catalog);
39822
39924
  }
39823
39925
 
39824
39926
  // apply name parameter
@@ -39830,12 +39932,12 @@ ${svg}
39830
39932
  }
39831
39933
 
39832
39934
  if (outputDataset) {
39833
- catalog.addDataset(outputDataset); // also sets default target
39935
+ job.catalog.addDataset(outputDataset); // also sets default target
39834
39936
  outputLayers = outputDataset.layers;
39835
39937
  if (targetLayers && !opts.no_replace) {
39836
39938
  // remove target layers from target dataset
39837
39939
  targetLayers.forEach(function(lyr) {
39838
- catalog.deleteLayer(lyr, targetDataset);
39940
+ job.catalog.deleteLayer(lyr, targetDataset);
39839
39941
  });
39840
39942
  }
39841
39943
  } else if (outputLayers && targetDataset && outputLayers != targetDataset.layers) {
@@ -39858,7 +39960,7 @@ ${svg}
39858
39960
  }
39859
39961
 
39860
39962
  if (opts.apart) {
39861
- catalog.setDefaultTargets(splitApartLayers( targetDataset, outputLayers).map(function(dataset) {
39963
+ job.catalog.setDefaultTargets(splitApartLayers( targetDataset, outputLayers).map(function(dataset) {
39862
39964
  return {
39863
39965
  dataset: dataset,
39864
39966
  layers: dataset.layers.concat()
@@ -39866,9 +39968,8 @@ ${svg}
39866
39968
  }));
39867
39969
  } else {
39868
39970
  // use command output as new default target
39869
- catalog.setDefaultTarget(outputLayers, targetDataset);
39971
+ job.catalog.setDefaultTarget(outputLayers, targetDataset);
39870
39972
  }
39871
-
39872
39973
  }
39873
39974
 
39874
39975
  // delete arcs if no longer needed (e.g. after -points command)
@@ -39880,11 +39981,13 @@ ${svg}
39880
39981
  return done(e);
39881
39982
  }
39882
39983
 
39984
+ // non-erroring synchronous commands are done
39883
39985
  done(null);
39884
39986
 
39885
39987
  function done(err) {
39988
+ job.endCommand();
39886
39989
  verbose('-', T$1.stop());
39887
- cb(err, err ? null : catalog);
39990
+ cb(err, err ? null : job);
39888
39991
  }
39889
39992
  }
39890
39993
 
@@ -39894,7 +39997,6 @@ ${svg}
39894
39997
  });
39895
39998
  }
39896
39999
 
39897
-
39898
40000
  // Apply a command to an array of target layers
39899
40001
  function applyCommandToEachLayer(func, targetLayers) {
39900
40002
  var args = utils.toArray(arguments).slice(2);
@@ -40028,7 +40130,7 @@ ${svg}
40028
40130
  }
40029
40131
 
40030
40132
  // add options to -i -o -join -clip -erase commands to bypass file i/o
40031
- // TODO: find a less kludgy solution, e.g. storing input data using setStateVar()
40133
+ // TODO: find a less kludgy solution
40032
40134
  commands.forEach(function(cmd) {
40033
40135
  if (commandTakesFileInput(cmd.name) && inputObj) {
40034
40136
  cmd.options.input = inputObj;
@@ -40074,8 +40176,8 @@ ${svg}
40074
40176
 
40075
40177
  // TODO: rewrite tests and remove this function
40076
40178
  function testCommands(argv, done) {
40077
- _runCommands(argv, {}, function(err, catalog) {
40078
- var targets = catalog ? catalog.getDefaultTargets() : [];
40179
+ _runCommands(argv, {}, function(err, job) {
40180
+ var targets = job ? job.catalog.getDefaultTargets() : [];
40079
40181
  var output;
40080
40182
  if (!err && targets.length > 0) {
40081
40183
  // returns dataset for compatibility with some older tests
@@ -40087,15 +40189,12 @@ ${svg}
40087
40189
 
40088
40190
  // Execute a sequence of parsed commands
40089
40191
  // @commands Array of parsed commands
40090
- // @catalog: Optional Catalog object containing previously imported data
40192
+ // @job: Optional Job object containing previously imported data
40091
40193
  // @cb: function(<error>, <catalog>)
40092
40194
  //
40093
- function runParsedCommands(commands, catalog, cb) {
40094
- if (!catalog) {
40095
- cb = createAsyncContext(cb); // use new context when creating new catalog
40096
- catalog = new Catalog();
40097
- } else if (catalog instanceof Catalog === false) {
40098
- error("Changed in v0.4: runParsedCommands() takes a Catalog object");
40195
+ function runParsedCommands(commands, job, cb) {
40196
+ if (!job) {
40197
+ job = new Job();
40099
40198
  }
40100
40199
 
40101
40200
  if (!utils.isArray(commands)) {
@@ -40105,7 +40204,7 @@ ${svg}
40105
40204
  if (commands.length === 0) {
40106
40205
  return done(new UserError("No commands to run"));
40107
40206
  }
40108
- commands = readAndRemoveSettings(commands);
40207
+ commands = readAndRemoveSettings(job, commands);
40109
40208
  if (!runningInBrowser()) {
40110
40209
  printStartupMessages();
40111
40210
  }
@@ -40122,25 +40221,22 @@ ${svg}
40122
40221
  var groups = divideImportCommand(commands);
40123
40222
  if (groups.length == 1) {
40124
40223
  // run a simple sequence of commands (input files are not batched)
40125
- return runParsedCommands2(commands, catalog, done);
40224
+ return runParsedCommands2(commands, job, done);
40126
40225
  }
40127
40226
 
40128
40227
  // run duplicated commands (i.e. batch mode)
40129
- utils.reduceAsync(groups, catalog, nextGroup, done);
40228
+ utils.reduceAsync(groups, job, nextGroup, done);
40130
40229
 
40131
- function nextGroup(catalog, commands, next) {
40132
- runParsedCommands2(commands, catalog, function(err, catalog) {
40230
+ function nextGroup(job, commands, next) {
40231
+ runParsedCommands2(commands, job, function(err, job) {
40133
40232
  err = filterError(err);
40134
- next(err, catalog);
40233
+ next(err, job);
40135
40234
  });
40136
40235
  }
40137
40236
 
40138
- function done(err, catalog) {
40237
+ function done(err, job) {
40139
40238
  err = filterError(err);
40140
- cb(err, catalog);
40141
- setStateVar('current_command', null);
40142
- setStateVar('verbose', false);
40143
- setStateVar('debug', false);
40239
+ cb(err, job);
40144
40240
  }
40145
40241
  }
40146
40242
 
@@ -40152,16 +40248,13 @@ ${svg}
40152
40248
  return err;
40153
40249
  }
40154
40250
 
40155
- function runParsedCommands2(commands, catalog, cb) {
40251
+ function runParsedCommands2(commands, job, cb) {
40156
40252
  // resetting closes any unterminated -if blocks from a previous command sequence
40157
- resetControlFlow();
40158
- utils.reduceAsync(commands, catalog, nextCommand, cb);
40253
+ resetControlFlow(job);
40254
+ utils.reduceAsync(commands, job, nextCommand, cb);
40159
40255
 
40160
- function nextCommand(catalog, cmd, next) {
40161
- setStateVar('current_command', cmd.name); // for log msgs
40162
- setStateVar('verbose', !!cmd.options.verbose);
40163
- setStateVar('debug', !!cmd.options.debug);
40164
- runCommand(cmd, catalog, next);
40256
+ function nextCommand(job, cmd, next) {
40257
+ runCommand(cmd, job, next);
40165
40258
  }
40166
40259
  }
40167
40260
 
@@ -40207,19 +40300,22 @@ ${svg}
40207
40300
  }
40208
40301
 
40209
40302
  // Some settings use command syntax and are parsed as commands.
40210
- function readAndRemoveSettings(commands) {
40211
- return commands.filter(function(cmd) {
40303
+ function readAndRemoveSettings(job, commands) {
40304
+ var settings = {VERBOSE: false, QUIET: false, DEBUG: false};
40305
+ var filtered = commands.filter(function(cmd) {
40212
40306
  if (cmd.name == 'verbose') {
40213
- setStateVar('VERBOSE', true);
40307
+ settings.VERBOSE = true;
40214
40308
  } else if (cmd.name == 'quiet') {
40215
- setStateVar('QUIET', true);
40309
+ settings.QUIET = true;
40216
40310
  } else if (cmd.name == 'debug') {
40217
- setStateVar('DEBUG', true);
40311
+ settings.DEBUG = true;
40218
40312
  } else {
40219
40313
  return true;
40220
40314
  }
40221
40315
  return false;
40222
40316
  });
40317
+ job.initSettings(settings);
40318
+ return filtered;
40223
40319
  }
40224
40320
 
40225
40321
  // Run informational commands and remove them from the array of parsed commands
@@ -40571,6 +40667,7 @@ ${svg}
40571
40667
  // to maintain compatibility with tests and to expose (some of) them to the GUI.
40572
40668
 
40573
40669
  Object.assign(internal, {
40670
+ Job,
40574
40671
  Dbf,
40575
40672
  DbfReader,
40576
40673
  DouglasPeucker,
@@ -40694,6 +40791,7 @@ ${svg}
40694
40791
  SourceUtils,
40695
40792
  Split,
40696
40793
  State,
40794
+ Stash,
40697
40795
  Stringify,
40698
40796
  Svg,
40699
40797
  SvgProperties,