mapshaper 0.5.77 → 0.5.78

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/CHANGELOG.md CHANGED
@@ -1,6 +1,9 @@
1
+ v0.5.78
2
+ * Added support for reading and writing fixed-width text files.
3
+
1
4
  v0.5.77
2
5
  * Added -dashlines command (formerly -split-lines).
3
- * Added support for joining polyline and polyline layers using point-method
6
+ * Added support for joining polyline and polygon layers using point-method
4
7
 
5
8
  v0.5.76
6
9
  * Fixed bug in mapshaper-gui -q.
package/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.76";
3
+ var VERSION = "0.5.78";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -52,6 +52,7 @@
52
52
  get initializeArray () { return initializeArray; },
53
53
  get replaceArray () { return replaceArray; },
54
54
  get repeatString () { return repeatString; },
55
+ get splitLines () { return splitLines; },
55
56
  get pluralSuffix () { return pluralSuffix; },
56
57
  get endsWith () { return endsWith; },
57
58
  get lpad () { return lpad; },
@@ -61,7 +62,9 @@
61
62
  get rtrim () { return rtrim; },
62
63
  get addThousandsSep () { return addThousandsSep; },
63
64
  get numToStr () { return numToStr; },
64
- get formatNumber () { return formatNumber$2; },
65
+ get formatNumber () { return formatNumber; },
66
+ get formatIntlNumber () { return formatIntlNumber; },
67
+ get formatNumberForDisplay () { return formatNumberForDisplay; },
65
68
  get shuffle () { return shuffle; },
66
69
  get sortOn () { return sortOn; },
67
70
  get genericSort () { return genericSort; },
@@ -532,6 +535,10 @@
532
535
  return str;
533
536
  }
534
537
 
538
+ function splitLines(str) {
539
+ return str.split(/\r?\n/);
540
+ }
541
+
535
542
  function pluralSuffix(count) {
536
543
  return count != 1 ? 's' : '';
537
544
  }
@@ -584,7 +591,16 @@
584
591
  return decimals >= 0 ? num.toFixed(decimals) : String(num);
585
592
  }
586
593
 
587
- function formatNumber$2(num, decimals, nullStr, showPos) {
594
+ function formatNumber(val) {
595
+ return val + '';
596
+ }
597
+
598
+ function formatIntlNumber(val) {
599
+ var str = formatNumber(val);
600
+ return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
601
+ }
602
+
603
+ function formatNumberForDisplay(num, decimals, nullStr, showPos) {
588
604
  var fmt;
589
605
  if (isNaN(num)) {
590
606
  fmt = nullStr || '-';
@@ -862,7 +878,8 @@
862
878
  str = str.toUpperCase();
863
879
  }
864
880
  else if (isNumber) {
865
- str = numToStr(val, isInt ? 0 : decimals);
881
+ // str = formatNumberForDisplay(val, isInt ? 0 : decimals);
882
+ str = numToStr(val, decimals);
866
883
  if (str[0] == '-') {
867
884
  isNeg = true;
868
885
  str = str.substr(1);
@@ -10296,6 +10313,12 @@
10296
10313
  };
10297
10314
  }
10298
10315
 
10316
+ /*
10317
+
10318
+
10319
+
10320
+ */
10321
+
10299
10322
  // TODO: use polygon pathfinder shared code
10300
10323
  function collectPolylineArcs(ids, nodes, testArc, useArc) {
10301
10324
  var parts = [];
@@ -11865,7 +11888,6 @@
11865
11888
  });
11866
11889
 
11867
11890
  // Map positive or negative integer ids to non-negative integer ids
11868
-
11869
11891
  function IdLookupIndex(n, clearable) {
11870
11892
  var fwdIndex = new Int32Array(n);
11871
11893
  var revIndex = new Int32Array(n);
@@ -15565,6 +15587,139 @@ ${svg}
15565
15587
  }];
15566
15588
  }
15567
15589
 
