mapshaper 0.6.38 → 0.6.40

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
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.6.38";
3
+ var VERSION = "0.6.40";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -65,7 +65,7 @@
65
65
  get rtrim () { return rtrim; },
66
66
  get addThousandsSep () { return addThousandsSep; },
67
67
  get numToStr () { return numToStr; },
68
- get formatNumber () { return formatNumber; },
68
+ get formatNumber () { return formatNumber$1; },
69
69
  get formatIntlNumber () { return formatIntlNumber; },
70
70
  get formatNumberForDisplay () { return formatNumberForDisplay; },
71
71
  get shuffle () { return shuffle; },
@@ -649,12 +649,12 @@
649
649
  return decimals >= 0 ? num.toFixed(decimals) : String(num);
650
650
  }
651
651
 
652
- function formatNumber(val) {
652
+ function formatNumber$1(val) {
653
653
  return val + '';
654
654
  }
655
655
 
656
656
  function formatIntlNumber(val) {
657
- var str = formatNumber(val);
657
+ var str = formatNumber$1(val);
658
658
  return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
659
659
  }
660
660
 
@@ -1247,7 +1247,8 @@
1247
1247
 
1248
1248
  // Handle an error caused by invalid input or misuse of API
1249
1249
  function stop() {
1250
- _stop.apply(null, utils.toArray(arguments));
1250
+ // _stop.apply(null, utils.toArray(arguments));
1251
+ _stop.apply(null, messageArgs(arguments));
1251
1252
  }
1252
1253
 
1253
1254
  function interrupt() {
@@ -4670,12 +4671,11 @@
4670
4671
  albersusa: AlbersUSA
4671
4672
  };
4672
4673
 
4673
- // This stub is replaced when loaded in GUI, which may need to load some files
4674
- function initProjLibrary(opts, done) {
4675
- if (!asyncLoader) return done();
4676
- asyncLoader(opts, done);
4674
+ async function initProjLibrary(opts) {
4675
+ if (asyncLoader) await asyncLoader(opts);
4677
4676
  }
4678
4677
 
4678
+ // used by web UI to support loading projection assets asyncronously
4679
4679
  function setProjectionLoader(loader) {
4680
4680
  asyncLoader = loader;
4681
4681
  }
@@ -13115,6 +13115,110 @@
13115
13115
  });
13116
13116
  }
13117
13117
 
13118
+ // Parse a formatted value in DMS DM or D to a numeric value. Returns NaN if unparsable.
13119
+ // Delimiters: degrees: D|d|°; minutes: '; seconds: "
13120
+ function parseDMS(str, fmt) {
13121
+ var defaultRxp = /^(?<prefix>[nsew+-]?)(?<d>[0-9.]+)[d°]? ?(?<m>[0-9.]*)['′]? ?(?<s>[0-9.]*)["″]? ?(?<suffix>[nsew]?)$/i;
13122
+ var rxp = fmt ? getParseRxp(fmt) : defaultRxp;
13123
+ var match = rxp.exec(str.trim());
13124
+ var d = NaN;
13125
+ var deg, min, sec;
13126
+ if (match) {
13127
+ deg = match.groups.d || '0';
13128
+ min = match.groups.m || '0';
13129
+ sec = match.groups.s || '0';
13130
+ d = (+deg) + (+min) / 60 + (+sec) / 3600;
13131
+ if (/[sw-]/i.test(match.groups.prefix || '') || /[sw]/i.test(match.groups.suffix || '')) {
13132
+ d = -d;
13133
+ }
13134
+ }
13135
+ return d;
13136
+ }
13137
+
13138
+ var cache$1 = {};
13139
+
13140
+ function getParseRxp(fmt) {
13141
+ if (fmt in cache$1) return cache$1[fmt];
13142
+ var rxp = fmt;
13143
+ rxp = rxp.replace('[-]', '(?<prefix>-)?'); // optional -
13144
+ rxp = rxp.replace(/\[[NSEW, +-]{2,}\]/, '(?<prefix>$&)');
13145
+ // TODO: validate that if there are degree decimals, there are no M or S codes
13146
+ rxp = rxp.replace(/D+(\.D+)?/, (m, g1) => {
13147
+ var s = '[0-9]+';
13148
+ if (g1) s += `\\.[0-9]+`;
13149
+ return `(?<d>${s})`;
13150
+ });
13151
+ // TODO: validate that if there are minutes decimals, there are no S codes
13152
+ rxp = rxp.replace(/(MM?)(\.M+)?/, (m, g1, g2) => {
13153
+ var s = g1.length == 1 ? '[0-9]+' : '[0-9][0-9]';
13154
+ if (g2) s += '\\.[0-9]+';
13155
+ return `(?<m>${s})`;
13156
+ });
13157
+ rxp = rxp.replace(/(SS?)(\.S+)?/, (m, g1, g2) => {
13158
+ var s = g1.length == 1 ? '[0-9]+' : '[0-9][0-9]';
13159
+ if (g2) s += '\\.[0-9]+';
13160
+ return `(?<s>${s})`;
13161
+ });
13162
+ rxp = '^' + rxp + '$';
13163
+ try {
13164
+ // TODO: make sure all DMS codes have been matched
13165
+ cache$1[fmt] = new RegExp(rxp);
13166
+ } catch(e) {
13167
+ stop('Invalid DMS format string:', fmt);
13168
+ }
13169
+ return cache$1[fmt];
13170
+ }
13171
+
13172
+ function formatNumber(val, integers, decimals) {
13173
+ var str = val.toFixed(decimals);
13174
+ var parts = str.split('.');
13175
+ if (parts.length > 0) {
13176
+ parts[0] = parts[0].padStart(integers, '0');
13177
+ str = parts.join('.');
13178
+ }
13179
+ return str;
13180
+ }
13181
+
13182
+ function formatDMS(coord, fmt) {
13183
+ if (!fmt) fmt = '[-]D°M\'S.SSS';
13184
+ var str = fmt;
13185
+ var dstr, mstr, sstr;
13186
+ var match = /(D+)[^M]*(M+)?[^S[\]]*(S+)?/.exec(fmt);
13187
+ var gotSeconds = match && !!match[3];
13188
+ var gotMinutes = match && !!match[2];
13189
+ if (!match || gotSeconds && !gotMinutes) {
13190
+ stop('Invalid DMS format string:', fmt);
13191
+ }
13192
+ var integers = gotSeconds && match[3].length || gotMinutes && match[2].length || match[1].length;
13193
+ var decimalRxp = gotSeconds && /S\.(S+)/ || gotMinutes && /M\.(M+)/ || /D\.(D+)/;
13194
+ var decimals = decimalRxp.test(fmt) ? decimalRxp.exec(fmt)[1].length : 0;
13195
+ if (gotMinutes) {
13196
+ var RES = Math.pow(10, decimals);
13197
+ var CONV = gotSeconds ? 3600 * RES : 60 * RES;
13198
+ var r = Math.floor(Math.abs(coord) * CONV + 0.5);
13199
+ var lastPart = formatNumber((r / RES) % 60, integers, decimals);
13200
+ if (gotSeconds) {
13201
+ r = Math.floor(r / (RES * 60));
13202
+ sstr = lastPart;
13203
+ mstr = String(r % 60).padStart(match[2].length, '0');
13204
+ } else {
13205
+ r = Math.floor(r / RES);
13206
+ mstr = lastPart;
13207
+ }
13208
+ dstr = String(Math.floor(r / 60)).padStart(match[1].length, '0');
13209
+ } else {
13210
+ dstr = Math.abs(coord).toFixed(decimals);
13211
+ }
13212
+ str = str.replace(/\[-\]/, s => coord < 0 ? '-' : '');
13213
+ str = str.replace(/\[[+-]+\]/, s => coord < 0 ? '-' : '+');
13214
+ str = str.replace(/\[[NS, ]+\]/, s => coord < 0 ? 'S' : 'N');
13215
+ str = str.replace(/\[[EW, ]+\]/, s => coord < 0 ? 'W' : 'E');
13216
+ str = str.replace(/D+(\.D+)?/, dstr);
13217
+ if (gotMinutes) str = str.replace(/M+(\.M+)?/, mstr);
13218
+ if (gotSeconds) str = str.replace(/S+(\.S+)?/, sstr);
13219
+ return str;
13220
+ }
13221
+
13118
13222
  function cleanExpression(exp) {
13119
13223
  // workaround for problem in GNU Make v4: end-of-line backslashes inside
13120
13224
  // quoted strings are left in the string (other shell environments remove them)
@@ -13126,7 +13230,9 @@
13126
13230
  round: roundToDigits2,
13127
13231
  int_median: interpolated_median,
13128
13232
  sprintf: utils.format,
13129
- blend: blend
13233
+ blend: blend,
13234
+ format_dms: formatDMS,
13235
+ parse_dms: parseDMS
13130
13236
  });
