mapshaper 0.5.89 → 0.5.93

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
  }
@@ -228,7 +326,7 @@
228
326
  }
229
327
  parsed = internal.parseCommands(str);
230
328
  if (!parsed.length || parsed[0].name != cmd) {
231
- stop("Unable to parse command line options");
329
+ stop$1("Unable to parse command line options");
232
330
  }
233
331
  return parsed[0].options;
234
332
  };
@@ -321,7 +419,7 @@
321
419
  evt = new EventData(this.type);
322
420
  evt.target = this.target;
323
421
  } else if (evt.target != this.target || evt.type != this.type) {
324
- error("[Handler] event target/type have changed.");
422
+ error$1("[Handler] event target/type have changed.");
325
423
  }
326
424
  this.callback.call(this.listener, evt);
327
425
  };
@@ -330,7 +428,7 @@
330
428
  this.type = type;
331
429
  this.target = target;
332
430
  if (data) {
333
- utils.defaults(this, data);
431
+ utils$1.defaults(this, data);
334
432
  this.data = data;
335
433
  }
336
434
  }
@@ -560,12 +658,12 @@
560
658
  try {
561
659
  els = root.querySelectorAll(selector);
562
660
  } catch (e) {
563
- error("Invalid selector:", selector);
661
+ error$1("Invalid selector:", selector);
564
662
  }
565
663
  } else {
566
- error("This browser doesn't support CSS query selectors");
664
+ error$1("This browser doesn't support CSS query selectors");
567
665
  }
568
- return utils.toArray(els);
666
+ return utils$1.toArray(els);
569
667
  };
570
668
 
571
669
  // Converts dash-separated names (e.g. background-color) to camelCase (e.g. backgroundColor)
@@ -602,7 +700,7 @@
602
700
  };
603
701
 
604
702
  function El(ref) {
605
- if (!ref) error("Element() needs a reference");
703
+ if (!ref) error$1("Element() needs a reference");
606
704
  if (ref instanceof El) {
607
705
  return ref;
608
706
  }
@@ -611,7 +709,7 @@
611
709
  }
612
710
 
613
711
  var node;