15590
+ function exportRecordsAsFixedWidthString(fields, records, opts) {
15591
+ var rows = [], col;
15592
+ for (var i=0; i<fields.length; i++) {
15593
+ col = formatFixedWidthColumn(fields[i], records, opts);
15594
+ if (i === 0) {
15595
+ rows = col;
15596
+ } else for (var j=0; j<rows.length; j++) {
15597
+ rows[j] += ' ' + col[j];
15598
+ }
15599
+ }
15600
+ return rows.join('\n');
15601
+ }
15602
+
15603
+ function formatFixedWidthColumn(field, records, opts) {
15604
+ var arr = [],
15605
+ maxLen = field.length,
15606
+ n = records.length,
15607
+ i, val;
15608
+ arr.push(field);
15609
+ for (i=0; i<n; i++) {
15610
+ val = formatFixedWidthValue(records[i][field], opts);
15611
+ maxLen = Math.max(maxLen, val.length);
15612
+ arr.push(val);
15613
+ }
15614
+ for (i=0; i<arr.length; i++) {
15615
+ arr[i] = arr[i].padEnd(maxLen, ' ');
15616
+ }
15617
+ return arr;
15618
+ }
15619
+
15620
+ function formatFixedWidthValue(val, opts) {
15621
+ // TODO: remove duplication with mapshaper-delim-export.js
15622
+ var s;
15623
+ if (val == null) {
15624
+ s = '';
15625
+ } else if (utils.isString(val)) {
15626
+ s = val; // TODO: handle wide characters, newlines etc.
15627
+ } else if (utils.isNumber(val)) {
15628
+ s = opts.decimal_comma ? utils.formatIntlNumber(val) : utils.formatNumber(val);
15629
+ } else if (utils.isObject(val)) {
15630
+ s = JSON.stringify(val);
15631
+ } else {
15632
+ s = val + '';
15633
+ }
15634
+ return s;
15635
+ }
15636
+
15637
+
15638
+ function readFixedWidthRecords(reader, opts) {
15639
+ var str = reader.toString(opts.encoding || 'ascii');
15640
+ return readFixedWidthRecordsFromString(str, opts);
15641
+ }
15642
+
15643
+ function readFixedWidthRecordsFromString(str, ops) {
15644
+ var fields = parseFixedWidthInfo(str.substring(0, 2000));
15645
+ if (!fields) return [];
15646
+ var lines = utils.splitLines(str);
15647
+ if (lines[lines.length - 1] === '') lines.pop(); // handle newline at end of string
15648
+ var records = [];
15649
+ for (var i=1; i<lines.length; i++) {
15650
+ records.push(parseFixedWidthLine(lines[i], fields));
15651
+ }
15652
+ return records;
15653
+ }
15654
+
15655
+ function parseFixedWidthInfo(sample) {
15656
+ var lines = utils.splitLines(sample);
15657
+ if (lines.length > 2) lines.pop(); // remove possible partial line
15658
+ var n = getMaxLineLength(lines);
15659
+ var headerLine = lines[0];
15660
+ var colInfo = [];
15661
+ var colStart = 0;
15662
+ var inContent = false;
15663
+ var inHeader = false;
15664
+ var isContentChar, isHeaderChar, isColStart, colEnd;
15665
+ for (var i=0; i<=n; i++) {
15666
+ isHeaderChar = testContentChar(headerLine, i);
15667
+ isContentChar = !testEmptyCol(lines, i);
15668
+ isColStart = isHeaderChar && !inHeader;
15669
+ if (isColStart && inContent) {
15670
+ // all lines should have a space char in the position right before a header starts
15671
+ return null;
15672
+ }
15673
+ if (i == n || i > 0 && isColStart) {
15674
+ colEnd = i == n ? undefined : i-1;
15675
+ colInfo.push({
15676
+ name: readValue$1(headerLine, colStart, colEnd),
15677
+ end: colEnd,
15678
+ start: colStart
15679
+ });
15680
+ colStart = i;
15681
+ }
15682
+ inContent = isContentChar;
15683
+ inHeader = isHeaderChar;
15684
+ }
15685
+ return colInfo.length > 0 ? colInfo : null;
15686
+ }
15687
+
15688
+ function getMaxLineLength(lines) {
15689
+ var max = 0;
15690
+ for (var i=0; i<lines.length; i++) {
15691
+ max = Math.max(max, lines[i].length);
15692
+ }
15693
+ return max;
15694
+ }
15695
+
15696
+ function readValue$1(line, start, end) {
15697
+ return line.substring(start, end).trim();
15698
+ }
15699
+
15700
+ function parseFixedWidthLine(str, fields) {
15701
+ var obj = {}, field;
15702
+ for (var i=0; i<fields.length; i++) {
15703
+ field = fields[i];
15704
+ obj[field.name] = readValue$1(str, field.start, field.end);
15705
+ }
15706
+ return obj;
15707
+ }
15708
+
15709
+ function testContentChar(str, i) {
15710
+ return i < str.length && str[i] !== ' ';
15711
+ }
15712
+
15713
+ // return true iff all samples are blank at index i
15714
+ function testEmptyCol(samples, i) {
15715
+ var line;
15716
+ for (var j=0; j<samples.length; j++) {
15717
+ line = samples[j];
15718
+ if (testContentChar(line, i)) return false;
15719
+ }
15720
+ return true;
15721
+ }
15722
+
15568
15723
  // Generate output content from a dataset object
15569
15724
  function exportDelim(dataset, opts) {
15570
15725
  var delim = getExportDelimiter(dataset.info, opts),
@@ -15586,6 +15741,9 @@ ${svg}
15586
15741
  var encoding = opts.encoding || 'utf8';
15587
15742
  var records = lyr.data.getRecords();
15588
15743
  var fields = findFieldNames(records, opts.field_order);
15744
+ if (delim == ' ') {
15745
+ return exportRecordsAsFixedWidthString(fields, records, opts);
15746
+ }
15589
15747
  var formatRow = getDelimRowFormatter(fields, delim, opts);
15590
15748
  // exporting utf8 and ascii text as string by default (for now)
15591
15749
  var exportAsString = encodingIsUtf8(encoding) && !opts.to_buffer &&
@@ -15643,15 +15801,6 @@ ${svg}
15643
15801
  };
15644
15802
  }
15645
15803
 
15646
- function formatNumber$1(val) {
15647
- return val + '';
15648
- }
15649
-
15650
- function formatIntlNumber(val) {
15651
- var str = formatNumber$1(val);
15652
- return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
15653
- }
15654
-
15655
15804
  function getDelimValueFormatter(delim, opts) {
15656
15805
  var dquoteRxp = new RegExp('["\n\r' + delim + ']');
15657
15806
  var decimalComma = opts && opts.decimal_comma || false;
@@ -15668,7 +15817,7 @@ ${svg}
15668
15817
  } else if (utils.isString(val)) {
15669
15818
  s = formatString(val);
15670
15819
  } else if (utils.isNumber(val)) {
15671
- s = decimalComma ? formatIntlNumber(val) : formatNumber$1(val);
15820
+ s = decimalComma ? utils.formatIntlNumber(val) : utils.formatNumber(val);
15672
15821
  } else if (utils.isObject(val)) {
15673
15822
  s = formatString(JSON.stringify(val));
15674
15823
  } else {
@@ -15711,8 +15860,6 @@ ${svg}
15711
15860
  __proto__: null,
15712
15861
  exportDelim: exportDelim,
15713
15862
  exportLayerAsDSV: exportLayerAsDSV,
15714
- formatNumber: formatNumber$1,
15715
- formatIntlNumber: formatIntlNumber,
15716
15863
  getDelimValueFormatter: getDelimValueFormatter
15717
15864
  });