13131
13237
  }
13132
13238
 
@@ -17598,12 +17704,18 @@
17598
17704
  }
17599
17705
 
17600
17706
  function calcFrameData(dataset, opts) {
17601
- var bounds = getDatasetBounds(dataset);
17602
- var bounds2 = calcOutputSizeInPixels(bounds, opts);
17707
+ var bounds;
17708
+ if (opts.svg_bbox) {
17709
+ bounds = new Bounds(opts.svg_bbox);
17710
+ opts = Object.assign({margin: 0}, opts); // prevent default pixel margin around content
17711
+ } else {
17712
+ bounds = getDatasetBounds(dataset);
17713
+ }
17714
+ var pixBounds = calcOutputSizeInPixels(bounds, opts);
17603
17715
  return {
17604
17716
  bbox: bounds.toArray(),
17605
- width: Math.round(bounds2.width()),
17606
- height: Math.round(bounds2.height()) || 1,
17717
+ width: Math.round(pixBounds.width()),
17718
+ height: Math.round(pixBounds.height()) || 1,
17607
17719
  type: 'frame'
17608
17720
  };
17609
17721
  }
@@ -22791,7 +22903,6 @@ ${svg}
22791
22903
 
22792
22904
  this.getHelpMessage = function(cmdName) {
22793
22905
  var helpCommands, singleCommand, lines;
22794
-
22795
22906
  if (cmdName) {
22796
22907
  singleCommand = findCommandDefn(cmdName, getCommands());
22797
22908
  if (!singleCommand) {
@@ -22910,10 +23021,6 @@ ${svg}
22910
23021
  }
22911
23022
  };
22912
23023
 
22913
- this.printHelp = function(command) {
22914
- print(this.getHelpMessage(command));
22915
- };
22916
-
22917
23024
  function getCommands() {
22918
23025
  return _commands.map(function(cmd) {
22919
23026
  return cmd.done();
@@ -23343,6 +23450,10 @@ ${svg}
23343
23450
  describe: '[SVG] source units per pixel (alternative to width= option)',
23344
23451
  type: 'number'
23345
23452
  })
23453
+ .option('svg-bbox', {
23454
+ describe: '[SVG] bounding box of SVG map in projected map units',
23455
+ type: 'bbox'
23456
+ })
23346
23457
  .option('point-symbol', {
23347
23458
  describe: '[SVG] circle or square (default is circle)'
23348
23459
  })
@@ -23633,7 +23744,7 @@ ${svg}
23633
23744
  type: 'flag'
23634
23745
  })
23635
23746
  .option('other', {
23636
- describe: 'default color for categorical scheme (defaults to no-data color)'
23747
+ describe: 'default color for categorical scheme (default is nodata color)'
23637
23748
  })
23638
23749
  .option('nodata', {
23639
23750
  describe: 'color to use for invalid or missing data (default is white)'
@@ -24561,7 +24672,7 @@ ${svg}
24561
24672
  parser.command('symbols')
24562
24673
  .describe('symbolize points as arrows, circles, stars, polygons, etc.')
24563
24674
  .option('type', {
24564
- describe: 'symbol type (e.g. arrow, circle, square, star, polygon, ring)'
24675
+ describe: 'types: arrow, circle, square, star, polygon, ring'
24565
24676
  })
24566
24677
  .option('stroke', {})
24567
24678
  .option('stroke-width', {})
@@ -24575,8 +24686,7 @@ ${svg}
24575
24686
  describe: 'symbol line width (linear symbols only)'
24576
24687
  })
