mapshaper 0.6.45 → 0.6.46

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.45";
3
+ var VERSION = "0.6.46";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -1813,6 +1813,23 @@
1813
1813
  return [xmin, ymin, xmax, ymax];
1814
1814
  }
1815
1815
 
1816
+
1817
+ function findArcIdFromVertexId(i, ii) {
1818
+ // binary search
1819
+ // possible optimization: use interpolation to find a better partition value.
1820
+ var lower = 0, upper = ii.length - 1;
1821
+ var middle;
1822
+ while (lower < upper) {
1823
+ middle = Math.ceil((lower + upper) / 2);
1824
+ if (i < ii[middle]) {
1825
+ upper = middle - 1;
1826
+ } else {
1827
+ lower = middle;
1828
+ }
1829
+ }
1830
+ return lower; // assumes dataset is not empty
1831
+ }
1832
+
1816
1833
  function deleteVertex(arcs, i) {
1817
1834
  var data = arcs.getVertexData();
1818
1835
  var nn = data.nn;
@@ -1939,6 +1956,7 @@
1939
1956
  __proto__: null,
1940
1957
  absArcId: absArcId,
1941
1958
  calcArcBounds: calcArcBounds,
1959
+ findArcIdFromVertexId: findArcIdFromVertexId,
1942
1960
  deleteVertex: deleteVertex,
1943
1961
  insertVertex: insertVertex,
1944
1962
  countFilteredVertices: countFilteredVertices,
@@ -3405,7 +3423,7 @@
3405
3423
  var item;
3406
3424
  for (var i=0; i<arr.length; i++) {
3407
3425
  item = arr[i];
3408
- if (item instanceof Array) {
3426
+ if (Array.isArray(item)) {
3409
3427
  forEachArcId(item, cb);
3410
3428
  } else if (utils.isInteger(item)) {
3411
3429
  var val = cb(item);
@@ -6953,6 +6971,15 @@
6953
6971
  utils.defaults(destInfo, srcInfo);
6954
6972
  }
6955
6973
 
6974
+ function copyDatasetInfo(info) {
6975
+ // not a deep copy... objects like info.crs are read-only, so copy-by-reference
6976
+ // should be ok
6977
+ var info2 = Object.assign({}, info);
6978
+ if (Array.isArray(info.input_files)) {
6979
+ info2.input_files = info.input_files.concat();
6980
+ }
6981
+ return info2;
6982
+ }
6956
6983
 
6957
6984
  function splitApartLayers(dataset, layers) {
6958
6985
  var datasets = [];
@@ -7119,6 +7146,7 @@
7119
7146
  __proto__: null,
7120
7147
  splitDataset: splitDataset,
7121
7148
  mergeDatasetInfo: mergeDatasetInfo,
7149
+ copyDatasetInfo: copyDatasetInfo,
7122
7150
  splitApartLayers: splitApartLayers,
7123
7151
  copyDataset: copyDataset,
7124
7152
  copyDatasetForExport: copyDatasetForExport,
@@ -15481,6 +15509,17 @@
15481
15509
 
15482
15510
  // Keep track of whether positive or negative integer ids are 'used' or not.
15483
15511
 
15512
+
15513
+ function SimpleIdTestIndex(n) {
15514
+ var index = new Uint8Array(n);
15515
+ this.setId = function(id) {
15516
+ index[id] = 1;
15517
+ };
15518
+ this.hasId = function(id) {
15519
+ return index[id] === 1;
15520
+ };
15521
+ }
15522
+
15484
15523
  function IdTestIndex(n) {
15485
15524
  var index = new Uint8Array(n);
15486
15525
  var setList = [];
@@ -15743,15 +15782,30 @@
15743
15782
  getHoleDivider: getHoleDivider
15744
15783
  });
15745
15784
 
15746
- // Convert an array of intersections into an ArcCollection (for display)
15747
- //
15748
-
15749
15785
  function getIntersectionPoints(intersections) {
15750
15786
  return intersections.map(function(obj) {
15751
15787
  return [obj.x, obj.y];
15752
15788
  });
15753
15789
  }
15754
15790
 
15791
+ function getIntersectionLayer(intersections, lyr, arcs) {
15792
+ // return {geometry_type: 'point', shapes: [getIntersectionPoints(XX)]};
15793
+ var ii = arcs.getVertexData().ii;
15794
+ var index = new SimpleIdTestIndex(arcs.size());
15795
+ forEachArcId(lyr.shapes, arcId => {
15796
+ index.setId(absArcId(arcId));
15797
+ });
15798
+ var points = [];
15799
+ intersections.forEach(obj => {
15800
+ var arc1 = findArcIdFromVertexId(obj.a[0], ii);
15801
+ var arc2 = findArcIdFromVertexId(obj.b[0], ii);
15802
+ if (index.hasId(arc1) && index.hasId(arc2)) {
15803
+ points.push([obj.x, obj.y]);
15804
+ }
15805
+ });
15806
+ return {geometry_type: 'point', shapes: [points]};
15807
+ }
15808
+
15755
15809
  // Identify intersecting segments in an ArcCollection
15756
15810
  //
15757
15811
  // To find all intersections:
@@ -15999,6 +16053,7 @@
15999
16053
  var SegmentIntersection = /*#__PURE__*/Object.freeze({
16000
16054
  __proto__: null,
16001
16055
  getIntersectionPoints: getIntersectionPoints,
16056
+ getIntersectionLayer: getIntersectionLayer,
16002
16057
  findSegmentIntersections: findSegmentIntersections,
16003
16058
  sortIntersections: sortIntersections,
16004
16059
  dedupIntersections: dedupIntersections,
@@ -33396,7 +33451,7 @@ ${svg}
33396
33451
  if (n != values.length) {
33397
33452
  stop('Mismatch in number of categories and number of values');
33398
33453
  }
33399
- return values;
33454
+ return parseValues(values); // convert numerical strings to numbers
33400
33455
  }
33401
33456
 
33402
33457
  function getIndexes(n) {
@@ -33564,7 +33619,7 @@ ${svg}
33564
33619
  if (opts.index_field) {
33565
33620
  dataField = opts.index_field;
33566
33621
  fieldType = getColumnType(opts.field, records);
33567
- } else if (opts.field) {
33622
+ } else if (opts.field) {
33568
33623
  dataField = opts.field;
33569
33624
  fieldType = getColumnType(opts.field, records);
33570
33625
  }
@@ -33596,6 +33651,9 @@ ${svg}
33596
33651
  if ((!opts.categories || opts.categories.includes('*')) && dataField) {
33597
33652
  opts.categories = getUniqFieldValues(records, dataField);
33598
33653
  }
33654
+ if (opts.categories && fieldType == 'number') {
33655
+ opts.categories = opts.categories.map(str => +str);
33656
+ }
33599
33657
  }
33600
33658
 
33601
33659
  if (opts.classes) {
@@ -34740,34 +34798,48 @@ ${svg}
34740
34798
  }
34741
34799
 
34742
34800
  function getFeatureEditor(lyr, dataset) {
34743
- var changed = false;
34744
34801
  var api = {};
34745
34802
  // need to copy attribute to avoid circular references if geojson is assigned
34746
34803
  // to a data property.
34747
34804
  var copy = copyLayer(lyr);
34748
34805
  var features = exportLayerAsGeoJSON(copy, dataset, {}, true);
34806
+ var features2 = [];
34749
34807
 
34750
34808
  api.get = function(i) {
34809
+ if (i > 0) features[i-1] = null; // garbage-collect old features
34751
34810
  return features[i];
34752
34811
  };
34753
34812
 
34754
34813
  api.set = function(feat, i) {
34755
- changed = true;
34814
+ var arr;
34815
+
34756
34816
  if (utils.isString(feat)) {
34757
34817
  feat = JSON.parse(feat);
34758
34818
  }
34759
- features[i] = GeoJSON.toFeature(feat); // TODO: validate
34819
+
34820
+ if (!feat) return;
34821
+
34822
+ if (feat.type == 'GeometryCollection') {
34823
+ arr = feat.geometries.map(geom => GeoJSON.toFeature(geom));
34824
+ } else if (feat.type == 'FeatureCollection') {
34825
+ arr = feat.features;
34826
+ } else {
34827
+ feat = GeoJSON.toFeature(feat);
34828
+ }
34829
+
34830
+ if (arr) {
34831
+ features2 = features2.concat(arr);
34832
+ } else {
34833
+ features2.push(feat);
34834
+ }
34760
34835
  };
34761
34836
 
34762
34837
  api.done = function() {
34763
- if (!changed) return; // read-only expression
34764
- // TODO: validate number of features, etc.
34838
+ if (features2.length === 0) return; // read-only expression
34765
34839
  var geojson = {
34766
34840
  type: 'FeatureCollection',
34767
- features: features
34841
+ features: features2
34768
34842
  };
34769
-
34770
- // console.log(JSON.stringify(geojson, null, 2))
34771
34843
  return importGeoJSON(geojson);
34772
34844
  };
34773
34845
  return api;
@@ -40530,9 +40602,8 @@ ${svg}
40530
40602
  cmd.polygonGrid = function(targetLayers, targetDataset, opts) {
40531
40603
  requireProjectedDataset(targetDataset);
40532
40604
  var params = getGridParams(targetLayers, targetDataset, opts);
40533
- var gridDataset = makeGridDataset(params);
40534
-
40535
- gridDataset.info = targetDataset.info; // copy CRS to grid dataset // TODO: improve
40605
+ var gridDataset = makeGridDataset(params); // grid is a new dataset
40606
+ gridDataset.info = copyDatasetInfo(targetDataset.info);
40536
40607
  setOutputLayerName(gridDataset.layers[0], null, 'grid', opts);
40537
40608
  if (opts.debug) gridDataset.layers.push(cmd.pointGrid2(targetLayers, targetDataset, opts));
40538
40609
  return gridDataset;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.6.45",
3
+ "version": "0.6.46",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
package/www/index.html CHANGED
@@ -156,7 +156,7 @@
156
156
  <div class="export-formats option-menu">
157
157
  </div>
158
158
 
159
- <div style="height:10px"></div>
159
+ <div style="height:11px"></div>
160
160
 
161
161
  <div class="option-menu"><input type="text" class="text-input advanced-options" placeholder="command line options" />
162
162
  <a href="https://github.com/mbloch/mapshaper/wiki/Command-Reference#-o-output" target="_mapshaper_output_docs">
@@ -164,15 +164,15 @@
164
164
  <div class="tip">Enter options from the command line interface for
165
165
  the -o command. Examples: bbox no-quantization
166
166
  precision=0.001. Click to see all options.</div></div></div></a>
167
- </div>
167
+
168
168
 
169
169
  <!-- <div class="option-menu">
170
170
  <input id="ofile-name" class="text-input" type="text" placeholder="output file name" />
171
171
  </div> -->
172
-
173
- <div class="save-btn btn dialog-btn">Export</div>
174
- <!-- <span id="save-to-clipboard" class="inline-checkbox" style="display: inline-block;"><input type="checkbox"/>save to clipboard</span> -->
175
- <span id="save-preference" class="inline-checkbox" style="display: none;"><input type="checkbox"/>choose directory</span>
172
+ <div><span id="save-preference" style="display: none;"><input type="checkbox"/>choose output directory</span></div>
173
+ <div><span id="save-to-clipboard" style="display: inline-block;"><input type="checkbox"/>save to clipboard</span></div>
174
+ </div>
175
+ <div id="export-btn" class="btn dialog-btn" style="margin-top: 4px;">Export</div>
176
176
 
177
177
  </div>
178
178
  </div>
@@ -3504,6 +3504,7 @@
3504
3504
  // keeping a reference to current arcs and intersections, so intersections
3505
3505
  // don't need to be recalculated when 'repair' button is pressed.
3506
3506
  _currArcs,
3507
+ _currLayer,
3507
3508
  _currXX;
3508
3509
 
3509
3510
  gui.on('simplify_drag_start', hide);
@@ -3524,8 +3525,8 @@
3524
3525
  });
3525
3526
 
3526
3527
  repairBtn.on('click', function() {
3527
- var fixed = internal.repairIntersections(_currArcs, _currXX);
3528
- showIntersections(fixed, _currArcs);
3528
+ _currXX = internal.repairIntersections(_currArcs, _currXX);
3529
+ showIntersections();
3529
3530
  repairBtn.addClass('disabled');
3530
3531
  model.updated({repair: true});
3531
3532
  gui.session.simplificationRepair();
@@ -3573,13 +3574,17 @@
3573
3574
  showBtn = false;
3574
3575
  }
3575
3576
  el.show();
3576
- showIntersections(XX, arcs);
3577
+ _currLayer = e.layer;
3578
+ _currArcs = arcs;
3579
+ _currXX = XX;
3580
+ showIntersections();
3577
3581
  repairBtn.classed('disabled', !showBtn);
3578
3582
  }
3579
3583
 
3580
3584
  function reset() {
3581
3585
  _currArcs = null;
3582
3586
  _currXX = null;
3587
+ _currLayer = null;
3583
3588
  hide();
3584
3589
  }
3585
3590
 
@@ -3590,13 +3595,11 @@
3590
3595
  reset();
3591
3596
  }
3592
3597
 
3593
- function showIntersections(XX, arcs) {
3594
- var n = XX.length, pointLyr;
3595
- _currXX = XX;
3596
- _currArcs = arcs;
3598
+ function showIntersections() {
3599
+ var n = _currXX.length, pointLyr;
3597
3600
  if (n > 0) {
3598
3601
  // console.log("first intersection:", internal.getIntersectionDebugData(XX[0], arcs));
3599
- pointLyr = {geometry_type: 'point', shapes: [internal.getIntersectionPoints(XX)]};
3602
+ pointLyr = internal.getIntersectionLayer(_currXX, _currLayer, _currArcs);
3600
3603
  map.setIntersectionLayer(pointLyr, {layers:[pointLyr]});
3601
3604
  readout.html(utils$1.format('<span class="icon"></span>%s line intersection%s <img class="close-btn" src="images/close.png">', n, utils$1.pluralSuffix(n)));
3602
3605
  readout.findChild('.close-btn').on('click', dismiss);
@@ -3672,11 +3675,12 @@
3672
3675
  return;
3673
3676
  }
3674
3677
 
3675
- new SimpleButton(menu.findChild('.save-btn').addClass('default-btn')).on('click', onExportClick);
3678
+ new SimpleButton(menu.findChild('#export-btn').addClass('default-btn')).on('click', onExportClick);
3676
3679
  gui.addMode('export', turnOn, turnOff, exportBtn);
3677
3680
  gui.keyboard.onMenuSubmit(menu, onExportClick);
3681
+ var savePreferenceCheckbox;
3678
3682
  if (window.showSaveFilePicker) {
3679
- menu.findChild('#save-preference')
3683
+ savePreferenceCheckbox = menu.findChild('#save-preference')
3680
3684
  .css('display', 'inline-block')
3681
3685
  .findChild('input')
3682
3686
  .on('change', function() {
@@ -3684,11 +3688,44 @@
3684
3688
  })
3685
3689
  .attr('checked', GUI.getSavedValue('choose-save-dir') || null);
3686
3690
  }
3691
+ var clipboardCheckbox = menu.findChild('#save-to-clipboard')
3692
+ .findChild('input')
3693
+ .on('change', function() {
3694
+ updateExportCheckboxes();
3695
+ });
3696
+
3697
+ function setDisabled(inputEl, flag) {
3698
+ if (!inputEl) return;
3699
+ inputEl.node().disabled = !!flag;
3700
+ inputEl.parent().css({color: flag ? '#bbb' : 'black'});
3701
+ }
3702
+
3703
+ function checkboxOn(inputEl) {
3704
+ if (!inputEl) return false;
3705
+ return inputEl.node().checked && !inputEl.node().disabled;
3706
+ }
3707
+
3708
+ function updateExportCheckboxes() {
3709
+ // disable cliboard if not usable
3710
+ var canUseClipboard = clipboardIsAvailable();
3711
+ setDisabled(clipboardCheckbox, !canUseClipboard);
3712
+
3713
+ // disable save to directory checkbox if clipboard is selected
3714
+ setDisabled(savePreferenceCheckbox, checkboxOn(clipboardCheckbox));
3715
+ }
3716
+
3717
+ function clipboardIsAvailable() {
3718
+ var layers = getSelectedLayerEntries();
3719
+ var fmt = getSelectedFormat();
3720
+ return layers.length == 1 && ['json', 'geojson', 'dsv', 'topojson'].includes(fmt);
3721
+ }
3722
+
3687
3723
 
3688
3724
  function turnOn() {
3689
3725
  layersArr = initLayerMenu();
3690
3726
  // initZipOption();
3691
3727
  initFormatMenu();
3728
+ updateExportCheckboxes();
3692
3729
  menu.show();
3693
3730
  }
3694
3731
 
@@ -3697,22 +3734,25 @@
3697
3734
  menu.hide();
3698
3735
  }
3699
3736
 
3700
- function getSelectedLayers() {
3701
- var targets = layersArr.reduce(function(memo, o) {
3737
+ function getSelectedLayerEntries() {
3738
+ return layersArr.reduce(function(memo, o) {
3702
3739
  return o.checkbox.checked ? memo.concat(o.target) : memo;
3703
3740
  }, []);
3704
- return internal.groupLayersByDataset(targets);
3741
+ }
3742
+
3743
+ function getExportTargets() {
3744
+ return internal.groupLayersByDataset(getSelectedLayerEntries());
3705
3745
  }
3706
3746
 
3707
3747
  function onExportClick() {
3708
- var layers = getSelectedLayers();
3709
- if (layers.length === 0) {
3748
+ var targets = getExportTargets();
3749
+ if (targets.length === 0) {
3710
3750
  return gui.alert('No layers were selected');
3711
3751
  }
3712
3752
  gui.clearMode();
3713
3753
  gui.showProgressMessage('Exporting');
3714
3754
  setTimeout(function() {
3715
- exportMenuSelection(layers).catch(function(err) {
3755
+ exportMenuSelection(targets).catch(function(err) {
3716
3756
  if (utils$1.isString(err)) {
3717
3757
  gui.alert(err);
3718
3758
  } else {
@@ -3742,12 +3782,22 @@
3742
3782
  }
3743
3783
 
3744
3784
  // done: function(string|Error|null)
3745
- async function exportMenuSelection(layers) {
3785
+ async function exportMenuSelection(targets) {
3746
3786
  var opts = getExportOpts();
3747
3787
  // note: command line "target" option gets ignored
3748
- var files = await internal.exportTargetLayers(layers, opts);
3788
+ var files = await internal.exportTargetLayers(targets, opts);
3749
3789
  gui.session.layersExported(getTargetLayerIds(), getExportOptsAsString());
3750
- await utils$1.promisify(internal.writeFiles)(files, opts);
3790
+ if (files.length == 1 && checkboxOn(clipboardCheckbox)) {
3791
+ await saveFileContentToClipboard(files[0].content);
3792
+ } else {
3793
+ await utils$1.promisify(internal.writeFiles)(files, opts);
3794
+ }
3795
+
3796
+ }
3797
+
3798
+ async function saveFileContentToClipboard(content) {
3799
+ var str = utils$1.isString(content) ? content : content.toString();
3800
+ await navigator.clipboard.writeText(str);
3751
3801
  }
3752
3802
 
3753
3803
  function initLayerItem(o, i) {
@@ -3833,6 +3883,7 @@
3833
3883
  }
3834
3884
 
3835
3885
  function updateToggleBtn() {
3886
+ updateExportCheckboxes(); // checkbox visibility is affected by number of export layers
3836
3887
  if (!toggleBtn) return;
3837
3888
  var state = getSelectionState();
3838
3889
  // style of intermediate checkbox state doesn't look right in Chrome --
@@ -3853,30 +3904,49 @@
3853
3904
  return 'some';
3854
3905
  }
3855
3906
 
3856
- function getInputFormats() {
3857
- return model.getDatasets().reduce(function(memo, d) {
3858
- var fmts = d.info && d.info.input_formats || [];
3859
- return memo.concat(fmts);
3860
- }, []);
3861
- }
3862
-
3863
3907
  function getDefaultExportFormat() {
3864
3908
  var dataset = model.getActiveLayer().dataset;
3865
- return dataset.info && dataset.info.input_formats &&
3866
- dataset.info.input_formats[0] || 'geojson';
3909
+ var inputFmt = dataset.info && dataset.info.input_formats &&
3910
+ dataset.info.input_formats[0];
3911
+ return getExportFormats().includes(inputFmt) ? inputFmt : 'geojson';
3912
+ }
3913
+
3914
+ function getExportFormats() {
3915
+ // return ['shapefile', 'geojson', 'topojson', 'json', 'dsv', 'kml', 'svg', internal.PACKAGE_EXT];
3916
+ return ['shapefile', 'json', 'geojson', 'dsv', 'topojson', 'kml', internal.PACKAGE_EXT, 'svg'];
3867
3917
  }
3868
3918
 
3869
3919
  function initFormatMenu() {
3870
- var defaults = ['shapefile', 'geojson', 'topojson', 'json', 'dsv', 'kml', 'svg', internal.PACKAGE_EXT];
3871
- var formats = utils$1.uniq(defaults.concat(getInputFormats()));
3920
+ var formats = getExportFormats();
3921
+ // var formats = utils.uniq(getExportFormats().concat(getInputFormats()));
3872
3922
  var items = formats.map(function(fmt) {
3873
- return utils$1.format('<div><label><input type="radio" name="format" value="%s"' +
3874
- ' class="radio">%s</label></div>', fmt, internal.getFormatName(fmt));
3923
+ return utils$1.format('<td><label><input type="radio" name="format" value="%s"' +
3924
+ ' class="radio">%s</label></td>', fmt, internal.getFormatName(fmt));
3875
3925
  });
3876
- menu.findChild('.export-formats').html(items.join('\n'));
3926
+ var table = '<table>';
3927
+ for (var i=0; i<items.length; i+=2) {
3928
+ table += '<tr>' + items[i] + items[i+1] + '<tr>';
3929
+ }
3930
+ table += '</table>';
3931
+
3932
+ // menu.findChild('.export-formats').html(items.join('\n'));
3933
+ menu.findChild('.export-formats').html(table);
3877
3934
  menu.findChild('.export-formats input[value="' + getDefaultExportFormat() + '"]').node().checked = true;
3935
+ // update save-as settings when value changes
3936
+ menu.findChildren('input[type="radio"]').forEach(el => {
3937
+ el.on('change', updateExportCheckboxes);
3938
+ });
3878
3939
  }
3879
3940
 
3941
+
3942
+ // function getInputFormats() {
3943
+ // return model.getDatasets().reduce(function(memo, d) {
3944
+ // var fmts = d.info && d.info.input_formats || [];
3945
+ // return memo.concat(fmts);
3946
+ // }, []);
3947
+ // }
3948
+
3949
+
3880
3950
  function initZipOption() {
3881
3951
  var html = `<label><input type="checkbox">Save to .zip file</label>`;
3882
3952
  menu.findChild('.export-zip-option').html(html);
@@ -6303,6 +6373,23 @@
6303
6373
  return [xmin, ymin, xmax, ymax];
6304
6374
  }
6305
6375
 
6376
+
6377
+ function findArcIdFromVertexId(i, ii) {
6378
+ // binary search
6379
+ // possible optimization: use interpolation to find a better partition value.
6380
+ var lower = 0, upper = ii.length - 1;
6381
+ var middle;
6382
+ while (lower < upper) {
6383
+ middle = Math.ceil((lower + upper) / 2);
6384
+ if (i < ii[middle]) {
6385
+ upper = middle - 1;
6386
+ } else {
6387
+ lower = middle;
6388
+ }
6389
+ }
6390
+ return lower; // assumes dataset is not empty
6391
+ }
6392
+
6306
6393
  function deleteVertex(arcs, i) {
6307
6394
  var data = arcs.getVertexData();
6308
6395
  var nn = data.nn;
@@ -8177,8 +8264,8 @@
8177
8264
  var popup = showPopupAlert('', 'Add field');
8178
8265
  var el = popup.container();
8179
8266
  el.addClass('option-menu');
8180
- var html = `<input type="text" class="field-name text-input" placeholder="field name"><br>
8181
- <input type="text" class="field-value text-input" placeholder="value"><br>
8267
+ var html = `<div><input type="text" class="field-name text-input" placeholder="field name"></div>
8268
+ <div><input type="text" class="field-value text-input" placeholder="value"><div>
8182
8269
  <div tabindex="0" class="btn dialog-btn">Apply</div> <span class="inline-checkbox"><input type="checkbox" class="all" />assign value to all records</span>`;
8183
8270
  el.html(html);
8184
8271
 
@@ -11495,6 +11582,9 @@
11495
11582
  new SessionSnapshots(gui);
11496
11583
  }
11497
11584
 
11585
+ var msgCount = 0;
11586
+ var clearMsg;
11587
+
11498
11588
  initModeRules(gui);
11499
11589
 
11500
11590
  gui.showProgressMessage = function(msg) {
@@ -11503,10 +11593,25 @@
11503
11593
  .appendTo('body');
11504
11594
  }
11505
11595
  El('<div>').text(msg).appendTo(gui.progressMessage.empty().show());
11596
+ clearMsg = getClearFunction(msgCount);
11506
11597
  };
11507
11598
 
11599
+ function getClearFunction(count) {
11600
+ var time = Date.now();
11601
+ // wait at least [min] milliseconds before closing
11602
+ var min = 400;
11603
+ msgCount = ++count;
11604
+ return function() {
11605
+ setTimeout(function() {
11606
+ if (count != msgCount) return;
11607
+ if (gui.progressMessage) gui.progressMessage.hide();
11608
+ }, Math.max(min - (Date.now() - time), 0));
11609
+ };
11610
+ }
11611
+
11508
11612
  gui.clearProgressMessage = function() {
11509
- if (gui.progressMessage) gui.progressMessage.hide();
11613
+ clearMsg();
11614
+ // if (gui.progressMessage) gui.progressMessage.hide();
11510
11615
  };
11511
11616
 
11512
11617
  gui.consoleIsOpen = function() {
package/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.6.45";
3
+ var VERSION = "0.6.46";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -1813,6 +1813,23 @@
1813
1813
  return [xmin, ymin, xmax, ymax];
1814
1814
  }
1815
1815
 
1816
+
1817
+ function findArcIdFromVertexId(i, ii) {
1818
+ // binary search
1819
+ // possible optimization: use interpolation to find a better partition value.
1820
+ var lower = 0, upper = ii.length - 1;
1821
+ var middle;
1822
+ while (lower < upper) {
1823
+ middle = Math.ceil((lower + upper) / 2);
1824
+ if (i < ii[middle]) {
1825
+ upper = middle - 1;
1826
+ } else {
1827
+ lower = middle;
1828
+ }
1829
+ }
1830
+ return lower; // assumes dataset is not empty
1831
+ }
1832
+
1816
1833
  function deleteVertex(arcs, i) {
1817
1834
  var data = arcs.getVertexData();
1818
1835
  var nn = data.nn;
@@ -1939,6 +1956,7 @@
1939
1956
  __proto__: null,
1940
1957
  absArcId: absArcId,
1941
1958
  calcArcBounds: calcArcBounds,
1959
+ findArcIdFromVertexId: findArcIdFromVertexId,
1942
1960
  deleteVertex: deleteVertex,
1943
1961
  insertVertex: insertVertex,
1944
1962
  countFilteredVertices: countFilteredVertices,
@@ -3405,7 +3423,7 @@
3405
3423
  var item;
3406
3424
  for (var i=0; i<arr.length; i++) {
3407
3425
  item = arr[i];
3408
- if (item instanceof Array) {
3426
+ if (Array.isArray(item)) {
3409
3427
  forEachArcId(item, cb);
3410
3428
  } else if (utils.isInteger(item)) {
3411
3429
  var val = cb(item);
@@ -6953,6 +6971,15 @@
6953
6971
  utils.defaults(destInfo, srcInfo);
6954
6972
  }
6955
6973
 
6974
+ function copyDatasetInfo(info) {
6975
+ // not a deep copy... objects like info.crs are read-only, so copy-by-reference
6976
+ // should be ok
6977
+ var info2 = Object.assign({}, info);
6978
+ if (Array.isArray(info.input_files)) {
6979
+ info2.input_files = info.input_files.concat();
6980
+ }
6981
+ return info2;
6982
+ }
6956
6983
 
6957
6984
  function splitApartLayers(dataset, layers) {
6958
6985
  var datasets = [];
@@ -7119,6 +7146,7 @@
7119
7146
  __proto__: null,
7120
7147
  splitDataset: splitDataset,
7121
7148
  mergeDatasetInfo: mergeDatasetInfo,
7149
+ copyDatasetInfo: copyDatasetInfo,
7122
7150
  splitApartLayers: splitApartLayers,
7123
7151
  copyDataset: copyDataset,
7124
7152
  copyDatasetForExport: copyDatasetForExport,
@@ -15481,6 +15509,17 @@
15481
15509
 
15482
15510
  // Keep track of whether positive or negative integer ids are 'used' or not.
15483
15511
 
15512
+
15513
+ function SimpleIdTestIndex(n) {
15514
+ var index = new Uint8Array(n);
15515
+ this.setId = function(id) {
15516
+ index[id] = 1;
15517
+ };
15518
+ this.hasId = function(id) {
15519
+ return index[id] === 1;
15520
+ };
15521
+ }
15522
+
15484
15523
  function IdTestIndex(n) {
15485
15524
  var index = new Uint8Array(n);
15486
15525
  var setList = [];
@@ -15743,15 +15782,30 @@
15743
15782
  getHoleDivider: getHoleDivider
15744
15783
  });
15745
15784
 
15746
- // Convert an array of intersections into an ArcCollection (for display)
15747
- //
15748
-
15749
15785
  function getIntersectionPoints(intersections) {
15750
15786
  return intersections.map(function(obj) {
15751
15787
  return [obj.x, obj.y];
15752
15788
  });
15753
15789
  }
15754
15790
 
15791
+ function getIntersectionLayer(intersections, lyr, arcs) {
15792
+ // return {geometry_type: 'point', shapes: [getIntersectionPoints(XX)]};
15793
+ var ii = arcs.getVertexData().ii;
15794
+ var index = new SimpleIdTestIndex(arcs.size());
15795
+ forEachArcId(lyr.shapes, arcId => {
15796
+ index.setId(absArcId(arcId));
15797
+ });
15798
+ var points = [];
15799
+ intersections.forEach(obj => {
15800
+ var arc1 = findArcIdFromVertexId(obj.a[0], ii);
15801
+ var arc2 = findArcIdFromVertexId(obj.b[0], ii);
15802
+ if (index.hasId(arc1) && index.hasId(arc2)) {
15803
+ points.push([obj.x, obj.y]);
15804
+ }
15805
+ });
15806
+ return {geometry_type: 'point', shapes: [points]};
15807
+ }
15808
+
15755
15809
  // Identify intersecting segments in an ArcCollection
15756
15810
  //
15757
15811
  // To find all intersections:
@@ -15999,6 +16053,7 @@
15999
16053
  var SegmentIntersection = /*#__PURE__*/Object.freeze({
16000
16054
  __proto__: null,
16001
16055
  getIntersectionPoints: getIntersectionPoints,
16056
+ getIntersectionLayer: getIntersectionLayer,
16002
16057
  findSegmentIntersections: findSegmentIntersections,
16003
16058
  sortIntersections: sortIntersections,
16004
16059
  dedupIntersections: dedupIntersections,
@@ -33396,7 +33451,7 @@ ${svg}
33396
33451
  if (n != values.length) {
33397
33452
  stop('Mismatch in number of categories and number of values');
33398
33453
  }
33399
- return values;
33454
+ return parseValues(values); // convert numerical strings to numbers
33400
33455
  }
33401
33456
 
33402
33457
  function getIndexes(n) {
@@ -33564,7 +33619,7 @@ ${svg}
33564
33619
  if (opts.index_field) {
33565
33620
  dataField = opts.index_field;
33566
33621
  fieldType = getColumnType(opts.field, records);
33567
- } else if (opts.field) {
33622
+ } else if (opts.field) {
33568
33623
  dataField = opts.field;
33569
33624
  fieldType = getColumnType(opts.field, records);
33570
33625
  }
@@ -33596,6 +33651,9 @@ ${svg}
33596
33651
  if ((!opts.categories || opts.categories.includes('*')) && dataField) {
33597
33652
  opts.categories = getUniqFieldValues(records, dataField);
33598
33653
  }
33654
+ if (opts.categories && fieldType == 'number') {
33655
+ opts.categories = opts.categories.map(str => +str);
33656
+ }
33599
33657
  }
33600
33658
 
33601
33659
  if (opts.classes) {
@@ -34740,34 +34798,48 @@ ${svg}
34740
34798
  }
34741
34799
 
34742
34800
  function getFeatureEditor(lyr, dataset) {
34743
- var changed = false;
34744
34801
  var api = {};
34745
34802
  // need to copy attribute to avoid circular references if geojson is assigned
34746
34803
  // to a data property.
34747
34804
  var copy = copyLayer(lyr);
34748
34805
  var features = exportLayerAsGeoJSON(copy, dataset, {}, true);
34806
+ var features2 = [];
34749
34807
 
34750
34808
  api.get = function(i) {
34809
+ if (i > 0) features[i-1] = null; // garbage-collect old features
34751
34810
  return features[i];
34752
34811
  };
34753
34812
 
34754
34813
  api.set = function(feat, i) {
34755
- changed = true;
34814
+ var arr;
34815
+
34756
34816
  if (utils.isString(feat)) {
34757
34817
  feat = JSON.parse(feat);
34758
34818
  }
34759
- features[i] = GeoJSON.toFeature(feat); // TODO: validate
34819
+
34820
+ if (!feat) return;
34821
+
34822
+ if (feat.type == 'GeometryCollection') {
34823
+ arr = feat.geometries.map(geom => GeoJSON.toFeature(geom));
34824
+ } else if (feat.type == 'FeatureCollection') {
34825
+ arr = feat.features;
34826
+ } else {
34827
+ feat = GeoJSON.toFeature(feat);
34828
+ }
34829
+
34830
+ if (arr) {
34831
+ features2 = features2.concat(arr);
34832
+ } else {
34833
+ features2.push(feat);
34834
+ }
34760
34835
  };
34761
34836
 
34762
34837
  api.done = function() {
34763
- if (!changed) return; // read-only expression
34764
- // TODO: validate number of features, etc.
34838
+ if (features2.length === 0) return; // read-only expression
34765
34839
  var geojson = {
34766
34840
  type: 'FeatureCollection',
34767
- features: features
34841
+ features: features2
34768
34842
  };
34769
-
34770
- // console.log(JSON.stringify(geojson, null, 2))
34771
34843
  return importGeoJSON(geojson);
34772
34844
  };
34773
34845
  return api;
@@ -40530,9 +40602,8 @@ ${svg}
40530
40602
  cmd.polygonGrid = function(targetLayers, targetDataset, opts) {
40531
40603
  requireProjectedDataset(targetDataset);
40532
40604
  var params = getGridParams(targetLayers, targetDataset, opts);
40533
- var gridDataset = makeGridDataset(params);
40534
-
40535
- gridDataset.info = targetDataset.info; // copy CRS to grid dataset // TODO: improve
40605
+ var gridDataset = makeGridDataset(params); // grid is a new dataset
40606
+ gridDataset.info = copyDatasetInfo(targetDataset.info);
40536
40607
  setOutputLayerName(gridDataset.layers[0], null, 'grid', opts);
40537
40608
  if (opts.debug) gridDataset.layers.push(cmd.pointGrid2(targetLayers, targetDataset, opts));
40538
40609
  return gridDataset;
package/www/page.css CHANGED
@@ -90,7 +90,7 @@ body {
90
90
  .dialog-btn {
91
91
  display: inline-block;
92
92
  margin-bottom: 1px;
93
- margin-top: 1px;
93
+ margin-top: 3px;
94
94
  font-size: 13px;
95
95
  color: white;
96
96
  min-width: 28px;
@@ -232,6 +232,13 @@ body {
232
232
  padding: 4px 6px 5px 6px;
233
233
  }
234
234
 
235
+ .export-options table {
236
+ margin: 0;
237
+ border-collapse: collapse;
238
+ border-spacing: 0;
239
+ width: 100%;
240
+ }
241
+
235
242
  .export-layer-list {
236
243
  max-height: 160px;
237
244
  overflow: hidden;
@@ -274,7 +281,7 @@ body {
274
281
  .alert-title {
275
282
  line-height: 1.1;
276
283
  font-weight: bold;
277
- margin-bottom: 5px;
284
+ margin-bottom: 7px;
278
285
  }
279
286
 
280
287
  div.alert-title, div.error-message {
@@ -461,6 +468,7 @@ body.dragover #import-options-drop-area .drop-area {
461
468
  margin: 0 0 5px 0;
462
469
  }
463
470
 
471
+
464
472
  #mshp-not-supported {
465
473
  display: none;
466
474
  z-index: 100;
@@ -549,10 +557,12 @@ body.dragover #import-options-drop-area .drop-area {
549
557
  font-size: 90%;
550
558
  }
551
559
 
560
+
561
+
552
562
  /*.option-menu input[type="radio"],
553
563
  .option-menu input[type="checkbox"] */
554
- .info-box input[type="radio"],
555
- .info-box input[type="checkbox"]
564
+ input[type="radio"],
565
+ input[type="checkbox"]
556
566
  {
557
567
  position: relative;
558
568
  top: 1px;
@@ -1126,7 +1136,7 @@ img.close-btn:hover,
1126
1136
 
1127
1137
  .basemap-styles > div {
1128
1138
  display: inline-block;
1129
- margin-bottom: 8px;
1139
+ margin-bottom: 6px;
1130
1140
  }
1131
1141
 
1132
1142
  .basemap-styles > div:nth-child(even) {
@@ -1234,9 +1244,7 @@ div.basemap-style-btn.active img {
1234
1244
  }
1235
1245
 
1236
1246
  .inline-checkbox {
1237
- position: relative;
1238
- top: 1px;
1239
- left: 5px;
1247
+ margin-left: 5px;
1240
1248
  }
1241
1249
 
1242
1250
  #save-preference {