15718
15865
 
@@ -17076,8 +17223,9 @@ ${svg}
17076
17223
  //
17077
17224
  // TODO: confirm compatibility with all supported encodings
17078
17225
  function readDelimRecords(reader, delim, optsArg) {
17226
+ var opts = optsArg || {};
17227
+ if (delim == ' ') return readFixedWidthRecords(reader, opts);
17079
17228
  var reader2 = new Reader2(reader),
17080
- opts = optsArg || {},
17081
17229
  headerStr = readLinesAsString(reader2, getDelimHeaderLines(opts), opts.encoding),
17082
17230
  header = parseDelimHeaderSection(headerStr, delim, opts),
17083
17231
  convertRowArr = getRowConverter(header.import_fields),
@@ -17100,6 +17248,7 @@ ${svg}
17100
17248
  // for delimiter characters and newlines. Input size is limited by the maximum
17101
17249
  // string size.
17102
17250
  function readDelimRecordsFromString(str, delim, opts) {
17251
+ if (delim == ' ') return readFixedWidthRecordsFromString(str, opts);
17103
17252
  var header = parseDelimHeaderSection(str, delim, opts);
17104
17253
  if (header.import_fields.length === 0 || !header.remainder) return [];
17105
17254
  var convert = getRowConverter(header.import_fields);
@@ -17503,7 +17652,7 @@ ${svg}
17503
17652
  };
17504
17653
  }
17505
17654
 
17506
- var supportedDelimiters = ['|', '\t', ',', ';'];
17655
+ var supportedDelimiters = ['|', '\t', ',', ';', ' '];
17507
17656
 
17508
17657
  function isSupportedDelimiter(d) {
17509
17658
  return utils.contains(supportedDelimiters, d);
@@ -17864,9 +18013,21 @@ ${svg}
17864
18013
  }
17865
18014
 
17866
18015
  function cleanArgv(argv) {
17867
- argv = argv.map(function(s) {return s.trim();}); // trim whitespace
18016
+ // Note: original trim caused some quoted spaces to be removed
18017
+ // (e.g. bash shell seems to convert [delimiter=" "] to [delimiter= ],
18018
+ // which then got trimmed to [delimiter=] below)
18019
+ //// argv = argv.map(function(s) {return s.trim();}); // trim whitespace
18020
+
18021
+ // Updated: don't trim space from tokens like [delimeter= ]
18022
+ argv = argv.map(function(s) {
18023
+ if (!/= $/.test(s)) {
18024
+ s = s.trimEnd();
18025
+ }
18026
+ s = s.trimStart();
18027
+ return s;
18028
+ });
17868
18029
  argv = argv.filter(function(s) {return s !== '';}); // remove empty tokens
17869
- // removing trimQuotes() call... now, strings like 'name="Meg"' will no longer
18030
+ // Note: removing trimQuotes() call... now, strings like 'name="Meg"' will no longer
17870
18031
  // be parsed the same way as name=Meg and name="Meg"
17871
18032
  //// argv = argv.map(utils.trimQuotes); // remove one level of single or dbl quotes
17872
18033
  return argv;
@@ -19191,6 +19352,10 @@ ${svg}
19191
19352
  .option('keep-shapes', {
19192
19353
  type: 'flag'
19193
19354
  })
19355
+ .option('ids', {
19356
+ // describe: 'filter on a list of feature ids',
19357
+ type: 'numbers'
19358
+ })
19194
19359
  .option('cleanup', {type: 'flag'}) // TODO: document
19195
19360
  .option('name', nameOpt)
19196
19361
  .option('target', targetOpt)
@@ -19759,6 +19924,11 @@ ${svg}
19759
19924
  DEFAULT: true,
19760
19925
  describe: 'expression or field for grouping features and naming split layers'
19761
19926
  })
19927
+ .option('ids', {
19928
+ // used by gui history to split on selected features
19929
+ // describe: 'split on a list of feature ids',
19930
+ type: 'numbers'
19931
+ })
19762
19932
  .option('apart', {
19763
19933
  describe: 'save output layers to independent datasets',
19764
19934
  type: 'flag'
@@ -31672,6 +31842,10 @@ ${svg}
31672
31842
  filter = compileValueExpression(opts.expression, lyr, arcs);
31673
31843
  }
31674
31844
 
31845
+ if (opts.ids) {
31846
+ filter = combineFilters(filter, getIdFilter(opts.ids));
31847
+ }
31848
+
31675
31849
  if (opts.remove_empty) {
31676
31850
  filter = combineFilters(filter, getNullGeometryFilter(lyr, arcs));
31677
31851
  }
@@ -31730,6 +31904,13 @@ ${svg}
31730
31904
  lyr.data = filteredRecords ? new DataTable(filteredRecords) : null;
31731
31905
  }
31732
31906
 