24577
24688
  .option('opacity', {
24578
- describe: 'symbol opacity',
24579
- type: 'number'
24689
+ describe: 'symbol opacity'
24580
24690
  })
24581
24691
  .option('geographic', {
24582
24692
  old_alias: 'polygons',
@@ -24678,7 +24788,6 @@ ${svg}
24678
24788
  .option('no-replace', noReplaceOpt);
24679
24789
  // .option('name', nameOpt);
24680
24790
 
24681
-
24682
24791
  parser.command('target')
24683
24792
  .describe('set active layer (or layers)')
24684
24793
  .option('target', {
@@ -24688,6 +24797,14 @@ ${svg}
24688
24797
  .option('type', {
24689
24798
  describe: 'type of layer to target (polygon|polyline|point)'
24690
24799
  })
24800
+ // .option('combine', {
24801
+ // type: 'flag',
24802
+ // describe: 'place all targeted layers in one dataset together with any associated layers'
24803
+ // })
24804
+ // .option('isolate', {
24805
+ // type: 'flag',
24806
+ // describe: 'place all targeted layers in one dataset exclusive of associated layers'
24807
+ // })
24691
24808
  .option('name', {
24692
24809
  describe: 'rename the target layer'
24693
24810
  });
@@ -38520,6 +38637,11 @@ ${svg}
38520
38637
  };
38521
38638
  }
38522
38639
 
38640
+ cmd.printHelp = function(opts) {
38641
+ var str = getOptionParser().getHelpMessage(opts.command);
38642
+ print(str);
38643
+ };
38644
+
38523
38645
  function resetControlFlow(job) {
38524
38646
  job.control = null;
38525
38647
  }
@@ -39159,25 +39281,6 @@ ${svg}
39159
39281
  };
39160
39282
  }
39161
39283
 
39162
- // Parse a formatted value in DMS DM or D to a numeric value. Returns NaN if unparsable.
39163
- // Delimiters: degrees: D|d|°; minutes: '; seconds: "
39164
- function parseDMS(str) {
39165
- var rxp = /^([nsew+-]?)([0-9.]+)[d°]? ?([0-9.]*)['′]? ?([0-9.]*)["″]? ?([nsew]?)$/i;
39166
- var match = rxp.exec(str.trim());
39167
- var d = NaN;
39168
- var deg, min, sec;
39169
- if (match) {
39170
- deg = match[2] || '0';
39171
- min = match[3] || '0';
39172
- sec = match[4] || '0';
39173
- d = (+deg) + (+min) / 60 + (+sec) / 3600;
39174
- if (/[sw-]/i.test(match[1]) || /[sw]/i.test(match[5])) {
39175
- d = -d;
39176
- }
39177
- }
39178
- return d;
39179
- }
39180
-
39181
39284
  function findNearestVertices(p, shp, arcs) {
39182
39285
  var p2 = findNearestVertex(p[0], p[1], shp, arcs);
39183
39286
  return findVertexIds(p2.x, p2.y, arcs);
@@ -42827,9 +42930,41 @@ ${svg}
42827
42930
  // TODO: improve this
42828
42931
  targets[0].layers[0].name = opts.name;
42829
42932
  }
42933
+ if (opts.combine) {
42934
+ targets = combineTargets(targets, catalog);
42935
+ } else if (opts.isolate) {
42936
+ targets = isolateTargets(targets, catalog);
42937
+ }
42830
42938
  catalog.setDefaultTargets(targets);
42831
42939
  };
42832
42940
 
