mapshaper 0.6.63 → 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/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
@@ -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
  }
@@ -39388,7 +39756,6 @@ ${svg}
39388
39756
  }
39389
39757
 
39390
39758
  function skipCommand(cmdName, job) {
39391
- // allow all control commands to run
39392
39759
  if (jobIsStopped(job)) return true;
39393
39760
  if (isControlFlowCommand(cmdName)) return false;
39394
39761
  return !inActiveBranch(job);
@@ -39481,240 +39848,6 @@ ${svg}
39481
39848
  utils.extend(getStashedVar('defs'), obj);
39482
39849
  };
39483
39850
 
39484
- var MAX_RULE_LEN = 50;
39485
-
39486
- cmd.info = function(targets, opts) {
39487
- var layers = expandCommandTargets(targets);
39488
- var arr = layers.map(function(o) {
39489
- return getLayerInfo(o.layer, o.dataset);
39490
- });
39491
-
39492
- if (opts.save_to) {
39493
- var output = [{
39494
- filename: opts.save_to + (opts.save_to.endsWith('.json') ? '' : '.json'),
39495
- content: JSON.stringify(arr, null, 2)
39496
- }];
39497
- writeFiles(output, opts);
39498
- }
39499
- if (opts.to_layer) {
39500
- return {
39501
- info: {},
39502
- layers: [{
39503
- name: opts.name || 'info',
39504
- data: new DataTable(arr)
39505
- }]
39506
- };
39507
- }
39508
- message(formatInfo(arr));
39509
- };
39510
-
39511
- cmd.printInfo = cmd.info; // old name
39512
-
39513
- function getLayerInfo(lyr, dataset) {
39514
- var n = getFeatureCount(lyr);
39515
- var o = {
39516
- layer_name: lyr.name,
39517
- geometry_type: lyr.geometry_type,
39518
- feature_count: n,
39519
- null_shape_count: 0,
39520
- null_data_count: lyr.data ? countNullRecords(lyr.data.getRecords()) : n
39521
- };
39522
- if (lyr.shapes && lyr.shapes.length > 0) {
39523
- o.null_shape_count = countNullShapes(lyr.shapes);
39524
- o.bbox = getLayerBounds(lyr, dataset.arcs).toArray();
39525
- o.proj4 = getProjInfo(dataset);
39526
- }
39527
- o.source_file = getLayerSourceFile(lyr, dataset) || null;
39528
- o.attribute_data = getAttributeTableInfo(lyr);
39529
- return o;
39530
- }
39531
-
39532
- // i: (optional) record index
39533
- function getAttributeTableInfo(lyr, i) {
39534
- if (!lyr.data || lyr.data.size() === 0 || lyr.data.getFields().length === 0) {
39535
- return null;
39536
- }
39537
- var fields = applyFieldOrder(lyr.data.getFields(), 'ascending');
39538
- var valueName = i === undefined ? 'first_value' : 'value';
39539
- return fields.map(function(fname) {
39540
- return {
39541
- field: fname,
39542
- [valueName]: lyr.data.getReadOnlyRecordAt(i || 0)[fname]
39543
- };
39544
- });
39545
- }
39546
-
39547
- function formatInfo(arr) {
39548
- var str = '';
39549
- arr.forEach(function(info, i) {
39550
- var title = 'Layer: ' + (info.layer_name || '[unnamed layer]');
39551
- var tableStr = formatAttributeTableInfo(info.attribute_data);
39552
- var tableWidth = measureLongestLine(tableStr);
39553
- var ruleLen = Math.min(Math.max(title.length, tableWidth), MAX_RULE_LEN);
39554
- str += '\n';
39555
- str += utils.lpad('', ruleLen, '=') + '\n';
39556
- str += title + '\n';
39557
- str += utils.lpad('', ruleLen, '-') + '\n';
39558
- str += formatLayerInfo(info);
39559
- str += tableStr;
39560
- });
39561
- return str;
39562
- }
39563
-
39564
- function formatLayerInfo(data) {
39565
- var str = '';
39566
- str += "Type: " + (data.geometry_type || "tabular data") + "\n";
39567
- str += utils.format("Records: %,d\n",data.feature_count);
39568
- if (data.null_shape_count > 0) {
39569
- str += utils.format("Nulls: %'d", data.null_shape_count) + "\n";
39570
- }
39571
- if (data.geometry_type && data.feature_count > data.null_shape_count) {
39572
- str += "Bounds: " + data.bbox.join(',') + "\n";
39573
- str += "CRS: " + data.proj4 + "\n";
39574
- }
39575
- str += "Source: " + (data.source_file || 'n/a') + "\n";
39576
- return str;
39577
- }
39578
-
39579
- function formatAttributeTableInfo(arr) {
39580
- if (!arr) return "Attribute data: [none]\n";
39581
- var header = "\nAttribute data\n";
39582
- var valKey = 'first_value' in arr[0] ? 'first_value' : 'value';
39583
- var vals = [];
39584
- var fields = [];
39585
- arr.forEach(function(o) {
39586
- fields.push(o.field);
39587
- vals.push(o[valKey]);
39588
- });
39589
- var maxIntegralChars = vals.reduce(function(max, val) {
39590
- if (utils.isNumber(val)) {
39591
- max = Math.max(max, countIntegralChars(val));
39592
- }
39593
- return max;
39594
- }, 0);
39595
- var col1Arr = ['Field'].concat(fields);
39596
- var col2Arr = vals.reduce(function(memo, val) {
39597
- memo.push(formatTableValue(val, maxIntegralChars));
39598
- return memo;
39599
- }, [valKey == 'first_value' ? 'First value' : 'Value']);
39600
- var col1Chars = maxChars(col1Arr);
39601
- var col2Chars = maxChars(col2Arr);
39602
- var sepStr = (utils.rpad('', col1Chars + 2, '-') + '+' +
39603
- utils.rpad('', col2Chars + 2, '-')).substr(0, MAX_RULE_LEN);
39604
- var sepLine = sepStr + '\n';
39605
- var table = '';
39606
- col1Arr.forEach(function(col1, i) {
39607
- var w = stringDisplayWidth(col1);
39608
- table += ' ' + col1 + utils.rpad('', col1Chars - w, ' ') + ' | ' +
39609
- col2Arr[i] + '\n';
39610
- if (i === 0) table += sepLine; // separator after first line
39611
- });
39612
- return header + sepLine + table + sepLine;
39613
- }
39614
-
39615
- function measureLongestLine(str) {
39616
- return Math.max.apply(null, str.split('\n').map(function(line) {return stringDisplayWidth(line);}));
39617
- }
39618
-
39619
- function stringDisplayWidth(str) {
39620
- var w = 0;
39621
- for (var i = 0, n=str.length; i < n; i++) {
39622
- w += charDisplayWidth(str.charCodeAt(i));
39623
- }
39624
- return w;
39625
- }
39626
-
39627
- // see https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
39628
- // this is a simplified version, focusing on double-width CJK chars and ignoring nonprinting etc chars
39629
- function charDisplayWidth(c) {
39630
- if (c >= 0x1100 &&
39631
- (c <= 0x115f || c == 0x2329 || c == 0x232a ||
39632
- (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */
39633
- (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */
39634
- (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */
39635
- (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */
39636
- (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */
39637
- (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */
39638
- (c >= 0xffe0 && c <= 0xffe6) ||
39639
- (c >= 0x20000 && c <= 0x2fffd) ||
39640
- (c >= 0x30000 && c <= 0x3fffd))) return 2;
39641
- return 1;
39642
- }
39643
-
39644
- // TODO: consider polygons with zero area or other invalid geometries
39645
- function countNullShapes(shapes) {
39646
- var count = 0;
39647
- for (var i=0; i<shapes.length; i++) {
39648
- if (!shapes[i] || shapes[i].length === 0) count++;
39649
- }
39650
- return count;
39651
- }
39652
-
39653
- function countNullRecords(records) {
39654
- var count = 0;
39655
- for (var i=0; i<records.length; i++) {
39656
- if (!records[i]) count++;
39657
- }
39658
- return count;
39659
- }
39660
-
39661
- function maxChars(arr) {
39662
- return arr.reduce(function(memo, str) {
39663
- var w = stringDisplayWidth(str);
39664
- return w > memo ? w : memo;
39665
- }, 0);
39666
- }
39667
-
39668
- function formatString(str) {
39669
- var replacements = {
39670
- '\n': '\\n',
39671
- '\r': '\\r',
39672
- '\t': '\\t'
39673
- };
39674
- var cleanChar = function(c) {
39675
- // convert newlines and carriage returns
39676
- // TODO: better handling of non-printing chars
39677
- return c in replacements ? replacements[c] : '';
39678
- };
39679
- str = str.replace(/[\r\t\n]/g, cleanChar);
39680
- return "'" + str + "'";
39681
- }
39682
-
39683
- function countIntegralChars(val) {
39684
- return utils.isNumber(val) ? (utils.formatNumber(val) + '.').indexOf('.') : 0;
39685
- }
39686
-
39687
- function formatTableValue(val, integralChars) {
39688
- var str;
39689
- if (utils.isNumber(val)) {
39690
- str = utils.lpad("", integralChars - countIntegralChars(val), ' ') +
39691
- utils.formatNumber(val);
39692
- } else if (utils.isString(val)) {
39693
- str = formatString(val);
39694
- } else if (utils.isDate(val)) {
39695
- str = utils.formatDateISO(val) + ' (Date)';
39696
- } else if (utils.isObject(val)) { // if {} or [], display JSON
39697
- str = JSON.stringify(val);
39698
- } else {
39699
- str = String(val);
39700
- }
39701
-
39702
- if (typeof str != 'string') {
39703
- // e.g. JSON.stringify converts functions to undefined
39704
- str = '[' + (typeof val) + ']';
39705
- }
39706
-
39707
- return str;
39708
- }
39709
-
39710
- var Info = /*#__PURE__*/Object.freeze({
39711
- __proto__: null,
39712
- getLayerInfo: getLayerInfo,
39713
- getAttributeTableInfo: getAttributeTableInfo,
39714
- formatAttributeTableInfo: formatAttributeTableInfo,
39715
- formatTableValue: formatTableValue
39716
- });
39717
-
39718
39851
  // TODO: make sure that the inlay shapes and data are not shared
39719
39852
  cmd.inlay = function(targetLayers, src, targetDataset, opts) {
39720
39853
  var mergedDataset = mergeLayersForOverlay(targetLayers, targetDataset, src, opts);
@@ -42031,49 +42164,13 @@ ${svg}
42031
42164
  }, {});
42032
42165
  }
42033
42166
 
42034
- // import { importGeoJSON } from '../geojson/geojson-import';
42035
-
42036
- function getTargetProxy(target) {
42037
- var proxy = getLayerInfo(target.layer, target.dataset); // layer_name, feature_count etc
42038
- proxy.layer = target.layer;
42039
- proxy.dataset = target.dataset;
42040
- addGetters(proxy, {
42041
- // export as an object, not a string or buffer
42042
- geojson: getGeoJSON
42043
- });
42044
-
42045
- function getGeoJSON() {
42046
- var features = exportLayerAsGeoJSON(target.layer, target.dataset, {rfc7946: true}, true);
42047
- return {
42048
- type: 'FeatureCollection',
42049
- features: features
42050
- };
42051
- }
42052
-
42053
- return proxy;
42054
- }
42055
-
42056
42167
  // Support for evaluating expressions embedded in curly-brace templates
42057
42168
 
42058
42169
  // Returns: a string (e.g. a command string used by the -run command)
42059
42170
  async function evalTemplateExpression(expression, targets, ctx) {
42060
42171
  ctx = ctx || getBaseContext();
42061
42172
  // TODO: throw an error if target is used when there are multiple targets
42062
- if (targets) {
42063
- var proxies = expandCommandTargets(targets).reduce(function(memo, target) {
42064
- var proxy = getTargetProxy(target);
42065
- memo.push(proxy);
42066
- // index targets by layer name too
42067
- if (target.layer.name) {
42068
- memo[target.layer.name] = proxy;
42069
- }
42070
- return memo;
42071
- }, []);
42072
- Object.defineProperty(ctx, 'targets', {value: proxies});
42073
- if (proxies.length == 1) {
42074
- Object.defineProperty(ctx, 'target', {value: proxies[0]});
42075
- }
42076
- }
42173
+ addTargetProxies(targets, ctx);
42077
42174
  // Add global functions and data to the expression context
42078
42175
  // (e.g. functions imported via the -require command)
42079
42176
  var globals = getStashedVar('defs') || {};
@@ -44564,6 +44661,10 @@ ${svg}
44564
44661
  return done(null);
44565
44662
  }
44566
44663
 
44664
+ if (name == 'comment') {
44665
+ return done(null);
44666
+ }
44667
+
44567
44668
  if (!job) job = new Job();
44568
44669
  job.startCommand(command);
44569
44670
 
@@ -44992,7 +45093,7 @@ ${svg}
44992
45093
  });
44993
45094
  }
44994
45095
 
44995
- var version = "0.6.63";
45096
+ var version = "0.6.65";
44996
45097
 
44997
45098
  // Parse command line args into commands and run them
44998
45099
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -45734,6 +45835,7 @@ ${svg}
45734
45835
  Projections,
45735
45836
  ProjectionParams,
45736
45837
  Rectangle,
45838
+ RectangleGeom,
45737
45839
  Rounding,
45738
45840
  RunCommands,
45739
45841
  Scalebar,