31907
+ function getIdFilter(ids) {
31908
+ var set = new Set(ids);
31909
+ return function(i) {
31910
+ return set.has(i);
31911
+ };
31912
+ }
31913
+
31733
31914
  function getNullGeometryFilter(lyr, arcs) {
31734
31915
  var shapes = lyr.shapes;
31735
31916
  if (lyr.geometry_type == 'polygon') {
@@ -34002,10 +34183,6 @@ ${svg}
34002
34183
 
34003
34184
 
34004
34185
 
34005
- function formatNumber(val) {
34006
- return val + '';
34007
- }
34008
-
34009
34186
  function maxChars(arr) {
34010
34187
  return arr.reduce(function(memo, str) {
34011
34188
  var w = stringDisplayWidth(str);
@@ -34029,14 +34206,14 @@ ${svg}
34029
34206
  }
34030
34207
 
34031
34208
  function countIntegralChars(val) {
34032
- return utils.isNumber(val) ? (formatNumber(val) + '.').indexOf('.') : 0;
34209
+ return utils.isNumber(val) ? (utils.formatNumber(val) + '.').indexOf('.') : 0;
34033
34210
  }
34034
34211
 
34035
34212
  function formatTableValue(val, integralChars) {
34036
34213
  var str;
34037
34214
  if (utils.isNumber(val)) {
34038
34215
  str = utils.lpad("", integralChars - countIntegralChars(val), ' ') +
34039
- formatNumber(val);
34216
+ utils.formatNumber(val);
34040
34217
  } else if (utils.isString(val)) {
34041
34218
  str = formatString(val);
34042
34219
  } else if (utils.isDate(val)) {
@@ -37408,13 +37585,20 @@ ${svg}
37408
37585
 
37409
37586
  // @expression: optional field name or expression
37410
37587
  //
37411
- cmd.splitLayer = function(src, expression, opts) {
37412
- var lyr0 = opts && opts.no_replace ? copyLayer(src) : src,
37588
+ cmd.splitLayer = function(src, expression, optsArg) {
37589
+ var opts = optsArg || {},
37590
+ lyr0 = opts.no_replace ? copyLayer(src) : src,
37413
37591
  properties = lyr0.data ? lyr0.data.getRecords() : null,
37414
37592
  shapes = lyr0.shapes,
37415
37593
  index = {},
37416
37594
  splitLayers = [],
37417
- namer = getSplitNameFunction(lyr0, expression);
37595
+ namer;
37596
+
37597
+ if (opts.ids) {
37598
+ namer = getIdSplitFunction(opts.ids);
37599
+ } else {
37600
+ namer = getSplitNameFunction(lyr0, expression);
37601
+ }
37418
37602
 
37419
37603
  // if (splitField) {
37420
37604
  // internal.requireDataField(lyr0, splitField);
@@ -37446,15 +37630,24 @@ ${svg}
37446
37630
  return splitLayers;
37447
37631
  };
37448
37632
 
37633
+ function getIdSplitFunction(ids) {
37634
+ var set = new Set(ids);
37635
+ return function(i) {
37636
+ return set.has(i) ? '1' : '2';
37637
+ };
37638
+ }
37639
+
37640
+ function getDefaultSplitFunction(lyr) {
37641
+ // if not splitting on an expression and layer is unnamed, name split-apart layers
37642
+ // like: split-1, split-2, ...
37643
+ return function(i) {
37644
+ return (lyr && lyr.name || 'split') + '-' + (i + 1);
37645
+ };
37646
+ }
37647
+
37449
37648
  function getSplitNameFunction(lyr, exp) {
37450
37649
  var compiled;
37451
- if (!exp) {
37452
- // if not splitting on an expression and layer is unnamed, name split-apart layers
37453
- // like: split-1, split-2, ...
37454
- return function(i) {
37455
- return (lyr && lyr.name || 'split') + '-' + (i + 1);
37456
- };
37457
- }
37650
+ if (!exp) return getDefaultSplitFunction(lyr);
37458
37651
  lyr = {name: lyr.name, data: lyr.data}; // remove shape info
37459
37652
  compiled = compileValueExpression(exp, lyr, null);
37460
37653
  return function(i) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.5.77",
3
+ "version": "0.5.78",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
@@ -49,7 +49,7 @@
49
49
  "geokdbush": "^1.1.0",
50
50
  "iconv-lite": "0.4.24",
51
51
  "kdbush": "^3.0.0",
52
- "mproj": "0.0.34",
52
+ "mproj": "0.0.35",
53
53
  "opn": "^5.3.0",
54
54
  "rw": "~1.3.3",
55
55
  "sync-request": "5.0.0",
@@ -1751,7 +1751,7 @@
1751
1751
  else if (pct < 0.01) decimals = 3;
1752
1752
  else if (pct < 1) decimals = 2;
1753
1753
  else if (pct < 100) decimals = 1;
1754
- return utils.formatNumber(pct, decimals) + "%";
1754
+ return utils.formatNumberForDisplay(pct, decimals) + "%";
1755
1755
  });
1756
1756
 
1757
1757
  text.parser(function(s) {
@@ -5327,6 +5327,10 @@
5327
5327
  hit.clearSelection();
5328
5328
  }
5329
5329
 
5330
+ function getIdsOpt() {
5331
+ return hit.getSelectionIds().join(',');
5332
+ }
5333
+
5330
5334
  hit.on('change', function(e) {
5331
5335
  if (e.mode != 'selection') return;
5332
5336
  var ids = hit.getSelectionIds();
@@ -5341,18 +5345,17 @@
5341
5345
  });
5342
5346
 
5343
5347
  new SimpleButton(popup.findChild('.delete-btn')).on('click', function() {
5344
- var cmd = '-filter "$$set.has(this.id) === false"';
5348
+ var cmd = '-filter invert ids=' + getIdsOpt();
5345
5349
  runCommand(cmd);
5346
5350
  });
5347
5351
 
5348
5352
  new SimpleButton(popup.findChild('.filter-btn')).on('click', function() {
5349
-
5350
- var cmd = '-filter "$$set.has(this.id)"';
5353
+ var cmd = '-filter ids=' + getIdsOpt();
5351
5354
  runCommand(cmd);
5352
5355
  });
5353
5356
 
5354
5357
  new SimpleButton(popup.findChild('.split-btn')).on('click', function() {
5355
- var cmd = '-each "split_id = $$set.has(this.id) ? \'1\' : \'2\'" -split split_id';
5358
+ var cmd = '-split ids=' + getIdsOpt();
5356
5359
  runCommand(cmd);
5357
5360
  });
5358
5361
 
@@ -5361,10 +5364,6 @@
5361
5364
  });
5362
5365
 
5363
5366
  function runCommand(cmd) {
5364
- // var defs = internal.getStateVar('defs');
5365
- // defs.$$selection = utils.arrayToIndex(hit.getSelectionIds());
5366
- var ids = JSON.stringify(hit.getSelectionIds());
5367
- cmd = `-define "$$set = new Set(${ids})" ${cmd} -define "delete $$set"`;
5368
5367
  popup.hide();
5369
5368
  if (gui.console) gui.console.runMapshaperCommands(cmd, function(err) {
5370
5369
  reset();
package/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.76";
3
+ var VERSION = "0.5.78";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -52,6 +52,7 @@
52
52
  get initializeArray () { return initializeArray; },
53
53
  get replaceArray () { return replaceArray; },
54
54
  get repeatString () { return repeatString; },
55
+ get splitLines () { return splitLines; },
55
56
  get pluralSuffix () { return pluralSuffix; },
56
57
  get endsWith () { return endsWith; },
57
58
  get lpad () { return lpad; },
@@ -61,7 +62,9 @@
61
62
  get rtrim () { return rtrim; },
62
63
  get addThousandsSep () { return addThousandsSep; },
63
64
  get numToStr () { return numToStr; },
64
- get formatNumber () { return formatNumber$2; },
65
+ get formatNumber () { return formatNumber; },
66
+ get formatIntlNumber () { return formatIntlNumber; },
67
+ get formatNumberForDisplay () { return formatNumberForDisplay; },
65
68
  get shuffle () { return shuffle; },
66
69
  get sortOn () { return sortOn; },
67
70
  get genericSort () { return genericSort; },
@@ -532,6 +535,10 @@
532
535
  return str;
533
536
  }
534
537
 
538
+ function splitLines(str) {
539
+ return str.split(/\r?\n/);
540
+ }
541
+
535
542
  function pluralSuffix(count) {
536
543
  return count != 1 ? 's' : '';
537
544
  }
@@ -584,7 +591,16 @@
584
591
  return decimals >= 0 ? num.toFixed(decimals) : String(num);
585
592
  }
586
593
 
587
- function formatNumber$2(num, decimals, nullStr, showPos) {
594
+ function formatNumber(val) {
595
+ return val + '';
596
+ }
597
+
598
+ function formatIntlNumber(val) {
599
+ var str = formatNumber(val);
600
+ return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
601
+ }
602
+
603
+ function formatNumberForDisplay(num, decimals, nullStr, showPos) {
588
604
  var fmt;
589
605
  if (isNaN(num)) {
590
606
  fmt = nullStr || '-';
@@ -862,7 +878,8 @@
862
878
  str = str.toUpperCase();
863
879
  }
864
880
  else if (isNumber) {
865
- str = numToStr(val, isInt ? 0 : decimals);
881
+ // str = formatNumberForDisplay(val, isInt ? 0 : decimals);
882
+ str = numToStr(val, decimals);
866
883
  if (str[0] == '-') {
867
884
  isNeg = true;
868
885
  str = str.substr(1);
@@ -10296,6 +10313,12 @@
10296
10313
  };
10297
10314
  }
10298
10315
 
10316
+ /*
10317
+
10318
+
10319
+
10320
+ */
10321
+
10299
10322
  // TODO: use polygon pathfinder shared code
10300
10323
  function collectPolylineArcs(ids, nodes, testArc, useArc) {
10301
10324
  var parts = [];
@@ -11865,7 +11888,6 @@
11865
11888
  });
11866
11889
 
11867
11890
  // Map positive or negative integer ids to non-negative integer ids
11868
-
11869
11891
  function IdLookupIndex(n, clearable) {
11870
11892
  var fwdIndex = new Int32Array(n);
11871
11893
  var revIndex = new Int32Array(n);
@@ -15565,6 +15587,139 @@ ${svg}
15565
15587
  }];
15566
15588
  }
15567
15589
 
15590
+ function exportRecordsAsFixedWidthString(fields, records, opts) {
15591
+ var rows = [], col;
15592
+ for (var i=0; i<fields.length; i++) {
15593
+ col = formatFixedWidthColumn(fields[i], records, opts);
15594
+ if (i === 0) {
15595
+ rows = col;
15596
+ } else for (var j=0; j<rows.length; j++) {
15597
+ rows[j] += ' ' + col[j];
15598
+ }
15599
+ }
15600
+ return rows.join('\n');
15601
+ }
15602
+
15603
+ function formatFixedWidthColumn(field, records, opts) {
15604
+ var arr = [],
15605
+ maxLen = field.length,
15606
+ n = records.length,
15607
+ i, val;
15608
+ arr.push(field);
15609
+ for (i=0; i<n; i++) {
15610
+ val = formatFixedWidthValue(records[i][field], opts);
15611
+ maxLen = Math.max(maxLen, val.length);
15612
+ arr.push(val);
15613
+ }
15614
+ for (i=0; i<arr.length; i++) {
15615
+ arr[i] = arr[i].padEnd(maxLen, ' ');
15616
+ }
15617
+ return arr;
15618
+ }
15619
+
15620
+ function formatFixedWidthValue(val, opts) {
15621
+ // TODO: remove duplication with mapshaper-delim-export.js
15622
+ var s;
15623
+ if (val == null) {
15624
+ s = '';
15625
+ } else if (utils.isString(val)) {
15626
+ s = val; // TODO: handle wide characters, newlines etc.
15627
+ } else if (utils.isNumber(val)) {
15628
+ s = opts.decimal_comma ? utils.formatIntlNumber(val) : utils.formatNumber(val);
15629
+ } else if (utils.isObject(val)) {
15630
+ s = JSON.stringify(val);
15631
+ } else {
15632
+ s = val + '';
15633
+ }
15634
+ return s;
15635
+ }
15636
+
15637
+
15638
+ function readFixedWidthRecords(reader, opts) {
15639
+ var str = reader.toString(opts.encoding || 'ascii');
15640
+ return readFixedWidthRecordsFromString(str, opts);
15641
+ }
15642
+
15643
+ function readFixedWidthRecordsFromString(str, ops) {
15644
+ var fields = parseFixedWidthInfo(str.substring(0, 2000));
15645
+ if (!fields) return [];
15646
+ var lines = utils.splitLines(str);
15647
+ if (lines[lines.length - 1] === '') lines.pop(); // handle newline at end of string
15648
+ var records = [];
15649
+ for (var i=1; i<lines.length; i++) {
15650
+ records.push(parseFixedWidthLine(lines[i], fields));
15651
+ }
15652
+ return records;
15653
+ }
15654
+
15655
+ function parseFixedWidthInfo(sample) {
15656
+ var lines = utils.splitLines(sample);
15657
+ if (lines.length > 2) lines.pop(); // remove possible partial line
15658
+ var n = getMaxLineLength(lines);
15659
+ var headerLine = lines[0];
15660
+ var colInfo = [];
15661
+ var colStart = 0;
15662
+ var inContent = false;
15663
+ var inHeader = false;
15664
+ var isContentChar, isHeaderChar, isColStart, colEnd;
15665
+ for (var i=0; i<=n; i++) {
15666
+ isHeaderChar = testContentChar(headerLine, i);
15667
+ isContentChar = !testEmptyCol(lines, i);
15668
+ isColStart = isHeaderChar && !inHeader;
15669
+ if (isColStart && inContent) {
15670
+ // all lines should have a space char in the position right before a header starts
15671
+ return null;
15672
+ }
15673
+ if (i == n || i > 0 && isColStart) {
15674
+ colEnd = i == n ? undefined : i-1;
15675
+ colInfo.push({
15676
+ name: readValue$1(headerLine, colStart, colEnd),
15677
+ end: colEnd,
15678
+ start: colStart
15679
+ });
15680
+ colStart = i;
15681
+ }
15682
+ inContent = isContentChar;
15683
+ inHeader = isHeaderChar;
15684
+ }
15685
+ return colInfo.length > 0 ? colInfo : null;
15686
+ }
15687
+
15688
+ function getMaxLineLength(lines) {
15689
+ var max = 0;
15690
+ for (var i=0; i<lines.length; i++) {
15691
+ max = Math.max(max, lines[i].length);
15692
+ }
15693
+ return max;
15694
+ }
15695
+
15696
+ function readValue$1(line, start, end) {
15697
+ return line.substring(start, end).trim();
15698
+ }
15699
+
15700
+ function parseFixedWidthLine(str, fields) {
15701
+ var obj = {}, field;
15702
+ for (var i=0; i<fields.length; i++) {
15703
+ field = fields[i];
15704
+ obj[field.name] = readValue$1(str, field.start, field.end);
15705
+ }
15706
+ return obj;
15707
+ }
15708
+
15709
+ function testContentChar(str, i) {
15710
+ return i < str.length && str[i] !== ' ';
15711
+ }
15712
+
15713
+ // return true iff all samples are blank at index i
15714
+ function testEmptyCol(samples, i) {
15715
+ var line;
15716
+ for (var j=0; j<samples.length; j++) {
15717
+ line = samples[j];
15718
+ if (testContentChar(line, i)) return false;
15719
+ }
15720
+ return true;
15721
+ }
15722
+
15568
15723
  // Generate output content from a dataset object
15569
15724
  function exportDelim(dataset, opts) {
15570
15725
  var delim = getExportDelimiter(dataset.info, opts),
@@ -15586,6 +15741,9 @@ ${svg}
15586
15741
  var encoding = opts.encoding || 'utf8';
15587
15742
  var records = lyr.data.getRecords();
15588
15743
  var fields = findFieldNames(records, opts.field_order);
15744
+ if (delim == ' ') {
15745
+ return exportRecordsAsFixedWidthString(fields, records, opts);
15746
+ }
15589
15747
  var formatRow = getDelimRowFormatter(fields, delim, opts);
15590
15748
  // exporting utf8 and ascii text as string by default (for now)
15591
15749
  var exportAsString = encodingIsUtf8(encoding) && !opts.to_buffer &&
@@ -15643,15 +15801,6 @@ ${svg}
15643
15801
  };
15644
15802
  }
15645
15803
 
15646
- function formatNumber$1(val) {
15647
- return val + '';
15648
- }
15649
-
15650
- function formatIntlNumber(val) {
15651
- var str = formatNumber$1(val);
15652
- return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
15653
- }
15654
-
15655
15804
  function getDelimValueFormatter(delim, opts) {
15656
15805
  var dquoteRxp = new RegExp('["\n\r' + delim + ']');
15657
15806
  var decimalComma = opts && opts.decimal_comma || false;
@@ -15668,7 +15817,7 @@ ${svg}
15668
15817
  } else if (utils.isString(val)) {
15669
15818
  s = formatString(val);
15670
15819
  } else if (utils.isNumber(val)) {
15671
- s = decimalComma ? formatIntlNumber(val) : formatNumber$1(val);
15820
+ s = decimalComma ? utils.formatIntlNumber(val) : utils.formatNumber(val);
15672
15821
  } else if (utils.isObject(val)) {
15673
15822
  s = formatString(JSON.stringify(val));
15674
15823
  } else {
@@ -15711,8 +15860,6 @@ ${svg}
15711
15860
  __proto__: null,
15712
15861
  exportDelim: exportDelim,
15713
15862
  exportLayerAsDSV: exportLayerAsDSV,
15714
- formatNumber: formatNumber$1,
15715
- formatIntlNumber: formatIntlNumber,
15716
15863
  getDelimValueFormatter: getDelimValueFormatter
15717
15864
  });
15718
15865
 
@@ -17076,8 +17223,9 @@ ${svg}
17076
17223
  //
17077
17224
  // TODO: confirm compatibility with all supported encodings
17078
17225
  function readDelimRecords(reader, delim, optsArg) {
17226
+ var opts = optsArg || {};
17227
+ if (delim == ' ') return readFixedWidthRecords(reader, opts);
17079
17228
  var reader2 = new Reader2(reader),
17080
- opts = optsArg || {},
17081
17229
  headerStr = readLinesAsString(reader2, getDelimHeaderLines(opts), opts.encoding),
17082
17230
  header = parseDelimHeaderSection(headerStr, delim, opts),
17083
17231
  convertRowArr = getRowConverter(header.import_fields),
@@ -17100,6 +17248,7 @@ ${svg}
17100
17248
  // for delimiter characters and newlines. Input size is limited by the maximum
17101
17249
  // string size.
17102
17250
  function readDelimRecordsFromString(str, delim, opts) {
17251
+ if (delim == ' ') return readFixedWidthRecordsFromString(str, opts);
17103
17252
  var header = parseDelimHeaderSection(str, delim, opts);
17104
17253
  if (header.import_fields.length === 0 || !header.remainder) return [];
17105
17254
  var convert = getRowConverter(header.import_fields);
@@ -17503,7 +17652,7 @@ ${svg}
17503
17652
  };
17504
17653
  }
17505
17654
 
17506
- var supportedDelimiters = ['|', '\t', ',', ';'];
17655
+ var supportedDelimiters = ['|', '\t', ',', ';', ' '];
17507
17656
 
17508
17657
  function isSupportedDelimiter(d) {
17509
17658
  return utils.contains(supportedDelimiters, d);
@@ -17864,9 +18013,21 @@ ${svg}
17864
18013
  }
17865
18014
 
17866
18015
  function cleanArgv(argv) {
17867
- argv = argv.map(function(s) {return s.trim();}); // trim whitespace
18016
+ // Note: original trim caused some quoted spaces to be removed
18017
+ // (e.g. bash shell seems to convert [delimiter=" "] to [delimiter= ],
18018
+ // which then got trimmed to [delimiter=] below)
18019
+ //// argv = argv.map(function(s) {return s.trim();}); // trim whitespace
18020
+
18021
+ // Updated: don't trim space from tokens like [delimeter= ]
18022
+ argv = argv.map(function(s) {
18023
+ if (!/= $/.test(s)) {
18024
+ s = s.trimEnd();
18025
+ }
18026
+ s = s.trimStart();
18027
+ return s;
18028
+ });
17868
18029
  argv = argv.filter(function(s) {return s !== '';}); // remove empty tokens
17869
- // removing trimQuotes() call... now, strings like 'name="Meg"' will no longer
18030
+ // Note: removing trimQuotes() call... now, strings like 'name="Meg"' will no longer
17870
18031
  // be parsed the same way as name=Meg and name="Meg"
17871
18032
  //// argv = argv.map(utils.trimQuotes); // remove one level of single or dbl quotes
17872
18033
  return argv;
@@ -19191,6 +19352,10 @@ ${svg}
19191
19352
  .option('keep-shapes', {
19192
19353
  type: 'flag'
19193
19354
  })
19355
+ .option('ids', {
19356
+ // describe: 'filter on a list of feature ids',
19357
+ type: 'numbers'
19358
+ })
19194
19359
  .option('cleanup', {type: 'flag'}) // TODO: document
19195
19360
  .option('name', nameOpt)
19196
19361
  .option('target', targetOpt)
@@ -19759,6 +19924,11 @@ ${svg}
19759
19924
  DEFAULT: true,
19760
19925
  describe: 'expression or field for grouping features and naming split layers'
19761
19926
  })
19927
+ .option('ids', {
19928
+ // used by gui history to split on selected features
19929
+ // describe: 'split on a list of feature ids',
19930
+ type: 'numbers'
19931
+ })
19762
19932
  .option('apart', {
19763
19933
  describe: 'save output layers to independent datasets',
19764
19934
  type: 'flag'
@@ -31672,6 +31842,10 @@ ${svg}
31672
31842
  filter = compileValueExpression(opts.expression, lyr, arcs);
31673
31843
  }
31674
31844
 
31845
+ if (opts.ids) {
31846
+ filter = combineFilters(filter, getIdFilter(opts.ids));
31847
+ }
31848
+
31675
31849
  if (opts.remove_empty) {
31676
31850
  filter = combineFilters(filter, getNullGeometryFilter(lyr, arcs));
31677
31851
  }
@@ -31730,6 +31904,13 @@ ${svg}
31730
31904
  lyr.data = filteredRecords ? new DataTable(filteredRecords) : null;
31731
31905
  }
31732
31906
 
31907
+ function getIdFilter(ids) {
31908
+ var set = new Set(ids);
31909
+ return function(i) {
31910
+ return set.has(i);
31911
+ };
31912
+ }
31913
+
31733
31914
  function getNullGeometryFilter(lyr, arcs) {
31734
31915
  var shapes = lyr.shapes;
31735
31916
  if (lyr.geometry_type == 'polygon') {
@@ -34002,10 +34183,6 @@ ${svg}
34002
34183
 
34003
34184
 
34004
34185
 
34005
- function formatNumber(val) {
34006
- return val + '';
34007
- }
34008
-
34009
34186
  function maxChars(arr) {
34010
34187
  return arr.reduce(function(memo, str) {
34011
34188
  var w = stringDisplayWidth(str);
@@ -34029,14 +34206,14 @@ ${svg}
34029
34206
  }
34030
34207
 
34031
34208
  function countIntegralChars(val) {
34032
- return utils.isNumber(val) ? (formatNumber(val) + '.').indexOf('.') : 0;
34209
+ return utils.isNumber(val) ? (utils.formatNumber(val) + '.').indexOf('.') : 0;
34033
34210
  }
34034
34211
 
34035
34212
  function formatTableValue(val, integralChars) {
34036
34213
  var str;
34037
34214
  if (utils.isNumber(val)) {
34038
34215
  str = utils.lpad("", integralChars - countIntegralChars(val), ' ') +
34039
- formatNumber(val);
34216
+ utils.formatNumber(val);
34040
34217
  } else if (utils.isString(val)) {
34041
34218
  str = formatString(val);
34042
34219
  } else if (utils.isDate(val)) {
@@ -37408,13 +37585,20 @@ ${svg}
37408
37585
 
37409
37586
  // @expression: optional field name or expression
37410
37587
  //
37411
- cmd.splitLayer = function(src, expression, opts) {
37412
- var lyr0 = opts && opts.no_replace ? copyLayer(src) : src,
37588
+ cmd.splitLayer = function(src, expression, optsArg) {
37589
+ var opts = optsArg || {},
37590
+ lyr0 = opts.no_replace ? copyLayer(src) : src,
37413
37591
  properties = lyr0.data ? lyr0.data.getRecords() : null,
37414
37592
  shapes = lyr0.shapes,
37415
37593
  index = {},
37416
37594
  splitLayers = [],
37417
- namer = getSplitNameFunction(lyr0, expression);
37595
+ namer;
37596
+
37597
+ if (opts.ids) {
37598
+ namer = getIdSplitFunction(opts.ids);
37599
+ } else {
37600
+ namer = getSplitNameFunction(lyr0, expression);
37601
+ }
37418
37602
 
37419
37603
  // if (splitField) {
37420
37604
  // internal.requireDataField(lyr0, splitField);
@@ -37446,15 +37630,24 @@ ${svg}
37446
37630
  return splitLayers;
37447
37631
  };
37448
37632
 
37633
+ function getIdSplitFunction(ids) {
37634
+ var set = new Set(ids);
37635
+ return function(i) {
37636
+ return set.has(i) ? '1' : '2';
37637
+ };
37638
+ }
37639
+
37640
+ function getDefaultSplitFunction(lyr) {
37641
+ // if not splitting on an expression and layer is unnamed, name split-apart layers
37642
+ // like: split-1, split-2, ...
37643
+ return function(i) {
37644
+ return (lyr && lyr.name || 'split') + '-' + (i + 1);
37645
+ };
37646
+ }
37647
+
37449
37648
  function getSplitNameFunction(lyr, exp) {
37450
37649
  var compiled;
37451
- if (!exp) {
37452
- // if not splitting on an expression and layer is unnamed, name split-apart layers
37453
- // like: split-1, split-2, ...
37454
- return function(i) {
37455
- return (lyr && lyr.name || 'split') + '-' + (i + 1);
37456
- };
37457
- }
37650
+ if (!exp) return getDefaultSplitFunction(lyr);
37458
37651
  lyr = {name: lyr.name, data: lyr.data}; // remove shape info
37459
37652
  compiled = compileValueExpression(exp, lyr, null);
37460
37653
  return function(i) {
package/www/modules.js CHANGED
@@ -12464,8 +12464,9 @@ function wkt_parse(str) {
12464
12464
  // WKT format: http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#11
12465
12465
  function wkt_unpack(str) {
12466
12466
  var obj;
12467
- // Convert WKT escaped quote to JSON escaped quote
12468
- str = str.replace(/""/g, '\\"');
12467
+ // Convert WKT escaped quotes to JSON escaped quotes
12468
+ // str = str.replace(/""/g, '\\"'); // BUGGY
12469
+ str = convert_wkt_quotes(str);
12469
12470
 
12470
12471
  // Convert WKT entities to JSON arrays
12471
12472
  str = str.replace(/([A-Z0-9]+)\[/g, '["$1",');
@@ -12487,6 +12488,23 @@ function wkt_unpack(str) {
12487
12488
  return obj;
12488
12489
  }
12489
12490
 
12491
+ // Convert WKT escaped quotes to JSON escaped quotes ("" -> \")
12492
+ function convert_wkt_quotes(str) {
12493
+ var c = 0;
12494
+ return str.replace(/"+/g, function(s) {
12495
+ var even = c % 2 == 0;
12496
+ c += s.length;
12497
+ // ordinary, unescaped quotes
12498
+ if (s == '"' || s == '""' && even) return s;
12499
+ // WKT-escaped quotes
12500
+ if (even) {
12501
+ return '"' + s.substring(1).replace(/""/g, '\\"');
12502
+ } else {
12503
+ return s.replace(/""/g, '\\"');
12504
+ }
12505
+ });
12506
+ }
12507
+
12490
12508
  // Rearrange a subarray of a parsed WKT file for easier traversal
12491
12509
  // E.g.
12492
12510
  // ["WGS84", ...] to {NAME: "WGS84"}
@@ -21706,6 +21724,7 @@ api.internal = {
21706
21724
  RAD_TO_DEG: RAD_TO_DEG,
21707
21725
  wkt_parse: wkt_parse,
21708
21726
  wkt_unpack: wkt_unpack,
21727
+ convert_wkt_quotes: convert_wkt_quotes,
21709
21728
  wkt_to_proj4: wkt_to_proj4,
21710
21729
  wkt_from_proj4: wkt_from_proj4,
21711
21730
  wkt_make_projcs: wkt_make_projcs,