42941
+ function combineTargets(targets, catalog) {
42942
+ var datasets = [];
42943
+ var layers = [];
42944
+ targets.forEach(function(o) {
42945
+ datasets.push(o.dataset);
42946
+ catalog.removeDataset(o.dataset);
42947
+ layers = layers.concat(o.layers);
42948
+ });
42949
+ var combined = mergeDatasets(datasets);
42950
+ catalog.addDataset(combined);
42951
+ return [{
42952
+ dataset: combined,
42953
+ layers: layers
42954
+ }];
42955
+ }
42956
+
42957
+ function isolateTargets(targets, catalog) {
42958
+ var datasets = [];
42959
+ targets.forEach(function(o) {
42960
+ datasets.push(o.dataset);
42961
+ o.layers.forEach(function(lyr) {
42962
+ catalog.removeLayer(lyr, o.dataset);
42963
+
42964
+ });
42965
+ });
42966
+ }
42967
+
42833
42968
  cmd.union = function(targetLayers, targetDataset, opts) {
42834
42969
  if (targetLayers.length < 2) {
42835
42970
  stop('Command requires at least two target layers');
@@ -43214,7 +43349,7 @@ ${svg}
43214
43349
  function commandAcceptsMultipleTargetDatasets(name) {
43215
43350
  return name == 'rotate' || name == 'info' || name == 'proj' ||
43216
43351
  name == 'drop' || name == 'target' || name == 'if' || name == 'elif' ||
43217
- name == 'else' || name == 'endif' || name == 'run';
43352
+ name == 'else' || name == 'endif' || name == 'run' || name == 'i';
43218
43353
  }
43219
43354
 
43220
43355
  function commandAcceptsEmptyTarget(name) {
@@ -43403,8 +43538,8 @@ ${svg}
43403
43538
  job.catalog.addDataset(cmd.graticule(targetDataset, opts));
43404
43539
 
43405
43540
  } else if (name == 'help') {
43406
- // placing this here to handle errors from invalid command names
43407
- getOptionParser().printHelp(command.options.command);
43541
+ // placing help command here to handle errors from invalid command names
43542
+ cmd.printHelp(command.options);
43408
43543
 
43409
43544
  } else if (name == 'i') {
43410
43545
  if (opts.replace) job.catalog = new Catalog(); // is this what we want?
@@ -43486,7 +43621,7 @@ ${svg}
43486
43621
  cmd.print(command._.join(' '));
43487
43622
 
43488
43623
  } else if (name == 'proj') {
43489
- await utils.promisify(initProjLibrary)(opts);
43624
+ await initProjLibrary(opts);
43490
43625
  job.resumeCommand();
43491
43626
  targets.forEach(function(targ) {
43492
43627
  cmd.proj(targ.dataset, job.catalog, opts);
@@ -44321,6 +44456,7 @@ ${svg}
44321
44456
  Heap,
44322
44457
  NodeCollection,
44323
44458
  parseDMS,
44459
+ formatDMS,
44324
44460
  PathIndex,
44325
44461
  PolygonIndex,
44326
44462
  ShpReader,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.6.38",
3
+ "version": "0.6.40",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
package/www/index.html CHANGED
@@ -149,19 +149,27 @@
149
149
  </div>
150
150
  <div class="export-zip-option option-menu">
151
151
 
152
+ <div style="height:10px"></div>
153
+
152
154
  </div>
153
155
  <h4>File format</h4>
154
156
  <div class="export-formats option-menu">
155
157
  </div>
156
158
 
157
- <div class="option-menu"><input type="text" class="advanced-options" placeholder="command line options" />
159
+ <div style="height:10px"></div>
160
+
161
+ <div class="option-menu"><input type="text" class="text-input advanced-options" placeholder="command line options" />
158
162
  <a href="https://github.com/mbloch/mapshaper/wiki/Command-Reference#-o-output" target="_mapshaper_output_docs">
159
163
  <div class="tip-button">?<div class="tip-anchor">
160
164
  <div class="tip">Enter options from the command line interface for
161
165
  the -o command. Examples: bbox no-quantization
162
166
  precision=0.001. Click to see all options.</div></div></div></a>
163
-
164
167
  </div>
168
+
169
+ <!-- <div class="option-menu">
170
+ <input id="ofile-name" class="text-input" type="text" placeholder="output file name" />
171
+ </div> -->
172
+
165
173
  <!-- <div class="cancel-btn btn dialog-btn">Cancel</div> -->
166
174
  <div class="save-btn btn dialog-btn">Export</div>
167
175
  <span id="save-preference"><input type="checkbox"/>choose directory</span>
@@ -288,7 +296,7 @@ apply to TopoJSON files.</div></div></div></div> -->
288
296
 
289
297
  </div>
290
298
 
291
- <div><input type="text" class="advanced-options" placeholder="import options" />
299
+ <div><input type="text" class="text-input advanced-options" placeholder="import options" />
292
300
  <a href="https://github.com/mbloch/mapshaper/wiki/Command-Reference#-i-input" target="_mapshaper_import_docs">
293
301
  <div class="tip-button">?<div class="tip-anchor">
294
302
  <div class="tip">Enter options from the command line
@@ -1265,7 +1265,6 @@
1265
1265
  .onCancel(done);
1266
1266
  }
1267
1267
 
1268
-
1269
1268
  async function saveBlobToSelectedFile(filename, blob, done) {
1270
1269
  // see: https://developer.chrome.com/articles/file-system-access/
1271
1270
  // note: saving multiple files to a directory using showDirectoryPicker()
@@ -1427,7 +1426,6 @@
1427
1426
  // }
1428
1427
 
1429
1428
  snapshots.forEach(function(item, i) {
1430
- var id = i + 1;
1431
1429
  var line = El('div').appendTo(menu).addClass('save-menu-item');
1432
1430
  El('span').appendTo(line).html(`<span class="save-item-label">#${item.number}</span> `);
1433
1431
  // show snapshot size
@@ -1440,7 +1438,10 @@
1440
1438
  var obj = await idb.get(item.id);
1441
1439
  await internal.applyCompression(obj, {consume: true});
1442
1440
  var buf = internal.pack(obj);
1443
- saveBlobToLocalFile('mapshaper_snapshot.msx', new Blob([buf]));
1441
+ // choose output filename and directory every time
1442
+ // saveBlobToLocalFile('mapshaper_snapshot.msx', new Blob([buf]));
1443
+ var fileName = `snapshot-${String(item.number).padStart(2, '0')}.msx`;
1444
+ saveBlobToSelectedFile(fileName, new Blob([buf]), function() {});
1444
1445
  }).text('export');
1445
1446
  El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
1446
1447
  await removeSnapshotById(item.id);
@@ -2479,42 +2480,18 @@
2479
2480
  });
2480
2481
  }
2481
2482
 
2483
+ internal.setProjectionLoader(loadProjLibs);
2484
+
2482
2485
  // load Proj.4 CRS definition files dynamically
2483
2486
  //
2484
- internal.setProjectionLoader(function(opts, done) {
2487
+ async function loadProjLibs(opts) {
2485
2488
  var mproj = require('mproj');
2486
2489
  var libs = internal.findProjLibs([opts.init || '', opts.match || '', opts.crs || ''].join(' '));
2487
- // skip loaded libs
2488
- libs = libs.filter(function(name) {return !mproj.internal.mproj_search_libcache(name);});
2489
- loadProjLibs(libs, done);
2490
- });
2491
-
2492
- function loadProjLibs(libs, done) {
2493
- var mproj = require('mproj');
2494
- var i = 0;
2495
- next();
2496
-
2497
- function next() {
2498
- var libName = libs[i];
2499
- var content, req;
2500
- if (!libName) return done();
2501
- req = new XMLHttpRequest();
2502
- req.addEventListener('load', function(e) {
2503
- if (req.status == 200) {
2504
- content = req.response;
2505
- }
2506
- });
2507
- req.addEventListener('loadend', function() {
2508
- if (content) {
2509
- mproj.internal.mproj_insert_libcache(libName, content);
2510
- }
2511
- // TODO: consider stopping with an error message if no content was loaded
2512
- // (currently, a less specific error will occur when mapshaper tries to use the library)
2513
- next();
2514
- });
2515
- req.open('GET', 'assets/' + libName);
2516
- req.send();
2517
- i++;
2490
+ libs = libs.filter(function(name) {return !mproj.internal.mproj_search_libcache(name);}); // skip loaded libs
2491
+ for (var libName of libs) {
2492
+ var content = await fetch('assets/' + libName).then(resp => resp.ok ? resp.text() : null);
2493
+ if (!content) stop$1(`Unable to load projection resource [${libName}]`);
2494
+ mproj.internal.mproj_insert_libcache(libName, content);
2518
2495
  }
2519
2496
  }
2520
2497
 
@@ -3371,7 +3348,10 @@
3371
3348
  setDisplayProjection(gui, cmd);
3372
3349
  } else {
3373
3350
  line.hide(); // hide cursor while command is being run
3374
- runMapshaperCommands(cmd, function(err) {
3351
+ runMapshaperCommands(cmd, function(err, flags) {
3352
+ if (flags) {
3353
+ gui.clearMode();
3354
+ }
3375
3355
  if (err) {
3376
3356
  onError(err);
3377
3357
  }
@@ -3404,14 +3384,9 @@
3404
3384
  }
3405
3385
  }
3406
3386
  if (flags) {
3407
- // if the command may have changed data, and a tool with an edit mode is being used,
3408
- // close the tool. (we may need a better way to allow the console and other tools
3409
- // to be used at the same time).
3410
- gui.clearMode();
3411
-
3412
3387
  model.updated(flags); // info commands do not return flags
3413
3388
  }
3414
- done(err);
3389
+ done(err, flags);
3415
3390
  });
3416
3391
  }
3417
3392
 
@@ -3670,6 +3645,7 @@
3670
3645
  var layersArr = [];
3671
3646
  var toggleBtn = null; // checkbox <input> for toggling layer selection
3672
3647
  var exportBtn = gui.container.findChild('.export-btn');
3648
+ var ofileName = gui.container.findChild('#ofile-name');
3673
3649
  new SimpleButton(menu.findChild('.close2-btn')).on('click', gui.clearMode);
3674
3650
 
3675
3651
  if (!GUI.exportIsSupported()) {
@@ -3755,7 +3731,7 @@
3755
3731
  // done: function(string|Error|null)
3756
3732
  async function exportMenuSelection(layers) {
3757
3733
  var opts = getExportOpts();
3758
- // note: command line "target" option gets ignored
3734
+ // note: command line "target" option gets ignored
3759
3735
  var files = await internal.exportTargetLayers(layers, opts);
3760
3736
  gui.session.layersExported(getTargetLayerIds(), getExportOptsAsString());
3761
3737
  await utils$1.promisify(internal.writeFiles)(files, opts);
@@ -5068,7 +5044,8 @@
5068
5044
 
5069
5045
  // Handle an error caused by invalid input or misuse of API
5070
5046
  function stop() {
5071
- _stop.apply(null, utils.toArray(arguments));
5047
+ // _stop.apply(null, utils.toArray(arguments));
5048
+ _stop.apply(null, messageArgs(arguments));
5072
5049
  }
5073
5050
 
5074
5051
  function interrupt() {
@@ -7976,10 +7953,11 @@
7976
7953
  hit.clearSelection();
7977
7954
  });
7978
7955
 
7979
- function runCommand(cmd) {
7956
+ function runCommand(cmd, turnOff) {
7980
7957
  popup.hide();
7981
7958
  if (gui.console) gui.console.runMapshaperCommands(cmd, function(err) {
7982
7959
  reset();
7960
+ if (turnOff) gui.clearMode();
7983
7961
  });
7984
7962
  }
7985
7963
  }
@@ -10311,6 +10289,7 @@
10311
10289
  if (gui.console) {
10312
10290
  gui.console.runMapshaperCommands(cmd, function(err) {
10313
10291
  reset();
10292
+ gui.clearMode();
10314
10293
  });
10315
10294
  }
10316
10295
  // reset(); // TODO: exit interactive mode
package/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.6.38";
3
+ var VERSION = "0.6.40";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -65,7 +65,7 @@
65
65
  get rtrim () { return rtrim; },
66
66
  get addThousandsSep () { return addThousandsSep; },
67
67
  get numToStr () { return numToStr; },
68
- get formatNumber () { return formatNumber; },
68
+ get formatNumber () { return formatNumber$1; },
69
69
  get formatIntlNumber () { return formatIntlNumber; },
70
70
  get formatNumberForDisplay () { return formatNumberForDisplay; },
71
71
  get shuffle () { return shuffle; },
@@ -649,12 +649,12 @@
649
649
  return decimals >= 0 ? num.toFixed(decimals) : String(num);
650
650
  }
651
651
 
652
- function formatNumber(val) {
652
+ function formatNumber$1(val) {
653
653
  return val + '';
654
654
  }
655
655
 
656
656
  function formatIntlNumber(val) {
657
- var str = formatNumber(val);
657
+ var str = formatNumber$1(val);
658
658
  return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
659
659
  }
660
660
 
@@ -1247,7 +1247,8 @@
1247
1247
 
1248
1248
  // Handle an error caused by invalid input or misuse of API
1249
1249
  function stop() {
1250
- _stop.apply(null, utils.toArray(arguments));
1250
+ // _stop.apply(null, utils.toArray(arguments));
1251
+ _stop.apply(null, messageArgs(arguments));
1251
1252
  }
1252
1253
 
1253
1254
  function interrupt() {
@@ -4670,12 +4671,11 @@
4670
4671
  albersusa: AlbersUSA
4671
4672
  };
4672
4673
 
4673
- // This stub is replaced when loaded in GUI, which may need to load some files
4674
- function initProjLibrary(opts, done) {
4675
- if (!asyncLoader) return done();
4676
- asyncLoader(opts, done);
4674
+ async function initProjLibrary(opts) {
4675
+ if (asyncLoader) await asyncLoader(opts);
4677
4676
  }
4678
4677
 
4678
+ // used by web UI to support loading projection assets asyncronously
4679
4679
  function setProjectionLoader(loader) {
4680
4680
  asyncLoader = loader;
4681
4681
  }
@@ -13115,6 +13115,110 @@
13115
13115
  });
13116
13116
  }
13117
13117
 
13118
+ // Parse a formatted value in DMS DM or D to a numeric value. Returns NaN if unparsable.
13119
+ // Delimiters: degrees: D|d|°; minutes: '; seconds: "
13120
+ function parseDMS(str, fmt) {
13121
+ var defaultRxp = /^(?<prefix>[nsew+-]?)(?<d>[0-9.]+)[d°]? ?(?<m>[0-9.]*)['′]? ?(?<s>[0-9.]*)["″]? ?(?<suffix>[nsew]?)$/i;
13122
+ var rxp = fmt ? getParseRxp(fmt) : defaultRxp;
13123
+ var match = rxp.exec(str.trim());
13124
+ var d = NaN;
13125
+ var deg, min, sec;
13126
+ if (match) {
13127
+ deg = match.groups.d || '0';
13128
+ min = match.groups.m || '0';
13129
+ sec = match.groups.s || '0';
13130
+ d = (+deg) + (+min) / 60 + (+sec) / 3600;
13131
+ if (/[sw-]/i.test(match.groups.prefix || '') || /[sw]/i.test(match.groups.suffix || '')) {
13132
+ d = -d;
13133
+ }
13134
+ }
13135
+ return d;
13136
+ }
13137
+
13138
+ var cache$1 = {};
13139
+
13140
+ function getParseRxp(fmt) {
13141
+ if (fmt in cache$1) return cache$1[fmt];
13142
+ var rxp = fmt;
13143
+ rxp = rxp.replace('[-]', '(?<prefix>-)?'); // optional -
13144
+ rxp = rxp.replace(/\[[NSEW, +-]{2,}\]/, '(?<prefix>$&)');
13145
+ // TODO: validate that if there are degree decimals, there are no M or S codes
13146
+ rxp = rxp.replace(/D+(\.D+)?/, (m, g1) => {
13147
+ var s = '[0-9]+';
13148
+ if (g1) s += `\\.[0-9]+`;
13149
+ return `(?<d>${s})`;
13150
+ });
13151
+ // TODO: validate that if there are minutes decimals, there are no S codes
13152
+ rxp = rxp.replace(/(MM?)(\.M+)?/, (m, g1, g2) => {
13153
+ var s = g1.length == 1 ? '[0-9]+' : '[0-9][0-9]';
13154
+ if (g2) s += '\\.[0-9]+';
13155
+ return `(?<m>${s})`;
13156
+ });
13157
+ rxp = rxp.replace(/(SS?)(\.S+)?/, (m, g1, g2) => {
13158
+ var s = g1.length == 1 ? '[0-9]+' : '[0-9][0-9]';
13159
+ if (g2) s += '\\.[0-9]+';
13160
+ return `(?<s>${s})`;
13161
+ });
13162
+ rxp = '^' + rxp + '$';
13163
+ try {
13164
+ // TODO: make sure all DMS codes have been matched
13165
+ cache$1[fmt] = new RegExp(rxp);
13166
+ } catch(e) {
13167
+ stop('Invalid DMS format string:', fmt);
13168
+ }
13169
+ return cache$1[fmt];
13170
+ }
13171
+
13172
+ function formatNumber(val, integers, decimals) {
13173
+ var str = val.toFixed(decimals);
13174
+ var parts = str.split('.');
13175
+ if (parts.length > 0) {
13176
+ parts[0] = parts[0].padStart(integers, '0');
13177
+ str = parts.join('.');
13178
+ }
13179
+ return str;
13180
+ }
13181
+
13182
+ function formatDMS(coord, fmt) {
13183
+ if (!fmt) fmt = '[-]D°M\'S.SSS';
13184
+ var str = fmt;
13185
+ var dstr, mstr, sstr;
13186
+ var match = /(D+)[^M]*(M+)?[^S[\]]*(S+)?/.exec(fmt);
13187
+ var gotSeconds = match && !!match[3];
13188
+ var gotMinutes = match && !!match[2];
13189
+ if (!match || gotSeconds && !gotMinutes) {
13190
+ stop('Invalid DMS format string:', fmt);
13191
+ }
13192
+ var integers = gotSeconds && match[3].length || gotMinutes && match[2].length || match[1].length;
13193
+ var decimalRxp = gotSeconds && /S\.(S+)/ || gotMinutes && /M\.(M+)/ || /D\.(D+)/;
13194
+ var decimals = decimalRxp.test(fmt) ? decimalRxp.exec(fmt)[1].length : 0;
13195
+ if (gotMinutes) {
13196
+ var RES = Math.pow(10, decimals);
13197
+ var CONV = gotSeconds ? 3600 * RES : 60 * RES;
13198
+ var r = Math.floor(Math.abs(coord) * CONV + 0.5);
13199
+ var lastPart = formatNumber((r / RES) % 60, integers, decimals);
13200
+ if (gotSeconds) {
13201
+ r = Math.floor(r / (RES * 60));
13202
+ sstr = lastPart;
13203
+ mstr = String(r % 60).padStart(match[2].length, '0');
13204
+ } else {
13205
+ r = Math.floor(r / RES);
13206
+ mstr = lastPart;
13207
+ }
13208
+ dstr = String(Math.floor(r / 60)).padStart(match[1].length, '0');
13209
+ } else {
13210
+ dstr = Math.abs(coord).toFixed(decimals);
13211
+ }
13212
+ str = str.replace(/\[-\]/, s => coord < 0 ? '-' : '');
13213
+ str = str.replace(/\[[+-]+\]/, s => coord < 0 ? '-' : '+');
13214
+ str = str.replace(/\[[NS, ]+\]/, s => coord < 0 ? 'S' : 'N');
13215
+ str = str.replace(/\[[EW, ]+\]/, s => coord < 0 ? 'W' : 'E');
13216
+ str = str.replace(/D+(\.D+)?/, dstr);
13217
+ if (gotMinutes) str = str.replace(/M+(\.M+)?/, mstr);
13218
+ if (gotSeconds) str = str.replace(/S+(\.S+)?/, sstr);
13219
+ return str;
13220
+ }
13221
+
13118
13222
  function cleanExpression(exp) {
13119
13223
  // workaround for problem in GNU Make v4: end-of-line backslashes inside
13120
13224
  // quoted strings are left in the string (other shell environments remove them)
@@ -13126,7 +13230,9 @@
13126
13230
  round: roundToDigits2,
13127
13231
  int_median: interpolated_median,
13128
13232
  sprintf: utils.format,
13129
- blend: blend
13233
+ blend: blend,
13234
+ format_dms: formatDMS,
13235
+ parse_dms: parseDMS
13130
13236
  });
13131
13237
  }
13132
13238
 
@@ -17598,12 +17704,18 @@
17598
17704
  }
