mapshaper 0.6.62 → 0.6.65

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
@@ -4122,6 +4122,30 @@
4122
4122
  }
4123
4123
  };
4124
4124
 
4125
+ // TODO: make this stricter (could give false positive on some degenerate paths)
4126
+ function pathIsRectangle(ids, arcs) {
4127
+ var bbox = arcs.getSimpleShapeBounds(ids).toArray();
4128
+ var iter = arcs.getShapeIter(ids);
4129
+ while (iter.hasNext()) {
4130
+ if (iter.x != bbox[0] && iter.x != bbox[2] ||
4131
+ iter.y != bbox[1] && iter.y != bbox[3]) {
4132
+ return false;
4133
+ }
4134
+ }
4135
+ return true;
4136
+ }
4137
+
4138
+ function bboxToCoords(bbox) {
4139
+ return [[bbox[0], bbox[1]], [bbox[0], bbox[3]], [bbox[2], bbox[3]],
4140
+ [bbox[2], bbox[1]], [bbox[0], bbox[1]]];
4141
+ }
4142
+
4143
+ var RectangleGeom = /*#__PURE__*/Object.freeze({
4144
+ __proto__: null,
4145
+ pathIsRectangle: pathIsRectangle,
4146
+ bboxToCoords: bboxToCoords
4147
+ });
4148
+
4125
4149
  // Insert a column of values into a (new or existing) data field
4126
4150
  function insertFieldValues(lyr, fieldName, values) {
4127
4151
  var size = getFeatureCount(lyr) || values.length,
@@ -4162,6 +4186,15 @@
4162
4186
  return lyr.geometry_type == 'point' && layerHasNonNullShapes(lyr);
4163
4187
  }
4164
4188
 
