mapshaper 0.5.85 → 0.5.86

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.
@@ -1,16 +1,114 @@
1
1
  (function () {
2
2
 
3
+ var utils = /*#__PURE__*/Object.freeze({
4
+ __proto__: null,
5
+ get default () { return utils; },
6
+ get getUniqueName () { return getUniqueName; },
7
+ get isFunction () { return isFunction; },
8
+ get isObject () { return isObject; },
9
+ get clamp () { return clamp; },
10
+ get isArray () { return isArray; },
11
+ get isNumber () { return isNumber; },
12
+ get isValidNumber () { return isValidNumber; },
13
+ get isFiniteNumber () { return isFiniteNumber; },
14
+ get isNonNegNumber () { return isNonNegNumber; },
15
+ get isInteger () { return isInteger; },
16
+ get isEven () { return isEven; },
17
+ get isOdd () { return isOdd; },
18
+ get isString () { return isString; },
19
+ get isDate () { return isDate; },
20
+ get isBoolean () { return isBoolean; },
21
+ get formatDateISO () { return formatDateISO; },
22
+ get toArray () { return toArray; },
23
+ get isArrayLike () { return isArrayLike; },
24
+ get addslashes () { return addslashes; },
25
+ get regexEscape () { return regexEscape; },
26
+ get htmlEscape () { return htmlEscape; },
27
+ get defaults () { return defaults; },
28
+ get extend () { return extend; },
29
+ get inherit () { return inherit; },
30
+ get reduceAsync () { return reduceAsync; },
31
+ get merge () { return merge; },
32
+ get difference () { return difference; },
33
+ get intersection () { return intersection; },
34
+ get indexOf () { return indexOf; },
35
+ get contains () { return contains; },
36
+ get some () { return some; },
37
+ get every () { return every; },
38
+ get find () { return find; },
39
+ get range () { return range; },
40
+ get repeat () { return repeat; },
41
+ get sum () { return sum; },
42
+ get getArrayBounds () { return getArrayBounds; },
43
+ get uniq () { return uniq; },
44
+ get pluck () { return pluck; },
45
+ get countValues () { return countValues; },
46
+ get indexOn () { return indexOn; },
47
+ get groupBy () { return groupBy; },
48
+ get arrayToIndex () { return arrayToIndex; },
49
+ get forEach () { return forEach; },
50
+ get forEachProperty () { return forEachProperty; },
51
+ get initializeArray () { return initializeArray; },
52
+ get replaceArray () { return replaceArray; },
53
+ get repeatString () { return repeatString; },
54
+ get splitLines () { return splitLines; },
55
+ get pluralSuffix () { return pluralSuffix; },
56
+ get endsWith () { return endsWith; },
57
+ get lpad () { return lpad; },
58
+ get rpad () { return rpad; },
59
+ get trim () { return trim; },
60
+ get ltrim () { return ltrim; },
61
+ get rtrim () { return rtrim; },
62
+ get addThousandsSep () { return addThousandsSep; },
63
+ get numToStr () { return numToStr; },
64
+ get formatNumber () { return formatNumber; },
65
+ get formatIntlNumber () { return formatIntlNumber; },
66
+ get formatNumberForDisplay () { return formatNumberForDisplay; },
67
+ get shuffle () { return shuffle; },
68
+ get sortOn () { return sortOn; },
69
+ get genericSort () { return genericSort; },
70
+ get getSortedIds () { return getSortedIds; },
71
+ get sortArrayIndex () { return sortArrayIndex; },
72
+ get reorderArray () { return reorderArray; },
73
+ get getKeyComparator () { return getKeyComparator; },
74
+ get getGenericComparator () { return getGenericComparator; },
75
+ get quicksort () { return quicksort; },
76
+ get quicksortPartition () { return quicksortPartition; },
77
+ get findRankByValue () { return findRankByValue; },
78
+ get findValueByPct () { return findValueByPct; },
79
+ get findValueByRank () { return findValueByRank; },
80
+ get findMedian () { return findMedian; },
81
+ get mean () { return mean; },
82
+ get format () { return format; },
83
+ get formatter () { return formatter; },
84
+ get wildcardToRegExp () { return wildcardToRegExp; },
85
+ get createBuffer () { return createBuffer; },
86
+ get expandoBuffer () { return expandoBuffer; },
87
+ get copyElements () { return copyElements; },
88
+ get extendBuffer () { return extendBuffer; },
89
+ get mergeNames () { return mergeNames; },
90
+ get findStringPrefix () { return findStringPrefix; },
91
+ get parsePercent () { return parsePercent; },
92
+ get formatVersionedName () { return formatVersionedName; },
93
+ get uniqifyNames () { return uniqifyNames; },
94
+ get parseString () { return parseString; },
95
+ get parseNumber () { return parseNumber; },
96
+ get parseIntlNumber () { return parseIntlNumber; },
97
+ get cleanNumericString () { return cleanNumericString; },
98
+ get trimQuotes () { return trimQuotes; }
99
+ });
100
+
3
101
  var api = window.mapshaper; // assuming mapshaper is in global scope
4
102
  var mapshaper = api,
5
- utils = api.utils,
103
+ utils$1 = api.utils,
6
104
  cli = api.cli,
7
105
  geom = api.geom,
8
106
  internal = api.internal,
9
107
  Bounds = internal.Bounds,
10
- UserError = internal.UserError,
11
- message = internal.message, // stop, error and message are overridden in gui-proxy.js
12
- stop = internal.stop,
13
- error = internal.error;
108
+ UserError$1 = internal.UserError,
109
+ message$1 = internal.message, // stop, error and message are overridden in gui-proxy.js
110
+ stop$1 = internal.stop,
111
+ error$1 = internal.error;
14
112
 
15
113
  api.enableLogging();
16
114
 
@@ -46,7 +144,7 @@
46
144
 
47
145
  html = '<table>';
48
146
  if (catalog.title) {
49
- html += utils.format('<tr><th colspan="%d"><h4>%s</h4></th></tr>', cols, catalog.title);
147
+ html += utils$1.format('<tr><th colspan="%d"><h4>%s</h4></th></tr>', cols, catalog.title);
50
148
  }
51
149
  while (row < rows) {
52
150
  html += renderRow(items.slice(row * cols, row * cols + cols));
@@ -111,7 +209,7 @@
111
209
 
112
210
  function renderCell(item, i) {
113
211
  var template = '<td data-id="%d"><h4 class="title">%s</h4><div class="subtitle">%s</div></td>';
114
- return utils.format(template, i, item.title, item.subtitle || '');
212
+ return utils$1.format(template, i, item.title, item.subtitle || '');
115
213
  }
116
214
 
117
215
  }
@@ -224,7 +322,7 @@
224
322
  }
225
323
  parsed = internal.parseCommands(str);
226
324
  if (!parsed.length || parsed[0].name != cmd) {
227
- stop("Unable to parse command line options");
325
+ stop$1("Unable to parse command line options");
228
326
  }
229
327
  return parsed[0].options;
230
328
  };
@@ -293,7 +391,7 @@
293
391
  evt = new EventData(this.type);
294
392
  evt.target = this.target;
295
393
  } else if (evt.target != this.target || evt.type != this.type) {
296
- error("[Handler] event target/type have changed.");
394
+ error$1("[Handler] event target/type have changed.");
297
395
  }
298
396
  this.callback.call(this.listener, evt);
299
397
  };
@@ -302,7 +400,7 @@
302
400
  this.type = type;
303
401
  this.target = target;
304
402
  if (data) {
305
- utils.defaults(this, data);
403
+ utils$1.defaults(this, data);
306
404
  this.data = data;
307
405
  }
308
406
  }
@@ -532,12 +630,12 @@
532
630
  try {
533
631
  els = root.querySelectorAll(selector);
534
632
  } catch (e) {
535
- error("Invalid selector:", selector);
633
+ error$1("Invalid selector:", selector);
536
634
  }
537
635
  } else {
538
- error("This browser doesn't support CSS query selectors");
636
+ error$1("This browser doesn't support CSS query selectors");
539
637
  }
540
- return utils.toArray(els);
638
+ return utils$1.toArray(els);
541
639
  };
542
640
 
543
641
  // Converts dash-separated names (e.g. background-color) to camelCase (e.g. backgroundColor)
@@ -574,7 +672,7 @@
574
672
  };
575
673
 
576
674
  function El(ref) {
577
- if (!ref) error("Element() needs a reference");
675
+ if (!ref) error$1("Element() needs a reference");
578
676
  if (ref instanceof El) {
579
677
  return ref;
580
678
  }
@@ -583,7 +681,7 @@
583
681
  }
584
682
 
585
683
  var node;