17599
17705
 
17600
17706
  function calcFrameData(dataset, opts) {
17601
- var bounds = getDatasetBounds(dataset);
17602
- var bounds2 = calcOutputSizeInPixels(bounds, opts);
17707
+ var bounds;
17708
+ if (opts.svg_bbox) {
17709
+ bounds = new Bounds(opts.svg_bbox);
17710
+ opts = Object.assign({margin: 0}, opts); // prevent default pixel margin around content
17711
+ } else {
17712
+ bounds = getDatasetBounds(dataset);
17713
+ }
17714
+ var pixBounds = calcOutputSizeInPixels(bounds, opts);
17603
17715
  return {
17604
17716
  bbox: bounds.toArray(),
17605
- width: Math.round(bounds2.width()),
17606
- height: Math.round(bounds2.height()) || 1,
17717
+ width: Math.round(pixBounds.width()),
17718
+ height: Math.round(pixBounds.height()) || 1,
17607
17719
  type: 'frame'
17608
17720
  };
17609
17721
  }
@@ -22791,7 +22903,6 @@ ${svg}
22791
22903
 
22792
22904
  this.getHelpMessage = function(cmdName) {
22793
22905
  var helpCommands, singleCommand, lines;
22794
-
22795
22906
  if (cmdName) {
22796
22907
  singleCommand = findCommandDefn(cmdName, getCommands());
22797
22908
  if (!singleCommand) {
@@ -22910,10 +23021,6 @@ ${svg}
22910
23021
  }
22911
23022
  };
22912
23023
 
22913
- this.printHelp = function(command) {
22914
- print(this.getHelpMessage(command));
22915
- };
22916
-
22917
23024
  function getCommands() {
22918
23025
  return _commands.map(function(cmd) {
22919
23026
  return cmd.done();
@@ -23343,6 +23450,10 @@ ${svg}
23343
23450
  describe: '[SVG] source units per pixel (alternative to width= option)',
23344
23451
  type: 'number'
23345
23452
  })
23453
+ .option('svg-bbox', {
23454
+ describe: '[SVG] bounding box of SVG map in projected map units',
23455
+ type: 'bbox'
23456
+ })
23346
23457
  .option('point-symbol', {
23347
23458
  describe: '[SVG] circle or square (default is circle)'
23348
23459
  })
@@ -23633,7 +23744,7 @@ ${svg}
23633
23744
  type: 'flag'
23634
23745
  })