614
- if (utils.isString(ref)) {
712
+ if (utils$1.isString(ref)) {
615
713
  if (ref[0] == '<') {
616
714
  var parent = El('div').html(ref).node();
617
715
  node = parent.childNodes.length == 1 ? parent.childNodes[0] : parent;
@@ -623,13 +721,13 @@
623
721
  } else if (ref.tagName) {
624
722
  node = ref;
625
723
  }
626
- if (!node) error("Unmatched element selector:", ref);
724
+ if (!node) error$1("Unmatched element selector:", ref);
627
725
  this.el = node;
628
726
  }
629
727
 
630
- utils.inherit(El, EventDispatcher);
728
+ utils$1.inherit(El, EventDispatcher);
631
729
 
632
- utils.extend(El.prototype, {
730
+ utils$1.extend(El.prototype, {
633
731
 
634
732
  clone: function() {
635
733
  var el = this.el.cloneNode(true);
@@ -638,7 +736,7 @@
638
736
  // can ...
639
737
  el = El('div').addClass(el.className).html(el.innerHTML).node();
640
738
  }
641
- el.id = utils.getUniqueName();
739
+ el.id = utils$1.getUniqueName();
642
740
  this.el = el;
643
741
  return this;
644
742
  },
@@ -668,11 +766,11 @@
668
766
  if (val != null) {
669
767
  El.setStyle(this.el, css, val);
670
768
  }
671
- else if (utils.isString(css)) {
769
+ else if (utils$1.isString(css)) {
672
770
  addCSS(this.el, css);
673
771
  }
674
- else if (utils.isObject(css)) {
675
- utils.forEachProperty(css, function(val, key) {
772
+ else if (utils$1.isObject(css)) {
773
+ utils$1.forEachProperty(css, function(val, key) {
676
774
  El.setStyle(this.el, key, val);
677
775
  }, this);
678
776
  }
@@ -680,7 +778,7 @@
680
778
  },
681
779
 
682
780
  attr: function(obj, value) {
683
- if (utils.isString(obj)) {
781
+ if (utils$1.isString(obj)) {
684
782
  if (arguments.length == 1) {
685
783
  return this.el.getAttribute(obj);
686
784
  }
@@ -764,7 +862,7 @@
764
862
  },
765
863
 
766
864
  text: function(str) {
767
- this.html(utils.htmlEscape(str));
865
+ this.html(utils$1.htmlEscape(str));
768
866
  return this;
769
867
  },
770
868
 
@@ -854,7 +952,7 @@
854
952
  El.prototype.__on = El.prototype.on;
855
953
  El.prototype.on = function(type, func, ctx) {
856
954
  if (ctx) {
857
- error("[El#on()] Third argument no longer supported.");
955
+ error$1("[El#on()] Third argument no longer supported.");
858
956
  }
859
957
  if (this.constructor == El) {
860
958
  this.el.addEventListener(type, func);
@@ -910,7 +1008,7 @@
910
1008
  }
911
1009
 
912
1010
  this.value = function(str) {
913
- if (utils.isString(str)) {
1011
+ if (utils$1.isString(str)) {
914
1012
  el.node().textContent = str;
915
1013
  } else {
916
1014
  return el.node().textContent;
@@ -918,7 +1016,7 @@
918
1016
  };
919
1017
  }
920
1018
 
921
- utils.inherit(ClickText2, EventDispatcher);
1019
+ utils$1.inherit(ClickText2, EventDispatcher);
922
1020
 
923
1021
  // @ref reference to a text input element
924
1022
  function ClickText(ref) {
@@ -989,9 +1087,9 @@
989
1087
  // return _parser ? _parser(valStr) : parseFloat(valStr);
990
1088
  return _value;
991
1089
  }
992
- var val = utils.clamp(arg, _min, _max);
1090
+ var val = utils$1.clamp(arg, _min, _max);
993
1091
  if (!_validator(val)) {
994
- error("ClickText#value() invalid value:", arg);
1092
+ error$1("ClickText#value() invalid value:", arg);
995
1093
  } else {
996
1094
  _value = val;
997
1095
  }
@@ -1000,14 +1098,14 @@
1000
1098
  };
1001
1099
  }
1002
1100
 
1003
- utils.inherit(ClickText, EventDispatcher);
1101
+ utils$1.inherit(ClickText, EventDispatcher);
1004
1102
 
1005
1103
 
1006
1104
  function Checkbox(ref) {
1007
1105
  var _el = El(ref);
1008
1106
  }
1009
1107
 
1010
- utils.inherit(Checkbox, EventDispatcher);
1108
+ utils$1.inherit(Checkbox, EventDispatcher);
1011
1109
 
1012
1110
  function SimpleButton(ref) {
1013
1111
  var _el = El(ref),
@@ -1036,7 +1134,7 @@
1036
1134
  }
1037
1135
  }
1038
1136
 
1039
- utils.inherit(SimpleButton, EventDispatcher);
1137
+ utils$1.inherit(SimpleButton, EventDispatcher);
1040
1138
 
1041
1139
  // @cb function(<FileList>)
1042
1140
  function DropControl(gui, el, cb) {
@@ -1232,6 +1330,7 @@
1232
1330
  }
1233
1331
  }
1234
1332
  model.updated({select: true});
1333
+
1235
1334
  }
1236
1335
 
1237
1336
  function clearQueuedFiles() {
@@ -1315,7 +1414,7 @@
1315
1414
  }
1316
1415
 
1317
1416
  function filesMayContainPaths(files) {
1318
- return utils.some(files, function(f) {
1417
+ return utils$1.some(files, function(f) {
1319
1418
  var type = internal.guessInputFileType(f.name);
1320
1419
  return type == 'shp' || type == 'json' || internal.isZipFile(f.name);
1321
1420
  });
@@ -1371,7 +1470,7 @@
1371
1470
  }
1372
1471
 
1373
1472
  function handleImportError(e, fileName) {
1374
- var msg = utils.isString(e) ? e : e.message;
1473
+ var msg = utils$1.isString(e) ? e : e.message;
1375
1474
  if (fileName) {
1376
1475
  msg = "Error importing <i>" + fileName + "</i><br>" + msg;
1377
1476
  }
@@ -1401,7 +1500,7 @@
1401
1500
 
1402
1501
  function downloadFiles(paths) {
1403
1502
  var items = prepFilesForDownload(paths);
1404
- utils.reduceAsync(items, [], downloadNextFile, function(err, files) {
1503
+ utils$1.reduceAsync(items, [], downloadNextFile, function(err, files) {
1405
1504
  if (err) {
1406
1505
  gui.alert(err);
1407
1506
  } else if (!files.length) {
@@ -1567,7 +1666,7 @@
1567
1666
  var defaults = {
1568
1667
  space: 7
1569
1668
  };
1570
- opts = utils.extend(defaults, opts);
1669
+ opts = utils$1.extend(defaults, opts);
1571
1670
 
1572
1671
  var _pct = 0;
1573
1672
  var _track,
@@ -1620,7 +1719,7 @@
1620
1719
  };
1621
1720
 
1622
1721
  function setHandlePos(x, fire) {
1623
- x = utils.clamp(x, 0, size());
1722
+ x = utils$1.clamp(x, 0, size());
1624
1723
  var pct = x / size();
1625
1724
  if (pct != _pct) {
1626
1725
  _pct = pct;
@@ -1635,7 +1734,7 @@
1635
1734
  }
1636
1735
  }
1637
1736
 
1638
- utils.inherit(Slider, EventDispatcher);
1737
+ utils$1.inherit(Slider, EventDispatcher);
1639
1738
 
1640
1739
  /*
1641
1740
  How changes in the simplify control should affect other components
@@ -1703,7 +1802,7 @@
1703
1802
  slider.on('change', function(e) {
1704
1803
  var pct = fromSliderPct(e.pct);
1705
1804
  text.value(pct);
1706
- pct = utils.parsePercent(text.text()); // use rounded value (for consistency w/ cli)
1805
+ pct = utils$1.parsePercent(text.text()); // use rounded value (for consistency w/ cli)
1707
1806
  onChange(pct);
1708
1807
  });
1709
1808
  slider.on('start', function(e) {
@@ -1724,7 +1823,7 @@
1724
1823
  else if (pct < 0.01) decimals = 3;
1725
1824
  else if (pct < 1) decimals = 2;
1726
1825
  else if (pct < 100) decimals = 1;
1727
- return utils.formatNumberForDisplay(pct, decimals) + "%";
1826
+ return utils$1.formatNumberForDisplay(pct, decimals) + "%";
1728
1827
  });
1729
1828
 
1730
1829
  text.parser(function(s) {
@@ -1976,7 +2075,7 @@
1976
2075
 
1977
2076
  // Replace error function in mapshaper lib
1978
2077
  var error = function() {
1979
- stop.apply(null, utils.toArray(arguments));
2078
+ stop.apply(null, utils$1.toArray(arguments));
1980
2079
  };
1981
2080
 
1982
2081
  var message = function() {
@@ -1990,11 +2089,11 @@
1990
2089
  // replace CLI version of writeFiles()
1991
2090
  internal.replaceWriteFiles(function(files, opts, done) {
1992
2091
  var filename;
1993
- if (!utils.isArray(files) || files.length === 0) {
2092
+ if (!utils$1.isArray(files) || files.length === 0) {
1994
2093
  done("Nothing to export");
1995
2094
  } else if (GUI.canSaveToServer() && !opts.save_to_download_folder) {
1996
- var paths = internal.getOutputPaths(utils.pluck(files, 'filename'), opts);
1997
- var data = utils.pluck(files, 'content');
2095
+ var paths = internal.getOutputPaths(utils$1.pluck(files, 'filename'), opts);
2096
+ var data = utils$1.pluck(files, 'content');
1998
2097
  saveFilesToServer(paths, data, function(err) {
1999
2098
  var msg;
2000
2099
  if (err) {
@@ -2013,7 +2112,7 @@
2013
2112
  } else if (files.length == 1) {
2014
2113
  saveBlobToDownloadFolder(files[0].filename, new Blob([files[0].content]), done);
2015
2114
  } else {
2016
- filename = internal.getCommonFileBase(utils.pluck(files, 'filename')) || "output";
2115
+ filename = internal.getCommonFileBase(utils$1.pluck(files, 'filename')) || "output";
2017
2116
  saveZipFile(filename + ".zip", files, done);
2018
2117
  }
2019
2118
  });
@@ -2035,10 +2134,10 @@
2035
2134
  // try to match import filename of this dataset
2036
2135
  if (d.info.input_files[0] == src) return d;
2037
2136
  // try to match name of a layer in this dataset
2038
- lyr = utils.find(d.layers, function(lyr) {return lyr.name == src;});
2137
+ lyr = utils$1.find(d.layers, function(lyr) {return lyr.name == src;});
2039
2138
  return lyr ? internal.isolateLayer(lyr, d) : null;
2040
2139
  }, null);
2041
- if (!retn) stop("Missing data layer [" + src + "]");
2140
+ if (!retn) stop$1("Missing data layer [" + src + "]");
2042
2141
  return retn;
2043
2142
  }
2044
2143
 
@@ -2047,7 +2146,7 @@
2047
2146
  // Return a copy with layers duplicated, so changes won't affect original layers
2048
2147
  // This makes an (unsafe) assumption that the dataset arcs won't be changed...
2049
2148
  // need to rethink this.
2050
- return utils.defaults({
2149
+ return utils$1.defaults({
2051
2150
  layers: dataset.layers.map(internal.copyLayer)
2052
2151
  }, dataset);
2053
2152
  });
@@ -2119,7 +2218,7 @@
2119
2218
  }
2120
2219
 
2121
2220
  function projectPointsForDisplay(lyr, src, dest) {
2122
- var copy = utils.extend({}, lyr);
2221
+ var copy = utils$1.extend({}, lyr);
2123
2222
  var proj = internal.getProjTransform2(src, dest);
2124
2223
  copy.shapes = internal.cloneShapes(lyr.shapes);
2125
2224
  internal.projectPointLayer(copy, proj);
@@ -2187,6 +2286,13 @@
2187
2286
  // expose this function, so other components can run commands (e.g. box tool)
2188
2287
  this.runMapshaperCommands = runMapshaperCommands;
2189
2288
 
2289
+ this.runInitialCommands = function(str) {
2290
+ str = str.trim();
2291
+ if (!str) return;
2292
+ turnOn();
2293
+ submit(str);
2294
+ };
2295
+
2190
2296
  consoleMessage(PROMPT);
2191
2297
  gui.keyboard.on('keydown', onKeyDown);
2192
2298
  window.addEventListener('beforeunload', saveHistory); // save history if console is open on refresh
@@ -2517,7 +2623,7 @@
2517
2623
  } else if (cmd == 'history') {
2518
2624
  toLog(gui.session.toCommandLineString());
2519
2625
  } else if (cmd == 'layers') {
2520
- message("Available layers:",
2626
+ message$1("Available layers:",
2521
2627
  internal.getFormattedLayerList(model));
2522
2628
  } else if (cmd == 'close' || cmd == 'exit' || cmd == 'quit') {
2523
2629
  turnOff();
@@ -2606,7 +2712,7 @@
2606
2712
  }
2607
2713
 
2608
2714
  function onError(err) {
2609
- if (utils.isString(err)) {
2715
+ if (utils$1.isString(err)) {
2610
2716
  consoleStop(err);
2611
2717
  } else if (err.name == 'UserError') {
2612
2718
  // stop() has already been called, don't need to log
@@ -2621,7 +2727,7 @@
2621
2727
  function consoleStop() {
2622
2728
  var msg = GUI.formatMessageArgs(arguments);
2623
2729
  consoleWarning(msg);
2624
- throw new UserError(msg);
2730
+ throw new UserError$1(msg);
2625
2731
  }
2626
2732
 
2627
2733
  function consoleWarning() {
@@ -2785,7 +2891,7 @@
2785
2891
  // console.log("first intersection:", internal.getIntersectionDebugData(XX[0], arcs));
2786
2892
  pointLyr = {geometry_type: 'point', shapes: [internal.getIntersectionPoints(XX)]};
2787
2893
  map.setIntersectionLayer(pointLyr, {layers:[pointLyr]});
2788
- readout.html(utils.format('<span class="icon"></span>%s line intersection%s <img class="close-btn" src="images/close.png">', n, utils.pluralSuffix(n)));
2894
+ 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)));
2789
2895
  readout.findChild('.close-btn').on('click', dismiss);
2790
2896
  } else {
2791
2897
  map.setIntersectionLayer(null);
@@ -2794,7 +2900,7 @@
2794
2900
  }
2795
2901
  }
2796
2902
 
2797
- utils.inherit(RepairControl, EventDispatcher);
2903
+ utils$1.inherit(RepairControl, EventDispatcher);
2798
2904
 
2799
2905
  function filterLayerByIds(lyr, ids) {
2800
2906
  var shapes;
@@ -2802,7 +2908,7 @@
2802
2908
  shapes = ids.map(function(id) {
2803
2909
  return lyr.shapes[id];
2804
2910
  });
2805
- return utils.defaults({shapes: shapes, data: null}, lyr);
2911
+ return utils$1.defaults({shapes: shapes, data: null}, lyr);
2806
2912
  }
2807
2913
  return lyr;
2808
2914
  }
@@ -2855,7 +2961,7 @@
2855
2961
  });
2856
2962
 
2857
2963
  internal.writeFiles = function() {
2858
- error(unsupportedMsg);
2964
+ error$1(unsupportedMsg);
2859
2965
  };
2860
2966
  } else {
2861
2967
  new SimpleButton(menu.findChild('.save-btn').addClass('default-btn')).on('click', onExportClick);
@@ -2891,7 +2997,7 @@
2891
2997
  setTimeout(function() {
2892
2998
  exportMenuSelection(layers, function(err) {
2893
2999
  if (err) {
2894
- if (utils.isString(err)) {
3000
+ if (utils$1.isString(err)) {
2895
3001
  gui.alert(err);
2896
3002
  } else {
2897
3003
  // stack seems to change if Error is logged directly
@@ -2938,7 +3044,7 @@
2938
3044
  // without changing its name elsewhere
2939
3045
  layer: Object.assign({}, o.layer)
2940
3046
  };
2941
- var html = utils.format(template, i + 1, target.layer.name || '[unnamed layer]');
3047
+ var html = utils$1.format(template, i + 1, target.layer.name || '[unnamed layer]');
2942
3048
  // return {layer: o.layer, html: html};
2943
3049
  var el = El('div').html(html).addClass('layer-item');
2944
3050
  var box = el.findChild('input').node();
@@ -3048,9 +3154,9 @@
3048
3154
 
3049
3155
  function initFormatMenu() {
3050
3156
  var defaults = ['shapefile', 'geojson', 'topojson', 'json', 'dsv', 'svg'];
3051
- var formats = utils.uniq(defaults.concat(getInputFormats()));
3157
+ var formats = utils$1.uniq(defaults.concat(getInputFormats()));
3052
3158
  var items = formats.map(function(fmt) {
3053
- return utils.format('<div><label><input type="radio" name="format" value="%s"' +
3159
+ return utils$1.format('<div><label><input type="radio" name="format" value="%s"' +
3054
3160
  ' class="radio">%s</label></div>', fmt, internal.getFormatName(fmt));
3055
3161
  });
3056
3162
  menu.findChild('.export-formats').html(items.join('\n'));
@@ -3213,7 +3319,7 @@
3213
3319
  // can be used as unique identifiers for caching rendered HTML, and as
3214
3320
  // an id for layer menu event handlers
3215
3321
  if (!lyr.menu_id || uniqIds[lyr.menu_id]) {
3216
- lyr.menu_id = utils.getUniqueName();
3322
+ lyr.menu_id = utils$1.getUniqueName();
3217
3323
  }
3218
3324
  uniqIds[lyr.menu_id] = true;
3219
3325
  if (isPinnable(lyr)) pinnableCount++;
@@ -3386,7 +3492,7 @@
3386
3492
  type = lyr.geometry_type + ' feature';
3387
3493
  }
3388
3494
  if (type) {
3389
- str = utils.format('%,d %s%s', n, type, utils.pluralSuffix(n));
3495
+ str = utils$1.format('%,d %s%s', n, type, utils$1.pluralSuffix(n));
3390
3496
  } else {
3391
3497
  str = "[empty]";
3392
3498
  }
@@ -3397,7 +3503,7 @@
3397
3503
  var file = internal.getLayerSourceFile(lyr, dataset);
3398
3504
  var missing = [];
3399
3505
  var msg;
3400
- if (utils.endsWith(file, '.shp') && lyr == dataset.layers[0]) {
3506
+ if (utils$1.endsWith(file, '.shp') && lyr == dataset.layers[0]) {
3401
3507
  if (!lyr.data) {
3402
3508
  missing.push('.dbf');
3403
3509
  }
@@ -3423,7 +3529,7 @@
3423
3529
 
3424
3530
 
3425
3531
  function rowHTML(c1, c2, cname) {
3426
- return utils.format('<div class="row%s"><div class="col1">%s</div>' +
3532
+ return utils$1.format('<div class="row%s"><div class="col1">%s</div>' +
3427
3533
  '<div class="col2">%s</div></div>', cname ? ' ' + cname : '', c1, c2);
3428
3534
  }
3429
3535
  }
@@ -3549,7 +3655,7 @@
3549
3655
 
3550
3656
  // import { cloneShape } from '../paths/mapshaper-shape-utils';
3551
3657
  // import { copyRecord } from '../datatable/mapshaper-data-utils';
3552
- var snapVerticesToPoint$1 = internal.snapVerticesToPoint;
3658
+ var snapVerticesToPoint = internal.snapVerticesToPoint;
3553
3659
  var cloneShape = internal.cloneShape;
3554
3660
  var copyRecord = internal.copyRecord;
3555
3661
 
@@ -3632,6 +3738,18 @@
3632
3738
  this.addHistoryState(stashedUndo, redo);
3633
3739
  }, this);
3634
3740
 
3741
+ gui.on('vertex_insert', function(e) {
3742
+ var target = gui.model.getActiveLayer();
3743
+ var arcs = target.dataset.arcs;
3744
+ var undo = function() {
3745
+ internal.deleteVertex(arcs, e.vertex_id);
3746
+ };
3747
+ var redo = function() {
3748
+ internal.insertVertex(arcs, e.vertex_id, e.coordinates);
3749
+ };
3750
+ this.addHistoryState(undo, redo);
3751
+ }, this);
3752
+
3635
3753
  this.clear = function() {
3636
3754
  reset();
3637
3755
  };
@@ -3658,7 +3776,7 @@
3658
3776
  var arcs = target.dataset.arcs;
3659
3777
  var p = internal.getVertexCoords(ids[0], arcs);
3660
3778
  return function() {
3661
- snapVerticesToPoint$1(ids, p, arcs, true);
3779
+ snapVerticesToPoint(ids, p, arcs, true);
3662
3780
  };
3663
3781
  };
3664
3782
 
@@ -3791,7 +3909,7 @@
3791
3909
  };
3792
3910
  }
3793
3911
 
3794
- utils.inherit(ModeSwitcher, EventDispatcher);
3912
+ utils$1.inherit(ModeSwitcher, EventDispatcher);
3795
3913
 
3796
3914
  function KeyboardEvents(gui) {
3797
3915
  var self = this;
@@ -3819,7 +3937,7 @@
3819
3937
  };
3820
3938
  }
3821
3939
 
3822
- utils.inherit(KeyboardEvents, EventDispatcher);
3940
+ utils$1.inherit(KeyboardEvents, EventDispatcher);
3823
3941
 
3824
3942
  function InteractionMode(gui) {
3825
3943
 
@@ -3845,7 +3963,7 @@
3845
3963
  data: 'edit attributes',
3846
3964
  labels: 'position labels',
3847
3965
  location: 'drag points',
3848
- vertices: 'drag vertices',
3966
+ vertices: 'edit vertices',
3849
3967
  selection: 'select features',
3850
3968
  off: 'turn off'
3851
3969
  };
@@ -4017,100 +4135,1425 @@
4017
4135
  gui.dispatchEvent('interaction_mode_change', {mode: mode});
4018
4136
  }
4019
4137
 
4020
- // Update button highlight and selected menu item highlight (if any)
4021
- function updateArrowButton() {
4022
- if (!menu) return;
4023
- if (_menuOpen) {
4024
- btn.addClass('open');
4025
- } else {
4026
- btn.removeClass('open');
4027
- }
4028
- btn.classed('hover', _menuOpen);
4029
- // btn.classed('selected', active() && !_menuOpen);
4030
- btn.classed('selected', active());
4138
+ // Update button highlight and selected menu item highlight (if any)
4139
+ function updateArrowButton() {
4140
+ if (!menu) return;
4141
+ if (_menuOpen) {
4142
+ btn.addClass('open');
4143
+ } else {
4144
+ btn.removeClass('open');
4145
+ }
4146
+ btn.classed('hover', _menuOpen);
4147
+ // btn.classed('selected', active() && !_menuOpen);
4148
+ btn.classed('selected', active());
4149
+ }
4150
+
4151
+ function updateSelectionHighlight() {
4152
+ El.findAll('.nav-menu-item').forEach(function(el) {
4153
+ el = El(el);
4154
+ el.classed('selected', el.attr('data-name') == _editMode);
4155
+ });
4156
+ }
4157
+ }
4158
+
4159
+ function Model(gui) {
4160
+ var self = new internal.Catalog();
4161
+ var deleteLayer = self.deleteLayer;
4162
+ utils$1.extend(self, EventDispatcher.prototype);
4163
+
4164
+ // override Catalog method (so -drop command will work in web console)
4165
+ self.deleteLayer = function(lyr, dataset) {
4166
+ var active, flags;
4167
+ deleteLayer.call(self, lyr, dataset);
4168
+ if (self.isEmpty()) {
4169
+ // refresh browser if deleted layer was the last layer
4170
+ window.location.href = window.location.href.toString();
4171
+ } else {
4172
+ // trigger event to update layer list and, if needed, the map view
4173
+ flags = {};
4174
+ active = self.getActiveLayer();
4175
+ if (active.layer != lyr) {
4176
+ flags.select = true;
4177
+ }
4178
+ internal.cleanupArcs(active.dataset);
4179
+ if (internal.layerHasPaths(lyr)) {
4180
+ flags.arc_count = true; // looks like a kludge, try to remove
4181
+ }
4182
+ self.updated(flags, active.layer, active.dataset);
4183
+ }
4184
+ };
4185
+
4186
+ self.updated = function(flags) {
4187
+ var targets = self.getDefaultTargets();
4188
+ var active = self.getActiveLayer();
4189
+ if (internal.countTargetLayers(targets) > 1) {
4190
+ self.setDefaultTarget([active.layer], active.dataset);
4191
+ gui.session.setTargetLayer(active.layer); // add -target command to target single layer
4192
+ }
4193
+ if (flags.select) {
4194
+ self.dispatchEvent('select', active);
4195
+ }
4196
+ self.dispatchEvent('update', utils$1.extend({flags: flags}, active));
4197
+ };
4198
+
4199
+ self.selectLayer = function(lyr, dataset) {
4200
+ if (self.getActiveLayer().layer == lyr) return;
4201
+ self.setDefaultTarget([lyr], dataset);
4202
+ self.updated({select: true});
4203
+ gui.session.setTargetLayer(lyr);
4204
+ };
4205
+
4206
+ self.selectNextLayer = function() {
4207
+ var next = self.findNextLayer(self.getActiveLayer().layer);
4208
+ if (next) self.selectLayer(next.layer, next.dataset);
4209
+ };
4210
+
4211
+ self.selectPrevLayer = function() {
4212
+ var prev = self.findPrevLayer(self.getActiveLayer().layer);
4213
+ if (prev) self.selectLayer(prev.layer, prev.dataset);
4214
+ };
4215
+
4216
+ return self;
4217
+ }
4218
+
4219
+ var Buffer = require('buffer').Buffer; // works with browserify
4220
+
4221
+ var context = createContext(); // command context (persist for the current command cycle)
4222
+
4223
+ function runningInBrowser() {
4224
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined';
4225
+ }
4226
+
4227
+ function getStateVar(key) {
4228
+ return context[key];
4229
+ }
4230
+
4231
+ function setStateVar(key, val) {
4232
+ context[key] = val;
4233
+ }
4234
+
4235
+ function createContext() {
4236
+ return {
4237
+ DEBUG: false,
4238
+ QUIET: false,
4239
+ VERBOSE: false,
4240
+ defs: {},
4241
+ input_files: []
4242
+ };
4243
+ }
4244
+
4245
+ // Install a new set of context variables, clear them when an async callback is called.
4246
+ // @cb callback function to wrap
4247
+ // returns wrapped callback function
4248
+ function createAsyncContext(cb) {
4249
+ context = createContext();
4250
+ return function() {
4251
+ cb.apply(null, utils.toArray(arguments));
4252
+ // clear context after cb(), so output/errors can be handled in current context
4253
+ context = createContext();
4254
+ };
4255
+ }
4256
+
4257
+ // Save the current context, restore it when an async callback is called
4258
+ // @cb callback function to wrap
4259
+ // returns wrapped callback function
4260
+ function preserveContext(cb) {
4261
+ var ctx = context;
4262
+ return function() {
4263
+ context = ctx;
4264
+ cb.apply(null, utils.toArray(arguments));
4265
+ };
4266
+ }
4267
+
4268
+ var LOGGING = false;
4269
+ var STDOUT = false; // use stdout for status messages
4270
+
4271
+ // These three functions can be reset by GUI using setLoggingFunctions();
4272
+ var _error = function() {
4273
+ var msg = utils.toArray(arguments).join(' ');
4274
+ throw new Error(msg);
4275
+ };
4276
+
4277
+ var _stop = function() {
4278
+ throw new UserError(formatLogArgs(arguments));
4279
+ };
4280
+
4281
+ var _interrupt = function() {
4282
+ throw new NonFatalError(formatLogArgs(arguments));
4283
+ };
4284
+
4285
+ var _message = function() {
4286
+ logArgs(arguments);
4287
+ };
4288
+
4289
+ function enableLogging() {
4290
+ LOGGING = true;
4291
+ }
4292
+
4293
+ function loggingEnabled() {
4294
+ return !!LOGGING;
4295
+ }
4296
+
4297
+ // Handle an unexpected condition (internal error)
4298
+ function error() {
4299
+ _error.apply(null, utils.toArray(arguments));
4300
+ }
4301
+
4302
+ // Handle an error caused by invalid input or misuse of API
4303
+ function stop() {
4304
+ _stop.apply(null, utils.toArray(arguments));
4305
+ }
4306
+
4307
+ function interrupt() {
4308
+ _interrupt.apply(null, utils.toArray(arguments));
4309
+ }
4310
+
4311
+ // Print a status message
4312
+ function message() {
4313
+ _message.apply(null, messageArgs(arguments));
4314
+ }
4315
+
4316
+ // A way for the GUI to replace the CLI logging functions
4317
+ function setLoggingFunctions(message, error, stop) {
4318
+ _message = message;
4319
+ _error = error;
4320
+ _stop = stop;
4321
+ }
4322
+
4323
+
4324
+ // print a message to stdout
4325
+ function print() {
4326
+ STDOUT = true; // tell logArgs() to print to stdout, not stderr
4327
+ // calling message() adds the "[command name]" prefix
4328
+ _message(utils.toArray(arguments));
4329
+ STDOUT = false;
4330
+ }
4331
+
4332
+ function verbose() {
4333
+ // verbose can be set globally with the -verbose command or separately for each command
4334
+ if (getStateVar('VERBOSE') || getStateVar('verbose')) {
4335
+ message.apply(null, arguments);
4336
+ }
4337
+ }
4338
+
4339
+ function debug() {
4340
+ if (getStateVar('DEBUG') || getStateVar('debug')) {
4341
+ logArgs(arguments);
4342
+ }
4343
+ }
4344
+
4345
+ function printError(err) {
4346
+ var msg;
4347
+ if (!LOGGING) return;
4348
+ if (utils.isString(err)) {
4349
+ err = new UserError(err);
4350
+ }
4351
+ if (err.name == 'NonFatalError') {
4352
+ console.error(messageArgs([err.message]).join(' '));
4353
+ } else if (err.name == 'UserError') {
4354
+ msg = err.message;
4355
+ if (!/Error/.test(msg)) {
4356
+ msg = "Error: " + msg;
4357
+ }
4358
+ console.error(messageArgs([msg]).join(' '));
4359
+ console.error("Run mapshaper -h to view help");
4360
+ } else {
4361
+ // not a user error (i.e. a bug in mapshaper)
4362
+ console.error(err);
4363
+ // throw err;
4364
+ }
4365
+ }
4366
+
4367
+ function UserError(msg) {
4368
+ var err = new Error(msg);
4369
+ err.name = 'UserError';
4370
+ return err;
4371
+ }
4372
+
4373
+ function NonFatalError(msg) {
4374
+ var err = new Error(msg);
4375
+ err.name = 'NonFatalError';
4376
+ return err;
4377
+ }
4378
+
4379
+ function formatColumns(arr, alignments) {
4380
+ var widths = arr.reduce(function(memo, line) {
4381
+ return line.map(function(str, i) {
4382
+ return memo ? Math.max(memo[i], str.length) : str.length;
4383
+ });
4384
+ }, null);
4385
+ return arr.map(function(line) {
4386
+ line = line.map(function(str, i) {
4387
+ var rt = alignments && alignments[i] == 'right';
4388
+ var pad = (rt ? str.padStart : str.padEnd).bind(str);
4389
+ return pad(widths[i], ' ');
4390
+ });
4391
+ return ' ' + line.join(' ');
4392
+ }).join('\n');
4393
+ }
4394
+
4395
+ // Format an array of (preferably short) strings in columns for console logging.
4396
+ function formatStringsAsGrid(arr) {
4397
+ // TODO: variable column width
4398
+ var longest = arr.reduce(function(len, str) {
4399
+ return Math.max(len, str.length);
4400
+ }, 0),
4401
+ colWidth = longest + 2,
4402
+ perLine = Math.floor(80 / colWidth) || 1;
4403
+ return arr.reduce(function(memo, name, i) {
4404
+ var col = i % perLine;
4405
+ if (i > 0 && col === 0) memo += '\n';
4406
+ if (col < perLine - 1) { // right-pad all but rightmost column
4407
+ name = utils.rpad(name, colWidth - 2, ' ');
4408
+ }
4409
+ return memo + ' ' + name;
4410
+ }, '');
4411
+ }
4412
+
4413
+ // expose so GUI can use it
4414
+ function formatLogArgs(args) {
4415
+ return utils.toArray(args).join(' ');
4416
+ }
4417
+
4418
+ function messageArgs(args) {
4419
+ var arr = utils.toArray(args);
4420
+ var cmd = getStateVar('current_command');
4421
+ if (cmd && cmd != 'help') {
4422
+ arr.unshift('[' + cmd + ']');
4423
+ }
4424
+ return arr;
4425
+ }
4426
+
4427
+ function logArgs(args) {
4428
+ if (!LOGGING || getStateVar('QUIET') || !utils.isArrayLike(args)) return;
4429
+ var msg = formatLogArgs(args);
4430
+ if (STDOUT) console.log(msg);
4431
+ else console.error(msg);
4432
+ }
4433
+
4434
+ var uniqCount = 0;
4435
+ function getUniqueName(prefix) {
4436
+ return (prefix || "__id_") + (++uniqCount);
4437
+ }
4438
+
4439
+ function isFunction(obj) {
4440
+ return typeof obj == 'function';
4441
+ }
4442
+
4443
+ function isObject(obj) {
4444
+ return obj === Object(obj); // via underscore
4445
+ }
4446
+
4447
+ function clamp(val, min, max) {
4448
+ return val < min ? min : (val > max ? max : val);
4449
+ }
4450
+
4451
+ function isArray(obj) {
4452
+ return Array.isArray(obj);
4453
+ }
4454
+
4455
+ // Is obj a valid number or NaN? (test if obj is type number)
4456
+ function isNumber(obj) {
4457
+ return obj != null && obj.constructor == Number;
4458
+ }
4459
+
4460
+ function isValidNumber(val) {
4461
+ return isNumber(val) && !isNaN(val);
4462
+ }
4463
+
4464
+ // Similar to isFinite() but does not coerce strings or other types
4465
+ function isFiniteNumber(val) {
4466
+ return isValidNumber(val) && val !== Infinity && val !== -Infinity;
4467
+ }
4468
+
4469
+ // This uses type conversion
4470
+ // export function isFiniteNumber(val) {
4471
+ // return val > -Infinity && val < Infinity;
4472
+ // }
4473
+
4474
+ function isNonNegNumber(val) {
4475
+ return isNumber(val) && val >= 0;
4476
+ }
4477
+
4478
+ function isInteger(obj) {
4479
+ return isNumber(obj) && ((obj | 0) === obj);
4480
+ }
4481
+
4482
+ function isEven(obj) {
4483
+ return (obj % 2) === 0;
4484
+ }
4485
+
4486
+ function isOdd(obj) {
4487
+ return (obj % 2) === 1;
4488
+ }
4489
+
4490
+ function isString(obj) {
4491
+ return obj != null && obj.toString === String.prototype.toString;
4492
+ // TODO: replace w/ something better.
4493
+ }
4494
+
4495
+ function isDate(obj) {
4496
+ return !!obj && obj.getTime === Date.prototype.getTime;
4497
+ }
4498
+
4499
+ function isBoolean(obj) {
4500
+ return obj === true || obj === false;
4501
+ }
4502
+
4503
+ function formatDateISO(d) {
4504
+ if (!isDate(d)) return '';
4505
+ return d.toISOString().replace(':00.000Z', 'Z');
4506
+ }
4507
+
4508
+ // Convert an array-like object to an Array, or make a copy if @obj is an Array
4509
+ function toArray(obj) {
4510
+ var arr;
4511
+ if (!isArrayLike(obj)) error("toArray() requires an array-like object");
4512
+ try {
4513
+ arr = Array.prototype.slice.call(obj, 0); // breaks in ie8
4514
+ } catch(e) {
4515
+ // support ie8
4516
+ arr = [];
4517
+ for (var i=0, n=obj.length; i<n; i++) {
4518
+ arr[i] = obj[i];
4519
+ }
4520
+ }
4521
+ return arr;
4522
+ }
4523
+
4524
+ // Array like: has length property, is numerically indexed and mutable.
4525
+ // TODO: try to detect objects with length property but no indexed data elements
4526
+ function isArrayLike(obj) {
4527
+ if (!obj) return false;
4528
+ if (isArray(obj)) return true;
4529
+ if (isString(obj)) return false;
4530
+ if (obj.length === 0) return true;
4531
+ if (obj.length > 0) return true;
4532
+ return false;
4533
+ }
4534
+
4535
+ // See https://raw.github.com/kvz/phpjs/master/functions/strings/addslashes.js
4536
+ function addslashes(str) {
4537
+ return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
4538
+ }
4539
+
4540
+ // Escape a literal string to use in a regexp.
4541
+ // Ref.: http://simonwillison.net/2006/Jan/20/escape/
4542
+ function regexEscape(str) {
4543
+ return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
4544
+ }
4545
+
4546
+
4547
+ // See https://github.com/janl/mustache.js/blob/master/mustache.js
4548
+ var entityMap = {
4549
+ '&': '&amp;',
4550
+ '<': '&lt;',
4551
+ '>': '&gt;',
4552
+ '"': '&quot;',
4553
+ "'": '&#39;',
4554
+ '/': '&#x2F;'
4555
+ };
4556
+ function htmlEscape(s) {
4557
+ return String(s).replace(/[&<>"'\/]/g, function(s) {
4558
+ return entityMap[s];
4559
+ });
4560
+ }
4561
+
4562
+ function defaults(dest) {
4563
+ for (var i=1, n=arguments.length; i<n; i++) {
4564
+ var src = arguments[i] || {};
4565
+ for (var key in src) {
4566
+ if (key in dest === false && src.hasOwnProperty(key)) {
4567
+ dest[key] = src[key];
4568
+ }
4569
+ }
4570
+ }
4571
+ return dest;
4572
+ }
4573
+
4574
+ function extend(o) {
4575
+ var dest = o || {},
4576
+ n = arguments.length,
4577
+ key, i, src;
4578
+ for (i=1; i<n; i++) {
4579
+ src = arguments[i] || {};
4580
+ for (key in src) {
4581
+ if (src.hasOwnProperty(key)) {
4582
+ dest[key] = src[key];
4583
+ }
4584
+ }
4585
+ }
4586
+ return dest;
4587
+ }
4588
+
4589
+ // Pseudoclassical inheritance
4590
+ //
4591
+ // Inherit from a Parent function:
4592
+ // inherit(Child, Parent);
4593
+ // Call parent's constructor (inside child constructor):
4594
+ // this.__super__([args...]);
4595
+ function inherit(targ, src) {
4596
+ var f = function() {
4597
+ if (this.__super__ == f) {
4598
+ // add __super__ of parent to front of lookup chain
4599
+ // so parent class constructor can call its parent using this.__super__
4600
+ this.__super__ = src.prototype.__super__;
4601
+ // call parent constructor function. this.__super__ now points to parent-of-parent
4602
+ src.apply(this, arguments);
4603
+ // remove temp __super__, expose targ.prototype.__super__ again
4604
+ delete this.__super__;
4605
+ }
4606
+ };
4607
+
4608
+ f.prototype = src.prototype || src; // added || src to allow inheriting from objects as well as functions
4609
+ // Extend targ prototype instead of wiping it out --
4610
+ // in case inherit() is called after targ.prototype = {stuff}; statement
4611
+ targ.prototype = extend(new f(), targ.prototype); //
4612
+ targ.prototype.constructor = targ;
4613
+ targ.prototype.__super__ = f;
4614
+ }
4615
+
4616
+
4617
+ // Call @iter on each member of an array (similar to Array#reduce(iter))
4618
+ // iter: function(memo, item, callback)
4619
+ // Call @done when all members have been processed or if an error occurs
4620
+ // done: function(err, memo)
4621
+ // @memo: Initial value
4622
+ //
4623
+ function reduceAsync(arr, memo, iter, done) {
4624
+ var call = typeof setImmediate == 'undefined' ? setTimeout : setImmediate;
4625
+ var i=0;
4626
+ next(null, memo);
4627
+
4628
+ function next(err, memo) {
4629
+ // Detach next operation from call stack to prevent overflow
4630
+ // Don't use setTimeout(, 0) if setImmediate is available
4631
+ // (setTimeout() can introduce a long delay if previous operation was slow,
4632
+ // as of Node 0.10.32 -- a bug?)
4633
+ if (err) {
4634
+ return done(err, null);
4635
+ }
4636
+ call(function() {
4637
+ if (i < arr.length === false) {
4638
+ done(null, memo);
4639
+ } else {
4640
+ iter(memo, arr[i++], next);
4641
+ }
4642
+ }, 0);
4643
+ }
4644
+ }
4645
+
4646
+
4647
+ // Append elements of @src array to @dest array
4648
+ function merge(dest, src) {
4649
+ if (!isArray(dest) || !isArray(src)) {
4650
+ error("Usage: merge(destArray, srcArray);");
4651
+ }
4652
+ for (var i=0, n=src.length; i<n; i++) {
4653
+ dest.push(src[i]);
4654
+ }
4655
+ return dest;
4656
+ }
4657
+
4658
+ // Returns elements in arr and not in other
4659
+ // (similar to underscore diff)
4660
+ function difference(arr, other) {
4661
+ var index = arrayToIndex(other);
4662
+ return arr.filter(function(el) {
4663
+ return !Object.prototype.hasOwnProperty.call(index, el);
4664
+ });
4665
+ }
4666
+
4667
+ // Return the intersection of two arrays
4668
+ function intersection(a, b) {
4669
+ return a.filter(function(el) {
4670
+ return b.includes(el);
4671
+ });
4672
+ }
4673
+
4674
+ function indexOf(arr, item) {
4675
+ var nan = item !== item;
4676
+ for (var i = 0, len = arr.length || 0; i < len; i++) {
4677
+ if (arr[i] === item) return i;
4678
+ if (nan && arr[i] !== arr[i]) return i;
4679
+ }
4680
+ return -1;
4681
+ }
4682
+
4683
+ // Test a string or array-like object for existence of substring or element
4684
+ function contains(container, item) {
4685
+ if (isString(container)) {
4686
+ return container.indexOf(item) != -1;
4687
+ }
4688
+ else if (isArrayLike(container)) {
4689
+ return indexOf(container, item) != -1;
4690
+ }
4691
+ error("Expected Array or String argument");
4692
+ }
4693
+
4694
+ function some(arr, test) {
4695
+ return arr.reduce(function(val, item) {
4696
+ return val || test(item); // TODO: short-circuit?
4697
+ }, false);
4698
+ }
4699
+
4700
+ function every(arr, test) {
4701
+ return arr.reduce(function(val, item) {
4702
+ return val && test(item);
4703
+ }, true);
4704
+ }
4705
+
4706
+ function find(arr, test, ctx) {
4707
+ var matches = arr.filter(test, ctx);
4708
+ return matches.length === 0 ? null : matches[0];
4709
+ }
4710
+
4711
+ function range(len, start, inc) {
4712
+ var arr = [],
4713
+ v = start === void 0 ? 0 : start,
4714
+ i = inc === void 0 ? 1 : inc;
4715
+ while(len--) {
4716
+ arr.push(v);
4717
+ v += i;
4718
+ }
4719
+ return arr;
4720
+ }
4721
+
4722
+ function repeat(times, func) {
4723
+ var values = [],
4724
+ val;
4725
+ for (var i=0; i<times; i++) {
4726
+ val = func(i);
4727
+ if (val !== void 0) {
4728
+ values[i] = val;
4729
+ }
4730
+ }
4731
+ return values.length > 0 ? values : void 0;
4732
+ }
4733
+
4734
+ // Calc sum, skip falsy and NaN values
4735
+ // Assumes: no other non-numeric objects in array
4736
+ //
4737
+ function sum(arr, info) {
4738
+ if (!isArrayLike(arr)) error ("sum() expects an array, received:", arr);
4739
+ var tot = 0,
4740
+ nan = 0,
4741
+ val;
4742
+ for (var i=0, n=arr.length; i<n; i++) {
4743
+ val = arr[i];
4744
+ if (val) {
4745
+ tot += val;
4746
+ } else if (isNaN(val)) {
4747
+ nan++;
4748
+ }
4749
+ }
4750
+ if (info) {
4751
+ info.nan = nan;
4752
+ }
4753
+ return tot;
4754
+ }
4755
+
4756
+ // Calculate min and max values of an array, ignoring NaN values
4757
+ function getArrayBounds(arr) {
4758
+ var min = Infinity,
4759
+ max = -Infinity,
4760
+ nan = 0, val;
4761
+ for (var i=0, len=arr.length; i<len; i++) {
4762
+ val = arr[i];
4763
+ if (val !== val) nan++;
4764
+ if (val < min) min = val;
4765
+ if (val > max) max = val;
4766
+ }
4767
+ return {
4768
+ min: min,
4769
+ max: max,
4770
+ nan: nan
4771
+ };
4772
+ }
4773
+
4774
+ // export function uniq(src) {
4775
+ // var index = {};
4776
+ // return src.reduce(function(memo, el) {
4777
+ // if (el in index === false) {
4778
+ // index[el] = true;
4779
+ // memo.push(el);
4780
+ // }
4781
+ // return memo;
4782
+ // }, []);
4783
+ // }
4784
+
4785
+ function uniq(src) {
4786
+ var index = new Set();
4787
+ var arr = [];
4788
+ var item;
4789
+ for (var i=0, n=src.length; i<n; i++) {
4790
+ item = src[i];
4791
+ if (!index.has(item)) {
4792
+ arr.push(item);
4793
+ index.add(item);
4794
+ }
4795
+ }
4796
+ return arr;
4797
+ }
4798
+
4799
+ function pluck(arr, key) {
4800
+ return arr.map(function(obj) {
4801
+ return obj[key];
4802
+ });
4803
+ }
4804
+
4805
+ function countValues(arr) {
4806
+ return arr.reduce(function(memo, val) {
4807
+ memo[val] = (val in memo) ? memo[val] + 1 : 1;
4808
+ return memo;
4809
+ }, {});
4810
+ }
4811
+
4812
+ function indexOn(arr, k) {
4813
+ return arr.reduce(function(index, o) {
4814
+ index[o[k]] = o;
4815
+ return index;
4816
+ }, {});
4817
+ }
4818
+
4819
+ function groupBy(arr, k) {
4820
+ return arr.reduce(function(index, o) {
4821
+ var keyval = o[k];
4822
+ if (keyval in index) {
4823
+ index[keyval].push(o);
4824
+ } else {
4825
+ index[keyval] = [o];
4826
+ }
4827
+ return index;
4828
+ }, {});
4829
+ }
4830
+
4831
+ function arrayToIndex(arr, val) {
4832
+ var init = arguments.length > 1;
4833
+ return arr.reduce(function(index, key) {
4834
+ index[key] = init ? val : true;
4835
+ return index;
4836
+ }, {});
4837
+ }
4838
+
4839
+ // Support for iterating over array-like objects, like typed arrays
4840
+ function forEach(arr, func, ctx) {
4841
+ if (!isArrayLike(arr)) {
4842
+ throw new Error("#forEach() takes an array-like argument. " + arr);
4843
+ }
4844
+ for (var i=0, n=arr.length; i < n; i++) {
4845
+ func.call(ctx, arr[i], i);
4846
+ }
4847
+ }
4848
+
4849
+ function forEachProperty(o, func, ctx) {
4850
+ Object.keys(o).forEach(function(key) {
4851
+ func.call(ctx, o[key], key);
4852
+ });
4853
+ }
4854
+
4855
+ function initializeArray(arr, init) {
4856
+ for (var i=0, len=arr.length; i<len; i++) {
4857
+ arr[i] = init;
4858
+ }
4859
+ return arr;
4860
+ }
4861
+
4862
+ function replaceArray(arr, arr2) {
4863
+ arr.splice(0, arr.length);
4864
+ for (var i=0, n=arr2.length; i<n; i++) {
4865
+ arr.push(arr2[i]);
4866
+ }
4867
+ }
4868
+
4869
+ function repeatString(src, n) {
4870
+ var str = "";
4871
+ for (var i=0; i<n; i++)
4872
+ str += src;
4873
+ return str;
4874
+ }
4875
+
4876
+ function splitLines(str) {
4877
+ return str.split(/\r?\n/);
4878
+ }
4879
+
4880
+ function pluralSuffix(count) {
4881
+ return count != 1 ? 's' : '';
4882
+ }
4883
+
4884
+ function endsWith(str, ending) {
4885
+ return str.indexOf(ending, str.length - ending.length) !== -1;
4886
+ }
4887
+
4888
+ function lpad(str, size, pad) {
4889
+ pad = pad || ' ';
4890
+ str = String(str);
4891
+ return repeatString(pad, size - str.length) + str;
4892
+ }
4893
+
4894
+ function rpad(str, size, pad) {
4895
+ pad = pad || ' ';
4896
+ str = String(str);
4897
+ return str + repeatString(pad, size - str.length);
4898
+ }
4899
+
4900
+ function trim(str) {
4901
+ return ltrim(rtrim(str));
4902
+ }
4903
+
4904
+ var ltrimRxp = /^\s+/;
4905
+ function ltrim(str) {
4906
+ return str.replace(ltrimRxp, '');
4907
+ }
4908
+
4909
+ var rtrimRxp = /\s+$/;
4910
+ function rtrim(str) {
4911
+ return str.replace(rtrimRxp, '');
4912
+ }
4913
+
4914
+ function addThousandsSep(str) {
4915
+ var fmt = '',
4916
+ start = str[0] == '-' ? 1 : 0,
4917
+ dec = str.indexOf('.'),
4918
+ end = str.length,
4919
+ ins = (dec == -1 ? end : dec) - 3;
4920
+ while (ins > start) {
4921
+ fmt = ',' + str.substring(ins, end) + fmt;
4922
+ end = ins;
4923
+ ins -= 3;
4924
+ }
4925
+ return str.substring(0, end) + fmt;
4926
+ }
4927
+
4928
+ function numToStr(num, decimals) {
4929
+ return decimals >= 0 ? num.toFixed(decimals) : String(num);
4930
+ }
4931
+
4932
+ function formatNumber(val) {
4933
+ return val + '';
4934
+ }
4935
+
4936
+ function formatIntlNumber(val) {
4937
+ var str = formatNumber(val);
4938
+ return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
4939
+ }
4940
+
4941
+ function formatNumberForDisplay(num, decimals, nullStr, showPos) {
4942
+ var fmt;
4943
+ if (isNaN(num)) {
4944
+ fmt = nullStr || '-';
4945
+ } else {
4946
+ fmt = numToStr(num, decimals);
4947
+ fmt = addThousandsSep(fmt);
4948
+ if (showPos && parseFloat(fmt) > 0) {
4949
+ fmt = "+" + fmt;
4950
+ }
4951
+ }
4952
+ return fmt;
4953
+ }
4954
+
4955
+ function shuffle(arr) {
4956
+ var tmp, i, j;
4957
+ for (i = arr.length - 1; i > 0; i--) {
4958
+ j = Math.floor(Math.random() * (i + 1));
4959
+ tmp = arr[i];
4960
+ arr[i] = arr[j];
4961
+ arr[j] = tmp;
4962
+ }
4963
+ }
4964
+
4965
+ // Sort an array of objects based on one or more properties.
4966
+ // Usage: sortOn(array, key1, asc?[, key2, asc? ...])
4967
+ //
4968
+ function sortOn(arr) {
4969
+ var comparators = [];
4970
+ for (var i=1; i<arguments.length; i+=2) {
4971
+ comparators.push(getKeyComparator(arguments[i], arguments[i+1]));
4972
+ }
4973
+ arr.sort(function(a, b) {
4974
+ var cmp = 0,
4975
+ i = 0,
4976
+ n = comparators.length;
4977
+ while (i < n && cmp === 0) {
4978
+ cmp = comparators[i](a, b);
4979
+ i++;
4980
+ }
4981
+ return cmp;
4982
+ });
4983
+ return arr;
4984
+ }
4985
+
4986
+ // Sort array of values that can be compared with < > operators (strings, numbers)
4987
+ // null, undefined and NaN are sorted to the end of the array
4988
+ // default order is ascending
4989
+ //
4990
+ function genericSort(arr, ascending) {
4991
+ var compare = getGenericComparator(ascending);
4992
+ Array.prototype.sort.call(arr, compare);
4993
+ return arr;
4994
+ }
4995
+
4996
+ function getSortedIds(arr, asc) {
4997
+ var ids = range(arr.length);
4998
+ sortArrayIndex(ids, arr, asc);
4999
+ return ids;
5000
+ }
5001
+
5002
+ function sortArrayIndex(ids, arr, asc) {
5003
+ var compare = getGenericComparator(asc);
5004
+ ids.sort(function(i, j) {
5005
+ // added i, j comparison to guarantee that sort is stable
5006
+ var cmp = compare(arr[i], arr[j]);
5007
+ return cmp > 0 || cmp === 0 && i > j ? 1 : -1;
5008
+ });
5009
+ }
5010
+
5011
+ function reorderArray(arr, idxs) {
5012
+ var len = idxs.length;
5013
+ var arr2 = [];
5014
+ for (var i=0; i<len; i++) {
5015
+ var idx = idxs[i];
5016
+ if (idx < 0 || idx >= len) error("Out-of-bounds array idx");
5017
+ arr2[i] = arr[idx];
5018
+ }
5019
+ replaceArray(arr, arr2);
5020
+ }
5021
+
5022
+ function getKeyComparator(key, asc) {
5023
+ var compare = getGenericComparator(asc);
5024
+ return function(a, b) {
5025
+ return compare(a[key], b[key]);
5026
+ };
5027
+ }
5028
+
5029
+ function getGenericComparator(asc) {
5030
+ asc = asc !== false;
5031
+ return function(a, b) {
5032
+ var retn = 0;
5033
+ if (b == null) {
5034
+ retn = a == null ? 0 : -1;
5035
+ } else if (a == null) {
5036
+ retn = 1;
5037
+ } else if (a < b) {
5038
+ retn = asc ? -1 : 1;
5039
+ } else if (a > b) {
5040
+ retn = asc ? 1 : -1;
5041
+ } else if (a !== a) {
5042
+ retn = 1;
5043
+ } else if (b !== b) {
5044
+ retn = -1;
5045
+ }
5046
+ return retn;
5047
+ };
5048
+ }
5049
+
5050
+
5051
+ // Generic in-place sort (null, NaN, undefined not handled)
5052
+ function quicksort(arr, asc) {
5053
+ quicksortPartition(arr, 0, arr.length-1);
5054
+ if (asc === false) Array.prototype.reverse.call(arr); // Works with typed arrays
5055
+ return arr;
5056
+ }
5057
+
5058
+ // Moved out of quicksort() (saw >100% speedup in Chrome with deep recursion)
5059
+ function quicksortPartition (a, lo, hi) {
5060
+ var i = lo,
5061
+ j = hi,
5062
+ pivot, tmp;
5063
+ while (i < hi) {
5064
+ pivot = a[lo + hi >> 1]; // avoid n^2 performance on sorted arrays
5065
+ while (i <= j) {
5066
+ while (a[i] < pivot) i++;
5067
+ while (a[j] > pivot) j--;
5068
+ if (i <= j) {
5069
+ tmp = a[i];
5070
+ a[i] = a[j];
5071
+ a[j] = tmp;
5072
+ i++;
5073
+ j--;
5074
+ }
5075
+ }
5076
+ if (lo < j) quicksortPartition(a, lo, j);
5077
+ lo = i;
5078
+ j = hi;
5079
+ }
5080
+ }
5081
+
5082
+
5083
+ function findRankByValue(arr, value) {
5084
+ if (isNaN(value)) return arr.length;
5085
+ var rank = 1;
5086
+ for (var i=0, n=arr.length; i<n; i++) {
5087
+ if (value > arr[i]) rank++;
5088
+ }
5089
+ return rank;
5090
+ }
5091
+
5092
+ function findValueByPct(arr, pct) {
5093
+ var rank = Math.ceil((1-pct) * (arr.length));
5094
+ return findValueByRank(arr, rank);
5095
+ }
5096
+
5097
+ // See http://ndevilla.free.fr/median/median/src/wirth.c
5098
+ // Elements of @arr are reordered
5099
+ //
5100
+ function findValueByRank(arr, rank) {
5101
+ if (!arr.length || rank < 1 || rank > arr.length) error("[findValueByRank()] invalid input");
5102
+
5103
+ rank = clamp(rank | 0, 1, arr.length);
5104
+ var k = rank - 1, // conv. rank to array index
5105
+ n = arr.length,
5106
+ l = 0,
5107
+ m = n - 1,
5108
+ i, j, val, tmp;
5109
+
5110
+ while (l < m) {
5111
+ val = arr[k];
5112
+ i = l;
5113
+ j = m;
5114
+ do {
5115
+ while (arr[i] < val) {i++;}
5116
+ while (val < arr[j]) {j--;}
5117
+ if (i <= j) {
5118
+ tmp = arr[i];
5119
+ arr[i] = arr[j];
5120
+ arr[j] = tmp;
5121
+ i++;
5122
+ j--;
5123
+ }
5124
+ } while (i <= j);
5125
+ if (j < k) l = i;
5126
+ if (k < i) m = j;
5127
+ }
5128
+ return arr[k];
5129
+ }
5130
+
5131
+ //
5132
+ //
5133
+ function findMedian(arr) {
5134
+ var n = arr.length,
5135
+ rank = Math.floor(n / 2) + 1,
5136
+ median = findValueByRank(arr, rank);
5137
+ if ((n & 1) == 0) {
5138
+ median = (median + findValueByRank(arr, rank - 1)) / 2;
5139
+ }
5140
+ return median;
5141
+ }
5142
+
5143
+
5144
+ function mean(arr) {
5145
+ var count = 0,
5146
+ avg = NaN,
5147
+ val;
5148
+ for (var i=0, n=arr.length; i<n; i++) {
5149
+ val = arr[i];
5150
+ if (isNaN(val)) continue;
5151
+ avg = ++count == 1 ? val : val / count + (count - 1) / count * avg;
5152
+ }
5153
+ return avg;
5154
+ }
5155
+
5156
+
5157
+ /*
5158
+ A simplified version of printf formatting
5159
+ Format codes: %[flags][width][.precision]type
5160
+
5161
+ supported flags:
5162
+ + add '+' before positive numbers
5163
+ 0 left-pad with '0'
5164
+ ' Add thousands separator
5165
+ width: 1 to many
5166
+ precision: .(1 to many)
5167
+ type:
5168
+ s string
5169
+ di integers
5170
+ f decimal numbers
5171
+ xX hexidecimal (unsigned)
5172
+ % literal '%'
5173
+
5174
+ Examples:
5175
+ code val formatted
5176
+ %+d 1 '+1'
5177
+ %4i 32 ' 32'
5178
+ %04i 32 '0032'
5179
+ %x 255 'ff'
5180
+ %.2f 0.125 '0.13'
5181
+ %'f 1000 '1,000'
5182
+ */
5183
+
5184
+ // Usage: format(formatString, [values])
5185
+ // Tip: When reusing the same format many times, use formatter() for 5x - 10x better performance
5186
+ //
5187
+ function format(fmt) {
5188
+ var fn = formatter(fmt);
5189
+ var str = fn.apply(null, Array.prototype.slice.call(arguments, 1));
5190
+ return str;
5191
+ }
5192
+
5193
+ function formatValue(val, matches) {
5194
+ var flags = matches[1];
5195
+ var padding = matches[2];
5196
+ var decimals = matches[3] ? parseInt(matches[3].substr(1)) : void 0;
5197
+ var type = matches[4];
5198
+ var isString = type == 's',
5199
+ isHex = type == 'x' || type == 'X',
5200
+ isInt = type == 'd' || type == 'i',
5201
+ isFloat = type == 'f',
5202
+ isNumber = !isString;
5203
+
5204
+ var sign = "",
5205
+ padDigits = 0,
5206
+ isZero = false,
5207
+ isNeg = false;
5208
+
5209
+ var str, padChar, padStr;
5210
+ if (isString) {
5211
+ str = String(val);
5212
+ }
5213
+ else if (isHex) {
5214
+ str = val.toString(16);
5215
+ if (type == 'X')
5216
+ str = str.toUpperCase();
5217
+ }
5218
+ else if (isNumber) {
5219
+ // str = formatNumberForDisplay(val, isInt ? 0 : decimals);
5220
+ str = numToStr(val, decimals);
5221
+ if (str[0] == '-') {
5222
+ isNeg = true;
5223
+ str = str.substr(1);
5224
+ }
5225
+ isZero = parseFloat(str) == 0;
5226
+ if (flags.indexOf("'") != -1 || flags.indexOf(',') != -1) {
5227
+ str = addThousandsSep(str);
5228
+ }
5229
+ if (!isZero) { // BUG: sign is added when num rounds to 0
5230
+ if (isNeg) {
5231
+ sign = "\u2212"; // U+2212
5232
+ } else if (flags.indexOf('+') != -1) {
5233
+ sign = '+';
5234
+ }
5235
+ }
5236
+ }
5237
+
5238
+ if (padding) {
5239
+ var strLen = str.length + sign.length;
5240
+ var minWidth = parseInt(padding, 10);
5241
+ if (strLen < minWidth) {
5242
+ padDigits = minWidth - strLen;
5243
+ padChar = flags.indexOf('0') == -1 ? ' ' : '0';
5244
+ padStr = repeatString(padChar, padDigits);
5245
+ }
5246
+ }
5247
+
5248
+ if (padDigits == 0) {
5249
+ str = sign + str;
5250
+ } else if (padChar == '0') {
5251
+ str = sign + padStr + str;
5252
+ } else {
5253
+ str = padStr + sign + str;
5254
+ }
5255
+ return str;
5256
+ }
5257
+
5258
+ // Get a function for interpolating formatted values into a string.
5259
+ function formatter(fmt) {
5260
+ var codeRxp = /%([\',+0]*)([1-9]?)((?:\.[1-9])?)([sdifxX%])/g;
5261
+ var literals = [],
5262
+ formatCodes = [],
5263
+ startIdx = 0,
5264
+ prefix = "",
5265
+ matches = codeRxp.exec(fmt),
5266
+ literal;
5267
+
5268
+ while (matches) {
5269
+ literal = fmt.substring(startIdx, codeRxp.lastIndex - matches[0].length);
5270
+ if (matches[0] == '%%') {
5271
+ prefix += literal + '%';
5272
+ } else {
5273
+ literals.push(prefix + literal);
5274
+ prefix = '';
5275
+ formatCodes.push(matches);
5276
+ }
5277
+ startIdx = codeRxp.lastIndex;
5278
+ matches = codeRxp.exec(fmt);
5279
+ }
5280
+ literals.push(prefix + fmt.substr(startIdx));
5281
+
5282
+ return function() {
5283
+ var str = literals[0],
5284
+ n = arguments.length;
5285
+ if (n != formatCodes.length) {
5286
+ error("[format()] Data does not match format string; format:", fmt, "data:", arguments);
5287
+ }
5288
+ for (var i=0; i<n; i++) {
5289
+ str += formatValue(arguments[i], formatCodes[i]) + literals[i+1];
5290
+ }
5291
+ return str;
5292
+ };
5293
+ }
5294
+
5295
+ function wildcardToRegExp(name) {
5296
+ var rxp = name.split('*').map(function(str) {
5297
+ return regexEscape(str);
5298
+ }).join('.*');
5299
+ return new RegExp('^' + rxp + '$');
5300
+ }
5301
+
5302
+ function createBuffer(arg, arg2) {
5303
+ if (isInteger(arg)) {
5304
+ return Buffer.allocUnsafe ? Buffer.allocUnsafe(arg) : new Buffer(arg);
5305
+ } else {
5306
+ // check allocUnsafe to make sure Buffer.from() will accept strings (it didn't before Node v5.10)
5307
+ return Buffer.from && Buffer.allocUnsafe ? Buffer.from(arg, arg2) : new Buffer(arg, arg2);
5308
+ }
5309
+ }
5310
+
5311
+ function expandoBuffer(constructor, rate) {
5312
+ var capacity = 0,
5313
+ k = rate >= 1 ? rate : 1.2,
5314
+ buf;
5315
+ return function(size) {
5316
+ if (size > capacity) {
5317
+ capacity = Math.ceil(size * k);
5318
+ buf = constructor ? new constructor(capacity) : createBuffer(capacity);
5319
+ }
5320
+ return buf;
5321
+ };
5322
+ }
5323
+
5324
+ function copyElements(src, i, dest, j, n, rev) {
5325
+ var same = src == dest || src.buffer && src.buffer == dest.buffer;
5326
+ var inc = 1,
5327
+ offs = 0,
5328
+ k;
5329
+ if (rev) {
5330
+ if (same) error('copy error');
5331
+ inc = -1;
5332
+ offs = n - 1;
5333
+ }
5334
+ if (same && j > i) {
5335
+ for (k=n-1; k>=0; k--) {
5336
+ dest[j + k] = src[i + k];
5337
+ }
5338
+ } else {
5339
+ for (k=0; k<n; k++, offs += inc) {
5340
+ dest[k + j] = src[i + offs];
5341
+ }
5342
+ }
5343
+ }
5344
+
5345
+ function extendBuffer(src, newLen, copyLen) {
5346
+ var len = Math.max(src.length, newLen);
5347
+ var n = copyLen || src.length;
5348
+ var dest = new src.constructor(len);
5349
+ copyElements(src, 0, dest, 0, n);
5350
+ return dest;
5351
+ }
5352
+
5353
+ function mergeNames(name1, name2) {
5354
+ var merged;
5355
+ if (name1 && name2) {
5356
+ merged = findStringPrefix(name1, name2).replace(/[-_]$/, '');
5357
+ }
5358
+ return merged || '';
5359
+ }
5360
+
5361
+ function findStringPrefix(a, b) {
5362
+ var i = 0;
5363
+ for (var n=a.length; i<n; i++) {
5364
+ if (a[i] !== b[i]) break;
5365
+ }
5366
+ return a.substr(0, i);
5367
+ }
5368
+
5369
+ function parsePercent(o) {
5370
+ var str = String(o);
5371
+ var isPct = str.indexOf('%') > 0;
5372
+ var pct;
5373
+ if (isPct) {
5374
+ pct = Number(str.replace('%', '')) / 100;
5375
+ } else {
5376
+ pct = Number(str);
4031
5377
  }
5378
+ if (!(pct >= 0 && pct <= 1)) {
5379
+ stop(format("Invalid percentage: %s", str));
5380
+ }
5381
+ return pct;
5382
+ }
4032
5383
 
4033
- function updateSelectionHighlight() {
4034
- El.findAll('.nav-menu-item').forEach(function(el) {
4035
- el = El(el);
4036
- el.classed('selected', el.attr('data-name') == _editMode);
4037
- });
5384
+ function formatVersionedName(name, i) {
5385
+ var suffix = String(i);
5386
+ if (/[0-9]$/.test(name)) {
5387
+ suffix = '-' + suffix;
4038
5388
  }
5389
+ return name + suffix;
4039
5390
  }
4040
5391
 
4041
- function Model(gui) {
4042
- var self = new internal.Catalog();
4043
- var deleteLayer = self.deleteLayer;
4044
- utils.extend(self, EventDispatcher.prototype);
5392
+ function uniqifyNames(names, formatter) {
5393
+ var counts = countValues(names),
5394
+ format = formatter || formatVersionedName,
5395
+ names2 = [];
4045
5396
 
4046
- // override Catalog method (so -drop command will work in web console)
4047
- self.deleteLayer = function(lyr, dataset) {
4048
- var active, flags;
4049
- deleteLayer.call(self, lyr, dataset);
4050
- if (self.isEmpty()) {
4051
- // refresh browser if deleted layer was the last layer
4052
- window.location.href = window.location.href.toString();
4053
- } else {
4054
- // trigger event to update layer list and, if needed, the map view
4055
- flags = {};
4056
- active = self.getActiveLayer();
4057
- if (active.layer != lyr) {
4058
- flags.select = true;
4059
- }
4060
- internal.cleanupArcs(active.dataset);
4061
- if (internal.layerHasPaths(lyr)) {
4062
- flags.arc_count = true; // looks like a kludge, try to remove
5397
+ names.forEach(function(name) {
5398
+ var i = 0,
5399
+ candidate = name,
5400
+ versionedName;
5401
+ while (
5402
+ names2.indexOf(candidate) > -1 || // candidate name has already been used
5403
+ candidate == name && counts[candidate] > 1 || // duplicate unversioned names
5404
+ candidate != name && counts[candidate] > 0) { // versioned name is a preexisting name
5405
+ i++;
5406
+ versionedName = format(name, i);
5407
+ if (!versionedName || versionedName == candidate) {
5408
+ throw new Error("Naming error"); // catch buggy versioning function
4063
5409
  }
4064
- self.updated(flags, active.layer, active.dataset);
5410
+ candidate = versionedName;
4065
5411
  }
4066
- };
5412
+ names2.push(candidate);
5413
+ });
5414
+ return names2;
5415
+ }
4067
5416
 
4068
- self.updated = function(flags) {
4069
- var targets = self.getDefaultTargets();
4070
- var active = self.getActiveLayer();
4071
- if (internal.countTargetLayers(targets) > 1) {
4072
- self.setDefaultTarget([active.layer], active.dataset);
4073
- gui.session.setTargetLayer(active.layer); // add -target command to target single layer
4074
- }
4075
- if (flags.select) {
4076
- self.dispatchEvent('select', active);
4077
- }
4078
- self.dispatchEvent('update', utils.extend({flags: flags}, active));
4079
- };
4080
5417
 
4081
- self.selectLayer = function(lyr, dataset) {
4082
- if (self.getActiveLayer().layer == lyr) return;
4083
- self.setDefaultTarget([lyr], dataset);
4084
- self.updated({select: true});
4085
- gui.session.setTargetLayer(lyr);
4086
- };
5418
+ // Assume: @raw is string, undefined or null
5419
+ function parseString(raw) {
5420
+ return raw ? raw : "";
5421
+ }
4087
5422
 
4088
- self.selectNextLayer = function() {
4089
- var next = self.findNextLayer(self.getActiveLayer().layer);
4090
- if (next) self.selectLayer(next.layer, next.dataset);
4091
- };
5423
+ // Assume: @raw is string, undefined or null
5424
+ // Use null instead of NaN for unparsable values
5425
+ // (in part because if NaN is used, empty strings get converted to "NaN"
5426
+ // when re-exported).
5427
+ function parseNumber(raw) {
5428
+ return parseToNum(raw, cleanNumericString);
5429
+ }
4092
5430
 
4093
- self.selectPrevLayer = function() {
4094
- var prev = self.findPrevLayer(self.getActiveLayer().layer);
4095
- if (prev) self.selectLayer(prev.layer, prev.dataset);
4096
- };
5431
+ function parseIntlNumber(raw) {
5432
+ return parseToNum(raw, convertIntlNumString);
5433
+ }
4097
5434
 
4098
- return self;
5435
+ function parseToNum(raw, clean) {
5436
+ var str = String(raw).trim();
5437
+ var parsed = str ? Number(clean(str)) : NaN;
5438
+ return isNaN(parsed) ? null : parsed;
5439
+ }
5440
+
5441
+ // Remove comma separators from strings
5442
+ function cleanNumericString(str) {
5443
+ return (str.indexOf(',') > 0) ? str.replace(/,([0-9]{3})/g, '$1') : str;
5444
+ }
5445
+
5446
+ function convertIntlNumString(str) {
5447
+ str = str.replace(/[ .]([0-9]{3})/g, '$1');
5448
+ return str.replace(',', '.');
5449
+ }
5450
+
5451
+ function trimQuotes(raw) {
5452
+ var len = raw.length, first, last;
5453
+ if (len >= 2) {
5454
+ first = raw.charAt(0);
5455
+ last = raw.charAt(len-1);
5456
+ if (first == '"' && last == '"' && !raw.includes('","') ||
5457
+ first == "'" && last == "'" && !raw.includes("','")) {
5458
+ return raw.substr(1, len-2);
5459
+ }
5460
+ }
5461
+ return raw;
5462
+ }
5463
+
5464
+ function absArcId(arcId) {
5465
+ return arcId >= 0 ? arcId : ~arcId;
5466
+ }
5467
+
5468
+ function calcArcBounds(xx, yy, start, len) {
5469
+ var i = start | 0,
5470
+ n = isNaN(len) ? xx.length - i : len + i,
5471
+ x, y, xmin, ymin, xmax, ymax;
5472
+ if (n > 0) {
5473
+ xmin = xmax = xx[i];
5474
+ ymin = ymax = yy[i];
5475
+ }
5476
+ for (i++; i<n; i++) {
5477
+ x = xx[i];
5478
+ y = yy[i];
5479
+ if (x < xmin) xmin = x;
5480
+ if (x > xmax) xmax = x;
5481
+ if (y < ymin) ymin = y;
5482
+ if (y > ymax) ymax = y;
5483
+ }
5484
+ return [xmin, ymin, xmax, ymax];
5485
+ }
5486
+
5487
+ function deleteVertex(arcs, i) {
5488
+ var data = arcs.getVertexData();
5489
+ var nn = data.nn;
5490
+ var n = data.xx.length;
5491
+ // avoid re-allocating memory
5492
+ var xx2 = new Float64Array(data.xx.buffer, 0, n-1);
5493
+ var yy2 = new Float64Array(data.yy.buffer, 0, n-1);
5494
+ var count = 0;
5495
+ var found = false;
5496
+ for (var j=0; j<nn.length; j++) {
5497
+ count += nn[j];
5498
+ if (count >= i && !found) { // TODO: confirm this
5499
+ nn[j] = nn[j] - 1;
5500
+ found = true;
5501
+ }
5502
+ }
5503
+ utils.copyElements(data.xx, 0, xx2, 0, i);
5504
+ utils.copyElements(data.yy, 0, yy2, 0, i);
5505
+ utils.copyElements(data.xx, i+1, xx2, i, n-i-1);
5506
+ utils.copyElements(data.yy, i+1, yy2, i, n-i-1);
5507
+ arcs.updateVertexData(nn, xx2, yy2, null);
5508
+ }
5509
+
5510
+ function insertVertex(arcs, i, p) {
5511
+ // TODO: add extra bytes to the buffers, to reduce new memory allocation
5512
+ var data = arcs.getVertexData();
5513
+ var nn = data.nn;
5514
+ var n = data.xx.length;
5515
+ var count = 0;
5516
+ var found = false;
5517
+ var xx2, yy2;
5518
+ // avoid re-allocating memory on each insertion
5519
+ if (data.xx.buffer.byteLength >= data.xx.length * 8 + 8) {
5520
+ xx2 = new Float64Array(data.xx.buffer, 0, n+1);
5521
+ yy2 = new Float64Array(data.yy.buffer, 0, n+1);
5522
+ } else {
5523
+ xx2 = new Float64Array(new ArrayBuffer((n + 20) * 8), 0, n+1);
5524
+ yy2 = new Float64Array(new ArrayBuffer((n + 20) * 8), 0, n+1);
5525
+ }
5526
+ for (var j=0; j<nn.length; j++) {
5527
+ count += nn[j];
5528
+ if (count >= i && !found) { // TODO: confirm this
5529
+ nn[j] = nn[j] + 1;
5530
+ found = true;
5531
+ }
5532
+ }
5533
+ utils.copyElements(data.xx, 0, xx2, 0, i);
5534
+ utils.copyElements(data.yy, 0, yy2, 0, i);
5535
+ utils.copyElements(data.xx, i, xx2, i+1, n-i);
5536
+ utils.copyElements(data.yy, i, yy2, i+1, n-i);
5537
+ xx2[i] = p[0];
5538
+ yy2[i] = p[1];
5539
+ arcs.updateVertexData(nn, xx2, yy2, null);
4099
5540
  }
4100
5541
 
4101
- function getShapeHitTest(displayLayer, ext) {
5542
+ function getShapeHitTest(displayLayer, ext, interactionMode) {
4102
5543
  var geoType = displayLayer.layer.geometry_type;
4103
5544
  var test;
4104
5545
  if (geoType == 'point' && displayLayer.style.type == 'styled') {
4105
5546
  test = getGraduatedCircleTest(getRadiusFunction(displayLayer.style));
4106
5547
  } else if (geoType == 'point') {
4107
5548
  test = pointTest;
5549
+ } else if (interactionMode == 'vertices') {
5550
+ test = vertexTest;
4108
5551
  } else if (geoType == 'polyline') {
4109
5552
  test = polylineTest;
4110
5553
  } else if (geoType == 'polygon') {
4111
5554
  test = polygonTest;
4112
5555
  } else {
4113
- error("Unexpected geometry type:", geoType);
5556
+ error$1("Unexpected geometry type:", geoType);
4114
5557
  }
4115
5558
  return test;
4116
5559
 
@@ -4130,14 +5573,14 @@
4130
5573
  }
4131
5574
 
4132
5575
  function polygonTest(x, y) {
4133
- var maxDist = getZoomAdjustedHitBuffer(5, 1),
5576
+ var maxDist = getZoomAdjustedHitBuffer(10, 1),
4134
5577
  cands = findHitCandidates(x, y, maxDist),
4135
5578
  hits = [],
4136
5579
  cand, hitId;
4137
5580
  for (var i=0; i<cands.length; i++) {
4138
5581
  cand = cands[i];
4139
5582
  if (geom.testPointInPolygon(x, y, cand.shape, displayLayer.arcs)) {
4140
- hits.push(cand.id);
5583
+ hits.push(cand);
4141
5584
  }
4142
5585
  }
4143
5586
  if (cands.length > 0 && hits.length === 0) {
@@ -4145,7 +5588,9 @@
4145
5588
  sortByDistance(x, y, cands, displayLayer.arcs);
4146
5589
  hits = pickNearestCandidates(cands, 0, maxDist);
4147
5590
  }
4148
- return hits;
5591
+ return {
5592
+ ids: utils$1.pluck(hits, 'id')
5593
+ };
4149
5594
  }
4150
5595
 
4151
5596
  function pickNearestCandidates(sorted, bufDist, maxDist) {
@@ -4160,24 +5605,43 @@
4160
5605
  } else if (cand.dist - minDist > bufDist) {
4161
5606
  break;
4162
5607
  }
4163
- hits.push(cand.id);
5608
+ hits.push(cand);
4164
5609
  }
4165
5610
  return hits;
4166
5611
  }
4167
5612
 
5613
+ function vertexTest(x, y) {
5614
+ var maxDist = getZoomAdjustedHitBuffer(20, 2),
5615
+ bufDist = getZoomAdjustedHitBuffer(0.05), // tiny threshold for hitting almost-identical lines
5616
+ cands = findHitCandidates(x, y, maxDist);
5617
+ sortByDistance(x, y, cands, displayLayer.arcs);
5618
+ cands = pickNearestCandidates(cands, bufDist, maxDist);
5619
+ var arcs = cands.map(function(cand) { return absArcId(cand.info.arcId); });
5620
+ return {
5621
+ arcs: utils$1.uniq(arcs),
5622
+ ids: utils$1.pluck(cands, 'id')
5623
+ };
5624
+ }
5625
+
4168
5626
  function polylineTest(x, y) {
4169
5627
  var maxDist = getZoomAdjustedHitBuffer(15, 2),
4170
5628
  bufDist = getZoomAdjustedHitBuffer(0.05), // tiny threshold for hitting almost-identical lines
4171
5629
  cands = findHitCandidates(x, y, maxDist);
4172
5630
  sortByDistance(x, y, cands, displayLayer.arcs);
4173
- return pickNearestCandidates(cands, bufDist, maxDist);
5631
+ cands = pickNearestCandidates(cands, bufDist, maxDist);
5632
+ return {
5633
+ ids: utils$1.pluck(cands, 'id')
5634
+ };
4174
5635
  }
4175
5636
 
4176
5637
  function sortByDistance(x, y, cands, arcs) {
5638
+ var cand;
4177
5639
  for (var i=0; i<cands.length; i++) {
4178
- cands[i].dist = geom.getPointToShapeDistance(x, y, cands[i].shape, arcs);
5640
+ cand = cands[i];
5641
+ cand.info = geom.getPointToShapeInfo(x, y, cands[i].shape, arcs);
5642
+ cand.dist = cand.info.distance;
4179
5643
  }
4180
- utils.sortOn(cands, 'dist');
5644
+ utils$1.sortOn(cands, 'dist');
4181
5645
  }
4182
5646
 
4183
5647
  function pointTest(x, y) {
@@ -4203,7 +5667,9 @@
4203
5667
  }
4204
5668
  });
4205
5669
  // console.log(hitThreshold, bullseye);
4206
- return utils.uniq(hits); // multipoint features can register multiple hits
5670
+ return {
5671
+ ids: utils$1.uniq(hits) // multipoint features can register multiple hits
5672
+ };
4207
5673
  }
4208
5674
 
4209
5675
  function getRadiusFunction(style) {
@@ -4257,7 +5723,9 @@
4257
5723
  hits.push(id);
4258
5724
  }
4259
5725
  });
4260
- return hits;
5726
+ return {
5727
+ ids: hits
5728
+ };
4261
5729
  };
4262
5730
  }
4263
5731
 
@@ -4394,25 +5862,23 @@
4394
5862
 
4395
5863
  }
4396
5864
 
4397
- function getPointerHitTest(mapLayer, ext) {
5865
+ function getPointerHitTest(mapLayer, ext, interactionMode) {
4398
5866
  var shapeTest, svgTest, targetLayer;
4399
5867
  if (!mapLayer || !internal.layerHasGeometry(mapLayer.layer)) {
4400
- return null;
5868
+ return function() {return {ids: []};};
4401
5869
  }
4402
- shapeTest = getShapeHitTest(mapLayer, ext);
5870
+ shapeTest = getShapeHitTest(mapLayer, ext, interactionMode);
4403
5871
  svgTest = getSvgHitTest(mapLayer);
4404
5872
 
4405
5873
  // e: pointer event
4406
5874
  return function(e) {
4407
5875
  var p = ext.translatePixelCoords(e.x, e.y);
4408
- var data = {
4409
- ids: shapeTest(p[0], p[1]) || []
4410
- };
5876
+ var data = shapeTest(p[0], p[1]) || {ids:[]};
4411
5877
  var svgData = svgTest(e); // null or a data object
4412
5878
  if (svgData) { // mouse is over an SVG symbol
4413
- utils.extend(data, svgData);
5879
+ utils$1.extend(data, svgData);
4414
5880
  // placing symbol id in front of any other hits
4415
- data.ids = utils.uniq([svgData.targetId].concat(data.ids));
5881
+ data.ids = utils$1.uniq([svgData.targetId].concat(data.ids));
4416
5882
  }
4417
5883
  data.id = data.ids.length > 0 ? data.ids[0] : -1;
4418
5884
  return data;
@@ -4467,22 +5933,25 @@
4467
5933
  }, !!'capture'); // preempt the layer control's arrow key handler
4468
5934
 
4469
5935
  self.setLayer = function(mapLayer) {
4470
- hitTest = getPointerHitTest(mapLayer, ext);
4471
- if (!hitTest) {
4472
- hitTest = function() {return {ids: []};};
4473
- }
4474
5936
  targetLayer = mapLayer;
5937
+ updateHitTest();
4475
5938
  };
4476
5939
 
5940
+ function updateHitTest() {
5941
+ hitTest = getPointerHitTest(targetLayer, ext, interactionMode);
5942
+ }
5943
+
4477
5944
  function turnOn(mode) {
4478
5945
  interactionMode = mode;
4479
5946
  active = true;
5947
+ updateHitTest();
4480
5948
  }
4481
5949
 
4482
5950
  function turnOff() {
4483
5951
  if (active) {
4484
5952
  updateSelectionState(null); // no hit data, no event
4485
5953
  active = false;
5954
+ hitTest = null;
4486
5955
  }
4487
5956
  }
4488
5957
 
@@ -4513,8 +5982,8 @@
4513
5982
 
4514
5983
  self.addSelectionIds = function(ids) {
4515
5984
  turnOn('selection');
4516
- selectionIds = utils.uniq(selectionIds.concat(ids));
4517
- ids = utils.uniq(storedData.ids.concat(ids));
5985
+ selectionIds = utils$1.uniq(selectionIds.concat(ids));
5986
+ ids = utils$1.uniq(storedData.ids.concat(ids));
4518
5987
  updateSelectionState({ids: ids});
4519
5988
  };
4520
5989
 
@@ -4526,6 +5995,20 @@
4526
5995
  }
4527
5996
  };
4528
5997
 
5998
+ self.setHoverVertex = function(p) {
5999
+ var p2 = storedData.hit_coordinates;
6000
+ if (!active || !p) return;
6001
+ if (p2 && p2[0] == p[0] && p2[1] == p[1]) return;
6002
+ storedData.hit_coordinates = p;
6003
+ triggerHitEvent('change');
6004
+ };
6005
+
6006
+ self.clearVertexOverlay = function() {
6007
+ if (!storedData.hit_coordinates) return;
6008
+ delete storedData.hit_coordinates;
6009
+ triggerHitEvent('change');
6010
+ };
6011
+
4529
6012
  self.clearSelection = function() {
4530
6013
  updateSelectionState(null);
4531
6014
  };
@@ -4673,7 +6156,7 @@
4673
6156
 
4674
6157
  function toggleId(id, ids) {
4675
6158
  if (ids.indexOf(id) > -1) {
4676
- return utils.difference(ids, [id]);
6159
+ return utils$1.difference(ids, [id]);
4677
6160
  }
4678
6161
  return [id].concat(ids);
4679
6162
  }
@@ -4725,9 +6208,9 @@
4725
6208
  // d: event data (may be a pointer event object, an ordinary object or null)
4726
6209
  function triggerHitEvent(type, d) {
4727
6210
  // Merge stored hit data into the event data
4728
- var eventData = utils.extend({mode: interactionMode}, d || {}, storedData);
6211
+ var eventData = utils$1.extend({mode: interactionMode}, d || {}, storedData);
4729
6212
  if (transientIds.length) {
4730
- eventData.ids = utils.uniq(transientIds.concat(eventData.ids || []));
6213
+ eventData.ids = utils$1.uniq(transientIds.concat(eventData.ids || []));
4731
6214
  }
4732
6215
  self.dispatchEvent(type, eventData);
4733
6216
  }
@@ -4861,7 +6344,7 @@
4861
6344
  }
4862
6345
  }
4863
6346
 
4864
- utils.inherit(Timer, EventDispatcher);
6347
+ utils$1.inherit(Timer, EventDispatcher);
4865
6348
 
4866
6349
  function Tween(ease) {
4867
6350
  var self = this,
@@ -4883,7 +6366,7 @@
4883
6366
  }
4884
6367
  }
4885
6368
 
4886
- utils.inherit(Tween, EventDispatcher);
6369
+ utils$1.inherit(Tween, EventDispatcher);
4887
6370
 
4888
6371
  Tween.sineInOut = function(n) {
4889
6372
  return 0.5 - Math.cos(n * Math.PI) / 2;
@@ -4951,7 +6434,7 @@
4951
6434
  update();
4952
6435
  }
4953
6436
 
4954
- utils.inherit(ElementPosition, EventDispatcher);
6437
+ utils$1.inherit(ElementPosition, EventDispatcher);
4955
6438
 
4956
6439
  function MouseWheelDirection() {
4957
6440
  var prevTime = 0;
@@ -5049,13 +6532,13 @@
5049
6532
  // Decelerate towards the end of the sustain interval (for smoother zooming)
5050
6533
  multiplier *= Tween.quadraticOut(1 - fadeFactor);
5051
6534
  }
5052
- obj = utils.extend({direction: wheelDirection, multiplier: multiplier}, mouse.mouseData());
6535
+ obj = utils$1.extend({direction: wheelDirection, multiplier: multiplier}, mouse.mouseData());
5053
6536
  self.dispatchEvent('mousewheel', obj);
5054
6537
  }
5055
6538
  }
5056
6539
  }
5057
6540
 
5058
- utils.inherit(MouseWheel, EventDispatcher);
6541
+ utils$1.inherit(MouseWheel, EventDispatcher);
5059
6542
 
5060
6543
 
5061
6544
  function MouseArea(element, pos) {
@@ -5109,7 +6592,7 @@
5109
6592
  };
5110
6593
 
5111
6594
  this.mouseData = function() {
5112
- return utils.extend({}, _prevEvt);
6595
+ return utils$1.extend({}, _prevEvt);
5113
6596
  };
5114
6597
 
5115
6598
  function onAreaDown(e) {
@@ -5168,7 +6651,7 @@
5168
6651
  dragX: evt.pageX - _downEvt.pageX,
5169
6652
  dragY: evt.pageY - _downEvt.pageY
5170
6653
  };
5171
- _self.dispatchEvent('drag', utils.extend(obj, evt));
6654
+ _self.dispatchEvent('drag', utils$1.extend(obj, evt));
5172
6655
  } else {
5173
6656
  _self.dispatchEvent('hover', evt);
5174
6657
  }
@@ -5200,7 +6683,7 @@
5200
6683
  }
5201
6684
  }
5202
6685
 
5203
- utils.inherit(MouseArea, EventDispatcher);
6686
+ utils$1.inherit(MouseArea, EventDispatcher);
5204
6687
 
5205
6688
  function initVariableClick(node, cb) {
5206
6689
  var downEvent = null;
@@ -5347,7 +6830,7 @@
5347
6830
  maxScale = 4,
5348
6831
  minTime = 100,
5349
6832
  maxTime = 800,
5350
- time = utils.clamp(clickTime || 200, minTime, maxTime),
6833
+ time = utils$1.clamp(clickTime || 200, minTime, maxTime),
5351
6834
  k = (time - minTime) / (maxTime - minTime),
5352
6835
  scale = minScale + k * (maxScale - minScale);
5353
6836
  return 1 + scale * zoomScaleMultiplier;
@@ -5574,7 +7057,7 @@
5574
7057
  rows = 0;
5575
7058
  // self.hide(); // clean up if panel is already open
5576
7059
  el.empty(); // clean up if panel is already open
5577
- utils.forEachProperty(rec, function(v, k) {
7060
+ utils$1.forEachProperty(rec, function(v, k) {
5578
7061
  var type;
5579
7062
  // missing GeoJSON fields are set to undefined on import; skip these
5580
7063
  if (v !== undefined) {
@@ -5602,7 +7085,7 @@
5602
7085
  // Some individual features can have undefined values for some or all of
5603
7086
  // their data properties (properties are set to undefined when an input JSON file
5604
7087
  // has inconsistent fields, or after force-merging layers with inconsistent fields).
5605
- el.html(utils.format('<div class="note">This %s is missing attribute data.</div>',
7088
+ el.html(utils$1.format('<div class="note">This %s is missing attribute data.</div>',
5606
7089
  table && table.getFields().length > 0 ? 'feature': 'layer'));
5607
7090
  }
5608
7091
  }
@@ -5613,7 +7096,7 @@
5613
7096
  var str = formatInspectorValue(val, type);
5614
7097
  var cell = El('tr')
5615
7098
  .appendTo(table)
5616
- .html(utils.format(rowHtml, key, utils.htmlEscape(str)))
7099
+ .html(utils$1.format(rowHtml, key, utils$1.htmlEscape(str)))
5617
7100
  .findChild('.value');
5618
7101
  setFieldClass(cell, val, type);
5619
7102
  if (editable) {
@@ -5622,7 +7105,7 @@
5622
7105
  }
5623
7106
 
5624
7107
  function setFieldClass(el, val, type) {
5625
- var isNum = type ? type == 'number' : utils.isNumber(val);
7108
+ var isNum = type ? type == 'number' : utils$1.isNumber(val);
5626
7109
  var isNully = val === undefined || val === null || val !== val;
5627
7110
  var isEmpty = val === '';
5628
7111
  el.classed('num-field', isNum);
@@ -5662,7 +7145,7 @@
5662
7145
  function formatInspectorValue(val, type) {
5663
7146
  var str;
5664
7147
  if (type == 'date') {
5665
- str = utils.formatDateISO(val);
7148
+ str = utils$1.formatDateISO(val);
5666
7149
  } else if (type == 'object') {
5667
7150
  str = val ? JSON.stringify(val) : "";
5668
7151
  } else {
@@ -5861,55 +7344,113 @@
5861
7344
  // handle either numeric strings or numbers in fields
5862
7345
  function applyDelta(rec, key, delta) {
5863
7346
  var currVal = rec[key];
5864
- var isString = utils.isString(currVal);
7347
+ var isString = utils$1.isString(currVal);
5865
7348
  var newVal = (+currVal + delta) || 0;
5866
7349
  rec[key] = isString ? String(newVal) : newVal;
5867
7350
  }
5868
7351
 
5869
- var snapVerticesToPoint = internal.snapVerticesToPoint;
5870
-
5871
- function getDisplayCoordsById(id, layer, ext) {
5872
- var coords = getPointCoordsById(id, layer);
5873
- return ext.translateCoords(coords[0], coords[1]);
5874
- }
7352
+ function initLabelDragging(gui, ext, hit) {
7353
+ var downEvt;
7354
+ var activeId;
7355
+ var activeRecord;
5875
7356
 
5876
- function getPointCoordsById(id, layer) {
5877
- var coords = layer && layer.geometry_type == 'point' && layer.shapes[id];
5878
- if (!coords || coords.length != 1) {
5879
- return null;
7357
+ function active(e) {
7358
+ return e.id > -1 && gui.interaction.getMode() == 'labels';
5880
7359
  }
5881
- return coords[0];
5882
- }
5883
7360
 
5884
- function translateDeltaDisplayCoords(dx, dy, ext) {
5885
- var a = ext.translatePixelCoords(0, 0);
5886
- var b = ext.translatePixelCoords(dx, dy);
5887
- return [b[0] - a[0], b[1] - a[1]];
5888
- }
7361
+ hit.on('dragstart', function(e) {
7362
+ if (!active(e)) return;
7363
+ var textNode = getTextTarget3(e);
7364
+ var table = hit.getTargetDataTable();
7365
+ if (!textNode || !table) return false;
7366
+ activeId = e.id;
7367
+ activeRecord = getLabelRecordById(activeId);
7368
+ downEvt = e;
7369
+ gui.dispatchEvent('label_dragstart', {FID: activeId});
7370
+ });
5889
7371
 
7372
+ hit.on('drag', function(e) {
7373
+ if (!active(e)) return;
7374
+ if (e.id != activeId) {
7375
+ error$1("Mismatched hit ids:", e.id, activeId);
7376
+ }
7377
+ var scale = ext.getSymbolScale() || 1;
7378
+ var textNode;
7379
+ applyDelta(activeRecord, 'dx', e.dx / scale);
7380
+ applyDelta(activeRecord, 'dy', e.dy / scale);
7381
+ textNode = getTextTarget3(e);
7382
+ if (!isMultilineLabel(textNode)) {
7383
+ // update anchor position of single-line labels based on label position
7384
+ // relative to anchor point, for better placement when eventual display font is
7385
+ // different from mapshaper's font.
7386
+ autoUpdateTextAnchor(textNode, activeRecord, getDisplayCoordsById(activeId, hit.getHitTarget().layer, ext));
7387
+ }
7388
+ // updateSymbol(targetTextNode, activeRecord);
7389
+ updateSymbol2(textNode, activeRecord, activeId);
7390
+ });
5890
7391
 
5891
- function InteractiveEditor(gui, ext, hit) {
5892
- // var targetTextNode; // text node currently being dragged
5893
- var dragging = false;
5894
- var activeRecord;
5895
- var activeId = -1;
5896
- var self = new EventDispatcher();
5897
- var activeVertexIds = null; // for vertex dragging
7392
+ hit.on('dragend', function(e) {
7393
+ if (!active(e)) return;
7394
+ gui.dispatchEvent('label_dragend', {FID: e.id});
7395
+ activeId = -1;
7396
+ activeRecord = null;
7397
+ downEvt = null;
7398
+ });
5898
7399
 
5899
- initDragging();
7400
+ function getDisplayCoordsById(id, layer, ext) {
7401
+ var coords = getPointCoordsById(id, layer);
7402
+ return ext.translateCoords(coords[0], coords[1]);
7403
+ }
5900
7404
 
5901
- return self;
7405
+ function getPointCoordsById(id, layer) {
7406
+ var coords = layer && layer.geometry_type == 'point' && layer.shapes[id];
7407
+ if (!coords || coords.length != 1) {
7408
+ return null;
7409
+ }
7410
+ return coords[0];
7411
+ }
7412
+
7413
+ function getTextTarget3(e) {
7414
+ if (e.id > -1 === false || !e.container) return null;
7415
+ return getSymbolNodeById(e.id, e.container);
7416
+ }
7417
+
7418
+ function getSymbolNodeById(id, parent) {
7419
+ // TODO: optimize selector
7420
+ var sel = '[data-id="' + id + '"]';
7421
+ return parent.querySelector(sel);
7422
+ }
5902
7423
 
5903
- function labelEditingEnabled() {
5904
- return gui.interaction && gui.interaction.getMode() == 'labels' ? true : false;
7424
+ function getTextTarget2(e) {
7425
+ var el = e && e.targetSymbol || null;
7426
+ if (el && el.tagName == 'tspan') {
7427
+ el = el.parentNode;
7428
+ }
7429
+ return el && el.tagName == 'text' ? el : null;
5905
7430
  }
5906
7431
 
5907
- function locationEditingEnabled() {
5908
- return gui.interaction && gui.interaction.getMode() == 'location' ? true : false;
7432
+ function getTextTarget(e) {
7433
+ var el = e.target;
7434
+ if (el.tagName == 'tspan') {
7435
+ el = el.parentNode;
7436
+ }
7437
+ return el.tagName == 'text' ? el : null;
5909
7438
  }
5910
7439
 
5911
- function vertexEditingEnabled() {
5912
- return gui.interaction && gui.interaction.getMode() == 'vertices' ? true : false;
7440
+ function getLabelRecordById(id) {
7441
+ var table = hit.getTargetDataTable();
7442
+ if (id >= 0 === false || !table) return null;
7443
+ // add dx and dy properties, if not available
7444
+ if (!table.fieldExists('dx')) {
7445
+ table.addField('dx', 0);
7446
+ }
7447
+ if (!table.fieldExists('dy')) {
7448
+ table.addField('dy', 0);
7449
+ }
7450
+ if (!table.fieldExists('text-anchor')) {
7451
+ table.addField('text-anchor', '');
7452
+ }
7453
+ return table.getRecordAt(id);
5913
7454
  }
5914
7455
 
5915
7456
  // update symbol by setting attributes
@@ -5936,249 +7477,229 @@
5936
7477
  gui.dispatchEvent('popup-needs-refresh');
5937
7478
  return node2;
5938
7479
  }
7480
+ }
5939
7481
 
5940
- function initDragging() {
5941
- var downEvt;
5942
- var eventPriority = 1;
5943
-
5944
- // inspector and label editing aren't fully synced - stop editing if inspector opens
5945
- // gui.on('inspector_on', function() {
5946
- // stopEditing();
5947
- // });
5948
-
5949
- gui.on('interaction_mode_change', function(e) {
5950
- if (e.mode != 'labels') {
5951
- stopDragging();
5952
- }
5953
- gui.undo.clear(); // TODO: put this elsewhere?
5954
- });
7482
+ function initPointDragging(gui, ext, hit) {
5955
7483
 
5956
- // down event on svg
5957
- // a: off text
5958
- // -> stop editing
5959
- // b: on text
5960
- // 1: not editing -> nop
5961
- // 2: on selected text -> start dragging
5962
- // 3: on other text -> stop dragging, select new text
5963
-
5964
- hit.on('dragstart', function(e) {
5965
- if (e.id >= 0 === false) return;
5966
- if (labelEditingEnabled() && onLabelDragStart(e)) {
5967
- triggerGlobalEvent('label_dragstart', e);
5968
- startDragging();
5969
- } else if (locationEditingEnabled()) {
5970
- triggerGlobalEvent('symbol_dragstart', e);
5971
- startDragging();
5972
- } else if (vertexEditingEnabled()) {
5973
- onVertexDragStart(e);
5974
- triggerGlobalEvent('vertex_dragstart', e);
5975
- startDragging();
5976
- }
5977
- });
7484
+ function active(e) {
7485
+ return e.id > -1 && gui.interaction.getMode() == 'location';
7486
+ }
5978
7487
 
5979
- hit.on('drag', function(e) {
5980
- if (labelEditingEnabled()) {
5981
- onLabelDrag(e);
5982
- } else if (locationEditingEnabled()) {
5983
- onLocationDrag(e);
5984
- } else if (vertexEditingEnabled()) {
5985
- onVertexDrag(e);
5986
- }
5987
- });
7488
+ hit.on('dragstart', function(e) {
7489
+ if (!active(e)) return;
7490
+ gui.dispatchEvent('symbol_dragstart', {FID: e.id});
7491
+ });
5988
7492
 
5989
- hit.on('dragend', function(e) {
5990
- if (locationEditingEnabled()) {
5991
- triggerGlobalEvent('symbol_dragend', e);
5992
- stopDragging();
5993
- } else if (labelEditingEnabled()) {
5994
- triggerGlobalEvent('label_dragend', e);
5995
- stopDragging();
5996
- } else if (vertexEditingEnabled()) {
5997
- // kludge to get dataset to recalculate internal bounding boxes
5998
- hit.getHitTarget().arcs.transformPoints(function() {});
5999
- triggerGlobalEvent('vertex_dragend', e);
6000
- stopDragging();
6001
- }
6002
- });
7493
+ hit.on('drag', function(e) {
7494
+ if (!active(e)) return;
7495
+ var lyr = hit.getHitTarget().layer;
7496
+ var p = getPointCoordsById(e.id, lyr);
7497
+ if (!p) return;
7498
+ var diff = translateDeltaDisplayCoords(e.dx, e.dy, ext);
7499
+ p[0] += diff[0];
7500
+ p[1] += diff[1];
7501
+ gui.dispatchEvent('map-needs-refresh');
7502
+ gui.dispatchEvent('symbol_drag', {FID: e.id});
7503
+ });
6003
7504
 
6004
- hit.on('click', function(e) {
6005
- if (labelEditingEnabled()) {
6006
- var target = hit.getHitTarget();
6007
- onLabelClick(e);
6008
- }
6009
- });
7505
+ hit.on('dragend', function(e) {
7506
+ if (!active(e)) return;
7507
+ gui.dispatchEvent('symbol_dragend', {FID: e.id});
7508
+ });
6010
7509
 
6011
- // TODO: highlight hit vertex in path edit mode
6012
- if (false) hit.on('hover', function(e) {
6013
- if (vertexEditingEnabled() && !dragging) {
6014
- onVertexHover(e);
6015
- }
6016
- }, null, 100);
7510
+ function translateDeltaDisplayCoords(dx, dy, ext) {
7511
+ var a = ext.translatePixelCoords(0, 0);
7512
+ var b = ext.translatePixelCoords(dx, dy);
7513
+ return [b[0] - a[0], b[1] - a[1]];
7514
+ }
6017
7515
 
6018
- function onVertexHover(e) {
6019
- // hovering in vertex edit mode: find vertex insertion point
6020
- var target = hit.getHitTarget();
6021
- var shp = target.layer.shapes[e.id];
6022
- var p = ext.translatePixelCoords(e.x, e.y);
6023
- var o = internal.findInsertionPoint(p, shp, target.arcs, ext.getPixelSize());
7516
+ function getPointCoordsById(id, layer) {
7517
+ var coords = layer && layer.geometry_type == 'point' && layer.shapes[id];
7518
+ if (!coords || coords.length != 1) {
7519
+ return null;
6024
7520
  }
7521
+ return coords[0];
7522
+ }
7523
+ }
6025
7524
 
7525
+ var HOVER_THRESHOLD = 8;
7526
+ var MIDPOINT_THRESHOLD = 12;
6026
7527
 
6027
- function getVertexEventData(e) {
6028
- return {
6029
- FID: activeId,
6030
- vertexIds: activeVertexIds
6031
- };
6032
- }
6033
7528
 
6034
- function onLocationDrag(e) {
6035
- var lyr = hit.getHitTarget().layer;
6036
- var p = getPointCoordsById(e.id, lyr);
6037
- if (!p) return;
6038
- var diff = translateDeltaDisplayCoords(e.dx, e.dy, ext);
6039
- p[0] += diff[0];
6040
- p[1] += diff[1];
6041
- triggerRedraw();
6042
- triggerGlobalEvent('symbol_drag', e);
6043
- }
7529
+ function initVertexDragging(gui, ext, hit) {
7530
+ var activeShapeId = -1;
7531
+ var draggedVertexIds = null;
7532
+ var selectedVertexIds = null;
7533
+ var activeMidpoint; // {point, segment}
6044
7534
 
6045
- function onVertexDragStart(e) {
6046
- var target = hit.getHitTarget();
6047
- var shp = target.layer.shapes[e.id];
6048
- var p = ext.translatePixelCoords(e.x, e.y);
6049
- activeVertexIds = internal.findNearestVertices(p, shp, target.arcs);
6050
- activeId = e.id;
6051
- }
7535
+ function active(e) {
7536
+ return e.id > -1 && gui.interaction.getMode() == 'vertices';
7537
+ }
6052
7538
 
6053
- function onVertexDrag(e) {
6054
- var target = hit.getHitTarget();
6055
- if (!activeVertexIds) return; // ignore error condition
6056
- var p = ext.translatePixelCoords(e.x, e.y);
6057
- if (gui.keyboard.shiftIsPressed()) {
6058
- internal.snapPointToArcEndpoint(p, activeVertexIds, target.arcs);
6059
- }
6060
- snapVerticesToPoint(activeVertexIds, p, target.arcs);
6061
- triggerRedraw();
6062
- }
7539
+ function fire(type) {
7540
+ gui.dispatchEvent(type, {
7541
+ FID: activeShapeId,
7542
+ vertex_ids: draggedVertexIds
7543
+ });
7544
+ }
6063
7545
 
6064
- function onLabelClick(e) {
6065
- var textNode = getTextTarget3(e);
6066
- var rec = getLabelRecordById(e.id);
6067
- if (textNode && rec && isMultilineLabel(textNode)) {
6068
- toggleTextAlign(textNode, rec);
6069
- updateSymbol2(textNode, rec, e.id);
6070
- // e.stopPropagation(); // prevent pin/unpin on popup
6071
- }
6072
- }
7546
+ function setHoverVertex(id) {
7547
+ var target = hit.getHitTarget();
7548
+ hit.setHoverVertex(target.arcs.getVertex2(id));
7549
+ }
6073
7550
 
6074
- function triggerRedraw() {
6075
- gui.dispatchEvent('map-needs-refresh');
6076
- }
7551
+ function clearHoverVertex() {
7552
+ hit.clearVertexOverlay();
7553
+ // gui.state.vertex_overlay = null;
7554
+ }
6077
7555
 
6078
- function triggerGlobalEvent(type, e) {
6079
- if (e.id >= 0 === false) return;
6080
- var o = {
6081
- FID: e.id,
6082
- layer_name: hit.getHitTarget().layer.name,
6083
- vertex_ids: activeVertexIds
6084
- };
6085
- // fire event to signal external editor that symbol coords have changed
6086
- gui.dispatchEvent(type, o);
7556
+ function findDraggableVertices(e) {
7557
+ var target = hit.getHitTarget();
7558
+ var shp = target.layer.shapes[e.id];
7559
+ var p = ext.translatePixelCoords(e.x, e.y);
7560
+ var nearestIds = internal.findNearestVertices(p, shp, target.arcs);
7561
+ var p2 = target.arcs.getVertex2(nearestIds[0]);
7562
+ var dist = geom.distance2D(p[0], p[1], p2[0], p2[1]);
7563
+ var pixelDist = dist / ext.getPixelSize();
7564
+ if (pixelDist > HOVER_THRESHOLD) {
7565
+ draggedVertexIds = null;
7566
+ return null;
6087
7567
  }
7568
+ return nearestIds;
7569
+ }
6088
7570
 
6089
- function getLabelRecordById(id) {
6090
- var table = hit.getTargetDataTable();
6091
- if (id >= 0 === false || !table) return null;
6092
- // add dx and dy properties, if not available
6093
- if (!table.fieldExists('dx')) {
6094
- table.addField('dx', 0);
6095
- }
6096
- if (!table.fieldExists('dy')) {
6097
- table.addField('dy', 0);
6098
- }
6099
- if (!table.fieldExists('text-anchor')) {
6100
- table.addField('text-anchor', '');
6101
- }
6102
- return table.getRecordAt(id);
7571
+ function insertMidpoint(v) {
7572
+ var target = hit.getHitTarget();
7573
+ internal.insertVertex(target.arcs, v.i, v.point);
7574
+ }
7575
+
7576
+ hit.on('dragstart', function(e) {
7577
+ if (!active(e)) return;
7578
+ if (activeMidpoint) {
7579
+ insertMidpoint(activeMidpoint);
7580
+ draggedVertexIds = [activeMidpoint.i];
7581
+ // TODO: combine vertex insertion undo/redo actions with
7582
+ // vertex_dragend undo/redo actions
7583
+ gui.dispatchEvent('vertex_insert', {
7584
+ FID: activeShapeId,
7585
+ vertex_id: activeMidpoint.i,
7586
+ coordinates: activeMidpoint.point
7587
+ });
7588
+ activeMidpoint = null;
7589
+ } else {
7590
+ draggedVertexIds = findDraggableVertices(e);
6103
7591
  }
7592
+ if (!draggedVertexIds) return;
7593
+ setHoverVertex(draggedVertexIds[0]);
7594
+ activeShapeId = e.id;
7595
+ fire('vertex_dragstart');
7596
+ });
6104
7597
 
6105
- function onLabelDragStart(e) {
6106
- var textNode = getTextTarget3(e);
6107
- var table = hit.getTargetDataTable();
6108
- if (!textNode || !table) return false;
6109
- activeId = e.id;
6110
- activeRecord = getLabelRecordById(activeId);
6111
- downEvt = e;
6112
- return true;
7598
+ hit.on('drag', function(e) {
7599
+ if (!active(e) || !draggedVertexIds) return;
7600
+ var target = hit.getHitTarget();
7601
+ var p = ext.translatePixelCoords(e.x, e.y);
7602
+ if (gui.keyboard.shiftIsPressed()) {
7603
+ internal.snapPointToArcEndpoint(p, draggedVertexIds, target.arcs);
6113
7604
  }
7605
+ internal.snapVerticesToPoint(draggedVertexIds, p, target.arcs);
7606
+ setHoverVertex(draggedVertexIds[0]);
7607
+ // redrawing the whole map updates the data layer as well as the overlay layer
7608
+ // gui.dispatchEvent('map-needs-refresh');
7609
+ });
6114
7610
 
6115
- function onLabelDrag(e) {
6116
- var scale = ext.getSymbolScale() || 1;
6117
- var textNode;
6118
- if (!dragging) return;
6119
- if (e.id != activeId) {
6120
- error("Mismatched hit ids:", e.id, activeId);
6121
- }
6122
- applyDelta(activeRecord, 'dx', e.dx / scale);
6123
- applyDelta(activeRecord, 'dy', e.dy / scale);
6124
- textNode = getTextTarget3(e);
6125
- if (!isMultilineLabel(textNode)) {
6126
- // update anchor position of single-line labels based on label position
6127
- // relative to anchor point, for better placement when eventual display font is
6128
- // different from mapshaper's font.
6129
- autoUpdateTextAnchor(textNode, activeRecord, getDisplayCoordsById(activeId, hit.getHitTarget().layer, ext));
6130
- }
6131
- // updateSymbol(targetTextNode, activeRecord);
6132
- updateSymbol2(textNode, activeRecord, activeId);
6133
- }
7611
+ hit.on('dragend', function(e) {
7612
+ if (!active(e) || !draggedVertexIds) return;
7613
+ // kludge to get dataset to recalculate internal bounding boxes
7614
+ hit.getHitTarget().arcs.transformPoints(function() {});
7615
+ clearHoverVertex();
7616
+ fire('vertex_dragend');
7617
+ draggedVertexIds = null;
7618
+ activeShapeId = -1;
7619
+ // redraw data layer
7620
+ gui.dispatchEvent('map-needs-refresh');
7621
+ });
6134
7622
 
6135
- function getSymbolNodeById(id, parent) {
6136
- // TODO: optimize selector
6137
- var sel = '[data-id="' + id + '"]';
6138
- return parent.querySelector(sel);
6139
- }
7623
+ // select clicked vertices
7624
+ hit.on('click', function(e) {
7625
+ if (!active(e)) return;
7626
+ var vertices = findDraggableVertices(e); // same selection criteria as for dragging
7627
+ // TODO
7628
+ });
6140
7629
 
6141
- function getTextTarget3(e) {
6142
- if (e.id > -1 === false || !e.container) return null;
6143
- return getSymbolNodeById(e.id, e.container);
7630
+ // highlight hit vertex in path edit mode
7631
+ hit.on('hover', function(e) {
7632
+ activeMidpoint = null;
7633
+ if (!active(e) || draggedVertexIds) return; // no hover effect while dragging
7634
+ var vertexIds = findDraggableVertices(e);
7635
+ if (vertexIds) {
7636
+ setHoverVertex(vertexIds[0]);
7637
+ return;
6144
7638
  }
6145
-
6146
- function getTextTarget2(e) {
6147
- var el = e && e.targetSymbol || null;
6148
- if (el && el.tagName == 'tspan') {
6149
- el = el.parentNode;
6150
- }
6151
- return el && el.tagName == 'text' ? el : null;
7639
+ var target = hit.getHitTarget();
7640
+ // vertex insertion doesn't work yet with simplification applied
7641
+ if (!target.arcs.isFlat()) return;
7642
+ var shp = target.layer.shapes[e.id];
7643
+ var p = ext.translatePixelCoords(e.x, e.y);
7644
+ var midpoint = findNearestMidpoint(p, shp, target.arcs);
7645
+ if (midpoint && midpoint.distance / ext.getPixelSize() < MIDPOINT_THRESHOLD) {
7646
+ hit.setHoverVertex(midpoint.point);
7647
+ activeMidpoint = midpoint;
7648
+ } else {
7649
+ clearHoverVertex();
6152
7650
  }
7651
+ }, null, 100);
7652
+ }
6153
7653
 
6154
- function getTextTarget(e) {
6155
- var el = e.target;
6156
- if (el.tagName == 'tspan') {
6157
- el = el.parentNode;
6158
- }
6159
- return el.tagName == 'text' ? el : null;
6160
- }
6161
- }
6162
7654
 
6163
- function startDragging() {
6164
- dragging = true;
6165
- }
7655
+ // Given a location @p (e.g. corresponding to the mouse pointer location),
7656
+ // find the midpoint of two vertices on @shp suitable for inserting a new vertex,
7657
+ // but only if:
7658
+ // 1. point @p is closer to the midpoint than either adjacent vertex
7659
+ // 2. the segment containing @p is longer than a minimum distance in pixels.
7660
+ //
7661
+ function findNearestMidpoint(p, shp, arcs) {
7662
+ // var v1 = internal.findNearestVertex(p[0], p[1], shp, arcs);
7663
+ // var v0 = internal.findAdjacentVertex(v1, shp, arcs, -1);
7664
+ // var v2 = internal.findAdjacentVertex(v1, shp, arcs, 1);
7665
+ var minDist = Infinity, v;
7666
+ internal.forEachSegmentInShape(shp, arcs, function(i, j, xx, yy) {
7667
+ var x1 = xx[i],
7668
+ y1 = yy[i],
7669
+ x2 = xx[j],
7670
+ y2 = yy[j],
7671
+ cx = (x1 + x2) / 2,
7672
+ cy = (y1 + y2) / 2,
7673
+ dist = geom.distance2D(cx, cy, p[0], p[1]);
7674
+ if (dist < minDist) {
7675
+ minDist = dist;
7676
+ v = {
7677
+ i: (i < j ? i : j) + 1, // insertion point
7678
+ segment: [i, j],
7679
+ point: [cx, cy],
7680
+ distance: dist
7681
+ };
7682
+ }
7683
+ });
7684
+ return v || null;
7685
+ }
6166
7686
 
6167
- function stopDragging() {
6168
- dragging = false;
6169
- activeId = -1;
6170
- activeRecord = null;
6171
- activeVertexIds = null;
6172
- }
7687
+ function initInteractiveEditing(gui, ext, hit) {
7688
+ initLabelDragging(gui, ext, hit);
7689
+ initPointDragging(gui, ext, hit);
7690
+ initVertexDragging(gui, ext, hit);
6173
7691
 
6174
- function isClickEvent(up, down) {
6175
- var elapsed = Math.abs(down.timeStamp - up.timeStamp);
6176
- var dx = up.screenX - down.screenX;
6177
- var dy = up.screenY - down.screenY;
6178
- var dist = Math.sqrt(dx * dx + dy * dy);
6179
- return dist <= 4 && elapsed < 300;
6180
- }
7692
+ gui.on('interaction_mode_change', function(e) {
7693
+ gui.undo.clear(); // TODO: put this elsewhere?
7694
+ });
6181
7695
 
7696
+ // function isClickEvent(up, down) {
7697
+ // var elapsed = Math.abs(down.timeStamp - up.timeStamp);
7698
+ // var dx = up.screenX - down.screenX;
7699
+ // var dy = up.screenY - down.screenY;
7700
+ // var dist = Math.sqrt(dx * dx + dy * dy);
7701
+ // return dist <= 4 && elapsed < 300;
7702
+ // }
6182
7703
  }
6183
7704
 
6184
7705
  var darkStroke = "#334",
@@ -6203,7 +7724,7 @@
6203
7724
  },
6204
7725
  referenceStyle = { // outline style for reference layers
6205
7726
  type: 'outline',
6206
- strokeColors: [null, '#86c927'],
7727
+ strokeColors: [null, '#78c110'], // upped saturation from #86c927
6207
7728
  strokeWidth: 0.85,
6208
7729
  dotColor: "#73ba20",
6209
7730
  dotSize: 1
@@ -6284,7 +7805,7 @@
6284
7805
  }
6285
7806
 
6286
7807
  function getDefaultStyle(lyr, baseStyle) {
6287
- var style = utils.extend({}, baseStyle);
7808
+ var style = utils$1.extend({}, baseStyle);
6288
7809
  // reduce the dot size of large point layers
6289
7810
  if (lyr.geometry_type == 'point' && style.dotSize > 0) {
6290
7811
  style.dotSize *= getDotScale$1(lyr);
@@ -6349,8 +7870,14 @@
6349
7870
  var geomType = lyr.geometry_type;
6350
7871
  var topId = o.id; // pinned id (if pinned) or hover id
6351
7872
  var topIdx = -1;
6352
- var styler = function(o, i) {
6353
- utils.extend(o, i === topIdx ? topStyle: baseStyle);
7873
+ var styler = function(style, i) {
7874
+ utils$1.extend(style, i === topIdx ? topStyle: baseStyle);
7875
+ // kludge to show vertices when editing path shapes
7876
+ if (o.mode == 'vertices') {
7877
+ style.vertices = true;
7878
+ style.vertex_overlay = o.hit_coordinates || null;
7879
+ style.fillColor = null;
7880
+ }
6354
7881
  };
6355
7882
  var baseStyle = getDefaultStyle(lyr, selectionStyles[geomType]);
6356
7883
  var topStyle;
@@ -6463,7 +7990,7 @@
6463
7990
  if (lyr.geometry_type == 'point') {
6464
7991
  return fields.indexOf('r') > -1; // require 'r' field for point symbols
6465
7992
  }
6466
- return utils.difference(fields, ['opacity', 'class']).length > 0;
7993
+ return utils$1.difference(fields, ['opacity', 'class']).length > 0;
6467
7994
  }
6468
7995
 
6469
7996
 
@@ -6620,7 +8147,7 @@
6620
8147
  }
6621
8148
 
6622
8149
  function maxAbs() {
6623
- return Math.max.apply(null, utils.toArray(arguments).map(Math.abs));
8150
+ return Math.max.apply(null, utils$1.toArray(arguments).map(Math.abs));
6624
8151
  }
6625
8152
 
6626
8153
  function limitScale(scale) {
@@ -6674,7 +8201,7 @@
6674
8201
  }
6675
8202
  }
6676
8203
 
6677
- utils.inherit(MapExtent, EventDispatcher);
8204
+ utils$1.inherit(MapExtent, EventDispatcher);
6678
8205
 
6679
8206
  // TODO: consider moving this upstream
6680
8207
  function getArcsForRendering(obj, ext) {
@@ -6821,19 +8348,25 @@
6821
8348
  _self.drawVertices = function(shapes, arcs, style, filter) {
6822
8349
  var iter = new internal.ShapeIter(arcs);
6823
8350
  var t = getScaledTransform(_ext);
6824
- var radius = (style.strokeWidth * 0.9 || 2.2) * GUI.getPixelRatio() * getScaledLineScale(_ext);
8351
+ var radius = (style.strokeWidth > 2 ? style.strokeWidth * 0.9 : 2) * GUI.getPixelRatio() * getScaledLineScale(_ext);
6825
8352
  var color = style.strokeColor || 'black';
6826
- var shp;
8353
+ var radius2 = radius * 1.7;
6827
8354
  _ctx.beginPath();
6828
8355
  _ctx.fillStyle = color;
6829
8356
  for (var i=0; i<shapes.length; i++) {
6830
- shp = shapes[i];
8357
+ var shp = shapes[i];
6831
8358
  if (!shp || filter && !filter(shp)) continue;
6832
- iter.init(shp);
6833
- while (iter.hasNext()) {
6834
- drawCircle(iter.x * t.mx + t.bx, iter.y * t.my + t.by, radius, _ctx);
8359
+ for (var j=0; j<shp.length; j++) {
8360
+ iter.init(shp[j]);
8361
+ while (iter.hasNext()) {
8362
+ drawCircle(iter.x * t.mx + t.bx, iter.y * t.my + t.by, radius, _ctx);
8363
+ }
6835
8364
  }
6836
8365
  }
8366
+ if (style.vertex_overlay) {
8367
+ var p = style.vertex_overlay;
8368
+ drawCircle(p[0] * t.mx + t.bx, p[1] * t.my + t.by, radius2, _ctx);
8369
+ }
6837
8370
  _ctx.fill();
6838
8371
  _ctx.closePath();
6839
8372
  };
@@ -6858,7 +8391,7 @@
6858
8391
  key = getStyleKey(style);
6859
8392
  if (key in styleIndex === false) {
6860
8393
  styleIndex[key] = {
6861
- style: utils.defaults({}, style),
8394
+ style: utils$1.defaults({}, style),
6862
8395
  shapes: []
6863
8396
  };
6864
8397
  }
@@ -7198,7 +8731,8 @@
7198
8731
  if (style.strokeWidth > 0) {
7199
8732
  strokeWidth = style.strokeWidth;
7200
8733
  if (pixRatio > 1) {
7201
- // bump up thin lines on retina, but not to more than 1px (too slow)
8734
+ // bump up thin lines on retina, but not to more than 1px
8735
+ // (tests on Chrome showed much faster rendering of 1px lines)
7202
8736
  strokeWidth = strokeWidth < 1 ? 1 : strokeWidth * pixRatio;
7203
8737
  }
7204
8738
  ctx.lineCap = 'round';
@@ -7332,7 +8866,7 @@
7332
8866
  var frame = ext.getFrame(); // frame should be set if we're rendering a furniture layer
7333
8867
  var obj = internal.getEmptyLayerForSVG(lyr, {});
7334
8868
  if (!frame) {
7335
- stop('Missing map frame data');
8869
+ stop$1('Missing map frame data');
7336
8870
  }
7337
8871
  obj.properties.transform = getSvgFurnitureTransform(ext);
7338
8872
  obj.properties.class = 'mapshaper-svg-furniture';
@@ -7360,7 +8894,7 @@
7360
8894
  var html = '';
7361
8895
  // generate a unique id so layer can be identified when symbols are repositioned
7362
8896
  // use it as a class name to avoid id collisions
7363
- var id = utils.getUniqueName();
8897
+ var id = utils$1.getUniqueName();
7364
8898
  var classNames = [id, 'mapshaper-svg-layer', 'mapshaper-' + type + '-layer'];
7365
8899
  g.setAttribute('class', classNames.join(' '));
7366
8900
  target.svg_id = id;
@@ -7811,7 +9345,7 @@
7811
9345
  }
7812
9346
 
7813
9347
  if (obj.tabular) {
7814
- utils.extend(obj, getDisplayLayerForTable(layer.data));
9348
+ utils$1.extend(obj, getDisplayLayerForTable(layer.data));
7815
9349
  }
7816
9350
 
7817
9351
  // dynamic reprojection (arcs were already reprojected above)
@@ -7869,7 +9403,7 @@
7869
9403
  return ids;
7870
9404
  }
7871
9405
 
7872
- utils.inherit(MshpMap, EventDispatcher);
9406
+ utils$1.inherit(MshpMap, EventDispatcher);
7873
9407
 
7874
9408
  function MshpMap(gui) {
7875
9409
  var opts = gui.options,
@@ -7886,7 +9420,7 @@
7886
9420
  _visibleLayers = [], // cached visible map layers
7887
9421
  _fullBounds = null,
7888
9422
  _intersectionLyr, _activeLyr, _overlayLyr,
7889
- _inspector, _stack, _editor,
9423
+ _inspector, _stack,
7890
9424
  _dynamicCRS;
7891
9425
 
7892
9426
  if (gui.options.showMouseCoordinates) {
@@ -7963,7 +9497,7 @@
7963
9497
  this.setDisplayCRS = function(crs) {
7964
9498
  // TODO: update bounds of frame layer, if there is a frame layer
7965
9499
  var oldCRS = this.getDisplayCRS();
7966
- var newCRS = utils.isString(crs) ? internal.getCRS(crs) : crs;
9500
+ var newCRS = utils$1.isString(crs) ? internal.getCRS(crs) : crs;
7967
9501
  // TODO: handle case that old and new CRS are the same
7968
9502
  _dynamicCRS = newCRS;
7969
9503
  if (!_activeLyr) return; // stop here if no layers have been selected
@@ -8069,7 +9603,9 @@
8069
9603
  });
8070
9604
  }
8071
9605
 
8072
- _editor = new InteractiveEditor(gui, _ext, _hit);
9606
+ if (gui.interaction) {
9607
+ initInteractiveEditing(gui, _ext, _hit);
9608
+ }
8073
9609
 
8074
9610
  _ext.on('change', function(e) {
8075
9611
  if (e.reset) return; // don't need to redraw map here if extent has been reset
@@ -8089,11 +9625,7 @@
8089
9625
  function updateOverlayLayer(e) {
8090
9626
  var style = getOverlayStyle(_activeLyr.layer, e);
8091
9627
  if (style) {
8092
- // kludge to show vertices when editing path shapes
8093
- if (gui.state.interaction_mode == 'vertices') {
8094
- style.vertices = true;
8095
- }
8096
- _overlayLyr = utils.defaults({
9628
+ _overlayLyr = utils$1.defaults({
8097
9629
  layer: filterLayerByIds(_activeLyr.layer, style.ids),
8098
9630
  style: style
8099
9631
  }, _activeLyr);
@@ -8258,7 +9790,7 @@
8258
9790
  // kludge to hide ghosted layers when reference layers are present
8259
9791
  // TODO: consider never showing ghosted layers (which appear after
8260
9792
  // commands like dissolve and filter).
8261
- mapLayer.style = utils.defaults({
9793
+ mapLayer.style = utils$1.defaults({
8262
9794
  strokeColors: [null, mapLayer.style.strokeColors[1]]
8263
9795
  }, mapLayer.style);
8264
9796
  }
@@ -8314,7 +9846,7 @@
8314
9846
 
8315
9847
  function GuiInstance(container, opts) {
8316
9848
  var gui = new ModeSwitcher();
8317
- opts = utils.extend({
9849
+ opts = utils$1.extend({
8318
9850
  // defaults
8319
9851
  homeControl: true,
8320
9852
  zoomControl: true,
@@ -8396,14 +9928,14 @@
8396
9928
  startEditing();
8397
9929
  });
8398
9930
 
8399
- function getImportOpts() {
9931
+ function getManifest() {
9932
+ return window.mapshaper.manifest || {}; // kludge -- bin/mapshaper-gui sets this
9933
+ }
9934
+
9935
+ function getImportOpts(manifest) {
8400
9936
  var vars = GUI.getUrlVars();
8401
9937
  var opts = {};
8402
- var manifest = window.mapshaper.manifest || {}; // kludge -- bin/mapshaper-gui sets this
8403
- if (Array.isArray(manifest)) {
8404
- // old-style manifest: an array of filenames
8405
- opts.files = manifest;
8406
- } else if (manifest.files) {
9938
+ if (manifest.files) {
8407
9939
  opts.files = manifest.files.concat();
8408
9940
  } else {
8409
9941
  opts.files = [];
@@ -8421,11 +9953,20 @@
8421
9953
  return opts;
8422
9954
  }
8423
9955
 
9956
+ function getInitialConsoleCommands() {
9957
+ return getManifest().commands || '';
9958
+ }
9959
+
8424
9960
  var startEditing = function() {
8425
9961
  var dataLoaded = false,
8426
- importOpts = getImportOpts(),
9962
+ manifest = getManifest(),
9963
+ importOpts = getImportOpts(manifest),
8427
9964
  gui = new GuiInstance('body');
8428
9965
 
9966
+ if (manifest.blurb) {
9967
+ El('#splash-screen-blurb').text(manifest.blurb);
9968
+ }
9969
+
8429
9970
  new AlertControl(gui);
8430
9971
  new RepairControl(gui);
8431
9972
  new SimplifyControl(gui);
@@ -8462,6 +10003,8 @@
8462
10003
  gui.map.setLayerPinning(o, true);
8463
10004
  });
8464
10005
  }
10006
+ gui.console.runInitialCommands(getInitialConsoleCommands());
10007
+
8465
10008
  });
8466
10009
  };
8467
10010