4189
+ function layerOnlyHasRectangles(lyr, arcs) {
4190
+ if (!layerHasPaths(lyr)) return false;
4191
+ if (countMultiPartFeatures(lyr) > 0) return false;
4192
+ return lyr.shapes.every(function(shp) {
4193
+ if (!shp) return true;
4194
+ return pathIsRectangle(shp[0], arcs);
4195
+ });
4196
+ }
4197
+
4165
4198
  function layerHasNonNullShapes(lyr) {
4166
4199
  return utils.some(lyr.shapes || [], function(shp) {
4167
4200
  return !!shp;
@@ -4405,6 +4438,7 @@
4405
4438
  layerHasGeometry: layerHasGeometry,
4406
4439
  layerHasPaths: layerHasPaths,
4407
4440
  layerHasPoints: layerHasPoints,
4441
+ layerOnlyHasRectangles: layerOnlyHasRectangles,
4408
4442
  layerHasNonNullShapes: layerHasNonNullShapes,
4409
4443
  deleteFeatureById: deleteFeatureById,
4410
4444
  transformPointsInLayer: transformPointsInLayer,
@@ -5002,10 +5036,10 @@
5002
5036
  // x, y: a point location in projected coordinates
5003
5037
  // Returns k, the ratio of coordinate distance to distance on the ground
5004
5038
  function getScaleFactorAtXY(x, y, crs) {
5005
- var dist = 1;
5039
+ var dist = 1 / crs.to_meter;
5006
5040
  var lp = mproj.pj_inv_deg({x: x, y: y}, crs);
5007
5041
  var lp2 = mproj.pj_inv_deg({x: x + dist, y: y}, crs);
5008
- var k = dist / geom.greatCircleDistance(lp.lam, lp.phi, lp2.lam, lp2.phi);
5042
+ var k = 1 / geom.greatCircleDistance(lp.lam, lp.phi, lp2.lam, lp2.phi);
5009
5043
  return k;
5010
5044
  }
5011
5045
 
@@ -11058,7 +11092,7 @@
11058
11092
  // File looks like an importable file type
11059
11093
  // name: filename or path
11060
11094
  function looksLikeImportableFile(name) {
11061
- return !!guessInputFileType(name);
11095
+ return !!guessInputFileType(name) || isImportableAsBinary(name);
11062
11096
  }
11063
11097
 
11064
11098
  // File looks like a directly readable data file type
@@ -13419,7 +13453,7 @@
13419
13453
 
13420
13454
  // Accepts a single value or a list of four values. List order is l,b,t,r
13421
13455
  function convertFourSides(opt, crs, bounds) {
13422
- var arr = opt.split(',');
13456
+ var arr = opt.includes(',') ? opt.split(',') : opt.split(' ');
13423
13457
  if (arr.length == 1) {
13424
13458
  arr = [arr[0], arr[0], arr[0], arr[0]];
13425
13459
  } else if (arr.length != 4) {
@@ -19407,99 +19441,136 @@
19407
19441
  }
19408
19442
 
19409
19443
  // TODO: generalize to other kinds of furniture as they are developed
19410
- function getScalebarPosition(d) {
19411
- var opts = { // defaults
19412
- valign: 'top',
19413
- halign: 'left',
19414
- voffs: 10,
19415
- hoffs: 10
19444
+ function getScalebarPosition(opts) {
19445
+ var pos = opts.position || 'top-left';
19446
+ return {
19447
+ valign: pos.includes('top') ? 'top' : 'bottom',
19448
+ halign: pos.includes('left') ? 'left' : 'right'
19416
19449
  };
19417
- if (+d.left > 0) {
19418
- opts.hoffs = +d.left;
19450
+ }
19451
+
19452
+ var styleOpts = {
19453
+ a: {
19454
+ bar_width: 3,
19455
+ tic_length: 0
19456
+ },
19457
+ b: {
19458
+ bar_width: 1,
19459
+ tic_length: 5
19419
19460
  }
19420
- if (+d.top > 0) {
19421
- opts.voffs = +d.top;
19461
+ };
19462
+
19463
+ var defaultOpts = {
19464
+ position: 'top-left',
19465
+ label_position: 'top',
19466
+ label_offset: 4,
19467
+ font_size: 12,
19468
+ margin: 12
19469
+ };
19470
+
19471
+ function getScalebarOpts(d) {
19472
+ var style = d.style == 'b' || d.style == 'B' ? 'b' : 'a';
19473
+ return Object.assign({}, defaultOpts, styleOpts[style], d, {style: style});
19474
+ }
19475
+
19476
+ // approximate pixel height of the scalebar
19477
+ function getScalebarHeight(opts) {
19478
+ return Math.round(opts.bar_width + opts.label_offset +
19479
+ opts.tic_length + opts.font_size * 0.8);
19480
+ }
19481
+
19482
+ function renderAsSvg(length, text, opts) {
19483
+ // label part
19484
+ var xOff = opts.style == 'b' ? Math.round(opts.font_size / 4) : 0;
19485
+ var alignLeft = opts.style == 'a' && opts.position.includes('left');
19486
+ var anchorX = alignLeft ? -xOff : length + xOff;
19487
+ var anchorY = opts.bar_width + + opts.tic_length + opts.label_offset;
19488
+ if (opts.label_position == 'top') {
19489
+ anchorY = -opts.label_offset - opts.tic_length;
19422
19490
  }
19423
- if (+d.right > 0) {
19424
- opts.hoffs = +d.right;
19425
- opts.halign = 'right';
19491
+ var labelOpts = {
19492
+ 'label-text': text,
19493
+ 'font-size': opts.font_size,
19494
+ 'text-anchor': alignLeft ? 'start': 'end',
19495
+ 'dominant-baseline': opts.label_position == 'top' ? 'auto' : 'hanging'
19496
+ //// 'dominant-baseline': labelPos == 'top' ? 'text-after-edge' : 'text-before-edge'
19497
+ // 'text-after-edge' is buggy in Safari and unsupported by Illustrator,
19498
+ // so I'm using 'hanging' and 'auto', which seem to be well supported.
19499
+ // downside: requires a kludgy multiplier to calculate scalebar height (see above)
19500
+ };
19501
+ var labelPart = symbolRenderers.label(labelOpts, anchorX, anchorY);
19502
+ var zeroOpts = Object.assign({}, labelOpts, {'label-text': '0', 'text-anchor': 'start'});
19503
+ var zeroLabel = symbolRenderers.label(zeroOpts, -xOff, anchorY);
19504
+
19505
+ // bar part
19506
+ var y = 0;
19507
+ var y2 = opts.tic_length + opts.bar_width / 2;
19508
+ var coords;
19509
+ if (opts.label_position == "top") {
19510
+ y2 = -y2;
19426
19511
  }
19427
- if (+d.bottom > 0) {
19428
- opts.voffs = +d.bottom;
19429
- opts.valign = 'bottom';
19512
+ if (opts.tic_length > 0) {
19513
+ coords = [[0, y2], [0, y], [length, y], [length, y2]];
19514
+ } else {
19515
+ coords = [[0, y], [length, y]];
19430
19516
  }
19431
- return opts;
19517
+ var barPart = importLineString(coords);
19518
+ Object.assign(barPart.properties, {
19519
+ stroke: 'black',
19520
+ fill: 'none',
19521
+ 'stroke-width': opts.bar_width,
19522
+ 'stroke-linecap': 'butt',
19523
+ 'stroke-linejoin': 'miter'
19524
+ });
19525
+ var parts = opts.style == 'b' ? [zeroLabel, labelPart, barPart] : [labelPart, barPart];
19526
+ return {
19527
+ tag: 'g',
19528
+ children: parts
19529
+ };
19432
19530
  }
19433
19531
 
19434
19532
  function renderScalebar(d, frame) {
19435
- var pos = getScalebarPosition(d);
19533
+ if (!frame.crs) {
19534
+ message('Unable to render scalebar: unknown CRS.');
19535
+ return [];
19536
+ }
19537
+ if (frame.width > 0 === false) {
19538
+ return [];
19539
+ }
19540
+
19541
+ var opts = getScalebarOpts(d);
19436
19542
  var metersPerPx = getMapFrameMetersPerPixel(frame);
19437
19543
  var frameWidthPx = frame.width;
19438
19544
  var unit = d.label ? parseScalebarUnits(d.label) : 'mile';
19439
19545
  var number = d.label ? parseScalebarNumber(d.label) : null;
19440
19546
  var label = number && unit ? d.label : getAutoScalebarLabel(frameWidthPx, metersPerPx, unit);
19441
- var scalebarKm = parseScalebarLabelToKm(label);
19442
- var barHeight = 3;
19443
- var labelOffs = 4;
19444
- var fontSize = +d.font_size || 12;
19445
- var width = Math.round(scalebarKm / metersPerPx * 1000);
19446
- var height = Math.round(barHeight + labelOffs + fontSize * 0.8);
19447
- var labelPos = d.label_position == 'top' ? 'top' : 'bottom';
19448
- var anchorX = pos.halign == 'left' ? 0 : width;
19449
- var anchorY = barHeight + labelOffs;
19450
- var dx = pos.halign == 'right' ? frameWidthPx - width - pos.hoffs : pos.hoffs;
19451
- var dy = pos.valign == 'bottom' ? frame.height - height - pos.voffs : pos.voffs;
19452
19547
 
19548
+ var scalebarKm = parseScalebarLabelToKm(label);
19453
19549
  if (scalebarKm > 0 === false) {
19454
19550
  message('Unusable scalebar label:', label);
19455
19551
  return [];
19456
19552
  }
19457
19553
 
19458
- if (frameWidthPx > 0 === false) {
19459
- return [];
19460
- }
19461
-
19462
- if (!frame.crs) {
19463
- message('Unable to render scalebar: unknown CRS.');
19464
- return [];
19554
+ var width = Math.round(scalebarKm / metersPerPx * 1000);
19555
+ if (width > 0 === false) {
19556
+ stop("Null scalebar length");
19465
19557
  }
19466
19558
 
19467
- if (labelPos == 'top') {
19468
- anchorY = -labelOffs;
19469
- dy += Math.round(labelOffs + fontSize * 0.8);
19559
+ var pos = getScalebarPosition(opts);
19560
+ var height = getScalebarHeight(opts);
19561
+ var dx = pos.halign == 'right' ? frameWidthPx - width - opts.margin : opts.margin;
19562
+ var dy = pos.valign == 'bottom' ? frame.height - height - opts.margin : opts.margin;
19563
+ if (opts.label_position == 'top') {
19564
+ dy += Math.round(opts.label_offset + opts.tic_length + opts.font_size * 0.8 + opts.bar_width / 2);
19565
+ } else {
19566
+ dy += Math.round(opts.bar_width / 2);
19470
19567
  }
19471
19568
 
19472
- if (width > 0 === false) {
19473
- stop("Null scalebar length");
19474
- }
19475
- var barObj = {
19476
- tag: 'rect',
19477
- properties: {
19478
- fill: 'black',
19479
- x: 0,
19480
- y: 0,
19481
- width: width,
19482
- height: barHeight
19483
- }
19484
- };
19485
- var labelOpts = {
19486
- 'label-text': label,
19487
- 'font-size': fontSize,
19488
- 'text-anchor': pos.halign == 'left' ? 'start': 'end',
19489
- 'dominant-baseline': labelPos == 'top' ? 'auto' : 'hanging'
19490
- //// 'dominant-baseline': labelPos == 'top' ? 'text-after-edge' : 'text-before-edge'
19491
- // 'text-after-edge' is buggy in Safari and unsupported by Illustrator,
19492
- // so I'm using 'hanging' and 'auto', which seem to be well supported.
19493
- // downside: requires a kludgy multiplier to calculate scalebar height (see above)
19494
- };
19495
- var labelObj = symbolRenderers.label(labelOpts, anchorX, anchorY);
19496
- var g = {
19497
- tag: 'g',
19498
- children: [barObj, labelObj],
19499
- properties: {
19500
- transform: 'translate(' + dx + ' ' + dy + ')'
19501
- }
19569
+ var g = renderAsSvg(width, label, opts);
19570
+ g.properties = {
19571
+ transform: 'translate(' + dx + ' ' + dy + ')'
19502
19572
  };
19573
+
19503
19574
  return [g];
19504
19575
  }
19505
19576
 
@@ -25691,13 +25762,33 @@ ${svg}
25691
25762
  DEFAULT: true,
25692
25763
  describe: 'distance label, e.g. "35 miles"'
25693
25764
  })
25694
- .option('top', {})
25695
- .option('right', {})
25696
- .option('bottom', {})
25697
- .option('left', {})
25698
- .option('font-size', {})
25699
- // .option('font-family', {})
25700
- .option('label-position', {}); // top or bottom
25765
+ .option('style', {
25766
+ describe: 'two options: a or b'
25767
+ })
25768
+ .option('font-size', {
25769
+ type: 'number'
25770
+ })
25771
+ .option('tic-length', {
25772
+ describe: 'length of tic marks (style b)',
25773
+ type: 'number'
25774
+ })
25775
+ .option('bar-width', {
25776
+ describe: 'line width of bar',
25777
+ type: 'number'
25778
+ })
25779
+ .option('label-offset', {
25780
+ type: 'number'
25781
+ })
25782
+ .option('position', {
25783
+ describe: 'e.g. bottom-right (default is top-left)'
25784
+ })
25785
+ .option('label-position', {
25786
+ describe: 'top or bottom'
25787
+ })
25788
+ .option('margin', {
25789
+ describe: 'offset in pixels from edge of map',
25790
+ type: 'number'
25791
+ });
25701
25792
 
25702
25793
  parser.command('shape')
25703
25794
  .describe('create a polyline or polygon from coordinates')
@@ -35673,11 +35764,285 @@ ${svg}
35673
35764
  };
35674
35765
  }
35675
35766
 
35767
+ var MAX_RULE_LEN = 50;
35768
+
35769
+ cmd.info = function(targets, opts) {
35770
+ var layers = expandCommandTargets(targets);
35771
+ var arr = layers.map(function(o) {
35772
+ return getLayerInfo(o.layer, o.dataset);
35773
+ });
35774
+
35775
+ if (opts.save_to) {
35776
+ var output = [{
35777
+ filename: opts.save_to + (opts.save_to.endsWith('.json') ? '' : '.json'),
35778
+ content: JSON.stringify(arr, null, 2)
35779
+ }];
35780
+ writeFiles(output, opts);
35781
+ }
35782
+ if (opts.to_layer) {
35783
+ return {
35784
+ info: {},
35785
+ layers: [{
35786
+ name: opts.name || 'info',
35787
+ data: new DataTable(arr)
35788
+ }]
35789
+ };
35790
+ }
35791
+ message(formatInfo(arr));
35792
+ };
35793
+
35794
+ cmd.printInfo = cmd.info; // old name
35795
+
35796
+ function getLayerInfo(lyr, dataset) {
35797
+ var n = getFeatureCount(lyr);
35798
+ var o = {
35799
+ layer_name: lyr.name,
35800
+ geometry_type: lyr.geometry_type,
35801
+ feature_count: n,
35802
+ null_shape_count: 0,
35803
+ null_data_count: lyr.data ? countNullRecords(lyr.data.getRecords()) : n
35804
+ };
35805
+ if (lyr.shapes && lyr.shapes.length > 0) {
35806
+ o.null_shape_count = countNullShapes(lyr.shapes);
35807
+ o.bbox = getLayerBounds(lyr, dataset.arcs).toArray();
35808
+ o.proj4 = getProjInfo(dataset);
35809
+ }
35810
+ o.source_file = getLayerSourceFile(lyr, dataset) || null;
35811
+ o.attribute_data = getAttributeTableInfo(lyr);
35812
+ return o;
35813
+ }
35814
+
35815
+ // i: (optional) record index
35816
+ function getAttributeTableInfo(lyr, i) {
35817
+ if (!lyr.data || lyr.data.size() === 0 || lyr.data.getFields().length === 0) {
35818
+ return null;
35819
+ }
35820
+ var fields = applyFieldOrder(lyr.data.getFields(), 'ascending');
35821
+ var valueName = i === undefined ? 'first_value' : 'value';
35822
+ return fields.map(function(fname) {
35823
+ return {
35824
+ field: fname,
35825
+ [valueName]: lyr.data.getReadOnlyRecordAt(i || 0)[fname]
35826
+ };
35827
+ });
35828
+ }
35829
+
35830
+ function formatInfo(arr) {
35831
+ var str = '';
35832
+ arr.forEach(function(info, i) {
35833
+ var title = 'Layer: ' + (info.layer_name || '[unnamed layer]');
35834
+ var tableStr = formatAttributeTableInfo(info.attribute_data);
35835
+ var tableWidth = measureLongestLine(tableStr);
35836
+ var ruleLen = Math.min(Math.max(title.length, tableWidth), MAX_RULE_LEN);
35837
+ str += '\n';
35838
+ str += utils.lpad('', ruleLen, '=') + '\n';
35839
+ str += title + '\n';
35840
+ str += utils.lpad('', ruleLen, '-') + '\n';
35841
+ str += formatLayerInfo(info);
35842
+ str += tableStr;
35843
+ });
35844
+ return str;
35845
+ }
35846
+
35847
+ function formatLayerInfo(data) {
35848
+ var str = '';
35849
+ str += "Type: " + (data.geometry_type || "tabular data") + "\n";
35850
+ str += utils.format("Records: %,d\n",data.feature_count);
35851
+ if (data.null_shape_count > 0) {
35852
+ str += utils.format("Nulls: %'d", data.null_shape_count) + "\n";
35853
+ }
35854
+ if (data.geometry_type && data.feature_count > data.null_shape_count) {
35855
+ str += "Bounds: " + data.bbox.join(',') + "\n";
35856
+ str += "CRS: " + data.proj4 + "\n";
35857
+ }
35858
+ str += "Source: " + (data.source_file || 'n/a') + "\n";
35859
+ return str;
35860
+ }
35861
+
35862
+ function formatAttributeTableInfo(arr) {
35863
+ if (!arr) return "Attribute data: [none]\n";
35864
+ var header = "\nAttribute data\n";
35865
+ var valKey = 'first_value' in arr[0] ? 'first_value' : 'value';
35866
+ var vals = [];
35867
+ var fields = [];
35868
+ arr.forEach(function(o) {
35869
+ fields.push(o.field);
35870
+ vals.push(o[valKey]);
35871
+ });
35872
+ var maxIntegralChars = vals.reduce(function(max, val) {
35873
+ if (utils.isNumber(val)) {
35874
+ max = Math.max(max, countIntegralChars(val));
35875
+ }
35876
+ return max;
35877
+ }, 0);
35878
+ var col1Arr = ['Field'].concat(fields);
35879
+ var col2Arr = vals.reduce(function(memo, val) {
35880
+ memo.push(formatTableValue(val, maxIntegralChars));
35881
+ return memo;
35882
+ }, [valKey == 'first_value' ? 'First value' : 'Value']);
35883
+ var col1Chars = maxChars(col1Arr);
35884
+ var col2Chars = maxChars(col2Arr);
35885
+ var sepStr = (utils.rpad('', col1Chars + 2, '-') + '+' +
35886
+ utils.rpad('', col2Chars + 2, '-')).substr(0, MAX_RULE_LEN);
35887
+ var sepLine = sepStr + '\n';
35888
+ var table = '';
35889
+ col1Arr.forEach(function(col1, i) {
35890
+ var w = stringDisplayWidth(col1);
35891
+ table += ' ' + col1 + utils.rpad('', col1Chars - w, ' ') + ' | ' +
35892
+ col2Arr[i] + '\n';
35893
+ if (i === 0) table += sepLine; // separator after first line
35894
+ });
35895
+ return header + sepLine + table + sepLine;
35896
+ }
35897
+
35898
+ function measureLongestLine(str) {
35899
+ return Math.max.apply(null, str.split('\n').map(function(line) {return stringDisplayWidth(line);}));
35900
+ }
35901
+
35902
+ function stringDisplayWidth(str) {
35903
+ var w = 0;
35904
+ for (var i = 0, n=str.length; i < n; i++) {
35905
+ w += charDisplayWidth(str.charCodeAt(i));
35906
+ }
35907
+ return w;
35908
+ }
35909
+
35910
+ // see https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
35911
+ // this is a simplified version, focusing on double-width CJK chars and ignoring nonprinting etc chars
35912
+ function charDisplayWidth(c) {
35913
+ if (c >= 0x1100 &&
35914
+ (c <= 0x115f || c == 0x2329 || c == 0x232a ||
35915
+ (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */
35916
+ (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */
35917
+ (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */
35918
+ (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */
35919
+ (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */
35920
+ (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */
35921
+ (c >= 0xffe0 && c <= 0xffe6) ||
35922
+ (c >= 0x20000 && c <= 0x2fffd) ||
35923
+ (c >= 0x30000 && c <= 0x3fffd))) return 2;
35924
+ return 1;
35925
+ }
35926
+
35927
+ // TODO: consider polygons with zero area or other invalid geometries
35928
+ function countNullShapes(shapes) {
35929
+ var count = 0;
35930
+ for (var i=0; i<shapes.length; i++) {
35931
+ if (!shapes[i] || shapes[i].length === 0) count++;
35932
+ }
35933
+ return count;
35934
+ }
35935
+
35936
+ function countNullRecords(records) {
35937
+ var count = 0;
35938
+ for (var i=0; i<records.length; i++) {
35939
+ if (!records[i]) count++;
35940
+ }
35941
+ return count;
35942
+ }
35943
+
35944
+ function maxChars(arr) {
35945
+ return arr.reduce(function(memo, str) {
35946
+ var w = stringDisplayWidth(str);
35947
+ return w > memo ? w : memo;
35948
+ }, 0);
35949
+ }
35950
+
35951
+ function formatString(str) {
35952
+ var replacements = {
35953
+ '\n': '\\n',
35954
+ '\r': '\\r',
35955
+ '\t': '\\t'
35956
+ };
35957
+ var cleanChar = function(c) {
35958
+ // convert newlines and carriage returns
35959
+ // TODO: better handling of non-printing chars
35960
+ return c in replacements ? replacements[c] : '';
35961
+ };
35962
+ str = str.replace(/[\r\t\n]/g, cleanChar);
35963
+ return "'" + str + "'";
35964
+ }
35965
+
35966
+ function countIntegralChars(val) {
35967
+ return utils.isNumber(val) ? (utils.formatNumber(val) + '.').indexOf('.') : 0;
35968
+ }
35969
+
35970
+ function formatTableValue(val, integralChars) {
35971
+ var str;
35972
+ if (utils.isNumber(val)) {
35973
+ str = utils.lpad("", integralChars - countIntegralChars(val), ' ') +
35974
+ utils.formatNumber(val);
35975
+ } else if (utils.isString(val)) {
35976
+ str = formatString(val);
35977
+ } else if (utils.isDate(val)) {
35978
+ str = utils.formatDateISO(val) + ' (Date)';
35979
+ } else if (utils.isObject(val)) { // if {} or [], display JSON
35980
+ str = JSON.stringify(val);
35981
+ } else {
35982
+ str = String(val);
35983
+ }
35984
+
35985
+ if (typeof str != 'string') {
35986
+ // e.g. JSON.stringify converts functions to undefined
35987
+ str = '[' + (typeof val) + ']';
35988
+ }
35989
+
35990
+ return str;
35991
+ }
35992
+
35993
+ var Info = /*#__PURE__*/Object.freeze({
35994
+ __proto__: null,
35995
+ getLayerInfo: getLayerInfo,
35996
+ getAttributeTableInfo: getAttributeTableInfo,
35997
+ formatAttributeTableInfo: formatAttributeTableInfo,
35998
+ formatTableValue: formatTableValue
35999
+ });
36000
+
36001
+ // import { importGeoJSON } from '../geojson/geojson-import';
36002
+
36003
+
36004
+ function addTargetProxies(targets, ctx) {
36005
+ if (targets && targets.length > 0) {
36006
+ var proxies = expandCommandTargets(targets).reduce(function(memo, target) {
36007
+ var proxy = getTargetProxy(target);
36008
+ memo.push(proxy);
36009
+ // index targets by layer name too
36010
+ if (target.layer.name) {
36011
+ memo[target.layer.name] = proxy;
36012
+ }
36013
+ return memo;
36014
+ }, []);
36015
+ Object.defineProperty(ctx, 'targets', {value: proxies});
36016
+ if (proxies.length == 1) {
36017
+ Object.defineProperty(ctx, 'target', {value: proxies[0]});
36018
+ }
36019
+ }
36020
+ }
36021
+
36022
+ function getTargetProxy(target) {
36023
+ var proxy = getLayerInfo(target.layer, target.dataset); // layer_name, feature_count etc
36024
+ proxy.layer = target.layer;
36025
+ proxy.dataset = target.dataset;
36026
+ addGetters(proxy, {
36027
+ // export as an object, not a string or buffer
36028
+ geojson: getGeoJSON
36029
+ });
36030
+
36031
+ function getGeoJSON() {
36032
+ var features = exportLayerAsGeoJSON(target.layer, target.dataset, {rfc7946: true}, true);
36033
+ return {
36034
+ type: 'FeatureCollection',
36035
+ features: features
36036
+ };
36037
+ }
36038
+
36039
+ return proxy;
36040
+ }
36041
+
35676
36042
  function compileIfCommandExpression(expr, catalog, opts) {
35677
36043
  return compileLayerExpression(expr, catalog, opts);
35678
36044
  }
35679
36045
 
35680
-
35681
36046
  function compileLayerExpression(expr, catalog, opts) {
35682
36047
  var targetId = opts.layer || opts.target || null;
35683
36048
  var targets = catalog.findCommandTargets(targetId);
@@ -35693,6 +36058,10 @@ ${svg}
35693
36058
  } else {
35694
36059
  ctx = getNullLayerProxy(targets);
35695
36060
  }
36061
+
36062
+ // add target/targets proxies, for consistency with the -run command
36063
+ addTargetProxies(targets, ctx);
36064
+
35696
36065
  ctx.global = defs; // TODO: remove duplication with mapshaper.expressions.mjs
35697
36066
  var func = compileExpressionToFunction(expr, opts);
35698
36067
 
@@ -38142,8 +38511,7 @@ ${svg}
38142
38511
 
38143
38512
  function convertBboxToGeoJSON(bbox, optsArg) {
38144
38513
  var opts = optsArg || {};
38145
- var coords = [[bbox[0], bbox[1]], [bbox[0], bbox[3]], [bbox[2], bbox[3]],
38146
- [bbox[2], bbox[1]], [bbox[0], bbox[1]]];
38514
+ var coords = bboxToCoords(bbox);
38147
38515
  if (opts.interval > 0) {
38148
38516
  coords = densifyPathByInterval(coords, opts.interval);
38149
38517
  }
@@ -39328,58 +39696,73 @@ ${svg}
39328
39696
  print(str);
39329
39697
  };
39330
39698
 
39331
- function resetControlFlow(job) {
39332
- job.control = null;
39333
- }
39334
-
39335
39699
  function stopJob(job) {
39336
- getState(job).stopped = true;
39700
+ job.stopped = true;
39337
39701
  }
39338
39702
 
39339
39703
  function jobIsStopped(job) {
39340
- return getState(job).stopped === true;
39704
+ return job.stopped === true;
39341
39705
  }
39342
39706
 
39343
39707
  function inControlBlock(job) {
39344
- return !!getState(job).inControlBlock;
39708
+ return getStack(job).length > 0;
39709
+ }
39710
+
39711
+ function enterBlock(job) {
39712
+ var stack = getStack(job);
39713
+ // skip over a block if it is inside an inactive branch
39714
+ stack.push({
39715
+ active: false,
39716
+ complete: !inActiveBranch(job)
39717
+ });
39718
+ }
39719
+
39720
+ function leaveBlock(job) {
39721
+ var stack = getStack(job);
39722
+ stack.pop();
39345
39723
  }
39346
39724
 
39347
39725
  function enterActiveBranch(job) {
39348
- var state = getState(job);
39349
- state.inControlBlock = true;
39350
- state.active = true;
39351
- state.complete = true;
39726
+ var block = getCurrentBlock(job);
39727
+ block.active = true;
39728
+ block.complete = true;
39352
39729
  }
39353
39730
 
39354
39731
  function enterInactiveBranch(job) {
39355
- var state = getState(job);
39356
- state.inControlBlock = true;
39357
- state.active = false;
39732
+ var block = getCurrentBlock(job);
39733
+ block.active = false;
39734
+ }
39735
+
39736
+ function blockIsComplete(job) {
39737
+ var block = getCurrentBlock(job);
39738
+ return block.complete;
39358
39739
  }
39359
39740
 
39360
- function blockWasActive(job) {
39361
- return !!getState(job).complete;
39741
+ function getCurrentBlock(job) {
39742
+ var stack = getStack(job);
39743
+ return stack[stack.length-1];
39362
39744
  }
39363
39745
 
39746
+ // A branch is considered to be active if it and all its parents are active
39747
+ // (Main branch is considered to be active)
39364
39748
  function inActiveBranch(job) {
39365
- return !!getState(job).active;
39749
+ var stack = getStack(job);
39750
+ return stack.length === 0 || stack.every(block => block.active);
39366
39751
  }
39367
39752
 
39368
- function getState(job) {
39369
- return job.control || (job.control = {});
39753
+ function getStack(job) {
39754
+ job.control = job.control || {stack: []};
39755
+ return job.control.stack;
39370
39756
  }
39371
39757
 
39372
39758
  function skipCommand(cmdName, job) {
39373
- // allow all control commands to run
39374
39759
  if (jobIsStopped(job)) return true;
39375
39760
  if (isControlFlowCommand(cmdName)) return false;
39376
- return inControlBlock(job) && !inActiveBranch(job);
39761
+ return !inActiveBranch(job);
39377
39762
  }
39378
39763
 
39379
39764
  cmd.if = function(job, opts) {
39380
- if (inControlBlock(job)) {
39381
- stop('Nested -if commands are not supported.');
39382
- }
39765
+ enterBlock(job);
39383
39766
  evaluateIf(job, opts);
39384
39767
  };
39385
39768
 
@@ -39394,7 +39777,7 @@ ${svg}
39394
39777
  if (!inControlBlock(job)) {
39395
39778
  stop('-else command must be preceded by an -if command.');
39396
39779
  }
39397
- if (blockWasActive(job)) {
39780
+ if (blockIsComplete(job)) {
39398
39781
  enterInactiveBranch(job);
39399
39782
  } else {
39400
39783
  enterActiveBranch(job);
@@ -39405,7 +39788,7 @@ ${svg}
39405
39788
  if (!inControlBlock(job)) {
39406
39789
  stop('-endif command must be preceded by an -if command.');
39407
39790
  }
39408
- resetControlFlow(job);
39791
+ leaveBlock(job);
39409
39792
  };
39410
39793
 
39411
39794
  function isControlFlowCommand(cmd) {
@@ -39413,21 +39796,14 @@ ${svg}
39413
39796
  }
39414
39797
 
39415
39798
  function test(catalog, opts) {
39416
- // var targ = getTargetLayer(catalog, opts);
39417
39799
  if (opts.expression) {
39418
39800
  return compileIfCommandExpression(opts.expression, catalog, opts)();
39419
39801
  }
39420
- // if (opts.empty) {
39421
- // return layerIsEmpty(targ.layer);
39422
- // }
39423
- // if (opts.not_empty) {
39424
- // return !layerIsEmpty(targ.layer);
39425
- // }
39426
39802
  return true;
39427
39803
  }
39428
39804
 
39429
39805
  function evaluateIf(job, opts) {
39430
- if (!blockWasActive(job) && test(job.catalog, opts)) {
39806
+ if (!blockIsComplete(job) && test(job.catalog, opts)) {
39431
39807
  enterActiveBranch(job);
39432
39808
  } else {
39433
39809
  enterInactiveBranch(job);
@@ -39472,240 +39848,6 @@ ${svg}
39472
39848
  utils.extend(getStashedVar('defs'), obj);
39473
39849
  };
39474
39850
 
39475
- var MAX_RULE_LEN = 50;
39476
-
39477
- cmd.info = function(targets, opts) {
39478
- var layers = expandCommandTargets(targets);
39479
- var arr = layers.map(function(o) {
39480
- return getLayerInfo(o.layer, o.dataset);
39481
- });
39482
-
39483
- if (opts.save_to) {
39484
- var output = [{
39485
- filename: opts.save_to + (opts.save_to.endsWith('.json') ? '' : '.json'),
39486
- content: JSON.stringify(arr, null, 2)
39487
- }];
39488
- writeFiles(output, opts);
39489
- }
39490
- if (opts.to_layer) {
39491
- return {
39492
- info: {},
39493
- layers: [{
39494
- name: opts.name || 'info',
39495
- data: new DataTable(arr)
39496
- }]
39497
- };
39498
- }
39499
- message(formatInfo(arr));
39500
- };
39501
-
39502
- cmd.printInfo = cmd.info; // old name
39503
-
39504
- function getLayerInfo(lyr, dataset) {
39505
- var n = getFeatureCount(lyr);
39506
- var o = {
39507
- layer_name: lyr.name,
39508
- geometry_type: lyr.geometry_type,
39509
- feature_count: n,
39510
- null_shape_count: 0,
39511
- null_data_count: lyr.data ? countNullRecords(lyr.data.getRecords()) : n
39512
- };
39513
- if (lyr.shapes && lyr.shapes.length > 0) {
39514
- o.null_shape_count = countNullShapes(lyr.shapes);
39515
- o.bbox = getLayerBounds(lyr, dataset.arcs).toArray();
39516
- o.proj4 = getProjInfo(dataset);
39517
- }
39518
- o.source_file = getLayerSourceFile(lyr, dataset) || null;
39519
- o.attribute_data = getAttributeTableInfo(lyr);
39520
- return o;
39521
- }
39522
-
39523
- // i: (optional) record index
39524
- function getAttributeTableInfo(lyr, i) {
39525
- if (!lyr.data || lyr.data.size() === 0 || lyr.data.getFields().length === 0) {
39526
- return null;
39527
- }
39528
- var fields = applyFieldOrder(lyr.data.getFields(), 'ascending');
39529
- var valueName = i === undefined ? 'first_value' : 'value';
39530
- return fields.map(function(fname) {
39531
- return {
39532
- field: fname,
39533
- [valueName]: lyr.data.getReadOnlyRecordAt(i || 0)[fname]
39534
- };
39535
- });
39536
- }
39537
-
39538
- function formatInfo(arr) {
39539
- var str = '';
39540
- arr.forEach(function(info, i) {
39541
- var title = 'Layer: ' + (info.layer_name || '[unnamed layer]');
39542
- var tableStr = formatAttributeTableInfo(info.attribute_data);
39543
- var tableWidth = measureLongestLine(tableStr);
39544
- var ruleLen = Math.min(Math.max(title.length, tableWidth), MAX_RULE_LEN);
39545
- str += '\n';
39546
- str += utils.lpad('', ruleLen, '=') + '\n';
39547
- str += title + '\n';
39548
- str += utils.lpad('', ruleLen, '-') + '\n';
39549
- str += formatLayerInfo(info);
39550
- str += tableStr;
39551
- });
39552
- return str;
39553
- }
39554
-
39555
- function formatLayerInfo(data) {
39556
- var str = '';
39557
- str += "Type: " + (data.geometry_type || "tabular data") + "\n";
39558
- str += utils.format("Records: %,d\n",data.feature_count);
39559
- if (data.null_shape_count > 0) {
39560
- str += utils.format("Nulls: %'d", data.null_shape_count) + "\n";
39561
- }
39562
- if (data.geometry_type && data.feature_count > data.null_shape_count) {
39563
- str += "Bounds: " + data.bbox.join(',') + "\n";
39564
- str += "CRS: " + data.proj4 + "\n";
39565
- }
39566
- str += "Source: " + (data.source_file || 'n/a') + "\n";
39567
- return str;
39568
- }
39569
-
39570
- function formatAttributeTableInfo(arr) {
39571
- if (!arr) return "Attribute data: [none]\n";
39572
- var header = "\nAttribute data\n";
39573
- var valKey = 'first_value' in arr[0] ? 'first_value' : 'value';
39574
- var vals = [];
39575
- var fields = [];
39576
- arr.forEach(function(o) {
39577
- fields.push(o.field);
39578
- vals.push(o[valKey]);
39579
- });
39580
- var maxIntegralChars = vals.reduce(function(max, val) {
39581
- if (utils.isNumber(val)) {
39582
- max = Math.max(max, countIntegralChars(val));
39583
- }
39584
- return max;
39585
- }, 0);
39586
- var col1Arr = ['Field'].concat(fields);
39587
- var col2Arr = vals.reduce(function(memo, val) {
39588
- memo.push(formatTableValue(val, maxIntegralChars));
39589
- return memo;
39590
- }, [valKey == 'first_value' ? 'First value' : 'Value']);
39591
- var col1Chars = maxChars(col1Arr);
39592
- var col2Chars = maxChars(col2Arr);
39593
- var sepStr = (utils.rpad('', col1Chars + 2, '-') + '+' +
39594
- utils.rpad('', col2Chars + 2, '-')).substr(0, MAX_RULE_LEN);
39595
- var sepLine = sepStr + '\n';
39596
- var table = '';
39597
- col1Arr.forEach(function(col1, i) {
39598
- var w = stringDisplayWidth(col1);
39599
- table += ' ' + col1 + utils.rpad('', col1Chars - w, ' ') + ' | ' +
39600
- col2Arr[i] + '\n';
39601
- if (i === 0) table += sepLine; // separator after first line
39602
- });
39603
- return header + sepLine + table + sepLine;
39604
- }
39605
-
39606
- function measureLongestLine(str) {
39607
- return Math.max.apply(null, str.split('\n').map(function(line) {return stringDisplayWidth(line);}));
39608
- }
39609
-
39610
- function stringDisplayWidth(str) {
39611
- var w = 0;
39612
- for (var i = 0, n=str.length; i < n; i++) {
39613
- w += charDisplayWidth(str.charCodeAt(i));
39614
- }
39615
- return w;
39616
- }
39617
-
39618
- // see https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
39619
- // this is a simplified version, focusing on double-width CJK chars and ignoring nonprinting etc chars
39620
- function charDisplayWidth(c) {
39621
- if (c >= 0x1100 &&
39622
- (c <= 0x115f || c == 0x2329 || c == 0x232a ||
39623
- (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */
39624
- (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */
39625
- (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */
39626
- (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */
39627
- (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */
39628
- (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */
39629
- (c >= 0xffe0 && c <= 0xffe6) ||
39630
- (c >= 0x20000 && c <= 0x2fffd) ||
39631
- (c >= 0x30000 && c <= 0x3fffd))) return 2;
39632
- return 1;
39633
- }
39634
-
39635
- // TODO: consider polygons with zero area or other invalid geometries
39636
- function countNullShapes(shapes) {
39637
- var count = 0;
39638
- for (var i=0; i<shapes.length; i++) {
39639
- if (!shapes[i] || shapes[i].length === 0) count++;
39640
- }
39641
- return count;
39642
- }
39643
-
39644
- function countNullRecords(records) {
39645
- var count = 0;
39646
- for (var i=0; i<records.length; i++) {
39647
- if (!records[i]) count++;
39648
- }
39649
- return count;
39650
- }
39651
-
39652
- function maxChars(arr) {
39653
- return arr.reduce(function(memo, str) {
39654
- var w = stringDisplayWidth(str);
39655
- return w > memo ? w : memo;
39656
- }, 0);
39657
- }
39658
-
39659
- function formatString(str) {
39660
- var replacements = {
39661
- '\n': '\\n',
39662
- '\r': '\\r',
39663
- '\t': '\\t'
39664
- };
39665
- var cleanChar = function(c) {
39666
- // convert newlines and carriage returns
39667
- // TODO: better handling of non-printing chars
39668
- return c in replacements ? replacements[c] : '';
39669
- };
39670
- str = str.replace(/[\r\t\n]/g, cleanChar);
39671
- return "'" + str + "'";
39672
- }
39673
-
39674
- function countIntegralChars(val) {
39675
- return utils.isNumber(val) ? (utils.formatNumber(val) + '.').indexOf('.') : 0;
39676
- }
39677
-
39678
- function formatTableValue(val, integralChars) {
39679
- var str;
39680
- if (utils.isNumber(val)) {
39681
- str = utils.lpad("", integralChars - countIntegralChars(val), ' ') +
39682
- utils.formatNumber(val);
39683
- } else if (utils.isString(val)) {
39684
- str = formatString(val);
39685
- } else if (utils.isDate(val)) {
39686
- str = utils.formatDateISO(val) + ' (Date)';
39687
- } else if (utils.isObject(val)) { // if {} or [], display JSON
39688
- str = JSON.stringify(val);
39689
- } else {
39690
- str = String(val);
39691
- }
39692
-
39693
- if (typeof str != 'string') {
39694
- // e.g. JSON.stringify converts functions to undefined
39695
- str = '[' + (typeof val) + ']';
39696
- }
39697
-
39698
- return str;
39699
- }
39700
-
39701
- var Info = /*#__PURE__*/Object.freeze({
39702
- __proto__: null,
39703
- getLayerInfo: getLayerInfo,
39704
- getAttributeTableInfo: getAttributeTableInfo,
39705
- formatAttributeTableInfo: formatAttributeTableInfo,
39706
- formatTableValue: formatTableValue
39707
- });
39708
-
39709
39851
  // TODO: make sure that the inlay shapes and data are not shared
39710
39852
  cmd.inlay = function(targetLayers, src, targetDataset, opts) {
39711
39853
  var mergedDataset = mergeLayersForOverlay(targetLayers, targetDataset, src, opts);
@@ -42022,49 +42164,13 @@ ${svg}
42022
42164
  }, {});
42023
42165
  }
42024
42166
 
42025
- // import { importGeoJSON } from '../geojson/geojson-import';
42026
-
42027
- function getTargetProxy(target) {
42028
- var proxy = getLayerInfo(target.layer, target.dataset); // layer_name, feature_count etc
42029
- proxy.layer = target.layer;
42030
- proxy.dataset = target.dataset;
42031
- addGetters(proxy, {
42032
- // export as an object, not a string or buffer
42033
- geojson: getGeoJSON
42034
- });
42035
-
42036
- function getGeoJSON() {
42037
- var features = exportLayerAsGeoJSON(target.layer, target.dataset, {rfc7946: true}, true);
42038
- return {
42039
- type: 'FeatureCollection',
42040
- features: features
42041
- };
42042
- }
42043
-
42044
- return proxy;
42045
- }
42046
-
42047
42167
  // Support for evaluating expressions embedded in curly-brace templates
42048
42168
 
42049
42169
  // Returns: a string (e.g. a command string used by the -run command)
42050
42170
  async function evalTemplateExpression(expression, targets, ctx) {
42051
42171
  ctx = ctx || getBaseContext();
42052
42172
  // TODO: throw an error if target is used when there are multiple targets
42053
- if (targets) {
42054
- var proxies = expandCommandTargets(targets).reduce(function(memo, target) {
42055
- var proxy = getTargetProxy(target);
42056
- memo.push(proxy);
42057
- // index targets by layer name too
42058
- if (target.layer.name) {
42059
- memo[target.layer.name] = proxy;
42060
- }
42061
- return memo;
42062
- }, []);
42063
- Object.defineProperty(ctx, 'targets', {value: proxies});
42064
- if (proxies.length == 1) {
42065
- Object.defineProperty(ctx, 'target', {value: proxies[0]});
42066
- }
42067
- }
42173
+ addTargetProxies(targets, ctx);
42068
42174
  // Add global functions and data to the expression context
42069
42175
  // (e.g. functions imported via the -require command)
42070
42176
  var globals = getStashedVar('defs') || {};
@@ -42113,7 +42219,12 @@ ${svg}
42113
42219
  function applyReplacements(template, replacements) {
42114
42220
  var parts = parseTemplateParts(template);
42115
42221
  return parts.reduce(function(memo, s, i) {
42116
- return i % 2 == 1 ? memo + (replacements.shift() || '') : memo + s;
42222
+ if (i % 2 == 1) {
42223
+ memo += replacements.length ? replacements.shift() : '';
42224
+ } else {
42225
+ memo += s;
42226
+ }
42227
+ return memo;
42117
42228
  }, '');
42118
42229
  }
42119
42230
 
@@ -44550,6 +44661,10 @@ ${svg}
44550
44661
  return done(null);
44551
44662
  }
44552
44663
 
44664
+ if (name == 'comment') {
44665
+ return done(null);
44666
+ }
44667
+
44553
44668
  if (!job) job = new Job();
44554
44669
  job.startCommand(command);
44555
44670
 
@@ -44978,7 +45093,7 @@ ${svg}
44978
45093
  });
44979
45094
  }
44980
45095
 
44981
- var version = "0.6.62";
45096
+ var version = "0.6.65";
44982
45097
 
44983
45098
  // Parse command line args into commands and run them
44984
45099
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -45720,6 +45835,7 @@ ${svg}
45720
45835
  Projections,
45721
45836
  ProjectionParams,
45722
45837
  Rectangle,
45838
+ RectangleGeom,
45723
45839
  Rounding,
45724
45840
  RunCommands,
45725
45841
  Scalebar,