23635
23746
  .option('other', {
23636
- describe: 'default color for categorical scheme (defaults to no-data color)'
23747
+ describe: 'default color for categorical scheme (default is nodata color)'
23637
23748
  })
23638
23749
  .option('nodata', {
23639
23750
  describe: 'color to use for invalid or missing data (default is white)'
@@ -24561,7 +24672,7 @@ ${svg}
24561
24672
  parser.command('symbols')
24562
24673
  .describe('symbolize points as arrows, circles, stars, polygons, etc.')
24563
24674
  .option('type', {
24564
- describe: 'symbol type (e.g. arrow, circle, square, star, polygon, ring)'
24675
+ describe: 'types: arrow, circle, square, star, polygon, ring'
24565
24676
  })
24566
24677
  .option('stroke', {})
24567
24678
  .option('stroke-width', {})
@@ -24575,8 +24686,7 @@ ${svg}
24575
24686
  describe: 'symbol line width (linear symbols only)'
24576
24687
  })
24577
24688
  .option('opacity', {
24578
- describe: 'symbol opacity',
24579
- type: 'number'
24689
+ describe: 'symbol opacity'
24580
24690
  })
24581
24691
  .option('geographic', {
24582
24692
  old_alias: 'polygons',
@@ -24678,7 +24788,6 @@ ${svg}
24678
24788
  .option('no-replace', noReplaceOpt);
24679
24789
  // .option('name', nameOpt);
24680
24790
 
24681
-
24682
24791
  parser.command('target')
24683
24792
  .describe('set active layer (or layers)')
24684
24793
  .option('target', {
@@ -24688,6 +24797,14 @@ ${svg}
24688
24797
  .option('type', {
24689
24798
  describe: 'type of layer to target (polygon|polyline|point)'
24690
24799
  })
24800
+ // .option('combine', {
24801
+ // type: 'flag',
24802
+ // describe: 'place all targeted layers in one dataset together with any associated layers'
24803
+ // })
24804
+ // .option('isolate', {
24805
+ // type: 'flag',
24806
+ // describe: 'place all targeted layers in one dataset exclusive of associated layers'
24807
+ // })
24691
24808
  .option('name', {
24692
24809
  describe: 'rename the target layer'
24693
24810
  });
@@ -38520,6 +38637,11 @@ ${svg}
38520
38637
  };
38521
38638
  }
38522
38639
 
38640
+ cmd.printHelp = function(opts) {
38641
+ var str = getOptionParser().getHelpMessage(opts.command);
38642
+ print(str);
38643
+ };
38644
+
38523
38645
  function resetControlFlow(job) {
38524
38646
  job.control = null;
38525
38647
  }
@@ -39159,25 +39281,6 @@ ${svg}
39159
39281
  };
39160
39282
  }