586
- if (utils.isString(ref)) {
684
+ if (utils$1.isString(ref)) {
587
685
  if (ref[0] == '<') {
588
686
  var parent = El('div').html(ref).node();
589
687
  node = parent.childNodes.length == 1 ? parent.childNodes[0] : parent;
@@ -595,13 +693,13 @@
595
693
  } else if (ref.tagName) {
596
694
  node = ref;
597
695
  }
598
- if (!node) error("Unmatched element selector:", ref);
696
+ if (!node) error$1("Unmatched element selector:", ref);
599
697
  this.el = node;
600
698
  }
601
699
 
602
- utils.inherit(El, EventDispatcher);
700
+ utils$1.inherit(El, EventDispatcher);
603
701
 
604
- utils.extend(El.prototype, {
702
+ utils$1.extend(El.prototype, {
605
703
 
606
704
  clone: function() {
607
705
  var el = this.el.cloneNode(true);
@@ -610,7 +708,7 @@
610
708
  // can ...
611
709
  el = El('div').addClass(el.className).html(el.innerHTML).node();
612
710
  }
613
- el.id = utils.getUniqueName();
711
+ el.id = utils$1.getUniqueName();
614
712
  this.el = el;
615
713
  return this;
616
714
  },
@@ -640,11 +738,11 @@
640
738
  if (val != null) {
641
739
  El.setStyle(this.el, css, val);
642
740
  }
643
- else if (utils.isString(css)) {
741
+ else if (utils$1.isString(css)) {
644
742
  addCSS(this.el, css);
645
743
  }
646
- else if (utils.isObject(css)) {
647
- utils.forEachProperty(css, function(val, key) {
744
+ else if (utils$1.isObject(css)) {
745
+ utils$1.forEachProperty(css, function(val, key) {
648
746
  El.setStyle(this.el, key, val);
649
747
  }, this);
650
748
  }
@@ -652,7 +750,7 @@
652
750
  },
653
751
 
654
752
  attr: function(obj, value) {
655
- if (utils.isString(obj)) {
753
+ if (utils$1.isString(obj)) {
656
754
  if (arguments.length == 1) {
657
755
  return this.el.getAttribute(obj);
658
756
  }
@@ -736,7 +834,7 @@
736
834
  },
737
835
 
738
836
  text: function(str) {
739
- this.html(utils.htmlEscape(str));
837
+ this.html(utils$1.htmlEscape(str));
740
838
  return this;
741
839
  },
742
840
 
@@ -826,7 +924,7 @@
826
924
  El.prototype.__on = El.prototype.on;
827
925
  El.prototype.on = function(type, func, ctx) {
828
926
  if (ctx) {
829
- error("[El#on()] Third argument no longer supported.");
927
+ error$1("[El#on()] Third argument no longer supported.");
830
928
  }
831
929
  if (this.constructor == El) {
832
930
  this.el.addEventListener(type, func);
@@ -882,7 +980,7 @@
882
980
  }
883
981
 
884
982
  this.value = function(str) {
885
- if (utils.isString(str)) {
983
+ if (utils$1.isString(str)) {
886
984
  el.node().textContent = str;
887
985
  } else {
888
986
  return el.node().textContent;
@@ -890,7 +988,7 @@
890
988
  };
891
989
  }
892
990
 
893
- utils.inherit(ClickText2, EventDispatcher);
991
+ utils$1.inherit(ClickText2, EventDispatcher);
894
992
 
895
993
  // @ref reference to a text input element
896
994
  function ClickText(ref) {
@@ -961,9 +1059,9 @@
961
1059
  // return _parser ? _parser(valStr) : parseFloat(valStr);
962
1060
  return _value;
963
1061
  }
964
- var val = utils.clamp(arg, _min, _max);
1062
+ var val = utils$1.clamp(arg, _min, _max);
965
1063
  if (!_validator(val)) {
966
- error("ClickText#value() invalid value:", arg);
1064
+ error$1("ClickText#value() invalid value:", arg);
967
1065
  } else {
968
1066
  _value = val;
969
1067
  }
@@ -972,14 +1070,14 @@
972
1070
  };
973
1071
  }
974
1072
 
975
- utils.inherit(ClickText, EventDispatcher);
1073
+ utils$1.inherit(ClickText, EventDispatcher);
976
1074
 
977
1075
 
978
1076
  function Checkbox(ref) {
979
1077
  var _el = El(ref);
980
1078
  }
981
1079
 
982
- utils.inherit(Checkbox, EventDispatcher);
1080
+ utils$1.inherit(Checkbox, EventDispatcher);
983
1081
 
984
1082
  function SimpleButton(ref) {
985
1083
  var _el = El(ref),
@@ -1008,7 +1106,7 @@
1008
1106
  }
1009
1107
  }
1010
1108
 
1011
- utils.inherit(SimpleButton, EventDispatcher);
1109
+ utils$1.inherit(SimpleButton, EventDispatcher);
1012
1110
 
1013
1111
  // @cb function(<FileList>)
1014
1112
  function DropControl(gui, el, cb) {
@@ -1256,7 +1354,7 @@
1256
1354
  function receiveFiles(files) {
1257
1355
  var prevSize = queuedFiles.length;
1258
1356
  useQuickView = useQuickView || overQuickView;
1259
- files = handleZipFiles(utils.toArray(files));
1357
+ files = handleZipFiles(utils$1.toArray(files));
1260
1358
  addFilesToQueue(files);
1261
1359
  if (queuedFiles.length === 0) return;
1262
1360
  gui.enterMode('import');
@@ -1271,7 +1369,7 @@
1271
1369
  }
1272
1370
 
1273
1371
  function filesMayContainPaths(files) {
1274
- return utils.some(files, function(f) {
1372
+ return utils$1.some(files, function(f) {
1275
1373
  var type = internal.guessInputFileType(f.name);
1276
1374
  return type == 'shp' || type == 'json' || internal.isZipFile(f.name);
1277
1375
  });
@@ -1375,7 +1473,7 @@
1375
1473
  lyr = dataset.layers[0];
1376
1474
  lyr.data = new internal.ShapefileTable(content, importOpts.encoding);
1377
1475
  if (lyr.shapes && lyr.data.size() != lyr.shapes.length) {
1378
- stop("Different number of records in .shp and .dbf files");
1476
+ stop$1("Different number of records in .shp and .dbf files");
1379
1477
  }
1380
1478
  if (!lyr.geometry_type) {
1381
1479
  // kludge: trigger display of table cells if .shp has null geometry
@@ -1440,7 +1538,7 @@
1440
1538
  }
1441
1539
 
1442
1540
  function handleImportError(e, fileName) {
1443
- var msg = utils.isString(e) ? e : e.message;
1541
+ var msg = utils$1.isString(e) ? e : e.message;
1444
1542
  if (fileName) {
1445
1543
  msg = "Error importing <i>" + fileName + "</i><br>" + msg;
1446
1544
  }
@@ -1498,7 +1596,7 @@
1498
1596
 
1499
1597
  function downloadFiles(paths) {
1500
1598
  var items = prepFilesForDownload(paths);
1501
- utils.reduceAsync(items, [], downloadNextFile, function(err, files) {
1599
+ utils$1.reduceAsync(items, [], downloadNextFile, function(err, files) {
1502
1600
  if (err) {
1503
1601
  gui.alert(err);
1504
1602
  } else if (!files.length) {
@@ -1594,7 +1692,7 @@
1594
1692
  var defaults = {
1595
1693
  space: 7
1596
1694
  };
1597
- opts = utils.extend(defaults, opts);
1695
+ opts = utils$1.extend(defaults, opts);
1598
1696
 
1599
1697
  var _pct = 0;
1600
1698
  var _track,
@@ -1647,7 +1745,7 @@
1647
1745
  };
1648
1746
 
1649
1747
  function setHandlePos(x, fire) {
1650
- x = utils.clamp(x, 0, size());
1748
+ x = utils$1.clamp(x, 0, size());
1651
1749
  var pct = x / size();
1652
1750
  if (pct != _pct) {
1653
1751
  _pct = pct;
@@ -1662,7 +1760,7 @@
1662
1760
  }
1663
1761
  }
1664
1762
 
1665
- utils.inherit(Slider, EventDispatcher);
1763
+ utils$1.inherit(Slider, EventDispatcher);
1666
1764
 
1667
1765
  /*
1668
1766
  How changes in the simplify control should affect other components
@@ -1730,7 +1828,7 @@
1730
1828
  slider.on('change', function(e) {
1731
1829
  var pct = fromSliderPct(e.pct);
1732
1830
  text.value(pct);
1733
- pct = utils.parsePercent(text.text()); // use rounded value (for consistency w/ cli)
1831
+ pct = utils$1.parsePercent(text.text()); // use rounded value (for consistency w/ cli)
1734
1832
  onChange(pct);
1735
1833
  });
1736
1834
  slider.on('start', function(e) {
@@ -1751,7 +1849,7 @@
1751
1849
  else if (pct < 0.01) decimals = 3;
1752
1850
  else if (pct < 1) decimals = 2;
1753
1851
  else if (pct < 100) decimals = 1;
1754
- return utils.formatNumberForDisplay(pct, decimals) + "%";
1852
+ return utils$1.formatNumberForDisplay(pct, decimals) + "%";
1755
1853
  });
1756
1854
 
1757
1855
  text.parser(function(s) {
@@ -2003,7 +2101,7 @@
2003
2101
 
2004
2102
  // Replace error function in mapshaper lib
2005
2103
  var error = function() {
2006
- stop.apply(null, utils.toArray(arguments));
2104
+ stop.apply(null, utils$1.toArray(arguments));
2007
2105
  };
2008
2106
 
2009
2107
  var message = function() {
@@ -2017,11 +2115,11 @@
2017
2115
  // replace CLI version of writeFiles()
2018
2116
  internal.replaceWriteFiles(function(files, opts, done) {
2019
2117
  var filename;
2020
- if (!utils.isArray(files) || files.length === 0) {
2118
+ if (!utils$1.isArray(files) || files.length === 0) {
2021
2119
  done("Nothing to export");
2022
2120
  } else if (GUI.canSaveToServer() && !opts.save_to_download_folder) {
2023
- var paths = internal.getOutputPaths(utils.pluck(files, 'filename'), opts);
2024
- var data = utils.pluck(files, 'content');
2121
+ var paths = internal.getOutputPaths(utils$1.pluck(files, 'filename'), opts);
2122
+ var data = utils$1.pluck(files, 'content');
2025
2123
  saveFilesToServer(paths, data, function(err) {
2026
2124
  var msg;
2027
2125
  if (err) {
@@ -2040,7 +2138,7 @@
2040
2138
  } else if (files.length == 1) {
2041
2139
  saveBlobToDownloadFolder(files[0].filename, new Blob([files[0].content]), done);
2042
2140
  } else {
2043
- filename = internal.getCommonFileBase(utils.pluck(files, 'filename')) || "output";
2141
+ filename = internal.getCommonFileBase(utils$1.pluck(files, 'filename')) || "output";
2044
2142
  saveZipFile(filename + ".zip", files, done);
2045
2143
  }
2046
2144
  });
@@ -2062,10 +2160,10 @@
2062
2160
  // try to match import filename of this dataset
2063
2161
  if (d.info.input_files[0] == src) return d;
2064
2162
  // try to match name of a layer in this dataset
2065
- lyr = utils.find(d.layers, function(lyr) {return lyr.name == src;});
2163
+ lyr = utils$1.find(d.layers, function(lyr) {return lyr.name == src;});
2066
2164
  return lyr ? internal.isolateLayer(lyr, d) : null;
2067
2165
  }, null);
2068
- if (!retn) stop("Missing data layer [" + src + "]");
2166
+ if (!retn) stop$1("Missing data layer [" + src + "]");
2069
2167
  return retn;
2070
2168
  }
2071
2169
 
@@ -2074,7 +2172,7 @@
2074
2172
  // Return a copy with layers duplicated, so changes won't affect original layers
2075
2173
  // This makes an (unsafe) assumption that the dataset arcs won't be changed...
2076
2174
  // need to rethink this.
2077
- return utils.defaults({
2175
+ return utils$1.defaults({
2078
2176
  layers: dataset.layers.map(internal.copyLayer)
2079
2177
  }, dataset);
2080
2178
  });
@@ -2146,7 +2244,7 @@
2146
2244
  }
2147
2245
 
2148
2246
  function projectPointsForDisplay(lyr, src, dest) {
2149
- var copy = utils.extend({}, lyr);
2247
+ var copy = utils$1.extend({}, lyr);
2150
2248
  var proj = internal.getProjTransform2(src, dest);
2151
2249
  copy.shapes = internal.cloneShapes(lyr.shapes);
2152
2250
  internal.projectPointLayer(copy, proj);
@@ -2544,7 +2642,7 @@
2544
2642
  } else if (cmd == 'history') {
2545
2643
  toLog(gui.session.toCommandLineString());
2546
2644
  } else if (cmd == 'layers') {
2547
- message("Available layers:",
2645
+ message$1("Available layers:",
2548
2646
  internal.getFormattedLayerList(model));
2549
2647
  } else if (cmd == 'close' || cmd == 'exit' || cmd == 'quit') {
2550
2648
  turnOff();
@@ -2633,7 +2731,7 @@
2633
2731
  }
2634
2732
 
2635
2733
  function onError(err) {
2636
- if (utils.isString(err)) {
2734
+ if (utils$1.isString(err)) {
2637
2735
  consoleStop(err);
2638
2736
  } else if (err.name == 'UserError') {
2639
2737
  // stop() has already been called, don't need to log
@@ -2648,7 +2746,7 @@
2648
2746
  function consoleStop() {
2649
2747
  var msg = GUI.formatMessageArgs(arguments);
2650
2748
  consoleWarning(msg);
2651
- throw new UserError(msg);
2749
+ throw new UserError$1(msg);
2652
2750
  }
2653
2751
 
2654
2752
  function consoleWarning() {
@@ -2812,7 +2910,7 @@
2812
2910
  // console.log("first intersection:", internal.getIntersectionDebugData(XX[0], arcs));
2813
2911
  pointLyr = {geometry_type: 'point', shapes: [internal.getIntersectionPoints(XX)]};
2814
2912
  map.setIntersectionLayer(pointLyr, {layers:[pointLyr]});
2815
- readout.html(utils.format('<span class="icon"></span>%s line intersection%s <img class="close-btn" src="images/close.png">', n, utils.pluralSuffix(n)));
2913
+ 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)));
2816
2914
  readout.findChild('.close-btn').on('click', dismiss);
2817
2915
  } else {
2818
2916
  map.setIntersectionLayer(null);
@@ -2821,7 +2919,7 @@
2821
2919
  }
2822
2920
  }
2823
2921
 
2824
- utils.inherit(RepairControl, EventDispatcher);
2922
+ utils$1.inherit(RepairControl, EventDispatcher);
2825
2923
 
2826
2924
  function filterLayerByIds(lyr, ids) {
2827
2925
  var shapes;
@@ -2829,7 +2927,7 @@
2829
2927
  shapes = ids.map(function(id) {
2830
2928
  return lyr.shapes[id];
2831
2929
  });
2832
- return utils.defaults({shapes: shapes, data: null}, lyr);
2930
+ return utils$1.defaults({shapes: shapes, data: null}, lyr);
2833
2931
  }
2834
2932
  return lyr;
2835
2933
  }
@@ -2882,7 +2980,7 @@
2882
2980
  });
2883
2981
 
2884
2982
  internal.writeFiles = function() {
2885
- error(unsupportedMsg);
2983
+ error$1(unsupportedMsg);
2886
2984
  };
2887
2985
  } else {
2888
2986
  new SimpleButton(menu.findChild('.save-btn').addClass('default-btn')).on('click', onExportClick);
@@ -2918,7 +3016,7 @@
2918
3016
  setTimeout(function() {
2919
3017
  exportMenuSelection(layers, function(err) {
2920
3018
  if (err) {
2921
- if (utils.isString(err)) {
3019
+ if (utils$1.isString(err)) {
2922
3020
  gui.alert(err);
2923
3021
  } else {
2924
3022
  // stack seems to change if Error is logged directly
@@ -2965,7 +3063,7 @@
2965
3063
  // without changing its name elsewhere
2966
3064
  layer: Object.assign({}, o.layer)
2967
3065
  };
2968
- var html = utils.format(template, i + 1, target.layer.name || '[unnamed layer]');
3066
+ var html = utils$1.format(template, i + 1, target.layer.name || '[unnamed layer]');
2969
3067
  // return {layer: o.layer, html: html};
2970
3068
  var el = El('div').html(html).addClass('layer-item');
2971
3069
  var box = el.findChild('input').node();
@@ -3075,9 +3173,9 @@
3075
3173
 
3076
3174
  function initFormatMenu() {
3077
3175
  var defaults = ['shapefile', 'geojson', 'topojson', 'json', 'dsv', 'svg'];
3078
- var formats = utils.uniq(defaults.concat(getInputFormats()));
3176
+ var formats = utils$1.uniq(defaults.concat(getInputFormats()));
3079
3177
  var items = formats.map(function(fmt) {
3080
- return utils.format('<div><label><input type="radio" name="format" value="%s"' +
3178
+ return utils$1.format('<div><label><input type="radio" name="format" value="%s"' +
3081
3179
  ' class="radio">%s</label></div>', fmt, internal.getFormatName(fmt));
3082
3180
  });
3083
3181
  menu.findChild('.export-formats').html(items.join('\n'));
@@ -3240,7 +3338,7 @@
3240
3338
  // can be used as unique identifiers for caching rendered HTML, and as
3241
3339
  // an id for layer menu event handlers
3242
3340
  if (!lyr.menu_id || uniqIds[lyr.menu_id]) {
3243
- lyr.menu_id = utils.getUniqueName();
3341
+ lyr.menu_id = utils$1.getUniqueName();
3244
3342
  }
3245
3343
  uniqIds[lyr.menu_id] = true;
3246
3344
  if (isPinnable(lyr)) pinnableCount++;
@@ -3374,204 +3472,1948 @@
3374
3472
  map.redraw();
3375
3473
  });
3376
3474
 
3377
- // catch click event on pin button
3378
- GUI.onClick(entry.findChild('img.unpinned'), function(e) {
3379
- e.stopPropagation();
3380
- });
3381
- }
3475
+ // catch click event on pin button
3476
+ GUI.onClick(entry.findChild('img.unpinned'), function(e) {
3477
+ e.stopPropagation();
3478
+ });
3479
+ }
3480
+
3481
+ // init name editor
3482
+ new ClickText2(entry.findChild('.layer-name'))
3483
+ .on('change', function(e) {
3484
+ var target = findLayerById(id);
3485
+ var str = cleanLayerName(this.value());
3486
+ this.value(formatLayerNameForDisplay(str));
3487
+ target.layer.name = str;
3488
+ gui.session.layerRenamed(target.layer, str);
3489
+ updateMenuBtn();
3490
+ });
3491
+
3492
+ // init click-to-select
3493
+ GUI.onClick(entry, function() {
3494
+ var target = findLayerById(id);
3495
+ // don't select if user is typing or dragging
3496
+ if (!GUI.getInputElement() && !dragging) {
3497
+ gui.clearMode();
3498
+ if (!map.isActiveLayer(target.layer)) {
3499
+ model.selectLayer(target.layer, target.dataset);
3500
+ }
3501
+ }
3502
+ });
3503
+ }
3504
+
3505
+ function describeLyr(lyr) {
3506
+ var n = internal.getFeatureCount(lyr),
3507
+ str, type;
3508
+ if (lyr.data && !lyr.shapes) {
3509
+ type = 'data record';
3510
+ } else if (lyr.geometry_type) {
3511
+ type = lyr.geometry_type + ' feature';
3512
+ }
3513
+ if (type) {
3514
+ str = utils$1.format('%,d %s%s', n, type, utils$1.pluralSuffix(n));
3515
+ } else {
3516
+ str = "[empty]";
3517
+ }
3518
+ return str;
3519
+ }
3520
+
3521
+ function getWarnings(lyr, dataset) {
3522
+ var file = internal.getLayerSourceFile(lyr, dataset);
3523
+ var missing = [];
3524
+ var msg;
3525
+ if (utils$1.endsWith(file, '.shp') && lyr == dataset.layers[0]) {
3526
+ if (!lyr.data) {
3527
+ missing.push('.dbf');
3528
+ }
3529
+ if (!dataset.info.prj && !dataset.info.crs) {
3530
+ missing.push('.prj');
3531
+ }
3532
+ }
3533
+ if (missing.length) {
3534
+ msg = 'missing ' + missing.join(' and ') + ' data';
3535
+ }
3536
+ return msg;
3537
+ }
3538
+
3539
+ function describeSrc(lyr, dataset) {
3540
+ return internal.getLayerSourceFile(lyr, dataset);
3541
+ }
3542
+
3543
+
3544
+ function isPinnable(lyr) {
3545
+ return internal.layerHasGeometry(lyr) || internal.layerHasFurniture(lyr);
3546
+ }
3547
+
3548
+
3549
+
3550
+ function rowHTML(c1, c2, cname) {
3551
+ return utils$1.format('<div class="row%s"><div class="col1">%s</div>' +
3552
+ '<div class="col2">%s</div></div>', cname ? ' ' + cname : '', c1, c2);
3553
+ }
3554
+ }
3555
+
3556
+ function SessionHistory(gui) {
3557
+ var commands = [];
3558
+ // commands that can be ignored when checking for unsaved changes
3559
+ var nonEditingCommands = 'i,target,info,version,verbose,projections,inspect,help,h,encodings,calc'.split(',');
3560
+
3561
+ this.unsavedChanges = function() {
3562
+ var cmd, cmdName;
3563
+ for (var i=commands.length - 1; i >= 0; i--) {
3564
+ cmdName = getCommandName(commands[i]);
3565
+ if (cmdName == 'o') break;
3566
+ if (nonEditingCommands.includes(cmdName)) continue;
3567
+ return true;
3568
+ }
3569
+ return false;
3570
+ };
3571
+
3572
+ this.fileImported = function(file, optStr) {
3573
+ var cmd = '-i ' + file;
3574
+ if (optStr) {
3575
+ cmd += ' ' + optStr;
3576
+ }
3577
+ commands.push(cmd);
3578
+ };
3579
+
3580
+ this.layerRenamed = function(lyr, name) {
3581
+ var currTarget = getCurrentTarget();
3582
+ var layerTarget = getTargetFromLayer(lyr);
3583
+ if (currTarget == layerTarget) {
3584
+ commands.push('-rename-layers ' + name);
3585
+ } else {
3586
+ commands.push('-rename-layers ' + name + ' target=' + layerTarget);
3587
+ commands.push('-target ' + currTarget);
3588
+ }
3589
+ };
3590
+
3591
+ this.consoleCommands = function(str) {
3592
+ commands.push(str); // todo: split commands?
3593
+ };
3594
+
3595
+ this.simplificationApplied = function(optStr) {
3596
+ commands.push('-simplify ' + optStr);
3597
+ };
3598
+
3599
+ this.simplificationRepair = function() {
3600
+ // TODO: improve this... repair does not necessarily apply to most recent
3601
+ // simplification command
3602
+ // consider adding a (hidden) repair command to handle this event
3603
+ var i = indexOfLastCommand('-simplify');
3604
+ if (i > -1) {
3605
+ commands[i] = commands[i].replace(' no-repair', '');
3606
+ }
3607
+ };
3608
+
3609
+ this.updateSimplificationPct = function(pct) {
3610
+ var i = indexOfLastCommand('-simplify');
3611
+ if (i > -1) {
3612
+ commands[i] = commands[i].replace(/percentage=[^ ]+/, 'percentage=' + pct);
3613
+ }
3614
+ };
3615
+
3616
+ this.dataValueUpdated = function(id, field, value) {
3617
+ var cmd = `-each 'd[${JSON.stringify(field)}] = ${JSON.stringify(value)}' where='this.id == ${id}'`;
3618
+ commands.push(cmd);
3619
+ };
3620
+
3621
+ this.layersExported = function(ids, optStr) {
3622
+ var layers = gui.model.getLayers();
3623
+ var cmd = '-o';
3624
+ if (layers.length > 1) {
3625
+ cmd += ' target=' + ids.map(getTargetFromId).join(',');
3626
+ }
3627
+ if (optStr) {
3628
+ cmd += ' ' + optStr;
3629
+ }
3630
+ commands.push(cmd);
3631
+ };
3632
+
3633
+ this.setTargetLayer = function(lyr) {
3634
+ var layers = gui.model.getLayers();
3635
+ if (layers.length > 1) {
3636
+ if (indexOfLastCommand('-target') == commands.length - 1) {
3637
+ commands.pop(); // if last commands was -target, remove it
3638
+ }
3639
+ commands.push('-target ' + getTargetFromLayer(lyr));
3640
+ }
3641
+ };
3642
+
3643
+ this.toCommandLineString = function() {
3644
+ var str = commands.join(' \\\n ');
3645
+ return 'mapshaper ' + str;
3646
+ };
3647
+
3648
+ function getCommandName(cmd) {
3649
+ var rxp = /^-([a-z0-9-]+)/;
3650
+ var match = rxp.exec(cmd);
3651
+ return match ? match[1] : null;
3652
+ }
3653
+
3654
+ function getCurrentTarget() {
3655
+ return getTargetFromLayer(gui.model.getActiveLayer().layer);
3656
+ }
3657
+
3658
+ function indexOfLastCommand(cmd) {
3659
+ return commands.reduce(function(memo, str, i) {
3660
+ return str.indexOf(cmd) === 0 ? i : memo;
3661
+ }, -1);
3662
+ }
3663
+
3664
+ function getTargetFromId(id) {
3665
+ var layers = gui.model.getLayers();
3666
+ return getTargetFromLayer(layers[id - 1].layer);
3667
+ }
3668
+
3669
+ function getTargetFromLayer(lyr) {
3670
+ var id = internal.getLayerTargetId(gui.model, lyr);
3671
+ return internal.formatOptionValue(id);
3672
+ }
3673
+ }
3674
+
3675
+ var Buffer = require('buffer').Buffer; // works with browserify
3676
+
3677
+ var uniqCount = 0;
3678
+ function getUniqueName(prefix) {
3679
+ return (prefix || "__id_") + (++uniqCount);
3680
+ }
3681
+
3682
+ function isFunction(obj) {
3683
+ return typeof obj == 'function';
3684
+ }
3685
+
3686
+ function isObject(obj) {
3687
+ return obj === Object(obj); // via underscore
3688
+ }
3689
+
3690
+ function clamp(val, min, max) {
3691
+ return val < min ? min : (val > max ? max : val);
3692
+ }
3693
+
3694
+ function isArray(obj) {
3695
+ return Array.isArray(obj);
3696
+ }
3697
+
3698
+ // Is obj a valid number or NaN? (test if obj is type number)
3699
+ function isNumber(obj) {
3700
+ return obj != null && obj.constructor == Number;
3701
+ }
3702
+
3703
+ function isValidNumber(val) {
3704
+ return isNumber(val) && !isNaN(val);
3705
+ }
3706
+
3707
+ // Similar to isFinite() but does not coerce strings or other types
3708
+ function isFiniteNumber(val) {
3709
+ return isValidNumber(val) && val !== Infinity && val !== -Infinity;
3710
+ }
3711
+
3712
+ // This uses type conversion
3713
+ // export function isFiniteNumber(val) {
3714
+ // return val > -Infinity && val < Infinity;
3715
+ // }
3716
+
3717
+ function isNonNegNumber(val) {
3718
+ return isNumber(val) && val >= 0;
3719
+ }
3720
+
3721
+ function isInteger(obj) {
3722
+ return isNumber(obj) && ((obj | 0) === obj);
3723
+ }
3724
+
3725
+ function isEven(obj) {
3726
+ return (obj % 2) === 0;
3727
+ }
3728
+
3729
+ function isOdd(obj) {
3730
+ return (obj % 2) === 1;
3731
+ }
3732
+
3733
+ function isString(obj) {
3734
+ return obj != null && obj.toString === String.prototype.toString;
3735
+ // TODO: replace w/ something better.
3736
+ }
3737
+
3738
+ function isDate(obj) {
3739
+ return !!obj && obj.getTime === Date.prototype.getTime;
3740
+ }
3741
+
3742
+ function isBoolean(obj) {
3743
+ return obj === true || obj === false;
3744
+ }
3745
+
3746
+ function formatDateISO(d) {
3747
+ if (!isDate(d)) return '';
3748
+ return d.toISOString().replace(':00.000Z', 'Z');
3749
+ }
3750
+
3751
+ // Convert an array-like object to an Array, or make a copy if @obj is an Array
3752
+ function toArray(obj) {
3753
+ var arr;
3754
+ if (!isArrayLike(obj)) error("toArray() requires an array-like object");
3755
+ try {
3756
+ arr = Array.prototype.slice.call(obj, 0); // breaks in ie8
3757
+ } catch(e) {
3758
+ // support ie8
3759
+ arr = [];
3760
+ for (var i=0, n=obj.length; i<n; i++) {
3761
+ arr[i] = obj[i];
3762
+ }
3763
+ }
3764
+ return arr;
3765
+ }
3766
+
3767
+ // Array like: has length property, is numerically indexed and mutable.
3768
+ // TODO: try to detect objects with length property but no indexed data elements
3769
+ function isArrayLike(obj) {
3770
+ if (!obj) return false;
3771
+ if (isArray(obj)) return true;
3772
+ if (isString(obj)) return false;
3773
+ if (obj.length === 0) return true;
3774
+ if (obj.length > 0) return true;
3775
+ return false;
3776
+ }
3777
+
3778
+ // See https://raw.github.com/kvz/phpjs/master/functions/strings/addslashes.js
3779
+ function addslashes(str) {
3780
+ return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
3781
+ }
3782
+
3783
+ // Escape a literal string to use in a regexp.
3784
+ // Ref.: http://simonwillison.net/2006/Jan/20/escape/
3785
+ function regexEscape(str) {
3786
+ return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
3787
+ }
3788
+
3789
+
3790
+ // See https://github.com/janl/mustache.js/blob/master/mustache.js
3791
+ var entityMap = {
3792
+ '&': '&amp;',
3793
+ '<': '&lt;',
3794
+ '>': '&gt;',
3795
+ '"': '&quot;',
3796
+ "'": '&#39;',
3797
+ '/': '&#x2F;'
3798
+ };
3799
+ function htmlEscape(s) {
3800
+ return String(s).replace(/[&<>"'\/]/g, function(s) {
3801
+ return entityMap[s];
3802
+ });
3803
+ }
3804
+
3805
+ function defaults(dest) {
3806
+ for (var i=1, n=arguments.length; i<n; i++) {
3807
+ var src = arguments[i] || {};
3808
+ for (var key in src) {
3809
+ if (key in dest === false && src.hasOwnProperty(key)) {
3810
+ dest[key] = src[key];
3811
+ }
3812
+ }
3813
+ }
3814
+ return dest;
3815
+ }
3816
+
3817
+ function extend(o) {
3818
+ var dest = o || {},
3819
+ n = arguments.length,
3820
+ key, i, src;
3821
+ for (i=1; i<n; i++) {
3822
+ src = arguments[i] || {};
3823
+ for (key in src) {
3824
+ if (src.hasOwnProperty(key)) {
3825
+ dest[key] = src[key];
3826
+ }
3827
+ }
3828
+ }
3829
+ return dest;
3830
+ }
3831
+
3832
+ // Pseudoclassical inheritance
3833
+ //
3834
+ // Inherit from a Parent function:
3835
+ // inherit(Child, Parent);
3836
+ // Call parent's constructor (inside child constructor):
3837
+ // this.__super__([args...]);
3838
+ function inherit(targ, src) {
3839
+ var f = function() {
3840
+ if (this.__super__ == f) {
3841
+ // add __super__ of parent to front of lookup chain
3842
+ // so parent class constructor can call its parent using this.__super__
3843
+ this.__super__ = src.prototype.__super__;
3844
+ // call parent constructor function. this.__super__ now points to parent-of-parent
3845
+ src.apply(this, arguments);
3846
+ // remove temp __super__, expose targ.prototype.__super__ again
3847
+ delete this.__super__;
3848
+ }
3849
+ };
3850
+
3851
+ f.prototype = src.prototype || src; // added || src to allow inheriting from objects as well as functions
3852
+ // Extend targ prototype instead of wiping it out --
3853
+ // in case inherit() is called after targ.prototype = {stuff}; statement
3854
+ targ.prototype = extend(new f(), targ.prototype); //
3855
+ targ.prototype.constructor = targ;
3856
+ targ.prototype.__super__ = f;
3857
+ }
3858
+
3859
+
3860
+ // Call @iter on each member of an array (similar to Array#reduce(iter))
3861
+ // iter: function(memo, item, callback)
3862
+ // Call @done when all members have been processed or if an error occurs
3863
+ // done: function(err, memo)
3864
+ // @memo: Initial value
3865
+ //
3866
+ function reduceAsync(arr, memo, iter, done) {
3867
+ var call = typeof setImmediate == 'undefined' ? setTimeout : setImmediate;
3868
+ var i=0;
3869
+ next(null, memo);
3870
+
3871
+ function next(err, memo) {
3872
+ // Detach next operation from call stack to prevent overflow
3873
+ // Don't use setTimeout(, 0) if setImmediate is available
3874
+ // (setTimeout() can introduce a long delay if previous operation was slow,
3875
+ // as of Node 0.10.32 -- a bug?)
3876
+ if (err) {
3877
+ return done(err, null);
3878
+ }
3879
+ call(function() {
3880
+ if (i < arr.length === false) {
3881
+ done(null, memo);
3882
+ } else {
3883
+ iter(memo, arr[i++], next);
3884
+ }
3885
+ }, 0);
3886
+ }
3887
+ }
3888
+
3889
+
3890
+ // Append elements of @src array to @dest array
3891
+ function merge(dest, src) {
3892
+ if (!isArray(dest) || !isArray(src)) {
3893
+ error("Usage: merge(destArray, srcArray);");
3894
+ }
3895
+ for (var i=0, n=src.length; i<n; i++) {
3896
+ dest.push(src[i]);
3897
+ }
3898
+ return dest;
3899
+ }
3900
+
3901
+ // Returns elements in arr and not in other
3902
+ // (similar to underscore diff)
3903
+ function difference(arr, other) {
3904
+ var index = arrayToIndex(other);
3905
+ return arr.filter(function(el) {
3906
+ return !Object.prototype.hasOwnProperty.call(index, el);
3907
+ });
3908
+ }
3909
+
3910
+ // Return the intersection of two arrays
3911
+ function intersection(a, b) {
3912
+ return a.filter(function(el) {
3913
+ return b.includes(el);
3914
+ });
3915
+ }
3916
+
3917
+ function indexOf(arr, item) {
3918
+ var nan = item !== item;
3919
+ for (var i = 0, len = arr.length || 0; i < len; i++) {
3920
+ if (arr[i] === item) return i;
3921
+ if (nan && arr[i] !== arr[i]) return i;
3922
+ }
3923
+ return -1;
3924
+ }
3925
+
3926
+ // Test a string or array-like object for existence of substring or element
3927
+ function contains(container, item) {
3928
+ if (isString(container)) {
3929
+ return container.indexOf(item) != -1;
3930
+ }
3931
+ else if (isArrayLike(container)) {
3932
+ return indexOf(container, item) != -1;
3933
+ }
3934
+ error("Expected Array or String argument");
3935
+ }
3936
+
3937
+ function some(arr, test) {
3938
+ return arr.reduce(function(val, item) {
3939
+ return val || test(item); // TODO: short-circuit?
3940
+ }, false);
3941
+ }
3942
+
3943
+ function every(arr, test) {
3944
+ return arr.reduce(function(val, item) {
3945
+ return val && test(item);
3946
+ }, true);
3947
+ }
3948
+
3949
+ function find(arr, test, ctx) {
3950
+ var matches = arr.filter(test, ctx);
3951
+ return matches.length === 0 ? null : matches[0];
3952
+ }
3953
+
3954
+ function range(len, start, inc) {
3955
+ var arr = [],
3956
+ v = start === void 0 ? 0 : start,
3957
+ i = inc === void 0 ? 1 : inc;
3958
+ while(len--) {
3959
+ arr.push(v);
3960
+ v += i;
3961
+ }
3962
+ return arr;
3963
+ }
3964
+
3965
+ function repeat(times, func) {
3966
+ var values = [],
3967
+ val;
3968
+ for (var i=0; i<times; i++) {
3969
+ val = func(i);
3970
+ if (val !== void 0) {
3971
+ values[i] = val;
3972
+ }
3973
+ }
3974
+ return values.length > 0 ? values : void 0;
3975
+ }
3976
+
3977
+ // Calc sum, skip falsy and NaN values
3978
+ // Assumes: no other non-numeric objects in array
3979
+ //
3980
+ function sum(arr, info) {
3981
+ if (!isArrayLike(arr)) error ("sum() expects an array, received:", arr);
3982
+ var tot = 0,
3983
+ nan = 0,
3984
+ val;
3985
+ for (var i=0, n=arr.length; i<n; i++) {
3986
+ val = arr[i];
3987
+ if (val) {
3988
+ tot += val;
3989
+ } else if (isNaN(val)) {
3990
+ nan++;
3991
+ }
3992
+ }
3993
+ if (info) {
3994
+ info.nan = nan;
3995
+ }
3996
+ return tot;
3997
+ }
3998
+
3999
+ // Calculate min and max values of an array, ignoring NaN values
4000
+ function getArrayBounds(arr) {
4001
+ var min = Infinity,
4002
+ max = -Infinity,
4003
+ nan = 0, val;
4004
+ for (var i=0, len=arr.length; i<len; i++) {
4005
+ val = arr[i];
4006
+ if (val !== val) nan++;
4007
+ if (val < min) min = val;
4008
+ if (val > max) max = val;
4009
+ }
4010
+ return {
4011
+ min: min,
4012
+ max: max,
4013
+ nan: nan
4014
+ };
4015
+ }
4016
+
4017
+ // export function uniq(src) {
4018
+ // var index = {};
4019
+ // return src.reduce(function(memo, el) {
4020
+ // if (el in index === false) {
4021
+ // index[el] = true;
4022
+ // memo.push(el);
4023
+ // }
4024
+ // return memo;
4025
+ // }, []);
4026
+ // }
4027
+
4028
+ function uniq(src) {
4029
+ var index = new Set();
4030
+ var arr = [];
4031
+ var item;
4032
+ for (var i=0, n=src.length; i<n; i++) {
4033
+ item = src[i];
4034
+ if (!index.has(item)) {
4035
+ arr.push(item);
4036
+ index.add(item);
4037
+ }
4038
+ }
4039
+ return arr;
4040
+ }
4041
+
4042
+ function pluck(arr, key) {
4043
+ return arr.map(function(obj) {
4044
+ return obj[key];
4045
+ });
4046
+ }
4047
+
4048
+ function countValues(arr) {
4049
+ return arr.reduce(function(memo, val) {
4050
+ memo[val] = (val in memo) ? memo[val] + 1 : 1;
4051
+ return memo;
4052
+ }, {});
4053
+ }
4054
+
4055
+ function indexOn(arr, k) {
4056
+ return arr.reduce(function(index, o) {
4057
+ index[o[k]] = o;
4058
+ return index;
4059
+ }, {});
4060
+ }
4061
+
4062
+ function groupBy(arr, k) {
4063
+ return arr.reduce(function(index, o) {
4064
+ var keyval = o[k];
4065
+ if (keyval in index) {
4066
+ index[keyval].push(o);
4067
+ } else {
4068
+ index[keyval] = [o];
4069
+ }
4070
+ return index;
4071
+ }, {});
4072
+ }
4073
+
4074
+ function arrayToIndex(arr, val) {
4075
+ var init = arguments.length > 1;
4076
+ return arr.reduce(function(index, key) {
4077
+ index[key] = init ? val : true;
4078
+ return index;
4079
+ }, {});
4080
+ }
4081
+
4082
+ // Support for iterating over array-like objects, like typed arrays
4083
+ function forEach(arr, func, ctx) {
4084
+ if (!isArrayLike(arr)) {
4085
+ throw new Error("#forEach() takes an array-like argument. " + arr);
4086
+ }
4087
+ for (var i=0, n=arr.length; i < n; i++) {
4088
+ func.call(ctx, arr[i], i);
4089
+ }
4090
+ }
4091
+
4092
+ function forEachProperty(o, func, ctx) {
4093
+ Object.keys(o).forEach(function(key) {
4094
+ func.call(ctx, o[key], key);
4095
+ });
4096
+ }
4097
+
4098
+ function initializeArray(arr, init) {
4099
+ for (var i=0, len=arr.length; i<len; i++) {
4100
+ arr[i] = init;
4101
+ }
4102
+ return arr;
4103
+ }
4104
+
4105
+ function replaceArray(arr, arr2) {
4106
+ arr.splice(0, arr.length);
4107
+ for (var i=0, n=arr2.length; i<n; i++) {
4108
+ arr.push(arr2[i]);
4109
+ }
4110
+ }
4111
+
4112
+ function repeatString(src, n) {
4113
+ var str = "";
4114
+ for (var i=0; i<n; i++)
4115
+ str += src;
4116
+ return str;
4117
+ }
4118
+
4119
+ function splitLines(str) {
4120
+ return str.split(/\r?\n/);
4121
+ }
4122
+
4123
+ function pluralSuffix(count) {
4124
+ return count != 1 ? 's' : '';
4125
+ }
4126
+
4127
+ function endsWith(str, ending) {
4128
+ return str.indexOf(ending, str.length - ending.length) !== -1;
4129
+ }
4130
+
4131
+ function lpad(str, size, pad) {
4132
+ pad = pad || ' ';
4133
+ str = String(str);
4134
+ return repeatString(pad, size - str.length) + str;
4135
+ }
4136
+
4137
+ function rpad(str, size, pad) {
4138
+ pad = pad || ' ';
4139
+ str = String(str);
4140
+ return str + repeatString(pad, size - str.length);
4141
+ }
4142
+
4143
+ function trim(str) {
4144
+ return ltrim(rtrim(str));
4145
+ }
4146
+
4147
+ var ltrimRxp = /^\s+/;
4148
+ function ltrim(str) {
4149
+ return str.replace(ltrimRxp, '');
4150
+ }
4151
+
4152
+ var rtrimRxp = /\s+$/;
4153
+ function rtrim(str) {
4154
+ return str.replace(rtrimRxp, '');
4155
+ }
4156
+
4157
+ function addThousandsSep(str) {
4158
+ var fmt = '',
4159
+ start = str[0] == '-' ? 1 : 0,
4160
+ dec = str.indexOf('.'),
4161
+ end = str.length,
4162
+ ins = (dec == -1 ? end : dec) - 3;
4163
+ while (ins > start) {
4164
+ fmt = ',' + str.substring(ins, end) + fmt;
4165
+ end = ins;
4166
+ ins -= 3;
4167
+ }
4168
+ return str.substring(0, end) + fmt;
4169
+ }
4170
+
4171
+ function numToStr(num, decimals) {
4172
+ return decimals >= 0 ? num.toFixed(decimals) : String(num);
4173
+ }
4174
+
4175
+ function formatNumber(val) {
4176
+ return val + '';
4177
+ }
4178
+
4179
+ function formatIntlNumber(val) {
4180
+ var str = formatNumber(val);
4181
+ return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
4182
+ }
4183
+
4184
+ function formatNumberForDisplay(num, decimals, nullStr, showPos) {
4185
+ var fmt;
4186
+ if (isNaN(num)) {
4187
+ fmt = nullStr || '-';
4188
+ } else {
4189
+ fmt = numToStr(num, decimals);
4190
+ fmt = addThousandsSep(fmt);
4191
+ if (showPos && parseFloat(fmt) > 0) {
4192
+ fmt = "+" + fmt;
4193
+ }
4194
+ }
4195
+ return fmt;
4196
+ }
4197
+
4198
+ function shuffle(arr) {
4199
+ var tmp, i, j;
4200
+ for (i = arr.length - 1; i > 0; i--) {
4201
+ j = Math.floor(Math.random() * (i + 1));
4202
+ tmp = arr[i];
4203
+ arr[i] = arr[j];
4204
+ arr[j] = tmp;
4205
+ }
4206
+ }
4207
+
4208
+ // Sort an array of objects based on one or more properties.
4209
+ // Usage: sortOn(array, key1, asc?[, key2, asc? ...])
4210
+ //
4211
+ function sortOn(arr) {
4212
+ var comparators = [];
4213
+ for (var i=1; i<arguments.length; i+=2) {
4214
+ comparators.push(getKeyComparator(arguments[i], arguments[i+1]));
4215
+ }
4216
+ arr.sort(function(a, b) {
4217
+ var cmp = 0,
4218
+ i = 0,
4219
+ n = comparators.length;
4220
+ while (i < n && cmp === 0) {
4221
+ cmp = comparators[i](a, b);
4222
+ i++;
4223
+ }
4224
+ return cmp;
4225
+ });
4226
+ return arr;
4227
+ }
4228
+
4229
+ // Sort array of values that can be compared with < > operators (strings, numbers)
4230
+ // null, undefined and NaN are sorted to the end of the array
4231
+ // default order is ascending
4232
+ //
4233
+ function genericSort(arr, ascending) {
4234
+ var compare = getGenericComparator(ascending);
4235
+ Array.prototype.sort.call(arr, compare);
4236
+ return arr;
4237
+ }
4238
+
4239
+ function getSortedIds(arr, asc) {
4240
+ var ids = range(arr.length);
4241
+ sortArrayIndex(ids, arr, asc);
4242
+ return ids;
4243
+ }
4244
+
4245
+ function sortArrayIndex(ids, arr, asc) {
4246
+ var compare = getGenericComparator(asc);
4247
+ ids.sort(function(i, j) {
4248
+ // added i, j comparison to guarantee that sort is stable
4249
+ var cmp = compare(arr[i], arr[j]);
4250
+ return cmp > 0 || cmp === 0 && i > j ? 1 : -1;
4251
+ });
4252
+ }
4253
+
4254
+ function reorderArray(arr, idxs) {
4255
+ var len = idxs.length;
4256
+ var arr2 = [];
4257
+ for (var i=0; i<len; i++) {
4258
+ var idx = idxs[i];
4259
+ if (idx < 0 || idx >= len) error("Out-of-bounds array idx");
4260
+ arr2[i] = arr[idx];
4261
+ }
4262
+ replaceArray(arr, arr2);
4263
+ }
4264
+
4265
+ function getKeyComparator(key, asc) {
4266
+ var compare = getGenericComparator(asc);
4267
+ return function(a, b) {
4268
+ return compare(a[key], b[key]);
4269
+ };
4270
+ }
4271
+
4272
+ function getGenericComparator(asc) {
4273
+ asc = asc !== false;
4274
+ return function(a, b) {
4275
+ var retn = 0;
4276
+ if (b == null) {
4277
+ retn = a == null ? 0 : -1;
4278
+ } else if (a == null) {
4279
+ retn = 1;
4280
+ } else if (a < b) {
4281
+ retn = asc ? -1 : 1;
4282
+ } else if (a > b) {
4283
+ retn = asc ? 1 : -1;
4284
+ } else if (a !== a) {
4285
+ retn = 1;
4286
+ } else if (b !== b) {
4287
+ retn = -1;
4288
+ }
4289
+ return retn;
4290
+ };
4291
+ }
4292
+
4293
+
4294
+ // Generic in-place sort (null, NaN, undefined not handled)
4295
+ function quicksort(arr, asc) {
4296
+ quicksortPartition(arr, 0, arr.length-1);
4297
+ if (asc === false) Array.prototype.reverse.call(arr); // Works with typed arrays
4298
+ return arr;
4299
+ }
4300
+
4301
+ // Moved out of quicksort() (saw >100% speedup in Chrome with deep recursion)
4302
+ function quicksortPartition (a, lo, hi) {
4303
+ var i = lo,
4304
+ j = hi,
4305
+ pivot, tmp;
4306
+ while (i < hi) {
4307
+ pivot = a[lo + hi >> 1]; // avoid n^2 performance on sorted arrays
4308
+ while (i <= j) {
4309
+ while (a[i] < pivot) i++;
4310
+ while (a[j] > pivot) j--;
4311
+ if (i <= j) {
4312
+ tmp = a[i];
4313
+ a[i] = a[j];
4314
+ a[j] = tmp;
4315
+ i++;
4316
+ j--;
4317
+ }
4318
+ }
4319
+ if (lo < j) quicksortPartition(a, lo, j);
4320
+ lo = i;
4321
+ j = hi;
4322
+ }
4323
+ }
4324
+
4325
+
4326
+ function findRankByValue(arr, value) {
4327
+ if (isNaN(value)) return arr.length;
4328
+ var rank = 1;
4329
+ for (var i=0, n=arr.length; i<n; i++) {
4330
+ if (value > arr[i]) rank++;
4331
+ }
4332
+ return rank;
4333
+ }
4334
+
4335
+ function findValueByPct(arr, pct) {
4336
+ var rank = Math.ceil((1-pct) * (arr.length));
4337
+ return findValueByRank(arr, rank);
4338
+ }
4339
+
4340
+ // See http://ndevilla.free.fr/median/median/src/wirth.c
4341
+ // Elements of @arr are reordered
4342
+ //
4343
+ function findValueByRank(arr, rank) {
4344
+ if (!arr.length || rank < 1 || rank > arr.length) error("[findValueByRank()] invalid input");
4345
+
4346
+ rank = clamp(rank | 0, 1, arr.length);
4347
+ var k = rank - 1, // conv. rank to array index
4348
+ n = arr.length,
4349
+ l = 0,
4350
+ m = n - 1,
4351
+ i, j, val, tmp;
4352
+
4353
+ while (l < m) {
4354
+ val = arr[k];
4355
+ i = l;
4356
+ j = m;
4357
+ do {
4358
+ while (arr[i] < val) {i++;}
4359
+ while (val < arr[j]) {j--;}
4360
+ if (i <= j) {
4361
+ tmp = arr[i];
4362
+ arr[i] = arr[j];
4363
+ arr[j] = tmp;
4364
+ i++;
4365
+ j--;
4366
+ }
4367
+ } while (i <= j);
4368
+ if (j < k) l = i;
4369
+ if (k < i) m = j;
4370
+ }
4371
+ return arr[k];
4372
+ }
4373
+
4374
+ //
4375
+ //
4376
+ function findMedian(arr) {
4377
+ var n = arr.length,
4378
+ rank = Math.floor(n / 2) + 1,
4379
+ median = findValueByRank(arr, rank);
4380
+ if ((n & 1) == 0) {
4381
+ median = (median + findValueByRank(arr, rank - 1)) / 2;
4382
+ }
4383
+ return median;
4384
+ }
4385
+
4386
+
4387
+ function mean(arr) {
4388
+ var count = 0,
4389
+ avg = NaN,
4390
+ val;
4391
+ for (var i=0, n=arr.length; i<n; i++) {
4392
+ val = arr[i];
4393
+ if (isNaN(val)) continue;
4394
+ avg = ++count == 1 ? val : val / count + (count - 1) / count * avg;
4395
+ }
4396
+ return avg;
4397
+ }
4398
+
4399
+
4400
+ /*
4401
+ A simplified version of printf formatting
4402
+ Format codes: %[flags][width][.precision]type
4403
+
4404
+ supported flags:
4405
+ + add '+' before positive numbers
4406
+ 0 left-pad with '0'
4407
+ ' Add thousands separator
4408
+ width: 1 to many
4409
+ precision: .(1 to many)
4410
+ type:
4411
+ s string
4412
+ di integers
4413
+ f decimal numbers
4414
+ xX hexidecimal (unsigned)
4415
+ % literal '%'
4416
+
4417
+ Examples:
4418
+ code val formatted
4419
+ %+d 1 '+1'
4420
+ %4i 32 ' 32'
4421
+ %04i 32 '0032'
4422
+ %x 255 'ff'
4423
+ %.2f 0.125 '0.13'
4424
+ %'f 1000 '1,000'
4425
+ */
4426
+
4427
+ // Usage: format(formatString, [values])
4428
+ // Tip: When reusing the same format many times, use formatter() for 5x - 10x better performance
4429
+ //
4430
+ function format(fmt) {
4431
+ var fn = formatter(fmt);
4432
+ var str = fn.apply(null, Array.prototype.slice.call(arguments, 1));
4433
+ return str;
4434
+ }
4435
+
4436
+ function formatValue(val, matches) {
4437
+ var flags = matches[1];
4438
+ var padding = matches[2];
4439
+ var decimals = matches[3] ? parseInt(matches[3].substr(1)) : void 0;
4440
+ var type = matches[4];
4441
+ var isString = type == 's',
4442
+ isHex = type == 'x' || type == 'X',
4443
+ isInt = type == 'd' || type == 'i',
4444
+ isFloat = type == 'f',
4445
+ isNumber = !isString;
4446
+
4447
+ var sign = "",
4448
+ padDigits = 0,
4449
+ isZero = false,
4450
+ isNeg = false;
4451
+
4452
+ var str, padChar, padStr;
4453
+ if (isString) {
4454
+ str = String(val);
4455
+ }
4456
+ else if (isHex) {
4457
+ str = val.toString(16);
4458
+ if (type == 'X')
4459
+ str = str.toUpperCase();
4460
+ }
4461
+ else if (isNumber) {
4462
+ // str = formatNumberForDisplay(val, isInt ? 0 : decimals);
4463
+ str = numToStr(val, decimals);
4464
+ if (str[0] == '-') {
4465
+ isNeg = true;
4466
+ str = str.substr(1);
4467
+ }
4468
+ isZero = parseFloat(str) == 0;
4469
+ if (flags.indexOf("'") != -1 || flags.indexOf(',') != -1) {
4470
+ str = addThousandsSep(str);
4471
+ }
4472
+ if (!isZero) { // BUG: sign is added when num rounds to 0
4473
+ if (isNeg) {
4474
+ sign = "\u2212"; // U+2212
4475
+ } else if (flags.indexOf('+') != -1) {
4476
+ sign = '+';
4477
+ }
4478
+ }
4479
+ }
4480
+
4481
+ if (padding) {
4482
+ var strLen = str.length + sign.length;
4483
+ var minWidth = parseInt(padding, 10);
4484
+ if (strLen < minWidth) {
4485
+ padDigits = minWidth - strLen;
4486
+ padChar = flags.indexOf('0') == -1 ? ' ' : '0';
4487
+ padStr = repeatString(padChar, padDigits);
4488
+ }
4489
+ }
4490
+
4491
+ if (padDigits == 0) {
4492
+ str = sign + str;
4493
+ } else if (padChar == '0') {
4494
+ str = sign + padStr + str;
4495
+ } else {
4496
+ str = padStr + sign + str;
4497
+ }
4498
+ return str;
4499
+ }
4500
+
4501
+ // Get a function for interpolating formatted values into a string.
4502
+ function formatter(fmt) {
4503
+ var codeRxp = /%([\',+0]*)([1-9]?)((?:\.[1-9])?)([sdifxX%])/g;
4504
+ var literals = [],
4505
+ formatCodes = [],
4506
+ startIdx = 0,
4507
+ prefix = "",
4508
+ matches = codeRxp.exec(fmt),
4509
+ literal;
4510
+
4511
+ while (matches) {
4512
+ literal = fmt.substring(startIdx, codeRxp.lastIndex - matches[0].length);
4513
+ if (matches[0] == '%%') {
4514
+ prefix += literal + '%';
4515
+ } else {
4516
+ literals.push(prefix + literal);
4517
+ prefix = '';
4518
+ formatCodes.push(matches);
4519
+ }
4520
+ startIdx = codeRxp.lastIndex;
4521
+ matches = codeRxp.exec(fmt);
4522
+ }
4523
+ literals.push(prefix + fmt.substr(startIdx));
4524
+
4525
+ return function() {
4526
+ var str = literals[0],
4527
+ n = arguments.length;
4528
+ if (n != formatCodes.length) {
4529
+ error("[format()] Data does not match format string; format:", fmt, "data:", arguments);
4530
+ }
4531
+ for (var i=0; i<n; i++) {
4532
+ str += formatValue(arguments[i], formatCodes[i]) + literals[i+1];
4533
+ }
4534
+ return str;
4535
+ };
4536
+ }
4537
+
4538
+ function wildcardToRegExp(name) {
4539
+ var rxp = name.split('*').map(function(str) {
4540
+ return regexEscape(str);
4541
+ }).join('.*');
4542
+ return new RegExp('^' + rxp + '$');
4543
+ }
4544
+
4545
+ function createBuffer(arg, arg2) {
4546
+ if (isInteger(arg)) {
4547
+ return Buffer.allocUnsafe ? Buffer.allocUnsafe(arg) : new Buffer(arg);
4548
+ } else {
4549
+ // check allocUnsafe to make sure Buffer.from() will accept strings (it didn't before Node v5.10)
4550
+ return Buffer.from && Buffer.allocUnsafe ? Buffer.from(arg, arg2) : new Buffer(arg, arg2);
4551
+ }
4552
+ }
4553
+
4554
+ function expandoBuffer(constructor, rate) {
4555
+ var capacity = 0,
4556
+ k = rate >= 1 ? rate : 1.2,
4557
+ buf;
4558
+ return function(size) {
4559
+ if (size > capacity) {
4560
+ capacity = Math.ceil(size * k);
4561
+ buf = constructor ? new constructor(capacity) : createBuffer(capacity);
4562
+ }
4563
+ return buf;
4564
+ };
4565
+ }
4566
+
4567
+ function copyElements(src, i, dest, j, n, rev) {
4568
+ if (src === dest && j > i) error ("copy error");
4569
+ var inc = 1,
4570
+ offs = 0;
4571
+ if (rev) {
4572
+ inc = -1;
4573
+ offs = n - 1;
4574
+ }
4575
+ for (var k=0; k<n; k++, offs += inc) {
4576
+ dest[k + j] = src[i + offs];
4577
+ }
4578
+ }
4579
+
4580
+ function extendBuffer(src, newLen, copyLen) {
4581
+ var len = Math.max(src.length, newLen);
4582
+ var n = copyLen || src.length;
4583
+ var dest = new src.constructor(len);
4584
+ copyElements(src, 0, dest, 0, n);
4585
+ return dest;
4586
+ }
4587
+
4588
+ function mergeNames(name1, name2) {
4589
+ var merged;
4590
+ if (name1 && name2) {
4591
+ merged = findStringPrefix(name1, name2).replace(/[-_]$/, '');
4592
+ }
4593
+ return merged || '';
4594
+ }
4595
+
4596
+ function findStringPrefix(a, b) {
4597
+ var i = 0;
4598
+ for (var n=a.length; i<n; i++) {
4599
+ if (a[i] !== b[i]) break;
4600
+ }
4601
+ return a.substr(0, i);
4602
+ }
4603
+
4604
+ function parsePercent(o) {
4605
+ var str = String(o);
4606
+ var isPct = str.indexOf('%') > 0;
4607
+ var pct;
4608
+ if (isPct) {
4609
+ pct = Number(str.replace('%', '')) / 100;
4610
+ } else {
4611
+ pct = Number(str);
4612
+ }
4613
+ if (!(pct >= 0 && pct <= 1)) {
4614
+ stop(format("Invalid percentage: %s", str));
4615
+ }
4616
+ return pct;
4617
+ }
4618
+
4619
+ function formatVersionedName(name, i) {
4620
+ var suffix = String(i);
4621
+ if (/[0-9]$/.test(name)) {
4622
+ suffix = '-' + suffix;
4623
+ }
4624
+ return name + suffix;
4625
+ }
4626
+
4627
+ function uniqifyNames(names, formatter) {
4628
+ var counts = countValues(names),
4629
+ format = formatter || formatVersionedName,
4630
+ names2 = [];
4631
+
4632
+ names.forEach(function(name) {
4633
+ var i = 0,
4634
+ candidate = name,
4635
+ versionedName;
4636
+ while (
4637
+ names2.indexOf(candidate) > -1 || // candidate name has already been used
4638
+ candidate == name && counts[candidate] > 1 || // duplicate unversioned names
4639
+ candidate != name && counts[candidate] > 0) { // versioned name is a preexisting name
4640
+ i++;
4641
+ versionedName = format(name, i);
4642
+ if (!versionedName || versionedName == candidate) {
4643
+ throw new Error("Naming error"); // catch buggy versioning function
4644
+ }
4645
+ candidate = versionedName;
4646
+ }
4647
+ names2.push(candidate);
4648
+ });
4649
+ return names2;
4650
+ }
4651
+
4652
+
4653
+ // Assume: @raw is string, undefined or null
4654
+ function parseString(raw) {
4655
+ return raw ? raw : "";
4656
+ }
4657
+
4658
+ // Assume: @raw is string, undefined or null
4659
+ // Use null instead of NaN for unparsable values
4660
+ // (in part because if NaN is used, empty strings get converted to "NaN"
4661
+ // when re-exported).
4662
+ function parseNumber(raw) {
4663
+ return parseToNum(raw, cleanNumericString);
4664
+ }
4665
+
4666
+ function parseIntlNumber(raw) {
4667
+ return parseToNum(raw, convertIntlNumString);
4668
+ }
4669
+
4670
+ function parseToNum(raw, clean) {
4671
+ var str = String(raw).trim();
4672
+ var parsed = str ? Number(clean(str)) : NaN;
4673
+ return isNaN(parsed) ? null : parsed;
4674
+ }
4675
+
4676
+ // Remove comma separators from strings
4677
+ function cleanNumericString(str) {
4678
+ return (str.indexOf(',') > 0) ? str.replace(/,([0-9]{3})/g, '$1') : str;
4679
+ }
4680
+
4681
+ function convertIntlNumString(str) {
4682
+ str = str.replace(/[ .]([0-9]{3})/g, '$1');
4683
+ return str.replace(',', '.');
4684
+ }
4685
+
4686
+ function trimQuotes(raw) {
4687
+ var len = raw.length, first, last;
4688
+ if (len >= 2) {
4689
+ first = raw.charAt(0);
4690
+ last = raw.charAt(len-1);
4691
+ if (first == '"' && last == '"' && !raw.includes('","') ||
4692
+ first == "'" && last == "'" && !raw.includes("','")) {
4693
+ return raw.substr(1, len-2);
4694
+ }
4695
+ }
4696
+ return raw;
4697
+ }
4698
+
4699
+ var context = createContext(); // command context (persist for the current command cycle)
4700
+
4701
+ function runningInBrowser() {
4702
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined';
4703
+ }
4704
+
4705
+ function getStateVar(key) {
4706
+ return context[key];
4707
+ }
4708
+
4709
+ function setStateVar(key, val) {
4710
+ context[key] = val;
4711
+ }
4712
+
4713
+ function createContext() {
4714
+ return {
4715
+ DEBUG: false,
4716
+ QUIET: false,
4717
+ VERBOSE: false,
4718
+ defs: {},
4719
+ input_files: []
4720
+ };
4721
+ }
4722
+
4723
+ // Install a new set of context variables, clear them when an async callback is called.
4724
+ // @cb callback function to wrap
4725
+ // returns wrapped callback function
4726
+ function createAsyncContext(cb) {
4727
+ context = createContext();
4728
+ return function() {
4729
+ cb.apply(null, utils.toArray(arguments));
4730
+ // clear context after cb(), so output/errors can be handled in current context
4731
+ context = createContext();
4732
+ };
4733
+ }
4734
+
4735
+ // Save the current context, restore it when an async callback is called
4736
+ // @cb callback function to wrap
4737
+ // returns wrapped callback function
4738
+ function preserveContext(cb) {
4739
+ var ctx = context;
4740
+ return function() {
4741
+ context = ctx;
4742
+ cb.apply(null, utils.toArray(arguments));
4743
+ };
4744
+ }
4745
+
4746
+ var LOGGING = false;
4747
+ var STDOUT = false; // use stdout for status messages
4748
+
4749
+ // These three functions can be reset by GUI using setLoggingFunctions();
4750
+ var _error = function() {
4751
+ var msg = utils.toArray(arguments).join(' ');
4752
+ throw new Error(msg);
4753
+ };
4754
+
4755
+ var _stop = function() {
4756
+ throw new UserError(formatLogArgs(arguments));
4757
+ };
4758
+
4759
+ var _interrupt = function() {
4760
+ throw new NonFatalError(formatLogArgs(arguments));
4761
+ };
4762
+
4763
+ var _message = function() {
4764
+ logArgs(arguments);
4765
+ };
4766
+
4767
+ function enableLogging() {
4768
+ LOGGING = true;
4769
+ }
4770
+
4771
+ function loggingEnabled() {
4772
+ return !!LOGGING;
4773
+ }
4774
+
4775
+ // Handle an unexpected condition (internal error)
4776
+ function error() {
4777
+ _error.apply(null, utils.toArray(arguments));
4778
+ }
4779
+
4780
+ // Handle an error caused by invalid input or misuse of API
4781
+ function stop() {
4782
+ _stop.apply(null, utils.toArray(arguments));
4783
+ }
4784
+
4785
+ function interrupt() {
4786
+ _interrupt.apply(null, utils.toArray(arguments));
4787
+ }
4788
+
4789
+ // Print a status message
4790
+ function message() {
4791
+ _message.apply(null, messageArgs(arguments));
4792
+ }
4793
+
4794
+ // A way for the GUI to replace the CLI logging functions
4795
+ function setLoggingFunctions(message, error, stop) {
4796
+ _message = message;
4797
+ _error = error;
4798
+ _stop = stop;
4799
+ }
4800
+
4801
+
4802
+ // print a message to stdout
4803
+ function print() {
4804
+ STDOUT = true; // tell logArgs() to print to stdout, not stderr
4805
+ message.apply(null, arguments);
4806
+ STDOUT = false;
4807
+ }
4808
+
4809
+ function verbose() {
4810
+ // verbose can be set globally with the -verbose command or separately for each command
4811
+ if (getStateVar('VERBOSE') || getStateVar('verbose')) {
4812
+ message.apply(null, arguments);
4813
+ }
4814
+ }
4815
+
4816
+ function debug() {
4817
+ if (getStateVar('DEBUG') || getStateVar('debug')) {
4818
+ logArgs(arguments);
4819
+ }
4820
+ }
4821
+
4822
+ function printError(err) {
4823
+ var msg;
4824
+ if (!LOGGING) return;
4825
+ if (utils.isString(err)) {
4826
+ err = new UserError(err);
4827
+ }
4828
+ if (err.name == 'NonFatalError') {
4829
+ console.error(messageArgs([err.message]).join(' '));
4830
+ } else if (err.name == 'UserError') {
4831
+ msg = err.message;
4832
+ if (!/Error/.test(msg)) {
4833
+ msg = "Error: " + msg;
4834
+ }
4835
+ console.error(messageArgs([msg]).join(' '));
4836
+ console.error("Run mapshaper -h to view help");
4837
+ } else {
4838
+ // not a user error (i.e. a bug in mapshaper)
4839
+ console.error(err);
4840
+ // throw err;
4841
+ }
4842
+ }
4843
+
4844
+ function UserError(msg) {
4845
+ var err = new Error(msg);
4846
+ err.name = 'UserError';
4847
+ return err;
4848
+ }
4849
+
4850
+ function NonFatalError(msg) {
4851
+ var err = new Error(msg);
4852
+ err.name = 'NonFatalError';
4853
+ return err;
4854
+ }
4855
+
4856
+ function formatColumns(arr, alignments) {
4857
+ var widths = arr.reduce(function(memo, line) {
4858
+ return line.map(function(str, i) {
4859
+ return memo ? Math.max(memo[i], str.length) : str.length;
4860
+ });
4861
+ }, null);
4862
+ return arr.map(function(line) {
4863
+ line = line.map(function(str, i) {
4864
+ var rt = alignments && alignments[i] == 'right';
4865
+ var pad = (rt ? str.padStart : str.padEnd).bind(str);
4866
+ return pad(widths[i], ' ');
4867
+ });
4868
+ return ' ' + line.join(' ');
4869
+ }).join('\n');
4870
+ }
4871
+
4872
+ // Format an array of (preferably short) strings in columns for console logging.
4873
+ function formatStringsAsGrid(arr) {
4874
+ // TODO: variable column width
4875
+ var longest = arr.reduce(function(len, str) {
4876
+ return Math.max(len, str.length);
4877
+ }, 0),
4878
+ colWidth = longest + 2,
4879
+ perLine = Math.floor(80 / colWidth) || 1;
4880
+ return arr.reduce(function(memo, name, i) {
4881
+ var col = i % perLine;
4882
+ if (i > 0 && col === 0) memo += '\n';
4883
+ if (col < perLine - 1) { // right-pad all but rightmost column
4884
+ name = utils.rpad(name, colWidth - 2, ' ');
4885
+ }
4886
+ return memo + ' ' + name;
4887
+ }, '');
4888
+ }
4889
+
4890
+ // expose so GUI can use it
4891
+ function formatLogArgs(args) {
4892
+ return utils.toArray(args).join(' ');
4893
+ }
4894
+
4895
+ function messageArgs(args) {
4896
+ var arr = utils.toArray(args);
4897
+ var cmd = getStateVar('current_command');
4898
+ if (cmd && cmd != 'help') {
4899
+ arr.unshift('[' + cmd + ']');
4900
+ }
4901
+ return arr;
4902
+ }
4903
+
4904
+ function logArgs(args) {
4905
+ if (!LOGGING || getStateVar('QUIET') || !utils.isArrayLike(args)) return;
4906
+ var msg = formatLogArgs(args);
4907
+ if (STDOUT) console.log(msg);
4908
+ else console.error(msg);
4909
+ }
4910
+
4911
+ // Utility functions for both paths and points
4912
+
4913
+ // @shp An element of the layer.shapes array
4914
+ // (may be null, or, depending on layer type, an array of points or an array of arrays of arc ids)
4915
+ function cloneShape(shp) {
4916
+ if (!shp) return null;
4917
+ return shp.map(function(part) {
4918
+ return part.concat();
4919
+ });
4920
+ }
4921
+
4922
+ function cloneShapes(arr) {
4923
+ return utils.isArray(arr) ? arr.map(cloneShape) : null;
4924
+ }
4925
+
4926
+ function forEachShapePart(paths, cb) {
4927
+ editShapeParts(paths, cb);
4928
+ }
4929
+
4930
+ // Updates shapes array in-place.
4931
+ // editPart: callback function
4932
+ function editShapes(shapes, editPart) {
4933
+ for (var i=0, n=shapes.length; i<n; i++) {
4934
+ shapes[i] = editShapeParts(shapes[i], editPart);
4935
+ }
4936
+ }
4937
+
4938
+ // @parts: geometry of a feature (array of paths, array of points or null)
4939
+ // @cb: function(part, i, parts)
4940
+ // If @cb returns an array, it replaces the existing value
4941
+ // If @cb returns null, the path is removed from the feature
4942
+ //
4943
+ function editShapeParts(parts, cb) {
4944
+ if (!parts) return null; // null geometry not edited
4945
+ if (!utils.isArray(parts)) error("Expected an array, received:", parts);
4946
+ var nulls = 0,
4947
+ n = parts.length,
4948
+ retn;
4949
+
4950
+ for (var i=0; i<n; i++) {
4951
+ retn = cb(parts[i], i, parts);
4952
+ if (retn === null) {
4953
+ nulls++;
4954
+ parts[i] = null;
4955
+ } else if (utils.isArray(retn)) {
4956
+ parts[i] = retn;
4957
+ }
4958
+ }
4959
+ if (nulls == n) {
4960
+ return null;
4961
+ } else if (nulls > 0) {
4962
+ return parts.filter(function(part) {return !!part;});
4963
+ } else {
4964
+ return parts;
4965
+ }
4966
+ }
4967
+
4968
+ // Get max number of parts in a single shape from an array of shapes.
4969
+ // Caveat: polygon holes are counted as separate parts.
4970
+ function findMaxPartCount(shapes) {
4971
+ var maxCount = 0, shp;
4972
+ for (var i=0, n=shapes.length; i<n; i++) {
4973
+ shp = shapes[i];
4974
+ if (shp && shp.length > maxCount) {
4975
+ maxCount = shp.length;
4976
+ }
4977
+ }
4978
+ return maxCount;
4979
+ }
4980
+
4981
+ // List of encodings supported by iconv-lite:
4982
+ // https://github.com/ashtuchkin/iconv-lite/wiki/Supported-Encodings
4983
+
4984
+ var iconv = require('iconv-lite');
4985
+ var toUtf8 = getNativeEncoder('utf8');
4986
+ var fromUtf8 = getNativeDecoder('utf8');
4987
+
4988
+ // Return list of supported encodings
4989
+ function getEncodings() {
4990
+ iconv.encodingExists('ascii'); // make iconv load its encodings
4991
+ return Object.keys(iconv.encodings);
4992
+ }
4993
+
4994
+ function validateEncoding(enc) {
4995
+ if (!encodingIsSupported(enc)) {
4996
+ stop("Unknown encoding:", enc, "\nRun the -encodings command see a list of supported encodings");
4997
+ }
4998
+ return enc;
4999
+ }
5000
+
5001
+ function stringsAreAscii(arr) {
5002
+ return stringIsAscii(arr.join(''));
5003
+ }
5004
+
5005
+ function stringIsAscii(str) {
5006
+ var c;
5007
+ for (var i=0, n=str.length; i<n; i++) {
5008
+ c = str.charCodeAt(i);
5009
+ if (c >= 128) return false;
5010
+ }
5011
+ return true;
5012
+ }
5013
+
5014
+ function encodingIsUtf8(enc) {
5015
+ // treating utf-8 as default
5016
+ return !enc || /^utf-?8$/i.test(String(enc));
5017
+ }
5018
+
5019
+ // Identify the most common encodings that are supersets of ascii at the
5020
+ // single-byte level (meaning that bytes in 0 - 0x7f range must be ascii)
5021
+ // (this allows identifying line breaks and other ascii patterns in buffers)
5022
+ function encodingIsAsciiCompat(enc) {
5023
+ enc = standardizeEncodingName(enc);
5024
+ // gb.* selects the Guo Biao encodings
5025
+ // big5 in not compatible -- second byte starts at 0x40
5026
+ return !enc || /^(win|latin|utf8|ascii|iso88|gb)/.test(enc);
5027
+ }
5028
+
5029
+ // Ex. convert UTF-8 to utf8
5030
+ function standardizeEncodingName(enc) {
5031
+ return (enc || '').toLowerCase().replace(/[_-]/g, '');
5032
+ }
5033
+
5034
+ // Similar to Buffer#toString(); tries to speed up utf8 conversion in
5035
+ // web browser (when using browserify Buffer shim)
5036
+ function bufferToString(buf, enc, start, end) {
5037
+ if (start >= 0) {
5038
+ buf = buf.slice(start, end);
5039
+ }
5040
+ return decodeString(buf, enc);
5041
+ }
5042
+
5043
+ function getNativeEncoder(enc) {
5044
+ var encoder = null;
5045
+ enc = standardizeEncodingName(enc);
5046
+ if (enc != 'utf8') {
5047
+ // TODO: support more encodings if TextEncoder is available
5048
+ return null;
5049
+ }
5050
+ if (typeof TextEncoder != 'undefined') {
5051
+ encoder = new TextEncoder(enc);
5052
+ }
5053
+ return function(str) {
5054
+ // Convert Uint8Array from encoder to Buffer (fix for issue #216)
5055
+ return encoder ? Buffer.from(encoder.encode(str).buffer) : utils.createBuffer(str, enc);
5056
+ };
5057
+ }
5058
+
5059
+ function encodeString(str, enc) {
5060
+ // TODO: faster ascii encoding?
5061
+ var buf;
5062
+ if (encodingIsUtf8(enc)) {
5063
+ buf = toUtf8(str);
5064
+ } else {
5065
+ buf = iconv.encode(str, enc);
5066
+ }
5067
+ return buf;
5068
+ }
5069
+
5070
+ function getNativeDecoder(enc) {
5071
+ var decoder = null;
5072
+ enc = standardizeEncodingName(enc);
5073
+ if (enc != 'utf8') {
5074
+ // TODO: support more encodings if TextDecoder is available
5075
+ return null;
5076
+ }
5077
+ if (typeof TextDecoder != 'undefined') {
5078
+ decoder = new TextDecoder(enc);
5079
+ }
5080
+ return function(buf) {
5081
+ return decoder ? decoder.decode(buf) : buf.toString(enc);
5082
+ };
5083
+ }
5084
+
5085
+ // @buf a Node Buffer
5086
+ function decodeString(buf, enc) {
5087
+ var str;
5088
+ if (encodingIsUtf8(enc)) {
5089
+ str = fromUtf8(buf);
5090
+ } else {
5091
+ str = iconv.decode(buf, enc);
5092
+ }
5093
+ return str;
5094
+ }
5095
+
5096
+ function encodingIsSupported(raw) {
5097
+ var enc = standardizeEncodingName(raw);
5098
+ return getEncodings().includes(enc);
5099
+ }
3382
5100
 
3383
- // init name editor
3384
- new ClickText2(entry.findChild('.layer-name'))
3385
- .on('change', function(e) {
3386
- var target = findLayerById(id);
3387
- var str = cleanLayerName(this.value());
3388
- this.value(formatLayerNameForDisplay(str));
3389
- target.layer.name = str;
3390
- gui.session.layerRenamed(target.layer, str);
3391
- updateMenuBtn();
3392
- });
5101
+ function trimBOM(str) {
5102
+ // remove BOM if present
5103
+ if (str.charCodeAt(0) == 0xfeff) {
5104
+ str = str.substr(1);
5105
+ }
5106
+ return str;
5107
+ }
3393
5108
 
3394
- // init click-to-select
3395
- GUI.onClick(entry, function() {
3396
- var target = findLayerById(id);
3397
- // don't select if user is typing or dragging
3398
- if (!GUI.getInputElement() && !dragging) {
3399
- gui.clearMode();
3400
- if (!map.isActiveLayer(target.layer)) {
3401
- model.selectLayer(target.layer, target.dataset);
3402
- }
5109
+ function printEncodings() {
5110
+ var encodings = getEncodings().filter(function(name) {
5111
+ // filter out some aliases and non-applicable encodings
5112
+ return !/^(_|cs|internal|ibm|isoir|singlebyte|table|[0-9]|l[0-9]|windows)/.test(name);
5113
+ });
5114
+ encodings.sort();
5115
+ print("Supported encodings:\n" + formatStringsAsGrid(encodings));
5116
+ }
5117
+
5118
+ // Not a general-purpose deep copy function
5119
+ function copyRecord(o) {
5120
+ var o2 = {}, key, val;
5121
+ if (!o) return null;
5122
+ for (key in o) {
5123
+ if (o.hasOwnProperty(key)) {
5124
+ val = o[key];
5125
+ if (val == o) {
5126
+ // avoid infinite recursion if val is a circular reference, by copying all properties except key
5127
+ val = utils.extend({}, val);
5128
+ delete val[key];
3403
5129
  }
3404
- });
5130
+ o2[key] = val && val.constructor === Object ? copyRecord(val) : val;
5131
+ }
3405
5132
  }
5133
+ return o2;
5134
+ }
3406
5135
 
3407
- function describeLyr(lyr) {
3408
- var n = internal.getFeatureCount(lyr),
3409
- str, type;
3410
- if (lyr.data && !lyr.shapes) {
3411
- type = 'data record';
3412
- } else if (lyr.geometry_type) {
3413
- type = lyr.geometry_type + ' feature';
3414
- }
3415
- if (type) {
3416
- str = utils.format('%,d %s%s', n, type, utils.pluralSuffix(n));
3417
- } else {
3418
- str = "[empty]";
5136
+ function getValueType(val) {
5137
+ var type = null;
5138
+ if (utils.isString(val)) {
5139
+ type = 'string';
5140
+ } else if (utils.isNumber(val)) {
5141
+ type = 'number';
5142
+ } else if (utils.isBoolean(val)) {
5143
+ type = 'boolean';
5144
+ } else if (utils.isDate(val)) {
5145
+ type = 'date';
5146
+ } else if (utils.isObject(val)) {
5147
+ type = 'object';
5148
+ }
5149
+ return type;
5150
+ }
5151
+
5152
+ // Fill out a data table with undefined values
5153
+ // The undefined members will disappear when records are exported as JSON,
5154
+ // but will show up when fields are listed using Object.keys()
5155
+ function fixInconsistentFields(records) {
5156
+ var fields = findIncompleteFields(records);
5157
+ patchMissingFields(records, fields);
5158
+ }
5159
+
5160
+ function findIncompleteFields(records) {
5161
+ var counts = {},
5162
+ i, j, keys;
5163
+ for (i=0; i<records.length; i++) {
5164
+ keys = Object.keys(records[i] || {});
5165
+ for (j=0; j<keys.length; j++) {
5166
+ counts[keys[j]] = (counts[keys[j]] | 0) + 1;
3419
5167
  }
3420
- return str;
3421
5168
  }
5169
+ return Object.keys(counts).filter(function(k) {return counts[k] < records.length;});
5170
+ }
3422
5171
 
3423
- function getWarnings(lyr, dataset) {
3424
- var file = internal.getLayerSourceFile(lyr, dataset);
3425
- var missing = [];
3426
- var msg;
3427
- if (utils.endsWith(file, '.shp') && lyr == dataset.layers[0]) {
3428
- if (!lyr.data) {
3429
- missing.push('.dbf');
5172
+ function patchMissingFields(records, fields) {
5173
+ var rec, i, j, f;
5174
+ for (i=0; i<records.length; i++) {
5175
+ rec = records[i] || (records[i] = {});
5176
+ for (j=0; j<fields.length; j++) {
5177
+ f = fields[j];
5178
+ if (f in rec === false) {
5179
+ rec[f] = undefined;
3430
5180
  }
3431
- if (!dataset.info.prj && !dataset.info.crs) {
3432
- missing.push('.prj');
3433
- }
3434
- }
3435
- if (missing.length) {
3436
- msg = 'missing ' + missing.join(' and ') + ' data';
3437
5181
  }
3438
- return msg;
3439
5182
  }
5183
+ }
3440
5184
 
3441
- function describeSrc(lyr, dataset) {
3442
- return internal.getLayerSourceFile(lyr, dataset);
5185
+ function fieldListContainsAll(list, fields) {
5186
+ return list.indexOf('*') > -1 || utils.difference(fields, list).length === 0;
5187
+ }
5188
+
5189
+ function getColumnType(key, records) {
5190
+ var type = null,
5191
+ rec;
5192
+ for (var i=0, n=records.length; i<n; i++) {
5193
+ rec = records[i];
5194
+ type = rec ? getValueType(rec[key]) : null;
5195
+ if (type) break;
3443
5196
  }
5197
+ return type;
5198
+ }
5199
+
5200
+ function deleteFields(table, test) {
5201
+ table.getFields().forEach(function(name) {
5202
+ if (test(name)) {
5203
+ table.deleteField(name);
5204
+ }
5205
+ });
5206
+ }
3444
5207
 
5208
+ function isInvalidFieldName(f) {
5209
+ // Reject empty and all-whitespace strings. TODO: consider other criteria
5210
+ return /^\s*$/.test(f);
5211
+ }
3445
5212
 
3446
- function isPinnable(lyr) {
3447
- return internal.layerHasGeometry(lyr) || internal.layerHasFurniture(lyr);
5213
+ // Resolve name conflicts in field names by appending numbers
5214
+ // @fields Array of field names
5215
+ // @maxLen (optional) Maximum chars in name
5216
+ //
5217
+ function getUniqFieldNames(fields, maxLen, encoding) {
5218
+ var used = {};
5219
+ return fields.map(function(name) {
5220
+ var i = 0,
5221
+ validName;
5222
+ do {
5223
+ validName = encoding && encoding != 'ascii' ?
5224
+ adjustEncodedFieldName(name, maxLen, i, encoding) :
5225
+ adjustFieldName(name, maxLen, i);
5226
+ i++;
5227
+ } while ((validName in used) ||
5228
+ // don't replace an existing valid field name with a truncated name
5229
+ name != validName && utils.contains(fields, validName));
5230
+ used[validName] = true;
5231
+ return validName;
5232
+ });
5233
+ }
5234
+
5235
+ function getFieldValues(records, field) {
5236
+ return records.map(function(rec) {
5237
+ return rec ? rec[field] : undefined;
5238
+ });
5239
+ }
5240
+
5241
+ function getUniqFieldValues(records, field) {
5242
+ var index = {};
5243
+ var values = [];
5244
+ records.forEach(function(rec) {
5245
+ var val = rec[field];
5246
+ if (val in index === false) {
5247
+ index[val] = true;
5248
+ values.push(val);
5249
+ }
5250
+ });
5251
+ return values;
5252
+ }
5253
+
5254
+ // Truncate and/or uniqify a name (if relevant params are present)
5255
+ function adjustFieldName(name, maxLen, i) {
5256
+ var name2, suff;
5257
+ maxLen = maxLen || 256;
5258
+ if (!i) {
5259
+ name2 = name.substr(0, maxLen);
5260
+ } else {
5261
+ suff = String(i);
5262
+ if (suff.length == 1) {
5263
+ suff = '_' + suff;
5264
+ }
5265
+ name2 = name.substr(0, maxLen - suff.length) + suff;
3448
5266
  }
5267
+ return name2;
5268
+ }
3449
5269
 
5270
+ // Truncate and/or uniqify a name (if relevant params are present)
5271
+ function adjustEncodedFieldName(name, maxLen, i, encoding) {
5272
+ var suff = i ? String(i) : '';
5273
+ var name2 = name + suff;
5274
+ var buf = encodeString(name2, encoding);
5275
+ if (buf.length > (maxLen || 256)) {
5276
+ name = name.substr(0, name.length - 1);
5277
+ return adjustEncodedFieldName(name, maxLen, i, encoding);
5278
+ }
5279
+ return name2;
5280
+ }
3450
5281
 
5282
+ function applyFieldOrder(arr, option) {
5283
+ if (option == 'ascending') {
5284
+ arr.sort(function(a, b) {
5285
+ return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
5286
+ });
5287
+ }
5288
+ return arr;
5289
+ }
3451
5290
 
3452
- function rowHTML(c1, c2, cname) {
3453
- return utils.format('<div class="row%s"><div class="col1">%s</div>' +
3454
- '<div class="col2">%s</div></div>', cname ? ' ' + cname : '', c1, c2);
5291
+ function getFirstNonEmptyRecord(records) {
5292
+ for (var i=0, n=records ? records.length : 0; i<n; i++) {
5293
+ if (records[i]) return records[i];
3455
5294
  }
5295
+ return null;
3456
5296
  }
3457
5297
 
3458
- function SessionHistory(gui) {
3459
- var commands = [];
3460
- // commands that can be ignored when checking for unsaved changes
3461
- var nonEditingCommands = 'i,target,info,version,verbose,projections,inspect,help,h,encodings,calc'.split(',');
5298
+ function findFieldNames(records, order) {
5299
+ var first = getFirstNonEmptyRecord(records);
5300
+ var names = first ? Object.keys(first) : [];
5301
+ return applyFieldOrder(names, order);
5302
+ }
3462
5303
 
3463
- this.unsavedChanges = function() {
3464
- var cmd, cmdName;
3465
- for (var i=commands.length - 1; i >= 0; i--) {
3466
- cmdName = getCommandName(commands[i]);
3467
- if (cmdName == 'o') break;
3468
- if (nonEditingCommands.includes(cmdName)) continue;
3469
- return true;
3470
- }
3471
- return false;
3472
- };
5304
+ function Undo(gui) {
5305
+ var history, offset, stashedUndo;
5306
+ reset();
3473
5307
 
3474
- this.fileImported = function(file, optStr) {
3475
- var cmd = '-i ' + file;
3476
- if (optStr) {
3477
- cmd += ' ' + optStr;
3478
- }
3479
- commands.push(cmd);
3480
- };
5308
+ function reset() {
5309
+ history = [];
5310
+ stashedUndo = null;
5311
+ offset = 0;
5312
+ }
3481
5313
 
3482
- this.layerRenamed = function(lyr, name) {
3483
- var currTarget = getCurrentTarget();
3484
- var layerTarget = getTargetFromLayer(lyr);
3485
- if (currTarget == layerTarget) {
3486
- commands.push('-rename-layers ' + name);
3487
- } else {
3488
- commands.push('-rename-layers ' + name + ' target=' + layerTarget);
3489
- commands.push('-target ' + currTarget);
3490
- }
3491
- };
5314
+ function refreshMap() {
5315
+ gui.dispatchEvent('undo_redo');
5316
+ }
3492
5317
 
3493
- this.consoleCommands = function(str) {
3494
- commands.push(str); // todo: split commands?
3495
- };
5318
+ function isUndoEvt(e) {
5319
+ return (e.ctrlKey || e.metaKey) && !e.shiftKey && e.key == 'z';
5320
+ }
3496
5321
 
3497
- this.simplificationApplied = function(optStr) {
3498
- commands.push('-simplify ' + optStr);
3499
- };
5322
+ function isRedoEvt(e) {
5323
+ return (e.ctrlKey || e.metaKey) && (e.shiftKey && e.key == 'z' || !e.shiftKey && e.key == 'y');
5324
+ }
3500
5325
 
3501
- this.simplificationRepair = function() {
3502
- // TODO: improve this... repair does not necessarily apply to most recent
3503
- // simplification command
3504
- // consider adding a (hidden) repair command to handle this event
3505
- var i = indexOfLastCommand('-simplify');
3506
- if (i > -1) {
3507
- commands[i] = commands[i].replace(' no-repair', '');
5326
+ gui.keyboard.on('keydown', function(evt) {
5327
+ var e = evt.originalEvent,
5328
+ kc = e.keyCode;
5329
+ if (isUndoEvt(e)) {
5330
+ this.undo();
5331
+ e.stopPropagation();
5332
+ e.preventDefault();
5333
+ }
5334
+ if (isRedoEvt(e)) {
5335
+ this.redo();
5336
+ e.stopPropagation();
5337
+ e.preventDefault();
3508
5338
  }
5339
+
5340
+ }, this, 10);
5341
+
5342
+ // undo/redo point/symbol dragging
5343
+ //
5344
+ gui.on('symbol_dragstart', function(e) {
5345
+ stashedUndo = this.makePointSetter(e.FID);
5346
+ }, this);
5347
+
5348
+ gui.on('symbol_dragend', function(e) {
5349
+ var redo = this.makePointSetter(e.FID);
5350
+ this.addHistoryState(stashedUndo, redo);
5351
+ }, this);
5352
+
5353
+ // undo/redo label dragging
5354
+ //
5355
+ gui.on('label_dragstart', function(e) {
5356
+ stashedUndo = this.makeDataSetter(e.FID);
5357
+ }, this);
5358
+
5359
+ gui.on('label_dragend', function(e) {
5360
+ var redo = this.makeDataSetter(e.FID);
5361
+ this.addHistoryState(stashedUndo, redo);
5362
+ }, this);
5363
+
5364
+ this.clear = function() {
5365
+ reset();
3509
5366
  };
3510
5367
 
3511
- this.updateSimplificationPct = function(pct) {
3512
- var i = indexOfLastCommand('-simplify');
3513
- if (i > -1) {
3514
- commands[i] = commands[i].replace(/percentage=[^ ]+/, 'percentage=' + pct);
3515
- }
5368
+ this.makePointSetter = function(i) {
5369
+ var target = gui.model.getActiveLayer();
5370
+ var shp = cloneShape(target.layer.shapes[i]);
5371
+ return function() {
5372
+ target.layer.shapes[i] = shp;
5373
+ };
3516
5374
  };
3517
5375
 
3518
- this.dataValueUpdated = function(id, field, value) {
3519
- var cmd = `-each 'd[${JSON.stringify(field)}] = ${JSON.stringify(value)}' where='this.id == ${id}'`;
3520
- commands.push(cmd);
5376
+
5377
+ this.makeDataSetter = function(id) {
5378
+ var target = gui.model.getActiveLayer();
5379
+ var rec = copyRecord(target.layer.data.getRecordAt(id));
5380
+ return function() {
5381
+ target.layer.data.getRecords()[id] = rec;
5382
+ gui.dispatchEvent('popup-needs-refresh');
5383
+ };
3521
5384
  };
3522
5385
 
3523
- this.layersExported = function(ids, optStr) {
3524
- var layers = gui.model.getLayers();
3525
- var cmd = '-o';
3526
- if (layers.length > 1) {
3527
- cmd += ' target=' + ids.map(getTargetFromId).join(',');
3528
- }
3529
- if (optStr) {
3530
- cmd += ' ' + optStr;
5386
+
5387
+ this.addHistoryState = function(undo, redo) {
5388
+ if (offset > 0) {
5389
+ history.splice(-offset);
5390
+ offset = 0;
3531
5391
  }
3532
- commands.push(cmd);
5392
+ history.push({undo, redo});
3533
5393
  };
3534
5394
 
3535
- this.setTargetLayer = function(lyr) {
3536
- var layers = gui.model.getLayers();
3537
- if (layers.length > 1) {
3538
- if (indexOfLastCommand('-target') == commands.length - 1) {
3539
- commands.pop(); // if last commands was -target, remove it
3540
- }
3541
- commands.push('-target ' + getTargetFromLayer(lyr));
5395
+ this.undo = function() {
5396
+ var item = getHistoryItem();
5397
+ if (item) {
5398
+ offset++;
5399
+ item.undo();
5400
+ refreshMap();
3542
5401
  }
3543
5402
  };
3544
5403
 
3545
- this.toCommandLineString = function() {
3546
- var str = commands.join(' \\\n ');
3547
- return 'mapshaper ' + str;
5404
+ this.redo = function() {
5405
+ if (offset <= 0) return;
5406
+ offset--;
5407
+ var item = getHistoryItem();
5408
+ item.redo();
5409
+ refreshMap();
3548
5410
  };
3549
5411
 
3550
- function getCommandName(cmd) {
3551
- var rxp = /^-([a-z0-9-]+)/;
3552
- var match = rxp.exec(cmd);
3553
- return match ? match[1] : null;
3554
- }
3555
-
3556
- function getCurrentTarget() {
3557
- return getTargetFromLayer(gui.model.getActiveLayer().layer);
3558
- }
3559
-
3560
- function indexOfLastCommand(cmd) {
3561
- return commands.reduce(function(memo, str, i) {
3562
- return str.indexOf(cmd) === 0 ? i : memo;
3563
- }, -1);
3564
- }
3565
-
3566
- function getTargetFromId(id) {
3567
- var layers = gui.model.getLayers();
3568
- return getTargetFromLayer(layers[id - 1].layer);
5412
+ function getHistoryItem() {
5413
+ var item = history[history.length - offset - 1];
5414
+ return item || null;
3569
5415
  }
3570
5416
 
3571
- function getTargetFromLayer(lyr) {
3572
- var id = internal.getLayerTargetId(gui.model, lyr);
3573
- return internal.formatOptionValue(id);
3574
- }
3575
5417
  }
3576
5418
 
3577
5419
  function SidebarButtons(gui) {
@@ -3671,7 +5513,7 @@
3671
5513
  };
3672
5514
  }
3673
5515
 
3674
- utils.inherit(ModeSwitcher, EventDispatcher);
5516
+ utils$1.inherit(ModeSwitcher, EventDispatcher);
3675
5517
 
3676
5518
  function KeyboardEvents(gui) {
3677
5519
  var self = this;
@@ -3699,7 +5541,7 @@
3699
5541
  };
3700
5542
  }
3701
5543
 
3702
- utils.inherit(KeyboardEvents, EventDispatcher);
5544
+ utils$1.inherit(KeyboardEvents, EventDispatcher);
3703
5545
 
3704
5546
  function InteractionMode(gui) {
3705
5547
 
@@ -3915,7 +5757,7 @@
3915
5757
  function Model(gui) {
3916
5758
  var self = new internal.Catalog();
3917
5759
  var deleteLayer = self.deleteLayer;
3918
- utils.extend(self, EventDispatcher.prototype);
5760
+ utils$1.extend(self, EventDispatcher.prototype);
3919
5761
 
3920
5762
  // override Catalog method (so -drop command will work in web console)
3921
5763
  self.deleteLayer = function(lyr, dataset) {
@@ -3949,7 +5791,7 @@
3949
5791
  if (flags.select) {
3950
5792
  self.dispatchEvent('select', active);
3951
5793
  }
3952
- self.dispatchEvent('update', utils.extend({flags: flags}, active));
5794
+ self.dispatchEvent('update', utils$1.extend({flags: flags}, active));
3953
5795
  };
3954
5796
 
3955
5797
  self.selectLayer = function(lyr, dataset) {
@@ -3984,7 +5826,7 @@
3984
5826
  } else if (geoType == 'polygon') {
3985
5827
  test = polygonTest;
3986
5828
  } else {
3987
- error("Unexpected geometry type:", geoType);
5829
+ error$1("Unexpected geometry type:", geoType);
3988
5830
  }
3989
5831
  return test;
3990
5832
 
@@ -4051,7 +5893,7 @@
4051
5893
  for (var i=0; i<cands.length; i++) {
4052
5894
  cands[i].dist = geom.getPointToShapeDistance(x, y, cands[i].shape, arcs);
4053
5895
  }
4054
- utils.sortOn(cands, 'dist');
5896
+ utils$1.sortOn(cands, 'dist');
4055
5897
  }
4056
5898
 
4057
5899
  function pointTest(x, y) {
@@ -4077,7 +5919,7 @@
4077
5919
  }
4078
5920
  });
4079
5921
  // console.log(hitThreshold, bullseye);
4080
- return utils.uniq(hits); // multipoint features can register multiple hits
5922
+ return utils$1.uniq(hits); // multipoint features can register multiple hits
4081
5923
  }
4082
5924
 
4083
5925
  function getRadiusFunction(style) {
@@ -4284,9 +6126,9 @@
4284
6126
  };
4285
6127
  var svgData = svgTest(e); // null or a data object
4286
6128
  if (svgData) { // mouse is over an SVG symbol
4287
- utils.extend(data, svgData);
6129
+ utils$1.extend(data, svgData);
4288
6130
  // placing symbol id in front of any other hits
4289
- data.ids = utils.uniq([svgData.targetId].concat(data.ids));
6131
+ data.ids = utils$1.uniq([svgData.targetId].concat(data.ids));
4290
6132
  }
4291
6133
  data.id = data.ids.length > 0 ? data.ids[0] : -1;
4292
6134
  return data;
@@ -4387,8 +6229,8 @@
4387
6229
 
4388
6230
  self.addSelectionIds = function(ids) {
4389
6231
  turnOn('selection');
4390
- selectionIds = utils.uniq(selectionIds.concat(ids));
4391
- ids = utils.uniq(storedData.ids.concat(ids));
6232
+ selectionIds = utils$1.uniq(selectionIds.concat(ids));
6233
+ ids = utils$1.uniq(storedData.ids.concat(ids));
4392
6234
  updateSelectionState({ids: ids});
4393
6235
  };
4394
6236
 
@@ -4546,7 +6388,7 @@
4546
6388
 
4547
6389
  function toggleId(id, ids) {
4548
6390
  if (ids.indexOf(id) > -1) {
4549
- return utils.difference(ids, [id]);
6391
+ return utils$1.difference(ids, [id]);
4550
6392
  }
4551
6393
  return [id].concat(ids);
4552
6394
  }
@@ -4598,9 +6440,9 @@
4598
6440
  // d: event data (may be a pointer event object, an ordinary object or null)
4599
6441
  function triggerHitEvent(type, d) {
4600
6442
  // Merge stored hit data into the event data
4601
- var eventData = utils.extend({mode: interactionMode}, d || {}, storedData);
6443
+ var eventData = utils$1.extend({mode: interactionMode}, d || {}, storedData);
4602
6444
  if (transientIds.length) {
4603
- eventData.ids = utils.uniq(transientIds.concat(eventData.ids || []));
6445
+ eventData.ids = utils$1.uniq(transientIds.concat(eventData.ids || []));
4604
6446
  }
4605
6447
  self.dispatchEvent(type, eventData);
4606
6448
  }
@@ -4734,7 +6576,7 @@
4734
6576
  }
4735
6577
  }
4736
6578
 
4737
- utils.inherit(Timer, EventDispatcher);
6579
+ utils$1.inherit(Timer, EventDispatcher);
4738
6580
 
4739
6581
  function Tween(ease) {
4740
6582
  var self = this,
@@ -4756,7 +6598,7 @@
4756
6598
  }
4757
6599
  }
4758
6600
 
4759
- utils.inherit(Tween, EventDispatcher);
6601
+ utils$1.inherit(Tween, EventDispatcher);
4760
6602
 
4761
6603
  Tween.sineInOut = function(n) {
4762
6604
  return 0.5 - Math.cos(n * Math.PI) / 2;
@@ -4824,7 +6666,7 @@
4824
6666
  update();
4825
6667
  }
4826
6668
 
4827
- utils.inherit(ElementPosition, EventDispatcher);
6669
+ utils$1.inherit(ElementPosition, EventDispatcher);
4828
6670
 
4829
6671
  function MouseWheelDirection() {
4830
6672
  var prevTime = 0;
@@ -4922,13 +6764,13 @@
4922
6764
  // Decelerate towards the end of the sustain interval (for smoother zooming)
4923
6765
  multiplier *= Tween.quadraticOut(1 - fadeFactor);
4924
6766
  }
4925
- obj = utils.extend({direction: wheelDirection, multiplier: multiplier}, mouse.mouseData());
6767
+ obj = utils$1.extend({direction: wheelDirection, multiplier: multiplier}, mouse.mouseData());
4926
6768
  self.dispatchEvent('mousewheel', obj);
4927
6769
  }
4928
6770
  }
4929
6771
  }
4930
6772
 
4931
- utils.inherit(MouseWheel, EventDispatcher);
6773
+ utils$1.inherit(MouseWheel, EventDispatcher);
4932
6774
 
4933
6775
 
4934
6776
  function MouseArea(element, pos) {
@@ -4982,7 +6824,7 @@
4982
6824
  };
4983
6825
 
4984
6826
  this.mouseData = function() {
4985
- return utils.extend({}, _prevEvt);
6827
+ return utils$1.extend({}, _prevEvt);
4986
6828
  };
4987
6829
 
4988
6830
  function onAreaDown(e) {
@@ -5041,7 +6883,7 @@
5041
6883
  dragX: evt.pageX - _downEvt.pageX,
5042
6884
  dragY: evt.pageY - _downEvt.pageY
5043
6885
  };
5044
- _self.dispatchEvent('drag', utils.extend(obj, evt));
6886
+ _self.dispatchEvent('drag', utils$1.extend(obj, evt));
5045
6887
  } else {
5046
6888
  _self.dispatchEvent('hover', evt);
5047
6889
  }
@@ -5073,7 +6915,7 @@
5073
6915
  }
5074
6916
  }
5075
6917
 
5076
- utils.inherit(MouseArea, EventDispatcher);
6918
+ utils$1.inherit(MouseArea, EventDispatcher);
5077
6919
 
5078
6920
  function initVariableClick(node, cb) {
5079
6921
  var downEvent = null;
@@ -5220,7 +7062,7 @@
5220
7062
  maxScale = 4,
5221
7063
  minTime = 100,
5222
7064
  maxTime = 800,
5223
- time = utils.clamp(clickTime || 200, minTime, maxTime),
7065
+ time = utils$1.clamp(clickTime || 200, minTime, maxTime),
5224
7066
  k = (time - minTime) / (maxTime - minTime),
5225
7067
  scale = minScale + k * (maxScale - minScale);
5226
7068
  return 1 + scale * zoomScaleMultiplier;
@@ -5447,7 +7289,7 @@
5447
7289
  rows = 0;
5448
7290
  // self.hide(); // clean up if panel is already open
5449
7291
  el.empty(); // clean up if panel is already open
5450
- utils.forEachProperty(rec, function(v, k) {
7292
+ utils$1.forEachProperty(rec, function(v, k) {
5451
7293
  var type;
5452
7294
  // missing GeoJSON fields are set to undefined on import; skip these
5453
7295
  if (v !== undefined) {
@@ -5475,7 +7317,7 @@
5475
7317
  // Some individual features can have undefined values for some or all of
5476
7318
  // their data properties (properties are set to undefined when an input JSON file
5477
7319
  // has inconsistent fields, or after force-merging layers with inconsistent fields).
5478
- el.html(utils.format('<div class="note">This %s is missing attribute data.</div>',
7320
+ el.html(utils$1.format('<div class="note">This %s is missing attribute data.</div>',
5479
7321
  table && table.getFields().length > 0 ? 'feature': 'layer'));
5480
7322
  }
5481
7323
  }
@@ -5486,7 +7328,7 @@
5486
7328
  var str = formatInspectorValue(val, type);
5487
7329
  var cell = El('tr')
5488
7330
  .appendTo(table)
5489
- .html(utils.format(rowHtml, key, utils.htmlEscape(str)))
7331
+ .html(utils$1.format(rowHtml, key, utils$1.htmlEscape(str)))
5490
7332
  .findChild('.value');
5491
7333
  setFieldClass(cell, val, type);
5492
7334
  if (editable) {
@@ -5495,7 +7337,7 @@
5495
7337
  }
5496
7338
 
5497
7339
  function setFieldClass(el, val, type) {
5498
- var isNum = type ? type == 'number' : utils.isNumber(val);
7340
+ var isNum = type ? type == 'number' : utils$1.isNumber(val);
5499
7341
  var isNully = val === undefined || val === null || val !== val;
5500
7342
  var isEmpty = val === '';
5501
7343
  el.classed('num-field', isNum);
@@ -5533,7 +7375,7 @@
5533
7375
  function formatInspectorValue(val, type) {
5534
7376
  var str;
5535
7377
  if (type == 'date') {
5536
- str = utils.formatDateISO(val);
7378
+ str = utils$1.formatDateISO(val);
5537
7379
  } else if (type == 'object') {
5538
7380
  str = val ? JSON.stringify(val) : "";
5539
7381
  } else {
@@ -5732,7 +7574,7 @@
5732
7574
  // handle either numeric strings or numbers in fields
5733
7575
  function applyDelta(rec, key, delta) {
5734
7576
  var currVal = rec[key];
5735
- var isString = utils.isString(currVal);
7577
+ var isString = utils$1.isString(currVal);
5736
7578
  var newVal = (+currVal + delta) || 0;
5737
7579
  rec[key] = isString ? String(newVal) : newVal;
5738
7580
  }
@@ -5819,6 +7661,7 @@
5819
7661
  if (e.mode != 'labels') {
5820
7662
  stopDragging();
5821
7663
  }
7664
+ gui.undo.clear(); // TODO: put this elsewhere?
5822
7665
  });
5823
7666
 
5824
7667
  // down event on svg
@@ -5830,12 +7673,16 @@
5830
7673
  // 3: on other text -> stop dragging, select new text
5831
7674
 
5832
7675
  hit.on('dragstart', function(e) {
5833
- if (labelEditingEnabled()) {
5834
- onLabelDragStart(e);
7676
+ if (e.id >= 0 === false) return;
7677
+ if (labelEditingEnabled() && onLabelDragStart(e)) {
7678
+ triggerGlobalEvent('label_dragstart', e);
7679
+ startDragging();
5835
7680
  } else if (locationEditingEnabled()) {
5836
- onLocationDragStart(e);
7681
+ triggerGlobalEvent('symbol_dragstart', e);
7682
+ startDragging();
5837
7683
  } else if (vertexEditingEnabled()) {
5838
- onVertexDragStart(e);
7684
+ triggerGlobalEvent('vertex_dragstart', e);
7685
+ startDragging();
5839
7686
  }
5840
7687
  });
5841
7688
 
@@ -5851,12 +7698,14 @@
5851
7698
 
5852
7699
  hit.on('dragend', function(e) {
5853
7700
  if (locationEditingEnabled()) {
5854
- onLocationDragEnd(e);
7701
+ triggerGlobalEvent('symbol_dragend', e);
5855
7702
  stopDragging();
5856
7703
  } else if (labelEditingEnabled()) {
7704
+ triggerGlobalEvent('label_dragend', e);
5857
7705
  stopDragging();
5858
7706
  } else if (vertexEditingEnabled()) {
5859
7707
  onVertexDragEnd(e);
7708
+ triggerGlobalEvent('vertex_dragend', e);
5860
7709
  stopDragging();
5861
7710
  }
5862
7711
  });
@@ -5867,18 +7716,6 @@
5867
7716
  }
5868
7717
  });
5869
7718
 
5870
- function onLocationDragStart(e) {
5871
- if (e.id >= 0) {
5872
- dragging = true;
5873
- triggerGlobalEvent('symbol_dragstart', e);
5874
- }
5875
- }
5876
-
5877
- function onVertexDragStart(e) {
5878
- if (e.id >= 0) {
5879
- dragging = true;
5880
- }
5881
- }
5882
7719
 
5883
7720
  function onLocationDrag(e) {
5884
7721
  var lyr = hit.getHitTarget().layer;
@@ -5907,10 +7744,6 @@
5907
7744
  self.dispatchEvent('location_change'); // signal map to redraw
5908
7745
  }
5909
7746
 
5910
- function onLocationDragEnd(e) {
5911
- triggerGlobalEvent('symbol_dragend', e);
5912
- }
5913
-
5914
7747
  function onVertexDragEnd(e) {
5915
7748
  // kludge to get dataset to recalculate internal bounding boxes
5916
7749
  hit.getHitTarget().arcs.transformPoints(function() {});
@@ -5953,11 +7786,11 @@
5953
7786
  function onLabelDragStart(e) {
5954
7787
  var textNode = getTextTarget3(e);
5955
7788
  var table = hit.getTargetDataTable();
5956
- if (!textNode || !table) return;
7789
+ if (!textNode || !table) return false;
5957
7790
  activeId = e.id;
5958
7791
  activeRecord = getLabelRecordById(activeId);
5959
- dragging = true;
5960
7792
  downEvt = e;
7793
+ return true;
5961
7794
  }
5962
7795
 
5963
7796
  function onLabelDrag(e) {
@@ -5965,7 +7798,7 @@
5965
7798
  var textNode;
5966
7799
  if (!dragging) return;
5967
7800
  if (e.id != activeId) {
5968
- error("Mismatched hit ids:", e.id, activeId);
7801
+ error$1("Mismatched hit ids:", e.id, activeId);
5969
7802
  }
5970
7803
  applyDelta(activeRecord, 'dx', e.dx / scale);
5971
7804
  applyDelta(activeRecord, 'dy', e.dy / scale);
@@ -6082,6 +7915,10 @@
6082
7915
  // }
6083
7916
  }
6084
7917
 
7918
+ function startDragging() {
7919
+ dragging = true;
7920
+ }
7921
+
6085
7922
  function stopDragging() {
6086
7923
  dragging = false;
6087
7924
  activeId = -1;
@@ -6098,16 +7935,6 @@
6098
7935
  return dist <= 4 && elapsed < 300;
6099
7936
  }
6100
7937
 
6101
-
6102
- // function deselectText(el) {
6103
- // el.removeAttribute('class');
6104
- // }
6105
-
6106
- // function selectText(el) {
6107
- // el.setAttribute('class', 'selected');
6108
- // }
6109
-
6110
-
6111
7938
  }
6112
7939
 
6113
7940
  var darkStroke = "#334",
@@ -6213,7 +8040,7 @@
6213
8040
  }
6214
8041
 
6215
8042
  function getDefaultStyle(lyr, baseStyle) {
6216
- var style = utils.extend({}, baseStyle);
8043
+ var style = utils$1.extend({}, baseStyle);
6217
8044
  // reduce the dot size of large point layers
6218
8045
  if (lyr.geometry_type == 'point' && style.dotSize > 0) {
6219
8046
  style.dotSize *= getDotScale$1(lyr);
@@ -6279,7 +8106,7 @@
6279
8106
  var topId = o.id; // pinned id (if pinned) or hover id
6280
8107
  var topIdx = -1;
6281
8108
  var styler = function(o, i) {
6282
- utils.extend(o, i === topIdx ? topStyle: baseStyle);
8109
+ utils$1.extend(o, i === topIdx ? topStyle: baseStyle);
6283
8110
  };
6284
8111
  var baseStyle = getDefaultStyle(lyr, selectionStyles[geomType]);
6285
8112
  var topStyle;
@@ -6392,7 +8219,7 @@
6392
8219
  if (lyr.geometry_type == 'point') {
6393
8220
  return fields.indexOf('r') > -1; // require 'r' field for point symbols
6394
8221
  }
6395
- return utils.difference(fields, ['opacity', 'class']).length > 0;
8222
+ return utils$1.difference(fields, ['opacity', 'class']).length > 0;
6396
8223
  }
6397
8224
 
6398
8225
 
@@ -6548,7 +8375,7 @@
6548
8375
  }
6549
8376
 
6550
8377
  function maxAbs() {
6551
- return Math.max.apply(null, utils.toArray(arguments).map(Math.abs));
8378
+ return Math.max.apply(null, utils$1.toArray(arguments).map(Math.abs));
6552
8379
  }
6553
8380
 
6554
8381
  function limitScale(scale) {
@@ -6602,7 +8429,7 @@
6602
8429
  }
6603
8430
  }
6604
8431
 
6605
- utils.inherit(MapExtent, EventDispatcher);
8432
+ utils$1.inherit(MapExtent, EventDispatcher);
6606
8433
 
6607
8434
  // TODO: consider moving this upstream
6608
8435
  function getArcsForRendering(obj, ext) {
@@ -6763,7 +8590,7 @@
6763
8590
  key = getStyleKey(style);
6764
8591
  if (key in styleIndex === false) {
6765
8592
  styleIndex[key] = {
6766
- style: utils.defaults({}, style),
8593
+ style: utils$1.defaults({}, style),
6767
8594
  shapes: []
6768
8595
  };
6769
8596
  }
@@ -7237,7 +9064,7 @@
7237
9064
  var frame = ext.getFrame(); // frame should be set if we're rendering a furniture layer
7238
9065
  var obj = internal.getEmptyLayerForSVG(lyr, {});
7239
9066
  if (!frame) {
7240
- stop('Missing map frame data');
9067
+ stop$1('Missing map frame data');
7241
9068
  }
7242
9069
  obj.properties.transform = getSvgFurnitureTransform(ext);
7243
9070
  obj.properties.class = 'mapshaper-svg-furniture';
@@ -7265,7 +9092,7 @@
7265
9092
  var html = '';
7266
9093
  // generate a unique id so layer can be identified when symbols are repositioned
7267
9094
  // use it as a class name to avoid id collisions
7268
- var id = utils.getUniqueName();
9095
+ var id = utils$1.getUniqueName();
7269
9096
  var classNames = [id, 'mapshaper-svg-layer', 'mapshaper-' + type + '-layer'];
7270
9097
  g.setAttribute('class', classNames.join(' '));
7271
9098
  target.svg_id = id;
@@ -7716,7 +9543,7 @@
7716
9543
  }
7717
9544
 
7718
9545
  if (obj.tabular) {
7719
- utils.extend(obj, getDisplayLayerForTable(layer.data));
9546
+ utils$1.extend(obj, getDisplayLayerForTable(layer.data));
7720
9547
  }
7721
9548
 
7722
9549
  // dynamic reprojection (arcs were already reprojected above)
@@ -7774,7 +9601,7 @@
7774
9601
  return ids;
7775
9602
  }
7776
9603
 
7777
- utils.inherit(MshpMap, EventDispatcher);
9604
+ utils$1.inherit(MshpMap, EventDispatcher);
7778
9605
 
7779
9606
  function MshpMap(gui) {
7780
9607
  var opts = gui.options,
@@ -7812,6 +9639,10 @@
7812
9639
  _mouse.disable();
7813
9640
  });
7814
9641
 
9642
+ gui.on('undo_redo', function() {
9643
+ drawLayers();
9644
+ });
9645
+
7815
9646
  model.on('update', onUpdate);
7816
9647
 
7817
9648
  // Update display of segment intersections
@@ -7864,7 +9695,7 @@
7864
9695
  this.setDisplayCRS = function(crs) {
7865
9696
  // TODO: update bounds of frame layer, if there is a frame layer
7866
9697
  var oldCRS = this.getDisplayCRS();
7867
- var newCRS = utils.isString(crs) ? internal.getCRS(crs) : crs;
9698
+ var newCRS = utils$1.isString(crs) ? internal.getCRS(crs) : crs;
7868
9699
  // TODO: handle case that old and new CRS are the same
7869
9700
  _dynamicCRS = newCRS;
7870
9701
  if (!_activeLyr) return; // stop here if no layers have been selected
@@ -7996,7 +9827,7 @@
7996
9827
  function updateOverlayLayer(e) {
7997
9828
  var style = getOverlayStyle(_activeLyr.layer, e);
7998
9829
  if (style) {
7999
- _overlayLyr = utils.defaults({
9830
+ _overlayLyr = utils$1.defaults({
8000
9831
  layer: filterLayerByIds(_activeLyr.layer, style.ids),
8001
9832
  style: style
8002
9833
  }, _activeLyr);
@@ -8161,7 +9992,7 @@
8161
9992
  // kludge to hide ghosted layers when reference layers are present
8162
9993
  // TODO: consider never showing ghosted layers (which appear after
8163
9994
  // commands like dissolve and filter).
8164
- mapLayer.style = utils.defaults({
9995
+ mapLayer.style = utils$1.defaults({
8165
9996
  strokeColors: [null, mapLayer.style.strokeColors[1]]
8166
9997
  }, mapLayer.style);
8167
9998
  }
@@ -8217,7 +10048,7 @@
8217
10048
 
8218
10049
  function GuiInstance(container, opts) {
8219
10050
  var gui = new ModeSwitcher();
8220
- opts = utils.extend({
10051
+ opts = utils$1.extend({
8221
10052
  // defaults
8222
10053
  homeControl: true,
8223
10054
  zoomControl: true,
@@ -8235,6 +10066,7 @@
8235
10066
  gui.map = new MshpMap(gui);
8236
10067
  gui.interaction = new InteractionMode(gui);
8237
10068
  gui.session = new SessionHistory(gui);
10069
+ gui.undo = new Undo(gui);
8238
10070
 
8239
10071
  gui.showProgressMessage = function(msg) {
8240
10072
  if (!gui.progressMessage) {