39161
39283
 
39162
- // Parse a formatted value in DMS DM or D to a numeric value. Returns NaN if unparsable.
39163
- // Delimiters: degrees: D|d|°; minutes: '; seconds: "
39164
- function parseDMS(str) {
39165
- var rxp = /^([nsew+-]?)([0-9.]+)[d°]? ?([0-9.]*)['′]? ?([0-9.]*)["″]? ?([nsew]?)$/i;
39166
- var match = rxp.exec(str.trim());
39167
- var d = NaN;
39168
- var deg, min, sec;
39169
- if (match) {
39170
- deg = match[2] || '0';
39171
- min = match[3] || '0';
39172
- sec = match[4] || '0';
39173
- d = (+deg) + (+min) / 60 + (+sec) / 3600;
39174
- if (/[sw-]/i.test(match[1]) || /[sw]/i.test(match[5])) {
39175
- d = -d;
39176
- }
39177
- }
39178
- return d;
39179
- }
39180
-
39181
39284
  function findNearestVertices(p, shp, arcs) {
39182
39285
  var p2 = findNearestVertex(p[0], p[1], shp, arcs);
39183
39286
  return findVertexIds(p2.x, p2.y, arcs);
@@ -42827,9 +42930,41 @@ ${svg}
42827
42930
  // TODO: improve this
42828
42931
  targets[0].layers[0].name = opts.name;
42829
42932
  }
42933
+ if (opts.combine) {
42934
+ targets = combineTargets(targets, catalog);
42935
+ } else if (opts.isolate) {
42936
+ targets = isolateTargets(targets, catalog);
42937
+ }
42830
42938
  catalog.setDefaultTargets(targets);
42831
42939
  };
42832
42940
 
42941
+ function combineTargets(targets, catalog) {
42942
+ var datasets = [];
42943
+ var layers = [];
42944
+ targets.forEach(function(o) {
42945
+ datasets.push(o.dataset);
42946
+ catalog.removeDataset(o.dataset);
42947
+ layers = layers.concat(o.layers);
42948
+ });
42949
+ var combined = mergeDatasets(datasets);
42950
+ catalog.addDataset(combined);
42951
+ return [{
42952
+ dataset: combined,
42953
+ layers: layers
42954
+ }];
42955
+ }
42956
+
42957
+ function isolateTargets(targets, catalog) {
42958
+ var datasets = [];
42959
+ targets.forEach(function(o) {
42960
+ datasets.push(o.dataset);
42961
+ o.layers.forEach(function(lyr) {
42962
+ catalog.removeLayer(lyr, o.dataset);
42963
+
42964
+ });
42965
+ });
42966
+ }
42967
+
42833
42968
  cmd.union = function(targetLayers, targetDataset, opts) {
42834
42969
  if (targetLayers.length < 2) {
42835
42970
  stop('Command requires at least two target layers');
@@ -43214,7 +43349,7 @@ ${svg}
43214
43349
  function commandAcceptsMultipleTargetDatasets(name) {
43215
43350
  return name == 'rotate' || name == 'info' || name == 'proj' ||
43216
43351
  name == 'drop' || name == 'target' || name == 'if' || name == 'elif' ||
43217
- name == 'else' || name == 'endif' || name == 'run';
43352
+ name == 'else' || name == 'endif' || name == 'run' || name == 'i';
43218
43353
  }
43219
43354
 
43220
43355
  function commandAcceptsEmptyTarget(name) {
@@ -43403,8 +43538,8 @@ ${svg}
43403
43538
  job.catalog.addDataset(cmd.graticule(targetDataset, opts));
43404
43539
 
43405
43540
  } else if (name == 'help') {
43406
- // placing this here to handle errors from invalid command names
43407
- getOptionParser().printHelp(command.options.command);
43541
+ // placing help command here to handle errors from invalid command names
43542
+ cmd.printHelp(command.options);
43408
43543
 
43409
43544
  } else if (name == 'i') {
43410
43545
  if (opts.replace) job.catalog = new Catalog(); // is this what we want?
@@ -43486,7 +43621,7 @@ ${svg}
43486
43621
  cmd.print(command._.join(' '));
43487
43622
 
43488
43623
  } else if (name == 'proj') {
43489
- await utils.promisify(initProjLibrary)(opts);
43624
+ await initProjLibrary(opts);
43490
43625
  job.resumeCommand();
43491
43626
  targets.forEach(function(targ) {
43492
43627
  cmd.proj(targ.dataset, job.catalog, opts);
@@ -44321,6 +44456,7 @@ ${svg}
44321
44456
  Heap,
44322
44457
  NodeCollection,
44323
44458
  parseDMS,
44459
+ formatDMS,
44324
44460
  PathIndex,
44325
44461
  PolygonIndex,
44326
44462
  ShpReader,
package/www/page.css CHANGED
@@ -242,10 +242,6 @@ body {
242
242
  white-space: nowrap;
243
243
  }
244
244
 
245
- .export-options .option-menu .advanced-options {
246
- width: 195px;
247
- }
248
-
249
245
  /* --- Main area ------------- */
250
246
 
251
247
  .main-area {
@@ -450,14 +446,14 @@ body.dragover #import-options-drop-area .drop-area {
450
446
 
451
447
  /* --- Import screen -------------- */
452
448
 
453
-
454
- .option-menu .advanced-options {
455
- width: 250px;
449
+ .option-menu .text-input {
450
+ width: 230px;
456
451
  background: rgba(255, 255, 255, 0.4);
457
452
  border: 1px solid #999;
458
453
  padding: 0 3px;
459
454
  height: 18px;
460
455
  font-size: 13px;
456
+ margin: 0 0 5px 0;
461
457
  }
462
458
 
463
459
  #mshp-not-supported {
@@ -548,10 +544,6 @@ body.dragover #import-options-drop-area .drop-area {
548
544
  font-size: 90%;
549
545
  }
550
546
 
551
- .option-menu {
552
- padding: 0 0 9px 0;
553
- }
554
-
555
547
  /*.option-menu input[type="radio"],
556
548
  .option-menu input[type="checkbox"] */
557
549
  .info-box input[type="radio"],