mapshaper 0.5.86 → 0.5.91

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,114 +1,16 @@
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
-
101
3
  var api = window.mapshaper; // assuming mapshaper is in global scope
102
4
  var mapshaper = api,
103
- utils$1 = api.utils,
5
+ utils = api.utils,
104
6
  cli = api.cli,
105
7
  geom = api.geom,
106
8
  internal = api.internal,
107
9
  Bounds = internal.Bounds,
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;
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;
112
14
 
113
15
  api.enableLogging();
114
16
 
@@ -144,7 +46,7 @@
144
46
 
145
47
  html = '<table>';
146
48
  if (catalog.title) {
147
- html += utils$1.format('<tr><th colspan="%d"><h4>%s</h4></th></tr>', cols, catalog.title);
49
+ html += utils.format('<tr><th colspan="%d"><h4>%s</h4></th></tr>', cols, catalog.title);
148
50
  }
149
51
  while (row < rows) {
150
52
  html += renderRow(items.slice(row * cols, row * cols + cols));
@@ -209,7 +111,7 @@
209
111
 
210
112
  function renderCell(item, i) {
211
113
  var template = '<td data-id="%d"><h4 class="title">%s</h4><div class="subtitle">%s</div></td>';
212
- return utils$1.format(template, i, item.title, item.subtitle || '');
114
+ return utils.format(template, i, item.title, item.subtitle || '');
213
115
  }
214
116
 
215
117
  }
@@ -280,6 +182,10 @@
280
182
  return (el && (el.tagName == 'INPUT' || el.contentEditable == 'true')) ? el : null;
281
183
  };
282
184
 
185
+ GUI.textIsSelected = function() {
186
+ return !!GUI.getInputElement();
187
+ };
188
+
283
189
  GUI.selectElement = function(el) {
284
190
  var range = document.createRange(),
285
191
  sel = window.getSelection();
@@ -322,11 +228,35 @@
322
228
  }
323
229
  parsed = internal.parseCommands(str);
324
230
  if (!parsed.length || parsed[0].name != cmd) {
325
- stop$1("Unable to parse command line options");
231
+ stop("Unable to parse command line options");
326
232
  }
327
233
  return parsed[0].options;
328
234
  };
329
235
 
236
+ // Convert an options object to a command line options string
237
+ // (used by gui-import-control.js)
238
+ // TODO: handle options with irregular string <-> object conversion
239
+ GUI.formatCommandOptions = function(o) {
240
+ var arr = [];
241
+ Object.keys(o).forEach(function(key) {
242
+ var name = key.replace(/_/g, '-');
243
+ var val = o[key];
244
+ var str;
245
+ // TODO: quote values that contain spaces
246
+ if (Array.isArray(val)) {
247
+ str = name + '=' + val.join(',');
248
+ } else if (val === true) {
249
+ str = name;
250
+ } else if (val === false) {
251
+ return;
252
+ } else {
253
+ str = name + '=' + val;
254
+ }
255
+ arr.push(str);
256
+ });
257
+ return arr.join(' ');
258
+ };
259
+
330
260
  // @file: Zip file
331
261
  // @cb: function(err, <files>)
332
262
  //
@@ -391,7 +321,7 @@
391
321
  evt = new EventData(this.type);
392
322
  evt.target = this.target;
393
323
  } else if (evt.target != this.target || evt.type != this.type) {
394
- error$1("[Handler] event target/type have changed.");
324
+ error("[Handler] event target/type have changed.");
395
325
  }
396
326
  this.callback.call(this.listener, evt);
397
327
  };
@@ -400,7 +330,7 @@
400
330
  this.type = type;
401
331
  this.target = target;
402
332
  if (data) {
403
- utils$1.defaults(this, data);
333
+ utils.defaults(this, data);
404
334
  this.data = data;
405
335
  }
406
336
  }
@@ -630,12 +560,12 @@
630
560
  try {
631
561
  els = root.querySelectorAll(selector);
632
562
  } catch (e) {
633
- error$1("Invalid selector:", selector);
563
+ error("Invalid selector:", selector);
634
564
  }
635
565
  } else {
636
- error$1("This browser doesn't support CSS query selectors");
566
+ error("This browser doesn't support CSS query selectors");
637
567
  }
638
- return utils$1.toArray(els);
568
+ return utils.toArray(els);
639
569
  };
640
570
 
641
571
  // Converts dash-separated names (e.g. background-color) to camelCase (e.g. backgroundColor)
@@ -672,7 +602,7 @@
672
602
  };
673
603
 
674
604
  function El(ref) {
675
- if (!ref) error$1("Element() needs a reference");
605
+ if (!ref) error("Element() needs a reference");
676
606
  if (ref instanceof El) {
677
607
  return ref;
678
608
  }
@@ -681,7 +611,7 @@
681
611
  }
682
612
 
683
613
  var node;
684
- if (utils$1.isString(ref)) {
614
+ if (utils.isString(ref)) {
685
615
  if (ref[0] == '<') {
686
616
  var parent = El('div').html(ref).node();
687
617
  node = parent.childNodes.length == 1 ? parent.childNodes[0] : parent;
@@ -693,13 +623,13 @@
693
623
  } else if (ref.tagName) {
694
624
  node = ref;
695
625
  }
696
- if (!node) error$1("Unmatched element selector:", ref);
626
+ if (!node) error("Unmatched element selector:", ref);
697
627
  this.el = node;
698
628
  }
699
629
 
700
- utils$1.inherit(El, EventDispatcher);
630
+ utils.inherit(El, EventDispatcher);
701
631
 
702
- utils$1.extend(El.prototype, {
632
+ utils.extend(El.prototype, {
703
633
 
704
634
  clone: function() {
705
635
  var el = this.el.cloneNode(true);
@@ -708,7 +638,7 @@
708
638
  // can ...
709
639
  el = El('div').addClass(el.className).html(el.innerHTML).node();
710
640
  }
711
- el.id = utils$1.getUniqueName();
641
+ el.id = utils.getUniqueName();
712
642
  this.el = el;
713
643
  return this;
714
644
  },
@@ -738,11 +668,11 @@
738
668
  if (val != null) {
739
669
  El.setStyle(this.el, css, val);
740
670
  }
741
- else if (utils$1.isString(css)) {
671
+ else if (utils.isString(css)) {
742
672
  addCSS(this.el, css);
743
673
  }
744
- else if (utils$1.isObject(css)) {
745
- utils$1.forEachProperty(css, function(val, key) {
674
+ else if (utils.isObject(css)) {
675
+ utils.forEachProperty(css, function(val, key) {
746
676
  El.setStyle(this.el, key, val);
747
677
  }, this);
748
678
  }
@@ -750,7 +680,7 @@
750
680
  },
751
681
 
752
682
  attr: function(obj, value) {
753
- if (utils$1.isString(obj)) {
683
+ if (utils.isString(obj)) {
754
684
  if (arguments.length == 1) {
755
685
  return this.el.getAttribute(obj);
756
686
  }
@@ -834,7 +764,7 @@
834
764
  },
835
765
 
836
766
  text: function(str) {
837
- this.html(utils$1.htmlEscape(str));
767
+ this.html(utils.htmlEscape(str));
838
768
  return this;
839
769
  },
840
770
 
@@ -924,7 +854,7 @@
924
854
  El.prototype.__on = El.prototype.on;
925
855
  El.prototype.on = function(type, func, ctx) {
926
856
  if (ctx) {
927
- error$1("[El#on()] Third argument no longer supported.");
857
+ error("[El#on()] Third argument no longer supported.");
928
858
  }
929
859
  if (this.constructor == El) {
930
860
  this.el.addEventListener(type, func);
@@ -980,7 +910,7 @@
980
910
  }
981
911
 
982
912
  this.value = function(str) {
983
- if (utils$1.isString(str)) {
913
+ if (utils.isString(str)) {
984
914
  el.node().textContent = str;
985
915
  } else {
986
916
  return el.node().textContent;
@@ -988,7 +918,7 @@
988
918
  };
989
919
  }
990
920
 
991
- utils$1.inherit(ClickText2, EventDispatcher);
921
+ utils.inherit(ClickText2, EventDispatcher);
992
922
 
993
923
  // @ref reference to a text input element
994
924
  function ClickText(ref) {
@@ -1059,9 +989,9 @@
1059
989
  // return _parser ? _parser(valStr) : parseFloat(valStr);
1060
990
  return _value;
1061
991
  }
1062
- var val = utils$1.clamp(arg, _min, _max);
992
+ var val = utils.clamp(arg, _min, _max);
1063
993
  if (!_validator(val)) {
1064
- error$1("ClickText#value() invalid value:", arg);
994
+ error("ClickText#value() invalid value:", arg);
1065
995
  } else {
1066
996
  _value = val;
1067
997
  }
@@ -1070,14 +1000,14 @@
1070
1000
  };
1071
1001
  }
1072
1002
 
1073
- utils$1.inherit(ClickText, EventDispatcher);
1003
+ utils.inherit(ClickText, EventDispatcher);
1074
1004
 
1075
1005
 
1076
1006
  function Checkbox(ref) {
1077
1007
  var _el = El(ref);
1078
1008
  }
1079
1009
 
1080
- utils$1.inherit(Checkbox, EventDispatcher);
1010
+ utils.inherit(Checkbox, EventDispatcher);
1081
1011
 
1082
1012
  function SimpleButton(ref) {
1083
1013
  var _el = El(ref),
@@ -1106,7 +1036,7 @@
1106
1036
  }
1107
1037
  }
1108
1038
 
1109
- utils$1.inherit(SimpleButton, EventDispatcher);
1039
+ utils.inherit(SimpleButton, EventDispatcher);
1110
1040
 
1111
1041
  // @cb function(<FileList>)
1112
1042
  function DropControl(gui, el, cb) {
@@ -1125,6 +1055,10 @@
1125
1055
  var types = Array.from(e.clipboardData.types || []).join(',');
1126
1056
  var items = Array.from(e.clipboardData.items || []);
1127
1057
  var files;
1058
+ if (GUI.textIsSelected()) {
1059
+ // user is probably pasting text into an editable text field
1060
+ return;
1061
+ }
1128
1062
  block(e);
1129
1063
  // Browser compatibility (tested on MacOS only):
1130
1064
  // Chrome and Safari: full support
@@ -1197,20 +1131,19 @@
1197
1131
 
1198
1132
  function ImportControl(gui, opts) {
1199
1133
  var model = gui.model;
1134
+ var initialImport = true;
1200
1135
  var importCount = 0;
1201
1136
  var importTotal = 0;
1202
1137
  var overQuickView = false;
1203
- var useQuickView = opts.quick_view; // may be set by mapshaper-gui
1204
1138
  var queuedFiles = [];
1205
1139
  var manifestFiles = opts.files || [];
1206
- var cachedFiles = {};
1207
1140
  var catalog;
1208
1141
 
1209
1142
  if (opts.catalog) {
1210
1143
  catalog = new CatalogControl(gui, opts.catalog, downloadFiles);
1211
1144
  }
1212
1145
 
1213
- new SimpleButton('#import-buttons .submit-btn').on('click', onSubmit);
1146
+ new SimpleButton('#import-buttons .submit-btn').on('click', importQueuedFiles);
1214
1147
  new SimpleButton('#import-buttons .cancel-btn').on('click', gui.clearMode);
1215
1148
  new DropControl(gui, 'body', receiveFiles);
1216
1149
  new FileChooser('#file-selection-btn', receiveFiles);
@@ -1218,7 +1151,7 @@
1218
1151
  new FileChooser('#add-file-btn', receiveFiles);
1219
1152
  initDropArea('#import-quick-drop', true);
1220
1153
  initDropArea('#import-drop');
1221
- gui.keyboard.onMenuSubmit(El('#import-options'), onSubmit);
1154
+ gui.keyboard.onMenuSubmit(El('#import-options'), importQueuedFiles);
1222
1155
 
1223
1156
  gui.addMode('import', turnOn, turnOff);
1224
1157
  gui.enterMode('import');
@@ -1230,6 +1163,10 @@
1230
1163
  }
1231
1164
  });
1232
1165
 
1166
+ function useQuickView() {
1167
+ return initialImport && (opts.quick_view || overQuickView);
1168
+ }
1169
+
1233
1170
  function initDropArea(el, isQuick) {
1234
1171
  var area = El(el)
1235
1172
  .on('dragleave', onout)
@@ -1247,15 +1184,19 @@
1247
1184
  }
1248
1185
  }
1249
1186
 
1250
- function findMatchingShp(filename) {
1251
- // use case-insensitive matching
1252
- var base = internal.getPathBase(filename).toLowerCase();
1253
- return model.getDatasets().filter(function(d) {
1254
- var fname = d.info.input_files && d.info.input_files[0] || "";
1255
- var ext = internal.getFileExtension(fname).toLowerCase();
1256
- var base2 = internal.getPathBase(fname).toLowerCase();
1257
- return base == base2 && ext == 'shp';
1258
- });
1187
+ async function importQueuedFiles() {
1188
+ gui.container.removeClass('queued-files');
1189
+ gui.container.removeClass('splash-screen');
1190
+ var files = queuedFiles;
1191
+ try {
1192
+ if (files.length > 0) {
1193
+ queuedFiles = [];
1194
+ await importFiles(files);
1195
+ }
1196
+ } catch(e) {
1197
+ console.error(e);
1198
+ }
1199
+ gui.clearMode();
1259
1200
  }
1260
1201
 
1261
1202
  function turnOn() {
@@ -1276,13 +1217,8 @@
1276
1217
  importCount = 0;
1277
1218
  }
1278
1219
  gui.clearProgressMessage();
1279
- useQuickView = false; // unset 'quick view' mode, if on
1280
- close();
1281
- }
1282
-
1283
- function close() {
1220
+ initialImport = false; // unset 'quick view' mode, if on
1284
1221
  clearQueuedFiles();
1285
- cachedFiles = {};
1286
1222
  }
1287
1223
 
1288
1224
  function onImportComplete() {
@@ -1316,34 +1252,6 @@
1316
1252
  }, []);
1317
1253
  }
1318
1254
 
1319
- // When a Shapefile component is at the head of the queue, move the entire
1320
- // Shapefile to the front of the queue, sorted in reverse alphabetical order,
1321
- // (a kludge), so .shp is read before .dbf and .prj
1322
- // (If a .dbf file is imported before a .shp, it becomes a separate dataset)
1323
- // TODO: import Shapefile parts without relying on this kludge
1324
- function sortQueue(queue) {
1325
- var nextFile = queue[0];
1326
- var basename, parts;
1327
- if (!isShapefilePart(nextFile.name)) {
1328
- return queue;
1329
- }
1330
- basename = internal.getFileBase(nextFile.name).toLowerCase();
1331
- parts = [];
1332
- queue = queue.filter(function(file) {
1333
- if (internal.getFileBase(file.name).toLowerCase() == basename) {
1334
- parts.push(file);
1335
- return false;
1336
- }
1337
- return true;
1338
- });
1339
- parts.sort(function(a, b) {
1340
- // Sorting on LC filename so Shapefiles with mixed-case
1341
- // extensions are sorted correctly
1342
- return a.name.toLowerCase() < b.name.toLowerCase() ? 1 : -1;
1343
- });
1344
- return parts.concat(queue);
1345
- }
1346
-
1347
1255
  function showQueuedFiles() {
1348
1256
  var list = gui.container.findChild('.dropped-file-list').empty();
1349
1257
  queuedFiles.forEach(function(f) {
@@ -1351,16 +1259,14 @@
1351
1259
  });
1352
1260
  }
1353
1261
 
1354
- function receiveFiles(files) {
1355
- var prevSize = queuedFiles.length;
1356
- useQuickView = useQuickView || overQuickView;
1357
- files = handleZipFiles(utils$1.toArray(files));
1358
- addFilesToQueue(files);
1262
+ async function receiveFiles(files) {
1263
+ // TODO: show importing message here?
1264
+ var expanded = await expandFiles(files);
1265
+ addFilesToQueue(expanded);
1359
1266
  if (queuedFiles.length === 0) return;
1360
1267
  gui.enterMode('import');
1361
-
1362
- if (useQuickView) {
1363
- onSubmit();
1268
+ if (useQuickView()) {
1269
+ importQueuedFiles();
1364
1270
  } else {
1365
1271
  gui.container.addClass('queued-files');
1366
1272
  El('#path-import-options').classed('hidden', !filesMayContainPaths(queuedFiles));
@@ -1368,25 +1274,51 @@
1368
1274
  }
1369
1275
  }
1370
1276
 
1371
- function filesMayContainPaths(files) {
1372
- return utils$1.some(files, function(f) {
1373
- var type = internal.guessInputFileType(f.name);
1374
- return type == 'shp' || type == 'json' || internal.isZipFile(f.name);
1375
- });
1277
+ async function expandFiles(files) {
1278
+ var files2 = [], expanded;
1279
+ for (var f of files) {
1280
+ if (internal.isZipFile(f.name)) {
1281
+ expanded = await readZipFile(f);
1282
+ files2 = files2.concat(expanded);
1283
+ } else {
1284
+ files2.push(f);
1285
+ }
1286
+ }
1287
+ return files2;
1376
1288
  }
1377
1289
 
1378
- function onSubmit() {
1379
- gui.container.removeClass('queued-files');
1380
- gui.container.removeClass('splash-screen');
1381
- procNextQueuedFile();
1290
+ async function importFiles(files) {
1291
+ var fileData = await readFiles(files);
1292
+ var importOpts = readImportOpts();
1293
+ var groups = groupFilesForImport(fileData, importOpts);
1294
+ for (var group of groups) {
1295
+ if (group.size > 4e7) {
1296
+ gui.showProgressMessage('Importing');
1297
+ await wait(35);
1298
+ }
1299
+ importDataset(group, importOpts);
1300
+ }
1382
1301
  }
1383
1302
 
1384
- function addDataset(dataset) {
1385
- if (!datasetIsEmpty(dataset)) {
1386
- model.addDataset(dataset);
1387
- importCount++;
1303
+ function importDataset(group, importOpts) {
1304
+ var optStr = GUI.formatCommandOptions(importOpts);
1305
+ var dataset = internal.importContent(group, importOpts);
1306
+ if (datasetIsEmpty(dataset)) return;
1307
+ if (group.layername) {
1308
+ dataset.layers.forEach(lyr => lyr.name = group.layername);
1388
1309
  }
1389
- procNextQueuedFile();
1310
+ // save import options for use by repair control, etc.
1311
+ dataset.info.import_options = importOpts;
1312
+ model.addDataset(dataset);
1313
+ importCount++;
1314
+ gui.session.fileImported(group.filename, optStr);
1315
+ }
1316
+
1317
+ function filesMayContainPaths(files) {
1318
+ return utils.some(files, function(f) {
1319
+ var type = internal.guessInputFileType(f.name);
1320
+ return type == 'shp' || type == 'json' || internal.isZipFile(f.name);
1321
+ });
1390
1322
  }
1391
1323
 
1392
1324
  function datasetIsEmpty(dataset) {
@@ -1395,41 +1327,27 @@
1395
1327
  });
1396
1328
  }
1397
1329
 
1398
- function procNextQueuedFile() {
1399
- if (queuedFiles.length === 0) {
1400
- gui.clearMode();
1401
- } else {
1402
- queuedFiles = sortQueue(queuedFiles);
1403
- readFile(queuedFiles.shift());
1404
- }
1405
- }
1406
1330
 
1407
1331
  // TODO: support .cpg
1408
1332
  function isShapefilePart(name) {
1409
1333
  return /\.(shp|shx|dbf|prj)$/i.test(name);
1410
1334
  }
1411
1335
 
1412
-
1413
1336
  function readImportOpts() {
1414
- if (useQuickView) return {};
1415
- var freeform = El('#import-options .advanced-options').node().value,
1416
- opts = GUI.parseFreeformOptions(freeform, 'i');
1417
- opts.no_repair = !El("#repair-intersections-opt").node().checked;
1418
- opts.snap = !!El("#snap-points-opt").node().checked;
1419
- return opts;
1420
- }
1421
-
1422
- // for CLI output
1423
- function readImportOptsAsString() {
1424
- if (useQuickView) return '';
1425
- var freeform = El('#import-options .advanced-options').node().value;
1426
- var opts = readImportOpts();
1427
- if (opts.snap) freeform = 'snap ' + freeform;
1428
- return freeform.trim();
1337
+ var importOpts;
1338
+ if (useQuickView()) {
1339
+ importOpts = {}; // default opts using quickview
1340
+ } else {
1341
+ var freeform = El('#import-options .advanced-options').node().value;
1342
+ importOpts = GUI.parseFreeformOptions(freeform, 'i');
1343
+ importOpts.no_repair = !El("#repair-intersections-opt").node().checked;
1344
+ importOpts.snap = !!El("#snap-points-opt").node().checked;
1345
+ }
1346
+ return importOpts;
1429
1347
  }
1430
1348
 
1431
1349
  // @file a File object
1432
- function readFile(file) {
1350
+ async function readContentFileAsync(file, cb) {
1433
1351
  var name = file.name,
1434
1352
  reader = new FileReader(),
1435
1353
  useBinary = internal.isSupportedBinaryInputType(name) ||
@@ -1439,9 +1357,9 @@
1439
1357
 
1440
1358
  reader.addEventListener('loadend', function(e) {
1441
1359
  if (!reader.result) {
1442
- handleImportError("Web browser was unable to load the file.", name);
1360
+ cb(new Error());
1443
1361
  } else {
1444
- importFileContent(name, reader.result);
1362
+ cb(null, reader.result);
1445
1363
  }
1446
1364
  });
1447
1365
  if (useBinary) {
@@ -1452,93 +1370,8 @@
1452
1370
  }
1453
1371
  }
1454
1372
 
1455
- function importFileContent(fileName, content) {
1456
- var fileType = internal.guessInputType(fileName, content),
1457
- importOpts = readImportOpts(),
1458
- matches = findMatchingShp(fileName),
1459
- dataset, lyr;
1460
-
1461
- // Add dbf data to a previously imported .shp file with a matching name
1462
- // (.shp should have been queued before .dbf)
1463
- if (fileType == 'dbf' && matches.length > 0) {
1464
- // find an imported .shp layer that is missing attribute data
1465
- // (if multiple matches, try to use the most recently imported one)
1466
- dataset = matches.reduce(function(memo, d) {
1467
- if (!d.layers[0].data) {
1468
- memo = d;
1469
- }
1470
- return memo;
1471
- }, null);
1472
- if (dataset) {
1473
- lyr = dataset.layers[0];
1474
- lyr.data = new internal.ShapefileTable(content, importOpts.encoding);
1475
- if (lyr.shapes && lyr.data.size() != lyr.shapes.length) {
1476
- stop$1("Different number of records in .shp and .dbf files");
1477
- }
1478
- if (!lyr.geometry_type) {
1479
- // kludge: trigger display of table cells if .shp has null geometry
1480
- // TODO: test case if lyr is not the current active layer
1481
- model.updated({});
1482
- }
1483
- procNextQueuedFile();
1484
- return;
1485
- }
1486
- }
1487
-
1488
- if (fileType == 'shx') {
1489
- // save .shx for use when importing .shp
1490
- // (queue should be sorted so that .shx is processed before .shp)
1491
- cachedFiles[fileName.toLowerCase()] = {filename: fileName, content: content};
1492
- procNextQueuedFile();
1493
- return;
1494
- }
1495
-
1496
- // Add .prj file to previously imported .shp file
1497
- if (fileType == 'prj') {
1498
- matches.forEach(function(d) {
1499
- if (!d.info.prj) {
1500
- d.info.prj = content;
1501
- }
1502
- });
1503
- procNextQueuedFile();
1504
- return;
1505
- }
1506
-
1507
- importNewDataset(fileType, fileName, content, importOpts);
1508
- }
1509
-
1510
- function importNewDataset(fileType, fileName, content, importOpts) {
1511
- var size = content.byteLength || content.length, // ArrayBuffer or string
1512
- delay = 0;
1513
-
1514
- // show importing message if file is large
1515
- if (size > 4e7) {
1516
- gui.showProgressMessage('Importing');
1517
- delay = 35;
1518
- }
1519
- setTimeout(function() {
1520
- var dataset;
1521
- var input = {};
1522
- try {
1523
- input[fileType] = {filename: fileName, content: content};
1524
- if (fileType == 'shp') {
1525
- // shx file should already be cached, if it was added together with the shp
1526
- input.shx = cachedFiles[fileName.replace(/shp$/i, 'shx').toLowerCase()] || null;
1527
- }
1528
- dataset = internal.importContent(input, importOpts);
1529
- // save import options for use by repair control, etc.
1530
- dataset.info.import_options = importOpts;
1531
- gui.session.fileImported(fileName, readImportOptsAsString());
1532
- addDataset(dataset);
1533
-
1534
- } catch(e) {
1535
- handleImportError(e, fileName);
1536
- }
1537
- }, delay);
1538
- }
1539
-
1540
1373
  function handleImportError(e, fileName) {
1541
- var msg = utils$1.isString(e) ? e : e.message;
1374
+ var msg = utils.isString(e) ? e : e.message;
1542
1375
  if (fileName) {
1543
1376
  msg = "Error importing <i>" + fileName + "</i><br>" + msg;
1544
1377
  }
@@ -1547,34 +1380,6 @@
1547
1380
  console.error(e);
1548
1381
  }
1549
1382
 
1550
- function handleZipFiles(files) {
1551
- return files.filter(function(file) {
1552
- var isZip = internal.isZipFile(file.name);
1553
- if (isZip) {
1554
- importZipFile(file);
1555
- }
1556
- return !isZip;
1557
- });
1558
- }
1559
-
1560
- function importZipFile(file) {
1561
- // gui.showProgressMessage('Importing');
1562
- setTimeout(function() {
1563
- GUI.readZipFile(file, function(err, files) {
1564
- if (err) {
1565
- handleImportError(err, file.name);
1566
- } else {
1567
- // don't try to import .txt files from zip files
1568
- // (these would be parsed as dsv and throw errows)
1569
- files = files.filter(function(f) {
1570
- return !/\.txt$/i.test(f.name);
1571
- });
1572
- receiveFiles(files);
1573
- }
1574
- });
1575
- }, 35);
1576
- }
1577
-
1578
1383
  function prepFilesForDownload(names) {
1579
1384
  var items = names.map(function(name) {
1580
1385
  var isUrl = /:\/\//.test(name);
@@ -1596,7 +1401,7 @@
1596
1401
 
1597
1402
  function downloadFiles(paths) {
1598
1403
  var items = prepFilesForDownload(paths);
1599
- utils$1.reduceAsync(items, [], downloadNextFile, function(err, files) {
1404
+ utils.reduceAsync(items, [], downloadNextFile, function(err, files) {
1600
1405
  if (err) {
1601
1406
  gui.alert(err);
1602
1407
  } else if (!files.length) {
@@ -1620,37 +1425,107 @@
1620
1425
  });
1621
1426
  }
1622
1427
 
1623
- function downloadNextFile_v1(memo, item, next) {
1624
- var req = new XMLHttpRequest();
1625
- var blob;
1626
- req.responseType = 'blob';
1627
- req.addEventListener('load', function(e) {
1628
- if (req.status == 200) {
1629
- blob = req.response;
1630
- }
1631
- });
1632
- req.addEventListener('progress', function(e) {
1633
- if (!e.lengthComputable) return;
1634
- var pct = e.loaded / e.total;
1635
- if (catalog) catalog.progress(pct);
1428
+ function wait(ms) {
1429
+ return new Promise(resolve => setTimeout(resolve, ms));
1430
+ }
1431
+
1432
+ function runAsync(fn, arg) {
1433
+ return new Promise((resolve, reject) => {
1434
+ fn(arg, function(err, data) {
1435
+ return err ? reject(err) : resolve(data);
1436
+ });
1636
1437
  });
1637
- req.addEventListener('loadend', function() {
1638
- var err;
1639
- if (req.status == 404) {
1640
- err = "Not&nbsp;found:&nbsp;" + item.name;
1641
- } else if (!blob) {
1642
- // Errors like DNS lookup failure, no CORS headers, no network connection
1643
- // all are status 0 - it seems impossible to show a more specific message
1644
- // actual reason is displayed on the console
1645
- err = "Error&nbsp;loading&nbsp;" + item.name + ". Possible causes include: wrong URL, no network connection, server not configured for cross-domain sharing (CORS).";
1438
+ }
1439
+
1440
+ async function readZipFile(file) {
1441
+ var files;
1442
+ await wait(35); // pause a beat so status message can display
1443
+ try {
1444
+ files = await runAsync(GUI.readZipFile, file);
1445
+ // don't try to import .txt files from zip files
1446
+ // (these would be parsed as dsv and throw errows)
1447
+ files = files.filter(function(f) {
1448
+ return !/\.txt$/i.test(f.name);
1449
+ });
1450
+ } catch(e) {
1451
+ handleImportError(e, file.name);
1452
+ files = [];
1453
+ }
1454
+ return files;
1455
+ }
1456
+
1457
+ async function readFileData(file) {
1458
+ try {
1459
+ var content = await runAsync(readContentFileAsync, file);
1460
+ return {
1461
+ content: content,
1462
+ size: content.byteLength || content.length, // ArrayBuffer or string
1463
+ name: file.name,
1464
+ basename: internal.getFileBase(file.name).toLowerCase(),
1465
+ type: internal.guessInputType(file.name, content)
1466
+ };
1467
+ } catch (e) {
1468
+ handleImportError("Web browser was unable to load the file.", file.name);
1469
+ }
1470
+ return null;
1471
+ }
1472
+
1473
+ async function readFiles(files) {
1474
+ var data = [], d;
1475
+ for (var file of files) {
1476
+ d = await readFileData(file);
1477
+ if (d) data.push(d);
1478
+ }
1479
+ return data;
1480
+ }
1481
+
1482
+ function groupFilesForImport(data, importOpts) {
1483
+ var names = importOpts.name ? [importOpts.name] : null;
1484
+ if (initialImport && opts.name) { // name from mapshaper-gui --name option
1485
+ names = opts.name.split(',');
1486
+ }
1487
+
1488
+ function key(basename, type) {
1489
+ return basename + '.' + type;
1490
+ }
1491
+ function hasShp(basename) {
1492
+ var shpKey = key(basename, 'shp');
1493
+ return data.some(d => key(d.basename, d.type) == shpKey);
1494
+ }
1495
+ data.forEach(d => {
1496
+ if (d.type == 'shp' || !isShapefilePart(d.name)) {
1497
+ d.group = key(d.basename, d.type);
1498
+ d.filename = d.name;
1499
+ } else if (hasShp(d.basename)) {
1500
+ d.group = key(d.basename, 'shp');
1501
+ } else if (d.type == 'dbf') {
1502
+ d.filename = d.name;
1503
+ d.group = key(d.basename, 'dbf');
1646
1504
  } else {
1647
- blob.name = item.basename;
1648
- memo.push(blob);
1505
+ // shapefile part without a .shp file
1506
+ d.group = null;
1649
1507
  }
1650
- next(err, memo);
1651
1508
  });
1652
- req.open('GET', item.url);
1653
- req.send();
1509
+ var index = {};
1510
+ var groups = [];
1511
+ data.forEach(d => {
1512
+ if (!d.group) return;
1513
+ var g = index[d.group];
1514
+ if (!g) {
1515
+ g = {};
1516
+ g.layername = names ? names[groups.length] || names[names.length - 1] : null;
1517
+ groups.push(g);
1518
+ index[d.group] = g;
1519
+ }
1520
+ g.size = (g.size || 0) + d.size; // accumulate size
1521
+ g[d.type] = {
1522
+ filename: d.name,
1523
+ content: d.content
1524
+ };
1525
+ // kludge: stash import name for session history
1526
+ if (d.filename) g.filename = d.filename;
1527
+ });
1528
+ return groups;
1654
1529
  }
1655
1530
  }
1656
1531
 
@@ -1692,7 +1567,7 @@
1692
1567
  var defaults = {
1693
1568
  space: 7
1694
1569
  };
1695
- opts = utils$1.extend(defaults, opts);
1570
+ opts = utils.extend(defaults, opts);
1696
1571
 
1697
1572
  var _pct = 0;
1698
1573
  var _track,
@@ -1745,7 +1620,7 @@
1745
1620
  };
1746
1621
 
1747
1622
  function setHandlePos(x, fire) {
1748
- x = utils$1.clamp(x, 0, size());
1623
+ x = utils.clamp(x, 0, size());
1749
1624
  var pct = x / size();
1750
1625
  if (pct != _pct) {
1751
1626
  _pct = pct;
@@ -1760,7 +1635,7 @@
1760
1635
  }
1761
1636
  }
1762
1637
 
1763
- utils$1.inherit(Slider, EventDispatcher);
1638
+ utils.inherit(Slider, EventDispatcher);
1764
1639
 
1765
1640
  /*
1766
1641
  How changes in the simplify control should affect other components
@@ -1828,7 +1703,7 @@
1828
1703
  slider.on('change', function(e) {
1829
1704
  var pct = fromSliderPct(e.pct);
1830
1705
  text.value(pct);
1831
- pct = utils$1.parsePercent(text.text()); // use rounded value (for consistency w/ cli)
1706
+ pct = utils.parsePercent(text.text()); // use rounded value (for consistency w/ cli)
1832
1707
  onChange(pct);
1833
1708
  });
1834
1709
  slider.on('start', function(e) {
@@ -1849,7 +1724,7 @@
1849
1724
  else if (pct < 0.01) decimals = 3;
1850
1725
  else if (pct < 1) decimals = 2;
1851
1726
  else if (pct < 100) decimals = 1;
1852
- return utils$1.formatNumberForDisplay(pct, decimals) + "%";
1727
+ return utils.formatNumberForDisplay(pct, decimals) + "%";
1853
1728
  });
1854
1729
 
1855
1730
  text.parser(function(s) {
@@ -2101,7 +1976,7 @@
2101
1976
 
2102
1977
  // Replace error function in mapshaper lib
2103
1978
  var error = function() {
2104
- stop.apply(null, utils$1.toArray(arguments));
1979
+ stop.apply(null, utils.toArray(arguments));
2105
1980
  };
2106
1981
 
2107
1982
  var message = function() {
@@ -2115,11 +1990,11 @@
2115
1990
  // replace CLI version of writeFiles()
2116
1991
  internal.replaceWriteFiles(function(files, opts, done) {
2117
1992
  var filename;
2118
- if (!utils$1.isArray(files) || files.length === 0) {
1993
+ if (!utils.isArray(files) || files.length === 0) {
2119
1994
  done("Nothing to export");
2120
1995
  } else if (GUI.canSaveToServer() && !opts.save_to_download_folder) {
2121
- var paths = internal.getOutputPaths(utils$1.pluck(files, 'filename'), opts);
2122
- var data = utils$1.pluck(files, 'content');
1996
+ var paths = internal.getOutputPaths(utils.pluck(files, 'filename'), opts);
1997
+ var data = utils.pluck(files, 'content');
2123
1998
  saveFilesToServer(paths, data, function(err) {
2124
1999
  var msg;
2125
2000
  if (err) {
@@ -2138,7 +2013,7 @@
2138
2013
  } else if (files.length == 1) {
2139
2014
  saveBlobToDownloadFolder(files[0].filename, new Blob([files[0].content]), done);
2140
2015
  } else {
2141
- filename = internal.getCommonFileBase(utils$1.pluck(files, 'filename')) || "output";
2016
+ filename = internal.getCommonFileBase(utils.pluck(files, 'filename')) || "output";
2142
2017
  saveZipFile(filename + ".zip", files, done);
2143
2018
  }
2144
2019
  });
@@ -2160,10 +2035,10 @@
2160
2035
  // try to match import filename of this dataset
2161
2036
  if (d.info.input_files[0] == src) return d;
2162
2037
  // try to match name of a layer in this dataset
2163
- lyr = utils$1.find(d.layers, function(lyr) {return lyr.name == src;});
2038
+ lyr = utils.find(d.layers, function(lyr) {return lyr.name == src;});
2164
2039
  return lyr ? internal.isolateLayer(lyr, d) : null;
2165
2040
  }, null);
2166
- if (!retn) stop$1("Missing data layer [" + src + "]");
2041
+ if (!retn) stop("Missing data layer [" + src + "]");
2167
2042
  return retn;
2168
2043
  }
2169
2044
 
@@ -2172,7 +2047,7 @@
2172
2047
  // Return a copy with layers duplicated, so changes won't affect original layers
2173
2048
  // This makes an (unsafe) assumption that the dataset arcs won't be changed...
2174
2049
  // need to rethink this.
2175
- return utils$1.defaults({
2050
+ return utils.defaults({
2176
2051
  layers: dataset.layers.map(internal.copyLayer)
2177
2052
  }, dataset);
2178
2053
  });
@@ -2244,7 +2119,7 @@
2244
2119
  }
2245
2120
 
2246
2121
  function projectPointsForDisplay(lyr, src, dest) {
2247
- var copy = utils$1.extend({}, lyr);
2122
+ var copy = utils.extend({}, lyr);
2248
2123
  var proj = internal.getProjTransform2(src, dest);
2249
2124
  copy.shapes = internal.cloneShapes(lyr.shapes);
2250
2125
  internal.projectPointLayer(copy, proj);
@@ -2642,7 +2517,7 @@
2642
2517
  } else if (cmd == 'history') {
2643
2518
  toLog(gui.session.toCommandLineString());
2644
2519
  } else if (cmd == 'layers') {
2645
- message$1("Available layers:",
2520
+ message("Available layers:",
2646
2521
  internal.getFormattedLayerList(model));
2647
2522
  } else if (cmd == 'close' || cmd == 'exit' || cmd == 'quit') {
2648
2523
  turnOff();
@@ -2731,7 +2606,7 @@
2731
2606
  }
2732
2607
 
2733
2608
  function onError(err) {
2734
- if (utils$1.isString(err)) {
2609
+ if (utils.isString(err)) {
2735
2610
  consoleStop(err);
2736
2611
  } else if (err.name == 'UserError') {
2737
2612
  // stop() has already been called, don't need to log
@@ -2746,7 +2621,7 @@
2746
2621
  function consoleStop() {
2747
2622
  var msg = GUI.formatMessageArgs(arguments);
2748
2623
  consoleWarning(msg);
2749
- throw new UserError$1(msg);
2624
+ throw new UserError(msg);
2750
2625
  }
2751
2626
 
2752
2627
  function consoleWarning() {
@@ -2910,7 +2785,7 @@
2910
2785
  // console.log("first intersection:", internal.getIntersectionDebugData(XX[0], arcs));
2911
2786
  pointLyr = {geometry_type: 'point', shapes: [internal.getIntersectionPoints(XX)]};
2912
2787
  map.setIntersectionLayer(pointLyr, {layers:[pointLyr]});
2913
- readout.html(utils$1.format('<span class="icon"></span>%s line intersection%s <img class="close-btn" src="images/close.png">', n, utils$1.pluralSuffix(n)));
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)));
2914
2789
  readout.findChild('.close-btn').on('click', dismiss);
2915
2790
  } else {
2916
2791
  map.setIntersectionLayer(null);
@@ -2919,7 +2794,7 @@
2919
2794
  }
2920
2795
  }
2921
2796
 
2922
- utils$1.inherit(RepairControl, EventDispatcher);
2797
+ utils.inherit(RepairControl, EventDispatcher);
2923
2798
 
2924
2799
  function filterLayerByIds(lyr, ids) {
2925
2800
  var shapes;
@@ -2927,7 +2802,7 @@
2927
2802
  shapes = ids.map(function(id) {
2928
2803
  return lyr.shapes[id];
2929
2804
  });
2930
- return utils$1.defaults({shapes: shapes, data: null}, lyr);
2805
+ return utils.defaults({shapes: shapes, data: null}, lyr);
2931
2806
  }
2932
2807
  return lyr;
2933
2808
  }
@@ -2980,7 +2855,7 @@
2980
2855
  });
2981
2856
 
2982
2857
  internal.writeFiles = function() {
2983
- error$1(unsupportedMsg);
2858
+ error(unsupportedMsg);
2984
2859
  };
2985
2860
  } else {
2986
2861
  new SimpleButton(menu.findChild('.save-btn').addClass('default-btn')).on('click', onExportClick);
@@ -3016,7 +2891,7 @@
3016
2891
  setTimeout(function() {
3017
2892
  exportMenuSelection(layers, function(err) {
3018
2893
  if (err) {
3019
- if (utils$1.isString(err)) {
2894
+ if (utils.isString(err)) {
3020
2895
  gui.alert(err);
3021
2896
  } else {
3022
2897
  // stack seems to change if Error is logged directly
@@ -3063,7 +2938,7 @@
3063
2938
  // without changing its name elsewhere
3064
2939
  layer: Object.assign({}, o.layer)
3065
2940
  };
3066
- var html = utils$1.format(template, i + 1, target.layer.name || '[unnamed layer]');
2941
+ var html = utils.format(template, i + 1, target.layer.name || '[unnamed layer]');
3067
2942
  // return {layer: o.layer, html: html};
3068
2943
  var el = El('div').html(html).addClass('layer-item');
3069
2944
  var box = el.findChild('input').node();
@@ -3173,9 +3048,9 @@
3173
3048
 
3174
3049
  function initFormatMenu() {
3175
3050
  var defaults = ['shapefile', 'geojson', 'topojson', 'json', 'dsv', 'svg'];
3176
- var formats = utils$1.uniq(defaults.concat(getInputFormats()));
3051
+ var formats = utils.uniq(defaults.concat(getInputFormats()));
3177
3052
  var items = formats.map(function(fmt) {
3178
- return utils$1.format('<div><label><input type="radio" name="format" value="%s"' +
3053
+ return utils.format('<div><label><input type="radio" name="format" value="%s"' +
3179
3054
  ' class="radio">%s</label></div>', fmt, internal.getFormatName(fmt));
3180
3055
  });
3181
3056
  menu.findChild('.export-formats').html(items.join('\n'));
@@ -3338,7 +3213,7 @@
3338
3213
  // can be used as unique identifiers for caching rendered HTML, and as
3339
3214
  // an id for layer menu event handlers
3340
3215
  if (!lyr.menu_id || uniqIds[lyr.menu_id]) {
3341
- lyr.menu_id = utils$1.getUniqueName();
3216
+ lyr.menu_id = utils.getUniqueName();
3342
3217
  }
3343
3218
  uniqIds[lyr.menu_id] = true;
3344
3219
  if (isPinnable(lyr)) pinnableCount++;
@@ -3493,7 +3368,7 @@
3493
3368
  GUI.onClick(entry, function() {
3494
3369
  var target = findLayerById(id);
3495
3370
  // don't select if user is typing or dragging
3496
- if (!GUI.getInputElement() && !dragging) {
3371
+ if (!GUI.textIsSelected() && !dragging) {
3497
3372
  gui.clearMode();
3498
3373
  if (!map.isActiveLayer(target.layer)) {
3499
3374
  model.selectLayer(target.layer, target.dataset);
@@ -3511,1795 +3386,172 @@
3511
3386
  type = lyr.geometry_type + ' feature';
3512
3387
  }
3513
3388
  if (type) {
3514
- str = utils$1.format('%,d %s%s', n, type, utils$1.pluralSuffix(n));
3389
+ str = utils.format('%,d %s%s', n, type, utils.pluralSuffix(n));
3515
3390
  } else {
3516
3391
  str = "[empty]";
3517
3392
  }
3518
3393
  return str;
3519
3394
  }
3520
3395
 
3521
- function getWarnings(lyr, dataset) {
3522
- var file = internal.getLayerSourceFile(lyr, dataset);
3523
- var missing = [];
3524
- var msg;
3525
- if (utils$1.endsWith(file, '.shp') && lyr == dataset.layers[0]) {
3526
- if (!lyr.data) {
3527
- missing.push('.dbf');
3528
- }
3529
- if (!dataset.info.prj && !dataset.info.crs) {
3530
- missing.push('.prj');
3531
- }
3532
- }
3533
- if (missing.length) {
3534
- msg = 'missing ' + missing.join(' and ') + ' data';
3535
- }
3536
- return msg;
3537
- }
3538
-
3539
- function describeSrc(lyr, dataset) {
3540
- return internal.getLayerSourceFile(lyr, dataset);
3541
- }
3542
-
3543
-
3544
- function isPinnable(lyr) {
3545
- return internal.layerHasGeometry(lyr) || internal.layerHasFurniture(lyr);
3546
- }
3547
-
3548
-
3549
-
3550
- function rowHTML(c1, c2, cname) {
3551
- return utils$1.format('<div class="row%s"><div class="col1">%s</div>' +
3552
- '<div class="col2">%s</div></div>', cname ? ' ' + cname : '', c1, c2);
3553
- }
3554
- }
3555
-
3556
- function SessionHistory(gui) {
3557
- var commands = [];
3558
- // commands that can be ignored when checking for unsaved changes
3559
- var nonEditingCommands = 'i,target,info,version,verbose,projections,inspect,help,h,encodings,calc'.split(',');
3560
-
3561
- this.unsavedChanges = function() {
3562
- var cmd, cmdName;
3563
- for (var i=commands.length - 1; i >= 0; i--) {
3564
- cmdName = getCommandName(commands[i]);
3565
- if (cmdName == 'o') break;
3566
- if (nonEditingCommands.includes(cmdName)) continue;
3567
- return true;
3568
- }
3569
- return false;
3570
- };
3571
-
3572
- this.fileImported = function(file, optStr) {
3573
- var cmd = '-i ' + file;
3574
- if (optStr) {
3575
- cmd += ' ' + optStr;
3576
- }
3577
- commands.push(cmd);
3578
- };
3579
-
3580
- this.layerRenamed = function(lyr, name) {
3581
- var currTarget = getCurrentTarget();
3582
- var layerTarget = getTargetFromLayer(lyr);
3583
- if (currTarget == layerTarget) {
3584
- commands.push('-rename-layers ' + name);
3585
- } else {
3586
- commands.push('-rename-layers ' + name + ' target=' + layerTarget);
3587
- commands.push('-target ' + currTarget);
3588
- }
3589
- };
3590
-
3591
- this.consoleCommands = function(str) {
3592
- commands.push(str); // todo: split commands?
3593
- };
3594
-
3595
- this.simplificationApplied = function(optStr) {
3596
- commands.push('-simplify ' + optStr);
3597
- };
3598
-
3599
- this.simplificationRepair = function() {
3600
- // TODO: improve this... repair does not necessarily apply to most recent
3601
- // simplification command
3602
- // consider adding a (hidden) repair command to handle this event
3603
- var i = indexOfLastCommand('-simplify');
3604
- if (i > -1) {
3605
- commands[i] = commands[i].replace(' no-repair', '');
3606
- }
3607
- };
3608
-
3609
- this.updateSimplificationPct = function(pct) {
3610
- var i = indexOfLastCommand('-simplify');
3611
- if (i > -1) {
3612
- commands[i] = commands[i].replace(/percentage=[^ ]+/, 'percentage=' + pct);
3613
- }
3614
- };
3615
-
3616
- this.dataValueUpdated = function(id, field, value) {
3617
- var cmd = `-each 'd[${JSON.stringify(field)}] = ${JSON.stringify(value)}' where='this.id == ${id}'`;
3618
- commands.push(cmd);
3619
- };
3620
-
3621
- this.layersExported = function(ids, optStr) {
3622
- var layers = gui.model.getLayers();
3623
- var cmd = '-o';
3624
- if (layers.length > 1) {
3625
- cmd += ' target=' + ids.map(getTargetFromId).join(',');
3626
- }
3627
- if (optStr) {
3628
- cmd += ' ' + optStr;
3629
- }
3630
- commands.push(cmd);
3631
- };
3632
-
3633
- this.setTargetLayer = function(lyr) {
3634
- var layers = gui.model.getLayers();
3635
- if (layers.length > 1) {
3636
- if (indexOfLastCommand('-target') == commands.length - 1) {
3637
- commands.pop(); // if last commands was -target, remove it
3638
- }
3639
- commands.push('-target ' + getTargetFromLayer(lyr));
3640
- }
3641
- };
3642
-
3643
- this.toCommandLineString = function() {
3644
- var str = commands.join(' \\\n ');
3645
- return 'mapshaper ' + str;
3646
- };
3647
-
3648
- function getCommandName(cmd) {
3649
- var rxp = /^-([a-z0-9-]+)/;
3650
- var match = rxp.exec(cmd);
3651
- return match ? match[1] : null;
3652
- }
3653
-
3654
- function getCurrentTarget() {
3655
- return getTargetFromLayer(gui.model.getActiveLayer().layer);
3656
- }
3657
-
3658
- function indexOfLastCommand(cmd) {
3659
- return commands.reduce(function(memo, str, i) {
3660
- return str.indexOf(cmd) === 0 ? i : memo;
3661
- }, -1);
3662
- }
3663
-
3664
- function getTargetFromId(id) {
3665
- var layers = gui.model.getLayers();
3666
- return getTargetFromLayer(layers[id - 1].layer);
3667
- }
3668
-
3669
- function getTargetFromLayer(lyr) {
3670
- var id = internal.getLayerTargetId(gui.model, lyr);
3671
- return internal.formatOptionValue(id);
3672
- }
3673
- }
3674
-
3675
- var Buffer = require('buffer').Buffer; // works with browserify
3676
-
3677
- var uniqCount = 0;
3678
- function getUniqueName(prefix) {
3679
- return (prefix || "__id_") + (++uniqCount);
3680
- }
3681
-
3682
- function isFunction(obj) {
3683
- return typeof obj == 'function';
3684
- }
3685
-
3686
- function isObject(obj) {
3687
- return obj === Object(obj); // via underscore
3688
- }
3689
-
3690
- function clamp(val, min, max) {
3691
- return val < min ? min : (val > max ? max : val);
3692
- }
3693
-
3694
- function isArray(obj) {
3695
- return Array.isArray(obj);
3696
- }
3697
-
3698
- // Is obj a valid number or NaN? (test if obj is type number)
3699
- function isNumber(obj) {
3700
- return obj != null && obj.constructor == Number;
3701
- }
3702
-
3703
- function isValidNumber(val) {
3704
- return isNumber(val) && !isNaN(val);
3705
- }
3706
-
3707
- // Similar to isFinite() but does not coerce strings or other types
3708
- function isFiniteNumber(val) {
3709
- return isValidNumber(val) && val !== Infinity && val !== -Infinity;
3710
- }
3711
-
3712
- // This uses type conversion
3713
- // export function isFiniteNumber(val) {
3714
- // return val > -Infinity && val < Infinity;
3715
- // }
3716
-
3717
- function isNonNegNumber(val) {
3718
- return isNumber(val) && val >= 0;
3719
- }
3720
-
3721
- function isInteger(obj) {
3722
- return isNumber(obj) && ((obj | 0) === obj);
3723
- }
3724
-
3725
- function isEven(obj) {
3726
- return (obj % 2) === 0;
3727
- }
3728
-
3729
- function isOdd(obj) {
3730
- return (obj % 2) === 1;
3731
- }
3732
-
3733
- function isString(obj) {
3734
- return obj != null && obj.toString === String.prototype.toString;
3735
- // TODO: replace w/ something better.
3736
- }
3737
-
3738
- function isDate(obj) {
3739
- return !!obj && obj.getTime === Date.prototype.getTime;
3740
- }
3741
-
3742
- function isBoolean(obj) {
3743
- return obj === true || obj === false;
3744
- }
3745
-
3746
- function formatDateISO(d) {
3747
- if (!isDate(d)) return '';
3748
- return d.toISOString().replace(':00.000Z', 'Z');
3749
- }
3750
-
3751
- // Convert an array-like object to an Array, or make a copy if @obj is an Array
3752
- function toArray(obj) {
3753
- var arr;
3754
- if (!isArrayLike(obj)) error("toArray() requires an array-like object");
3755
- try {
3756
- arr = Array.prototype.slice.call(obj, 0); // breaks in ie8
3757
- } catch(e) {
3758
- // support ie8
3759
- arr = [];
3760
- for (var i=0, n=obj.length; i<n; i++) {
3761
- arr[i] = obj[i];
3762
- }
3763
- }
3764
- return arr;
3765
- }
3766
-
3767
- // Array like: has length property, is numerically indexed and mutable.
3768
- // TODO: try to detect objects with length property but no indexed data elements
3769
- function isArrayLike(obj) {
3770
- if (!obj) return false;
3771
- if (isArray(obj)) return true;
3772
- if (isString(obj)) return false;
3773
- if (obj.length === 0) return true;
3774
- if (obj.length > 0) return true;
3775
- return false;
3776
- }
3777
-
3778
- // See https://raw.github.com/kvz/phpjs/master/functions/strings/addslashes.js
3779
- function addslashes(str) {
3780
- return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
3781
- }
3782
-
3783
- // Escape a literal string to use in a regexp.
3784
- // Ref.: http://simonwillison.net/2006/Jan/20/escape/
3785
- function regexEscape(str) {
3786
- return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
3787
- }
3788
-
3789
-
3790
- // See https://github.com/janl/mustache.js/blob/master/mustache.js
3791
- var entityMap = {
3792
- '&': '&amp;',
3793
- '<': '&lt;',
3794
- '>': '&gt;',
3795
- '"': '&quot;',
3796
- "'": '&#39;',
3797
- '/': '&#x2F;'
3798
- };
3799
- function htmlEscape(s) {
3800
- return String(s).replace(/[&<>"'\/]/g, function(s) {
3801
- return entityMap[s];
3802
- });
3803
- }
3804
-
3805
- function defaults(dest) {
3806
- for (var i=1, n=arguments.length; i<n; i++) {
3807
- var src = arguments[i] || {};
3808
- for (var key in src) {
3809
- if (key in dest === false && src.hasOwnProperty(key)) {
3810
- dest[key] = src[key];
3811
- }
3812
- }
3813
- }
3814
- return dest;
3815
- }
3816
-
3817
- function extend(o) {
3818
- var dest = o || {},
3819
- n = arguments.length,
3820
- key, i, src;
3821
- for (i=1; i<n; i++) {
3822
- src = arguments[i] || {};
3823
- for (key in src) {
3824
- if (src.hasOwnProperty(key)) {
3825
- dest[key] = src[key];
3826
- }
3827
- }
3828
- }
3829
- return dest;
3830
- }
3831
-
3832
- // Pseudoclassical inheritance
3833
- //
3834
- // Inherit from a Parent function:
3835
- // inherit(Child, Parent);
3836
- // Call parent's constructor (inside child constructor):
3837
- // this.__super__([args...]);
3838
- function inherit(targ, src) {
3839
- var f = function() {
3840
- if (this.__super__ == f) {
3841
- // add __super__ of parent to front of lookup chain
3842
- // so parent class constructor can call its parent using this.__super__
3843
- this.__super__ = src.prototype.__super__;
3844
- // call parent constructor function. this.__super__ now points to parent-of-parent
3845
- src.apply(this, arguments);
3846
- // remove temp __super__, expose targ.prototype.__super__ again
3847
- delete this.__super__;
3848
- }
3849
- };
3850
-
3851
- f.prototype = src.prototype || src; // added || src to allow inheriting from objects as well as functions
3852
- // Extend targ prototype instead of wiping it out --
3853
- // in case inherit() is called after targ.prototype = {stuff}; statement
3854
- targ.prototype = extend(new f(), targ.prototype); //
3855
- targ.prototype.constructor = targ;
3856
- targ.prototype.__super__ = f;
3857
- }
3858
-
3859
-
3860
- // Call @iter on each member of an array (similar to Array#reduce(iter))
3861
- // iter: function(memo, item, callback)
3862
- // Call @done when all members have been processed or if an error occurs
3863
- // done: function(err, memo)
3864
- // @memo: Initial value
3865
- //
3866
- function reduceAsync(arr, memo, iter, done) {
3867
- var call = typeof setImmediate == 'undefined' ? setTimeout : setImmediate;
3868
- var i=0;
3869
- next(null, memo);
3870
-
3871
- function next(err, memo) {
3872
- // Detach next operation from call stack to prevent overflow
3873
- // Don't use setTimeout(, 0) if setImmediate is available
3874
- // (setTimeout() can introduce a long delay if previous operation was slow,
3875
- // as of Node 0.10.32 -- a bug?)
3876
- if (err) {
3877
- return done(err, null);
3878
- }
3879
- call(function() {
3880
- if (i < arr.length === false) {
3881
- done(null, memo);
3882
- } else {
3883
- iter(memo, arr[i++], next);
3884
- }
3885
- }, 0);
3886
- }
3887
- }
3888
-
3889
-
3890
- // Append elements of @src array to @dest array
3891
- function merge(dest, src) {
3892
- if (!isArray(dest) || !isArray(src)) {
3893
- error("Usage: merge(destArray, srcArray);");
3894
- }
3895
- for (var i=0, n=src.length; i<n; i++) {
3896
- dest.push(src[i]);
3897
- }
3898
- return dest;
3899
- }
3900
-
3901
- // Returns elements in arr and not in other
3902
- // (similar to underscore diff)
3903
- function difference(arr, other) {
3904
- var index = arrayToIndex(other);
3905
- return arr.filter(function(el) {
3906
- return !Object.prototype.hasOwnProperty.call(index, el);
3907
- });
3908
- }
3909
-
3910
- // Return the intersection of two arrays
3911
- function intersection(a, b) {
3912
- return a.filter(function(el) {
3913
- return b.includes(el);
3914
- });
3915
- }
3916
-
3917
- function indexOf(arr, item) {
3918
- var nan = item !== item;
3919
- for (var i = 0, len = arr.length || 0; i < len; i++) {
3920
- if (arr[i] === item) return i;
3921
- if (nan && arr[i] !== arr[i]) return i;
3922
- }
3923
- return -1;
3924
- }
3925
-
3926
- // Test a string or array-like object for existence of substring or element
3927
- function contains(container, item) {
3928
- if (isString(container)) {
3929
- return container.indexOf(item) != -1;
3930
- }
3931
- else if (isArrayLike(container)) {
3932
- return indexOf(container, item) != -1;
3933
- }
3934
- error("Expected Array or String argument");
3935
- }
3936
-
3937
- function some(arr, test) {
3938
- return arr.reduce(function(val, item) {
3939
- return val || test(item); // TODO: short-circuit?
3940
- }, false);
3941
- }
3942
-
3943
- function every(arr, test) {
3944
- return arr.reduce(function(val, item) {
3945
- return val && test(item);
3946
- }, true);
3947
- }
3948
-
3949
- function find(arr, test, ctx) {
3950
- var matches = arr.filter(test, ctx);
3951
- return matches.length === 0 ? null : matches[0];
3952
- }
3953
-
3954
- function range(len, start, inc) {
3955
- var arr = [],
3956
- v = start === void 0 ? 0 : start,
3957
- i = inc === void 0 ? 1 : inc;
3958
- while(len--) {
3959
- arr.push(v);
3960
- v += i;
3961
- }
3962
- return arr;
3963
- }
3964
-
3965
- function repeat(times, func) {
3966
- var values = [],
3967
- val;
3968
- for (var i=0; i<times; i++) {
3969
- val = func(i);
3970
- if (val !== void 0) {
3971
- values[i] = val;
3972
- }
3973
- }
3974
- return values.length > 0 ? values : void 0;
3975
- }
3976
-
3977
- // Calc sum, skip falsy and NaN values
3978
- // Assumes: no other non-numeric objects in array
3979
- //
3980
- function sum(arr, info) {
3981
- if (!isArrayLike(arr)) error ("sum() expects an array, received:", arr);
3982
- var tot = 0,
3983
- nan = 0,
3984
- val;
3985
- for (var i=0, n=arr.length; i<n; i++) {
3986
- val = arr[i];
3987
- if (val) {
3988
- tot += val;
3989
- } else if (isNaN(val)) {
3990
- nan++;
3991
- }
3992
- }
3993
- if (info) {
3994
- info.nan = nan;
3995
- }
3996
- return tot;
3997
- }
3998
-
3999
- // Calculate min and max values of an array, ignoring NaN values
4000
- function getArrayBounds(arr) {
4001
- var min = Infinity,
4002
- max = -Infinity,
4003
- nan = 0, val;
4004
- for (var i=0, len=arr.length; i<len; i++) {
4005
- val = arr[i];
4006
- if (val !== val) nan++;
4007
- if (val < min) min = val;
4008
- if (val > max) max = val;
4009
- }
4010
- return {
4011
- min: min,
4012
- max: max,
4013
- nan: nan
4014
- };
4015
- }
4016
-
4017
- // export function uniq(src) {
4018
- // var index = {};
4019
- // return src.reduce(function(memo, el) {
4020
- // if (el in index === false) {
4021
- // index[el] = true;
4022
- // memo.push(el);
4023
- // }
4024
- // return memo;
4025
- // }, []);
4026
- // }
4027
-
4028
- function uniq(src) {
4029
- var index = new Set();
4030
- var arr = [];
4031
- var item;
4032
- for (var i=0, n=src.length; i<n; i++) {
4033
- item = src[i];
4034
- if (!index.has(item)) {
4035
- arr.push(item);
4036
- index.add(item);
4037
- }
4038
- }
4039
- return arr;
4040
- }
4041
-
4042
- function pluck(arr, key) {
4043
- return arr.map(function(obj) {
4044
- return obj[key];
4045
- });
4046
- }
4047
-
4048
- function countValues(arr) {
4049
- return arr.reduce(function(memo, val) {
4050
- memo[val] = (val in memo) ? memo[val] + 1 : 1;
4051
- return memo;
4052
- }, {});
4053
- }
4054
-
4055
- function indexOn(arr, k) {
4056
- return arr.reduce(function(index, o) {
4057
- index[o[k]] = o;
4058
- return index;
4059
- }, {});
4060
- }
4061
-
4062
- function groupBy(arr, k) {
4063
- return arr.reduce(function(index, o) {
4064
- var keyval = o[k];
4065
- if (keyval in index) {
4066
- index[keyval].push(o);
4067
- } else {
4068
- index[keyval] = [o];
4069
- }
4070
- return index;
4071
- }, {});
4072
- }
4073
-
4074
- function arrayToIndex(arr, val) {
4075
- var init = arguments.length > 1;
4076
- return arr.reduce(function(index, key) {
4077
- index[key] = init ? val : true;
4078
- return index;
4079
- }, {});
4080
- }
4081
-
4082
- // Support for iterating over array-like objects, like typed arrays
4083
- function forEach(arr, func, ctx) {
4084
- if (!isArrayLike(arr)) {
4085
- throw new Error("#forEach() takes an array-like argument. " + arr);
4086
- }
4087
- for (var i=0, n=arr.length; i < n; i++) {
4088
- func.call(ctx, arr[i], i);
4089
- }
4090
- }
4091
-
4092
- function forEachProperty(o, func, ctx) {
4093
- Object.keys(o).forEach(function(key) {
4094
- func.call(ctx, o[key], key);
4095
- });
4096
- }
4097
-
4098
- function initializeArray(arr, init) {
4099
- for (var i=0, len=arr.length; i<len; i++) {
4100
- arr[i] = init;
4101
- }
4102
- return arr;
4103
- }
4104
-
4105
- function replaceArray(arr, arr2) {
4106
- arr.splice(0, arr.length);
4107
- for (var i=0, n=arr2.length; i<n; i++) {
4108
- arr.push(arr2[i]);
4109
- }
4110
- }
4111
-
4112
- function repeatString(src, n) {
4113
- var str = "";
4114
- for (var i=0; i<n; i++)
4115
- str += src;
4116
- return str;
4117
- }
4118
-
4119
- function splitLines(str) {
4120
- return str.split(/\r?\n/);
4121
- }
4122
-
4123
- function pluralSuffix(count) {
4124
- return count != 1 ? 's' : '';
4125
- }
4126
-
4127
- function endsWith(str, ending) {
4128
- return str.indexOf(ending, str.length - ending.length) !== -1;
4129
- }
4130
-
4131
- function lpad(str, size, pad) {
4132
- pad = pad || ' ';
4133
- str = String(str);
4134
- return repeatString(pad, size - str.length) + str;
4135
- }
4136
-
4137
- function rpad(str, size, pad) {
4138
- pad = pad || ' ';
4139
- str = String(str);
4140
- return str + repeatString(pad, size - str.length);
4141
- }
4142
-
4143
- function trim(str) {
4144
- return ltrim(rtrim(str));
4145
- }
4146
-
4147
- var ltrimRxp = /^\s+/;
4148
- function ltrim(str) {
4149
- return str.replace(ltrimRxp, '');
4150
- }
4151
-
4152
- var rtrimRxp = /\s+$/;
4153
- function rtrim(str) {
4154
- return str.replace(rtrimRxp, '');
4155
- }
4156
-
4157
- function addThousandsSep(str) {
4158
- var fmt = '',
4159
- start = str[0] == '-' ? 1 : 0,
4160
- dec = str.indexOf('.'),
4161
- end = str.length,
4162
- ins = (dec == -1 ? end : dec) - 3;
4163
- while (ins > start) {
4164
- fmt = ',' + str.substring(ins, end) + fmt;
4165
- end = ins;
4166
- ins -= 3;
4167
- }
4168
- return str.substring(0, end) + fmt;
4169
- }
4170
-
4171
- function numToStr(num, decimals) {
4172
- return decimals >= 0 ? num.toFixed(decimals) : String(num);
4173
- }
4174
-
4175
- function formatNumber(val) {
4176
- return val + '';
4177
- }
4178
-
4179
- function formatIntlNumber(val) {
4180
- var str = formatNumber(val);
4181
- return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
4182
- }
4183
-
4184
- function formatNumberForDisplay(num, decimals, nullStr, showPos) {
4185
- var fmt;
4186
- if (isNaN(num)) {
4187
- fmt = nullStr || '-';
4188
- } else {
4189
- fmt = numToStr(num, decimals);
4190
- fmt = addThousandsSep(fmt);
4191
- if (showPos && parseFloat(fmt) > 0) {
4192
- fmt = "+" + fmt;
4193
- }
4194
- }
4195
- return fmt;
4196
- }
4197
-
4198
- function shuffle(arr) {
4199
- var tmp, i, j;
4200
- for (i = arr.length - 1; i > 0; i--) {
4201
- j = Math.floor(Math.random() * (i + 1));
4202
- tmp = arr[i];
4203
- arr[i] = arr[j];
4204
- arr[j] = tmp;
4205
- }
4206
- }
4207
-
4208
- // Sort an array of objects based on one or more properties.
4209
- // Usage: sortOn(array, key1, asc?[, key2, asc? ...])
4210
- //
4211
- function sortOn(arr) {
4212
- var comparators = [];
4213
- for (var i=1; i<arguments.length; i+=2) {
4214
- comparators.push(getKeyComparator(arguments[i], arguments[i+1]));
4215
- }
4216
- arr.sort(function(a, b) {
4217
- var cmp = 0,
4218
- i = 0,
4219
- n = comparators.length;
4220
- while (i < n && cmp === 0) {
4221
- cmp = comparators[i](a, b);
4222
- i++;
4223
- }
4224
- return cmp;
4225
- });
4226
- return arr;
4227
- }
4228
-
4229
- // Sort array of values that can be compared with < > operators (strings, numbers)
4230
- // null, undefined and NaN are sorted to the end of the array
4231
- // default order is ascending
4232
- //
4233
- function genericSort(arr, ascending) {
4234
- var compare = getGenericComparator(ascending);
4235
- Array.prototype.sort.call(arr, compare);
4236
- return arr;
4237
- }
4238
-
4239
- function getSortedIds(arr, asc) {
4240
- var ids = range(arr.length);
4241
- sortArrayIndex(ids, arr, asc);
4242
- return ids;
4243
- }
4244
-
4245
- function sortArrayIndex(ids, arr, asc) {
4246
- var compare = getGenericComparator(asc);
4247
- ids.sort(function(i, j) {
4248
- // added i, j comparison to guarantee that sort is stable
4249
- var cmp = compare(arr[i], arr[j]);
4250
- return cmp > 0 || cmp === 0 && i > j ? 1 : -1;
4251
- });
4252
- }
4253
-
4254
- function reorderArray(arr, idxs) {
4255
- var len = idxs.length;
4256
- var arr2 = [];
4257
- for (var i=0; i<len; i++) {
4258
- var idx = idxs[i];
4259
- if (idx < 0 || idx >= len) error("Out-of-bounds array idx");
4260
- arr2[i] = arr[idx];
4261
- }
4262
- replaceArray(arr, arr2);
4263
- }
4264
-
4265
- function getKeyComparator(key, asc) {
4266
- var compare = getGenericComparator(asc);
4267
- return function(a, b) {
4268
- return compare(a[key], b[key]);
4269
- };
4270
- }
4271
-
4272
- function getGenericComparator(asc) {
4273
- asc = asc !== false;
4274
- return function(a, b) {
4275
- var retn = 0;
4276
- if (b == null) {
4277
- retn = a == null ? 0 : -1;
4278
- } else if (a == null) {
4279
- retn = 1;
4280
- } else if (a < b) {
4281
- retn = asc ? -1 : 1;
4282
- } else if (a > b) {
4283
- retn = asc ? 1 : -1;
4284
- } else if (a !== a) {
4285
- retn = 1;
4286
- } else if (b !== b) {
4287
- retn = -1;
4288
- }
4289
- return retn;
4290
- };
4291
- }
4292
-
4293
-
4294
- // Generic in-place sort (null, NaN, undefined not handled)
4295
- function quicksort(arr, asc) {
4296
- quicksortPartition(arr, 0, arr.length-1);
4297
- if (asc === false) Array.prototype.reverse.call(arr); // Works with typed arrays
4298
- return arr;
4299
- }
4300
-
4301
- // Moved out of quicksort() (saw >100% speedup in Chrome with deep recursion)
4302
- function quicksortPartition (a, lo, hi) {
4303
- var i = lo,
4304
- j = hi,
4305
- pivot, tmp;
4306
- while (i < hi) {
4307
- pivot = a[lo + hi >> 1]; // avoid n^2 performance on sorted arrays
4308
- while (i <= j) {
4309
- while (a[i] < pivot) i++;
4310
- while (a[j] > pivot) j--;
4311
- if (i <= j) {
4312
- tmp = a[i];
4313
- a[i] = a[j];
4314
- a[j] = tmp;
4315
- i++;
4316
- j--;
4317
- }
4318
- }
4319
- if (lo < j) quicksortPartition(a, lo, j);
4320
- lo = i;
4321
- j = hi;
4322
- }
4323
- }
4324
-
4325
-
4326
- function findRankByValue(arr, value) {
4327
- if (isNaN(value)) return arr.length;
4328
- var rank = 1;
4329
- for (var i=0, n=arr.length; i<n; i++) {
4330
- if (value > arr[i]) rank++;
4331
- }
4332
- return rank;
4333
- }
4334
-
4335
- function findValueByPct(arr, pct) {
4336
- var rank = Math.ceil((1-pct) * (arr.length));
4337
- return findValueByRank(arr, rank);
4338
- }
4339
-
4340
- // See http://ndevilla.free.fr/median/median/src/wirth.c
4341
- // Elements of @arr are reordered
4342
- //
4343
- function findValueByRank(arr, rank) {
4344
- if (!arr.length || rank < 1 || rank > arr.length) error("[findValueByRank()] invalid input");
4345
-
4346
- rank = clamp(rank | 0, 1, arr.length);
4347
- var k = rank - 1, // conv. rank to array index
4348
- n = arr.length,
4349
- l = 0,
4350
- m = n - 1,
4351
- i, j, val, tmp;
4352
-
4353
- while (l < m) {
4354
- val = arr[k];
4355
- i = l;
4356
- j = m;
4357
- do {
4358
- while (arr[i] < val) {i++;}
4359
- while (val < arr[j]) {j--;}
4360
- if (i <= j) {
4361
- tmp = arr[i];
4362
- arr[i] = arr[j];
4363
- arr[j] = tmp;
4364
- i++;
4365
- j--;
4366
- }
4367
- } while (i <= j);
4368
- if (j < k) l = i;
4369
- if (k < i) m = j;
4370
- }
4371
- return arr[k];
4372
- }
4373
-
4374
- //
4375
- //
4376
- function findMedian(arr) {
4377
- var n = arr.length,
4378
- rank = Math.floor(n / 2) + 1,
4379
- median = findValueByRank(arr, rank);
4380
- if ((n & 1) == 0) {
4381
- median = (median + findValueByRank(arr, rank - 1)) / 2;
4382
- }
4383
- return median;
4384
- }
4385
-
4386
-
4387
- function mean(arr) {
4388
- var count = 0,
4389
- avg = NaN,
4390
- val;
4391
- for (var i=0, n=arr.length; i<n; i++) {
4392
- val = arr[i];
4393
- if (isNaN(val)) continue;
4394
- avg = ++count == 1 ? val : val / count + (count - 1) / count * avg;
4395
- }
4396
- return avg;
4397
- }
4398
-
4399
-
4400
- /*
4401
- A simplified version of printf formatting
4402
- Format codes: %[flags][width][.precision]type
4403
-
4404
- supported flags:
4405
- + add '+' before positive numbers
4406
- 0 left-pad with '0'
4407
- ' Add thousands separator
4408
- width: 1 to many
4409
- precision: .(1 to many)
4410
- type:
4411
- s string
4412
- di integers
4413
- f decimal numbers
4414
- xX hexidecimal (unsigned)
4415
- % literal '%'
4416
-
4417
- Examples:
4418
- code val formatted
4419
- %+d 1 '+1'
4420
- %4i 32 ' 32'
4421
- %04i 32 '0032'
4422
- %x 255 'ff'
4423
- %.2f 0.125 '0.13'
4424
- %'f 1000 '1,000'
4425
- */
4426
-
4427
- // Usage: format(formatString, [values])
4428
- // Tip: When reusing the same format many times, use formatter() for 5x - 10x better performance
4429
- //
4430
- function format(fmt) {
4431
- var fn = formatter(fmt);
4432
- var str = fn.apply(null, Array.prototype.slice.call(arguments, 1));
4433
- return str;
4434
- }
4435
-
4436
- function formatValue(val, matches) {
4437
- var flags = matches[1];
4438
- var padding = matches[2];
4439
- var decimals = matches[3] ? parseInt(matches[3].substr(1)) : void 0;
4440
- var type = matches[4];
4441
- var isString = type == 's',
4442
- isHex = type == 'x' || type == 'X',
4443
- isInt = type == 'd' || type == 'i',
4444
- isFloat = type == 'f',
4445
- isNumber = !isString;
4446
-
4447
- var sign = "",
4448
- padDigits = 0,
4449
- isZero = false,
4450
- isNeg = false;
4451
-
4452
- var str, padChar, padStr;
4453
- if (isString) {
4454
- str = String(val);
4455
- }
4456
- else if (isHex) {
4457
- str = val.toString(16);
4458
- if (type == 'X')
4459
- str = str.toUpperCase();
4460
- }
4461
- else if (isNumber) {
4462
- // str = formatNumberForDisplay(val, isInt ? 0 : decimals);
4463
- str = numToStr(val, decimals);
4464
- if (str[0] == '-') {
4465
- isNeg = true;
4466
- str = str.substr(1);
4467
- }
4468
- isZero = parseFloat(str) == 0;
4469
- if (flags.indexOf("'") != -1 || flags.indexOf(',') != -1) {
4470
- str = addThousandsSep(str);
4471
- }
4472
- if (!isZero) { // BUG: sign is added when num rounds to 0
4473
- if (isNeg) {
4474
- sign = "\u2212"; // U+2212
4475
- } else if (flags.indexOf('+') != -1) {
4476
- sign = '+';
4477
- }
4478
- }
4479
- }
4480
-
4481
- if (padding) {
4482
- var strLen = str.length + sign.length;
4483
- var minWidth = parseInt(padding, 10);
4484
- if (strLen < minWidth) {
4485
- padDigits = minWidth - strLen;
4486
- padChar = flags.indexOf('0') == -1 ? ' ' : '0';
4487
- padStr = repeatString(padChar, padDigits);
4488
- }
4489
- }
4490
-
4491
- if (padDigits == 0) {
4492
- str = sign + str;
4493
- } else if (padChar == '0') {
4494
- str = sign + padStr + str;
4495
- } else {
4496
- str = padStr + sign + str;
4497
- }
4498
- return str;
4499
- }
4500
-
4501
- // Get a function for interpolating formatted values into a string.
4502
- function formatter(fmt) {
4503
- var codeRxp = /%([\',+0]*)([1-9]?)((?:\.[1-9])?)([sdifxX%])/g;
4504
- var literals = [],
4505
- formatCodes = [],
4506
- startIdx = 0,
4507
- prefix = "",
4508
- matches = codeRxp.exec(fmt),
4509
- literal;
4510
-
4511
- while (matches) {
4512
- literal = fmt.substring(startIdx, codeRxp.lastIndex - matches[0].length);
4513
- if (matches[0] == '%%') {
4514
- prefix += literal + '%';
4515
- } else {
4516
- literals.push(prefix + literal);
4517
- prefix = '';
4518
- formatCodes.push(matches);
4519
- }
4520
- startIdx = codeRxp.lastIndex;
4521
- matches = codeRxp.exec(fmt);
4522
- }
4523
- literals.push(prefix + fmt.substr(startIdx));
4524
-
4525
- return function() {
4526
- var str = literals[0],
4527
- n = arguments.length;
4528
- if (n != formatCodes.length) {
4529
- error("[format()] Data does not match format string; format:", fmt, "data:", arguments);
4530
- }
4531
- for (var i=0; i<n; i++) {
4532
- str += formatValue(arguments[i], formatCodes[i]) + literals[i+1];
4533
- }
4534
- return str;
4535
- };
4536
- }
4537
-
4538
- function wildcardToRegExp(name) {
4539
- var rxp = name.split('*').map(function(str) {
4540
- return regexEscape(str);
4541
- }).join('.*');
4542
- return new RegExp('^' + rxp + '$');
4543
- }
4544
-
4545
- function createBuffer(arg, arg2) {
4546
- if (isInteger(arg)) {
4547
- return Buffer.allocUnsafe ? Buffer.allocUnsafe(arg) : new Buffer(arg);
4548
- } else {
4549
- // check allocUnsafe to make sure Buffer.from() will accept strings (it didn't before Node v5.10)
4550
- return Buffer.from && Buffer.allocUnsafe ? Buffer.from(arg, arg2) : new Buffer(arg, arg2);
4551
- }
4552
- }
4553
-
4554
- function expandoBuffer(constructor, rate) {
4555
- var capacity = 0,
4556
- k = rate >= 1 ? rate : 1.2,
4557
- buf;
4558
- return function(size) {
4559
- if (size > capacity) {
4560
- capacity = Math.ceil(size * k);
4561
- buf = constructor ? new constructor(capacity) : createBuffer(capacity);
4562
- }
4563
- return buf;
4564
- };
4565
- }
4566
-
4567
- function copyElements(src, i, dest, j, n, rev) {
4568
- if (src === dest && j > i) error ("copy error");
4569
- var inc = 1,
4570
- offs = 0;
4571
- if (rev) {
4572
- inc = -1;
4573
- offs = n - 1;
4574
- }
4575
- for (var k=0; k<n; k++, offs += inc) {
4576
- dest[k + j] = src[i + offs];
4577
- }
4578
- }
4579
-
4580
- function extendBuffer(src, newLen, copyLen) {
4581
- var len = Math.max(src.length, newLen);
4582
- var n = copyLen || src.length;
4583
- var dest = new src.constructor(len);
4584
- copyElements(src, 0, dest, 0, n);
4585
- return dest;
4586
- }
4587
-
4588
- function mergeNames(name1, name2) {
4589
- var merged;
4590
- if (name1 && name2) {
4591
- merged = findStringPrefix(name1, name2).replace(/[-_]$/, '');
4592
- }
4593
- return merged || '';
4594
- }
4595
-
4596
- function findStringPrefix(a, b) {
4597
- var i = 0;
4598
- for (var n=a.length; i<n; i++) {
4599
- if (a[i] !== b[i]) break;
4600
- }
4601
- return a.substr(0, i);
4602
- }
4603
-
4604
- function parsePercent(o) {
4605
- var str = String(o);
4606
- var isPct = str.indexOf('%') > 0;
4607
- var pct;
4608
- if (isPct) {
4609
- pct = Number(str.replace('%', '')) / 100;
4610
- } else {
4611
- pct = Number(str);
4612
- }
4613
- if (!(pct >= 0 && pct <= 1)) {
4614
- stop(format("Invalid percentage: %s", str));
4615
- }
4616
- return pct;
4617
- }
4618
-
4619
- function formatVersionedName(name, i) {
4620
- var suffix = String(i);
4621
- if (/[0-9]$/.test(name)) {
4622
- suffix = '-' + suffix;
4623
- }
4624
- return name + suffix;
4625
- }
4626
-
4627
- function uniqifyNames(names, formatter) {
4628
- var counts = countValues(names),
4629
- format = formatter || formatVersionedName,
4630
- names2 = [];
4631
-
4632
- names.forEach(function(name) {
4633
- var i = 0,
4634
- candidate = name,
4635
- versionedName;
4636
- while (
4637
- names2.indexOf(candidate) > -1 || // candidate name has already been used
4638
- candidate == name && counts[candidate] > 1 || // duplicate unversioned names
4639
- candidate != name && counts[candidate] > 0) { // versioned name is a preexisting name
4640
- i++;
4641
- versionedName = format(name, i);
4642
- if (!versionedName || versionedName == candidate) {
4643
- throw new Error("Naming error"); // catch buggy versioning function
4644
- }
4645
- candidate = versionedName;
4646
- }
4647
- names2.push(candidate);
4648
- });
4649
- return names2;
4650
- }
4651
-
4652
-
4653
- // Assume: @raw is string, undefined or null
4654
- function parseString(raw) {
4655
- return raw ? raw : "";
4656
- }
4657
-
4658
- // Assume: @raw is string, undefined or null
4659
- // Use null instead of NaN for unparsable values
4660
- // (in part because if NaN is used, empty strings get converted to "NaN"
4661
- // when re-exported).
4662
- function parseNumber(raw) {
4663
- return parseToNum(raw, cleanNumericString);
4664
- }
4665
-
4666
- function parseIntlNumber(raw) {
4667
- return parseToNum(raw, convertIntlNumString);
4668
- }
4669
-
4670
- function parseToNum(raw, clean) {
4671
- var str = String(raw).trim();
4672
- var parsed = str ? Number(clean(str)) : NaN;
4673
- return isNaN(parsed) ? null : parsed;
4674
- }
4675
-
4676
- // Remove comma separators from strings
4677
- function cleanNumericString(str) {
4678
- return (str.indexOf(',') > 0) ? str.replace(/,([0-9]{3})/g, '$1') : str;
4679
- }
4680
-
4681
- function convertIntlNumString(str) {
4682
- str = str.replace(/[ .]([0-9]{3})/g, '$1');
4683
- return str.replace(',', '.');
4684
- }
4685
-
4686
- function trimQuotes(raw) {
4687
- var len = raw.length, first, last;
4688
- if (len >= 2) {
4689
- first = raw.charAt(0);
4690
- last = raw.charAt(len-1);
4691
- if (first == '"' && last == '"' && !raw.includes('","') ||
4692
- first == "'" && last == "'" && !raw.includes("','")) {
4693
- return raw.substr(1, len-2);
4694
- }
4695
- }
4696
- return raw;
4697
- }
4698
-
4699
- var context = createContext(); // command context (persist for the current command cycle)
4700
-
4701
- function runningInBrowser() {
4702
- return typeof window !== 'undefined' && typeof window.document !== 'undefined';
4703
- }
4704
-
4705
- function getStateVar(key) {
4706
- return context[key];
4707
- }
4708
-
4709
- function setStateVar(key, val) {
4710
- context[key] = val;
4711
- }
4712
-
4713
- function createContext() {
4714
- return {
4715
- DEBUG: false,
4716
- QUIET: false,
4717
- VERBOSE: false,
4718
- defs: {},
4719
- input_files: []
4720
- };
4721
- }
4722
-
4723
- // Install a new set of context variables, clear them when an async callback is called.
4724
- // @cb callback function to wrap
4725
- // returns wrapped callback function
4726
- function createAsyncContext(cb) {
4727
- context = createContext();
4728
- return function() {
4729
- cb.apply(null, utils.toArray(arguments));
4730
- // clear context after cb(), so output/errors can be handled in current context
4731
- context = createContext();
4732
- };
4733
- }
4734
-
4735
- // Save the current context, restore it when an async callback is called
4736
- // @cb callback function to wrap
4737
- // returns wrapped callback function
4738
- function preserveContext(cb) {
4739
- var ctx = context;
4740
- return function() {
4741
- context = ctx;
4742
- cb.apply(null, utils.toArray(arguments));
4743
- };
4744
- }
4745
-
4746
- var LOGGING = false;
4747
- var STDOUT = false; // use stdout for status messages
4748
-
4749
- // These three functions can be reset by GUI using setLoggingFunctions();
4750
- var _error = function() {
4751
- var msg = utils.toArray(arguments).join(' ');
4752
- throw new Error(msg);
4753
- };
4754
-
4755
- var _stop = function() {
4756
- throw new UserError(formatLogArgs(arguments));
4757
- };
4758
-
4759
- var _interrupt = function() {
4760
- throw new NonFatalError(formatLogArgs(arguments));
4761
- };
4762
-
4763
- var _message = function() {
4764
- logArgs(arguments);
4765
- };
4766
-
4767
- function enableLogging() {
4768
- LOGGING = true;
4769
- }
4770
-
4771
- function loggingEnabled() {
4772
- return !!LOGGING;
4773
- }
4774
-
4775
- // Handle an unexpected condition (internal error)
4776
- function error() {
4777
- _error.apply(null, utils.toArray(arguments));
4778
- }
4779
-
4780
- // Handle an error caused by invalid input or misuse of API
4781
- function stop() {
4782
- _stop.apply(null, utils.toArray(arguments));
4783
- }
4784
-
4785
- function interrupt() {
4786
- _interrupt.apply(null, utils.toArray(arguments));
4787
- }
4788
-
4789
- // Print a status message
4790
- function message() {
4791
- _message.apply(null, messageArgs(arguments));
4792
- }
4793
-
4794
- // A way for the GUI to replace the CLI logging functions
4795
- function setLoggingFunctions(message, error, stop) {
4796
- _message = message;
4797
- _error = error;
4798
- _stop = stop;
4799
- }
4800
-
4801
-
4802
- // print a message to stdout
4803
- function print() {
4804
- STDOUT = true; // tell logArgs() to print to stdout, not stderr
4805
- message.apply(null, arguments);
4806
- STDOUT = false;
4807
- }
4808
-
4809
- function verbose() {
4810
- // verbose can be set globally with the -verbose command or separately for each command
4811
- if (getStateVar('VERBOSE') || getStateVar('verbose')) {
4812
- message.apply(null, arguments);
4813
- }
4814
- }
4815
-
4816
- function debug() {
4817
- if (getStateVar('DEBUG') || getStateVar('debug')) {
4818
- logArgs(arguments);
4819
- }
4820
- }
4821
-
4822
- function printError(err) {
4823
- var msg;
4824
- if (!LOGGING) return;
4825
- if (utils.isString(err)) {
4826
- err = new UserError(err);
4827
- }
4828
- if (err.name == 'NonFatalError') {
4829
- console.error(messageArgs([err.message]).join(' '));
4830
- } else if (err.name == 'UserError') {
4831
- msg = err.message;
4832
- if (!/Error/.test(msg)) {
4833
- msg = "Error: " + msg;
4834
- }
4835
- console.error(messageArgs([msg]).join(' '));
4836
- console.error("Run mapshaper -h to view help");
4837
- } else {
4838
- // not a user error (i.e. a bug in mapshaper)
4839
- console.error(err);
4840
- // throw err;
4841
- }
4842
- }
4843
-
4844
- function UserError(msg) {
4845
- var err = new Error(msg);
4846
- err.name = 'UserError';
4847
- return err;
4848
- }
4849
-
4850
- function NonFatalError(msg) {
4851
- var err = new Error(msg);
4852
- err.name = 'NonFatalError';
4853
- return err;
4854
- }
4855
-
4856
- function formatColumns(arr, alignments) {
4857
- var widths = arr.reduce(function(memo, line) {
4858
- return line.map(function(str, i) {
4859
- return memo ? Math.max(memo[i], str.length) : str.length;
4860
- });
4861
- }, null);
4862
- return arr.map(function(line) {
4863
- line = line.map(function(str, i) {
4864
- var rt = alignments && alignments[i] == 'right';
4865
- var pad = (rt ? str.padStart : str.padEnd).bind(str);
4866
- return pad(widths[i], ' ');
4867
- });
4868
- return ' ' + line.join(' ');
4869
- }).join('\n');
4870
- }
4871
-
4872
- // Format an array of (preferably short) strings in columns for console logging.
4873
- function formatStringsAsGrid(arr) {
4874
- // TODO: variable column width
4875
- var longest = arr.reduce(function(len, str) {
4876
- return Math.max(len, str.length);
4877
- }, 0),
4878
- colWidth = longest + 2,
4879
- perLine = Math.floor(80 / colWidth) || 1;
4880
- return arr.reduce(function(memo, name, i) {
4881
- var col = i % perLine;
4882
- if (i > 0 && col === 0) memo += '\n';
4883
- if (col < perLine - 1) { // right-pad all but rightmost column
4884
- name = utils.rpad(name, colWidth - 2, ' ');
4885
- }
4886
- return memo + ' ' + name;
4887
- }, '');
4888
- }
4889
-
4890
- // expose so GUI can use it
4891
- function formatLogArgs(args) {
4892
- return utils.toArray(args).join(' ');
4893
- }
4894
-
4895
- function messageArgs(args) {
4896
- var arr = utils.toArray(args);
4897
- var cmd = getStateVar('current_command');
4898
- if (cmd && cmd != 'help') {
4899
- arr.unshift('[' + cmd + ']');
4900
- }
4901
- return arr;
4902
- }
4903
-
4904
- function logArgs(args) {
4905
- if (!LOGGING || getStateVar('QUIET') || !utils.isArrayLike(args)) return;
4906
- var msg = formatLogArgs(args);
4907
- if (STDOUT) console.log(msg);
4908
- else console.error(msg);
4909
- }
4910
-
4911
- // Utility functions for both paths and points
4912
-
4913
- // @shp An element of the layer.shapes array
4914
- // (may be null, or, depending on layer type, an array of points or an array of arrays of arc ids)
4915
- function cloneShape(shp) {
4916
- if (!shp) return null;
4917
- return shp.map(function(part) {
4918
- return part.concat();
4919
- });
4920
- }
4921
-
4922
- function cloneShapes(arr) {
4923
- return utils.isArray(arr) ? arr.map(cloneShape) : null;
4924
- }
4925
-
4926
- function forEachShapePart(paths, cb) {
4927
- editShapeParts(paths, cb);
4928
- }
4929
-
4930
- // Updates shapes array in-place.
4931
- // editPart: callback function
4932
- function editShapes(shapes, editPart) {
4933
- for (var i=0, n=shapes.length; i<n; i++) {
4934
- shapes[i] = editShapeParts(shapes[i], editPart);
4935
- }
4936
- }
4937
-
4938
- // @parts: geometry of a feature (array of paths, array of points or null)
4939
- // @cb: function(part, i, parts)
4940
- // If @cb returns an array, it replaces the existing value
4941
- // If @cb returns null, the path is removed from the feature
4942
- //
4943
- function editShapeParts(parts, cb) {
4944
- if (!parts) return null; // null geometry not edited
4945
- if (!utils.isArray(parts)) error("Expected an array, received:", parts);
4946
- var nulls = 0,
4947
- n = parts.length,
4948
- retn;
4949
-
4950
- for (var i=0; i<n; i++) {
4951
- retn = cb(parts[i], i, parts);
4952
- if (retn === null) {
4953
- nulls++;
4954
- parts[i] = null;
4955
- } else if (utils.isArray(retn)) {
4956
- parts[i] = retn;
4957
- }
4958
- }
4959
- if (nulls == n) {
4960
- return null;
4961
- } else if (nulls > 0) {
4962
- return parts.filter(function(part) {return !!part;});
4963
- } else {
4964
- return parts;
4965
- }
4966
- }
4967
-
4968
- // Get max number of parts in a single shape from an array of shapes.
4969
- // Caveat: polygon holes are counted as separate parts.
4970
- function findMaxPartCount(shapes) {
4971
- var maxCount = 0, shp;
4972
- for (var i=0, n=shapes.length; i<n; i++) {
4973
- shp = shapes[i];
4974
- if (shp && shp.length > maxCount) {
4975
- maxCount = shp.length;
3396
+ function getWarnings(lyr, dataset) {
3397
+ var file = internal.getLayerSourceFile(lyr, dataset);
3398
+ var missing = [];
3399
+ var msg;
3400
+ if (utils.endsWith(file, '.shp') && lyr == dataset.layers[0]) {
3401
+ if (!lyr.data) {
3402
+ missing.push('.dbf');
3403
+ }
3404
+ if (!dataset.info.prj && !dataset.info.crs) {
3405
+ missing.push('.prj');
3406
+ }
4976
3407
  }
3408
+ if (missing.length) {
3409
+ msg = 'missing ' + missing.join(' and ') + ' data';
3410
+ }
3411
+ return msg;
4977
3412
  }
4978
- return maxCount;
4979
- }
4980
-
4981
- // List of encodings supported by iconv-lite:
4982
- // https://github.com/ashtuchkin/iconv-lite/wiki/Supported-Encodings
4983
-
4984
- var iconv = require('iconv-lite');
4985
- var toUtf8 = getNativeEncoder('utf8');
4986
- var fromUtf8 = getNativeDecoder('utf8');
4987
-
4988
- // Return list of supported encodings
4989
- function getEncodings() {
4990
- iconv.encodingExists('ascii'); // make iconv load its encodings
4991
- return Object.keys(iconv.encodings);
4992
- }
4993
3413
 
4994
- function validateEncoding(enc) {
4995
- if (!encodingIsSupported(enc)) {
4996
- stop("Unknown encoding:", enc, "\nRun the -encodings command see a list of supported encodings");
3414
+ function describeSrc(lyr, dataset) {
3415
+ return internal.getLayerSourceFile(lyr, dataset);
4997
3416
  }
4998
- return enc;
4999
- }
5000
3417
 
5001
- function stringsAreAscii(arr) {
5002
- return stringIsAscii(arr.join(''));
5003
- }
5004
3418
 
5005
- function stringIsAscii(str) {
5006
- var c;
5007
- for (var i=0, n=str.length; i<n; i++) {
5008
- c = str.charCodeAt(i);
5009
- if (c >= 128) return false;
3419
+ function isPinnable(lyr) {
3420
+ return internal.layerHasGeometry(lyr) || internal.layerHasFurniture(lyr);
5010
3421
  }
5011
- return true;
5012
- }
5013
-
5014
- function encodingIsUtf8(enc) {
5015
- // treating utf-8 as default
5016
- return !enc || /^utf-?8$/i.test(String(enc));
5017
- }
5018
-
5019
- // Identify the most common encodings that are supersets of ascii at the
5020
- // single-byte level (meaning that bytes in 0 - 0x7f range must be ascii)
5021
- // (this allows identifying line breaks and other ascii patterns in buffers)
5022
- function encodingIsAsciiCompat(enc) {
5023
- enc = standardizeEncodingName(enc);
5024
- // gb.* selects the Guo Biao encodings
5025
- // big5 in not compatible -- second byte starts at 0x40
5026
- return !enc || /^(win|latin|utf8|ascii|iso88|gb)/.test(enc);
5027
- }
5028
3422
 
5029
- // Ex. convert UTF-8 to utf8
5030
- function standardizeEncodingName(enc) {
5031
- return (enc || '').toLowerCase().replace(/[_-]/g, '');
5032
- }
5033
3423
 
5034
- // Similar to Buffer#toString(); tries to speed up utf8 conversion in
5035
- // web browser (when using browserify Buffer shim)
5036
- function bufferToString(buf, enc, start, end) {
5037
- if (start >= 0) {
5038
- buf = buf.slice(start, end);
5039
- }
5040
- return decodeString(buf, enc);
5041
- }
5042
3424
 
5043
- function getNativeEncoder(enc) {
5044
- var encoder = null;
5045
- enc = standardizeEncodingName(enc);
5046
- if (enc != 'utf8') {
5047
- // TODO: support more encodings if TextEncoder is available
5048
- return null;
5049
- }
5050
- if (typeof TextEncoder != 'undefined') {
5051
- encoder = new TextEncoder(enc);
3425
+ function rowHTML(c1, c2, cname) {
3426
+ return utils.format('<div class="row%s"><div class="col1">%s</div>' +
3427
+ '<div class="col2">%s</div></div>', cname ? ' ' + cname : '', c1, c2);
5052
3428
  }
5053
- return function(str) {
5054
- // Convert Uint8Array from encoder to Buffer (fix for issue #216)
5055
- return encoder ? Buffer.from(encoder.encode(str).buffer) : utils.createBuffer(str, enc);
5056
- };
5057
3429
  }
5058
3430
 
5059
- function encodeString(str, enc) {
5060
- // TODO: faster ascii encoding?
5061
- var buf;
5062
- if (encodingIsUtf8(enc)) {
5063
- buf = toUtf8(str);
5064
- } else {
5065
- buf = iconv.encode(str, enc);
5066
- }
5067
- return buf;
5068
- }
3431
+ function SessionHistory(gui) {
3432
+ var commands = [];
3433
+ // commands that can be ignored when checking for unsaved changes
3434
+ var nonEditingCommands = 'i,target,info,version,verbose,projections,inspect,help,h,encodings,calc'.split(',');
5069
3435
 
5070
- function getNativeDecoder(enc) {
5071
- var decoder = null;
5072
- enc = standardizeEncodingName(enc);
5073
- if (enc != 'utf8') {
5074
- // TODO: support more encodings if TextDecoder is available
5075
- return null;
5076
- }
5077
- if (typeof TextDecoder != 'undefined') {
5078
- decoder = new TextDecoder(enc);
5079
- }
5080
- return function(buf) {
5081
- return decoder ? decoder.decode(buf) : buf.toString(enc);
3436
+ this.unsavedChanges = function() {
3437
+ var cmd, cmdName;
3438
+ for (var i=commands.length - 1; i >= 0; i--) {
3439
+ cmdName = getCommandName(commands[i]);
3440
+ if (cmdName == 'o') break;
3441
+ if (nonEditingCommands.includes(cmdName)) continue;
3442
+ return true;
3443
+ }
3444
+ return false;
5082
3445
  };
5083
- }
5084
3446
 
5085
- // @buf a Node Buffer
5086
- function decodeString(buf, enc) {
5087
- var str;
5088
- if (encodingIsUtf8(enc)) {
5089
- str = fromUtf8(buf);
5090
- } else {
5091
- str = iconv.decode(buf, enc);
5092
- }
5093
- return str;
5094
- }
3447
+ this.fileImported = function(file, optStr) {
3448
+ var cmd = '-i ' + file;
3449
+ if (optStr) {
3450
+ cmd += ' ' + optStr;
3451
+ }
3452
+ commands.push(cmd);
3453
+ };
5095
3454
 
5096
- function encodingIsSupported(raw) {
5097
- var enc = standardizeEncodingName(raw);
5098
- return getEncodings().includes(enc);
5099
- }
3455
+ this.layerRenamed = function(lyr, name) {
3456
+ var currTarget = getCurrentTarget();
3457
+ var layerTarget = getTargetFromLayer(lyr);
3458
+ if (currTarget == layerTarget) {
3459
+ commands.push('-rename-layers ' + name);
3460
+ } else {
3461
+ commands.push('-rename-layers ' + name + ' target=' + layerTarget);
3462
+ commands.push('-target ' + currTarget);
3463
+ }
3464
+ };
5100
3465
 
5101
- function trimBOM(str) {
5102
- // remove BOM if present
5103
- if (str.charCodeAt(0) == 0xfeff) {
5104
- str = str.substr(1);
5105
- }
5106
- return str;
5107
- }
3466
+ this.consoleCommands = function(str) {
3467
+ commands.push(str); // todo: split commands?
3468
+ };
5108
3469
 
5109
- function printEncodings() {
5110
- var encodings = getEncodings().filter(function(name) {
5111
- // filter out some aliases and non-applicable encodings
5112
- return !/^(_|cs|internal|ibm|isoir|singlebyte|table|[0-9]|l[0-9]|windows)/.test(name);
5113
- });
5114
- encodings.sort();
5115
- print("Supported encodings:\n" + formatStringsAsGrid(encodings));
5116
- }
3470
+ this.simplificationApplied = function(optStr) {
3471
+ commands.push('-simplify ' + optStr);
3472
+ };
5117
3473
 
5118
- // Not a general-purpose deep copy function
5119
- function copyRecord(o) {
5120
- var o2 = {}, key, val;
5121
- if (!o) return null;
5122
- for (key in o) {
5123
- if (o.hasOwnProperty(key)) {
5124
- val = o[key];
5125
- if (val == o) {
5126
- // avoid infinite recursion if val is a circular reference, by copying all properties except key
5127
- val = utils.extend({}, val);
5128
- delete val[key];
5129
- }
5130
- o2[key] = val && val.constructor === Object ? copyRecord(val) : val;
3474
+ this.simplificationRepair = function() {
3475
+ // TODO: improve this... repair does not necessarily apply to most recent
3476
+ // simplification command
3477
+ // consider adding a (hidden) repair command to handle this event
3478
+ var i = indexOfLastCommand('-simplify');
3479
+ if (i > -1) {
3480
+ commands[i] = commands[i].replace(' no-repair', '');
5131
3481
  }
5132
- }
5133
- return o2;
5134
- }
3482
+ };
5135
3483
 
5136
- function getValueType(val) {
5137
- var type = null;
5138
- if (utils.isString(val)) {
5139
- type = 'string';
5140
- } else if (utils.isNumber(val)) {
5141
- type = 'number';
5142
- } else if (utils.isBoolean(val)) {
5143
- type = 'boolean';
5144
- } else if (utils.isDate(val)) {
5145
- type = 'date';
5146
- } else if (utils.isObject(val)) {
5147
- type = 'object';
5148
- }
5149
- return type;
5150
- }
3484
+ this.updateSimplificationPct = function(pct) {
3485
+ var i = indexOfLastCommand('-simplify');
3486
+ if (i > -1) {
3487
+ commands[i] = commands[i].replace(/percentage=[^ ]+/, 'percentage=' + pct);
3488
+ }
3489
+ };
5151
3490
 
5152
- // Fill out a data table with undefined values
5153
- // The undefined members will disappear when records are exported as JSON,
5154
- // but will show up when fields are listed using Object.keys()
5155
- function fixInconsistentFields(records) {
5156
- var fields = findIncompleteFields(records);
5157
- patchMissingFields(records, fields);
5158
- }
3491
+ this.dataValueUpdated = function(id, field, value) {
3492
+ var cmd = `-each 'd[${JSON.stringify(field)}] = ${JSON.stringify(value)}' where='this.id == ${id}'`;
3493
+ commands.push(cmd);
3494
+ };
5159
3495
 
5160
- function findIncompleteFields(records) {
5161
- var counts = {},
5162
- i, j, keys;
5163
- for (i=0; i<records.length; i++) {
5164
- keys = Object.keys(records[i] || {});
5165
- for (j=0; j<keys.length; j++) {
5166
- counts[keys[j]] = (counts[keys[j]] | 0) + 1;
3496
+ this.layersExported = function(ids, optStr) {
3497
+ var layers = gui.model.getLayers();
3498
+ var cmd = '-o';
3499
+ if (layers.length > 1) {
3500
+ cmd += ' target=' + ids.map(getTargetFromId).join(',');
5167
3501
  }
5168
- }
5169
- return Object.keys(counts).filter(function(k) {return counts[k] < records.length;});
5170
- }
3502
+ if (optStr) {
3503
+ cmd += ' ' + optStr;
3504
+ }
3505
+ commands.push(cmd);
3506
+ };
5171
3507
 
5172
- function patchMissingFields(records, fields) {
5173
- var rec, i, j, f;
5174
- for (i=0; i<records.length; i++) {
5175
- rec = records[i] || (records[i] = {});
5176
- for (j=0; j<fields.length; j++) {
5177
- f = fields[j];
5178
- if (f in rec === false) {
5179
- rec[f] = undefined;
3508
+ this.setTargetLayer = function(lyr) {
3509
+ var layers = gui.model.getLayers();
3510
+ if (layers.length > 1) {
3511
+ if (indexOfLastCommand('-target') == commands.length - 1) {
3512
+ commands.pop(); // if last commands was -target, remove it
5180
3513
  }
3514
+ commands.push('-target ' + getTargetFromLayer(lyr));
5181
3515
  }
5182
- }
5183
- }
3516
+ };
5184
3517
 
5185
- function fieldListContainsAll(list, fields) {
5186
- return list.indexOf('*') > -1 || utils.difference(fields, list).length === 0;
5187
- }
3518
+ this.toCommandLineString = function() {
3519
+ var str = commands.join(' \\\n ');
3520
+ return 'mapshaper ' + str;
3521
+ };
5188
3522
 
5189
- function getColumnType(key, records) {
5190
- var type = null,
5191
- rec;
5192
- for (var i=0, n=records.length; i<n; i++) {
5193
- rec = records[i];
5194
- type = rec ? getValueType(rec[key]) : null;
5195
- if (type) break;
3523
+ function getCommandName(cmd) {
3524
+ var rxp = /^-([a-z0-9-]+)/;
3525
+ var match = rxp.exec(cmd);
3526
+ return match ? match[1] : null;
5196
3527
  }
5197
- return type;
5198
- }
5199
-
5200
- function deleteFields(table, test) {
5201
- table.getFields().forEach(function(name) {
5202
- if (test(name)) {
5203
- table.deleteField(name);
5204
- }
5205
- });
5206
- }
5207
-
5208
- function isInvalidFieldName(f) {
5209
- // Reject empty and all-whitespace strings. TODO: consider other criteria
5210
- return /^\s*$/.test(f);
5211
- }
5212
-
5213
- // Resolve name conflicts in field names by appending numbers
5214
- // @fields Array of field names
5215
- // @maxLen (optional) Maximum chars in name
5216
- //
5217
- function getUniqFieldNames(fields, maxLen, encoding) {
5218
- var used = {};
5219
- return fields.map(function(name) {
5220
- var i = 0,
5221
- validName;
5222
- do {
5223
- validName = encoding && encoding != 'ascii' ?
5224
- adjustEncodedFieldName(name, maxLen, i, encoding) :
5225
- adjustFieldName(name, maxLen, i);
5226
- i++;
5227
- } while ((validName in used) ||
5228
- // don't replace an existing valid field name with a truncated name
5229
- name != validName && utils.contains(fields, validName));
5230
- used[validName] = true;
5231
- return validName;
5232
- });
5233
- }
5234
-
5235
- function getFieldValues(records, field) {
5236
- return records.map(function(rec) {
5237
- return rec ? rec[field] : undefined;
5238
- });
5239
- }
5240
-
5241
- function getUniqFieldValues(records, field) {
5242
- var index = {};
5243
- var values = [];
5244
- records.forEach(function(rec) {
5245
- var val = rec[field];
5246
- if (val in index === false) {
5247
- index[val] = true;
5248
- values.push(val);
5249
- }
5250
- });
5251
- return values;
5252
- }
5253
3528
 
5254
- // Truncate and/or uniqify a name (if relevant params are present)
5255
- function adjustFieldName(name, maxLen, i) {
5256
- var name2, suff;
5257
- maxLen = maxLen || 256;
5258
- if (!i) {
5259
- name2 = name.substr(0, maxLen);
5260
- } else {
5261
- suff = String(i);
5262
- if (suff.length == 1) {
5263
- suff = '_' + suff;
5264
- }
5265
- name2 = name.substr(0, maxLen - suff.length) + suff;
3529
+ function getCurrentTarget() {
3530
+ return getTargetFromLayer(gui.model.getActiveLayer().layer);
5266
3531
  }
5267
- return name2;
5268
- }
5269
3532
 
5270
- // Truncate and/or uniqify a name (if relevant params are present)
5271
- function adjustEncodedFieldName(name, maxLen, i, encoding) {
5272
- var suff = i ? String(i) : '';
5273
- var name2 = name + suff;
5274
- var buf = encodeString(name2, encoding);
5275
- if (buf.length > (maxLen || 256)) {
5276
- name = name.substr(0, name.length - 1);
5277
- return adjustEncodedFieldName(name, maxLen, i, encoding);
3533
+ function indexOfLastCommand(cmd) {
3534
+ return commands.reduce(function(memo, str, i) {
3535
+ return str.indexOf(cmd) === 0 ? i : memo;
3536
+ }, -1);
5278
3537
  }
5279
- return name2;
5280
- }
5281
3538
 
5282
- function applyFieldOrder(arr, option) {
5283
- if (option == 'ascending') {
5284
- arr.sort(function(a, b) {
5285
- return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
5286
- });
3539
+ function getTargetFromId(id) {
3540
+ var layers = gui.model.getLayers();
3541
+ return getTargetFromLayer(layers[id - 1].layer);
5287
3542
  }
5288
- return arr;
5289
- }
5290
3543
 
5291
- function getFirstNonEmptyRecord(records) {
5292
- for (var i=0, n=records ? records.length : 0; i<n; i++) {
5293
- if (records[i]) return records[i];
3544
+ function getTargetFromLayer(lyr) {
3545
+ var id = internal.getLayerTargetId(gui.model, lyr);
3546
+ return internal.formatOptionValue(id);
5294
3547
  }
5295
- return null;
5296
3548
  }
5297
3549
 
5298
- function findFieldNames(records, order) {
5299
- var first = getFirstNonEmptyRecord(records);
5300
- var names = first ? Object.keys(first) : [];
5301
- return applyFieldOrder(names, order);
5302
- }
3550
+ // import { cloneShape } from '../paths/mapshaper-shape-utils';
3551
+ // import { copyRecord } from '../datatable/mapshaper-data-utils';
3552
+ var snapVerticesToPoint$1 = internal.snapVerticesToPoint;
3553
+ var cloneShape = internal.cloneShape;
3554
+ var copyRecord = internal.copyRecord;
5303
3555
 
5304
3556
  function Undo(gui) {
5305
3557
  var history, offset, stashedUndo;
@@ -5311,9 +3563,6 @@
5311
3563
  offset = 0;
5312
3564
  }
5313
3565
 
5314
- function refreshMap() {
5315
- gui.dispatchEvent('undo_redo');
5316
- }
5317
3566
 
5318
3567
  function isUndoEvt(e) {
5319
3568
  return (e.ctrlKey || e.metaKey) && !e.shiftKey && e.key == 'z';
@@ -5361,6 +3610,28 @@
5361
3610
  this.addHistoryState(stashedUndo, redo);
5362
3611
  }, this);
5363
3612
 
3613
+ // undo/redo data editing
3614
+ // TODO: consider setting selected feature to the undo/redo target feature
3615
+ //
3616
+ gui.on('data_preupdate', function(e) {
3617
+ stashedUndo = this.makeDataSetter(e.FID);
3618
+ }, this);
3619
+
3620
+ gui.on('data_postupdate', function(e) {
3621
+ var redo = this.makeDataSetter(e.FID);
3622
+ this.addHistoryState(stashedUndo, redo);
3623
+ }, this);
3624
+
3625
+ // undo/redo vertex dragging
3626
+ gui.on('vertex_dragstart', function(e) {
3627
+ stashedUndo = this.makeVertexSetter(e.FID, e.vertex_ids);
3628
+ }, this);
3629
+
3630
+ gui.on('vertex_dragend', function(e) {
3631
+ var redo = this.makeVertexSetter(e.FID, e.vertex_ids);
3632
+ this.addHistoryState(stashedUndo, redo);
3633
+ }, this);
3634
+
5364
3635
  this.clear = function() {
5365
3636
  reset();
5366
3637
  };
@@ -5373,7 +3644,6 @@
5373
3644
  };
5374
3645
  };
5375
3646
 
5376
-
5377
3647
  this.makeDataSetter = function(id) {
5378
3648
  var target = gui.model.getActiveLayer();
5379
3649
  var rec = copyRecord(target.layer.data.getRecordAt(id));
@@ -5383,6 +3653,14 @@
5383
3653
  };
5384
3654
  };
5385
3655
 
3656
+ this.makeVertexSetter = function(fid, ids) {
3657
+ var target = gui.model.getActiveLayer();
3658
+ var arcs = target.dataset.arcs;
3659
+ var p = internal.getVertexCoords(ids[0], arcs);
3660
+ return function() {
3661
+ snapVerticesToPoint$1(ids, p, arcs, true);
3662
+ };
3663
+ };
5386
3664
 
5387
3665
  this.addHistoryState = function(undo, redo) {
5388
3666
  if (offset > 0) {
@@ -5397,7 +3675,7 @@
5397
3675
  if (item) {
5398
3676
  offset++;
5399
3677
  item.undo();
5400
- refreshMap();
3678
+ gui.dispatchEvent('map-needs-refresh');
5401
3679
  }
5402
3680
  };
5403
3681
 
@@ -5406,7 +3684,7 @@
5406
3684
  offset--;
5407
3685
  var item = getHistoryItem();
5408
3686
  item.redo();
5409
- refreshMap();
3687
+ gui.dispatchEvent('map-needs-refresh');
5410
3688
  };
5411
3689
 
5412
3690
  function getHistoryItem() {
@@ -5513,7 +3791,7 @@
5513
3791
  };
5514
3792
  }
5515
3793
 
5516
- utils$1.inherit(ModeSwitcher, EventDispatcher);
3794
+ utils.inherit(ModeSwitcher, EventDispatcher);
5517
3795
 
5518
3796
  function KeyboardEvents(gui) {
5519
3797
  var self = this;
@@ -5541,12 +3819,13 @@
5541
3819
  };
5542
3820
  }
5543
3821
 
5544
- utils$1.inherit(KeyboardEvents, EventDispatcher);
3822
+ utils.inherit(KeyboardEvents, EventDispatcher);
5545
3823
 
5546
3824
  function InteractionMode(gui) {
5547
3825
 
5548
3826
  var menus = {
5549
3827
  standard: ['info', 'selection', 'data', 'box'],
3828
+ polygons: ['info', 'selection', 'data', 'box', 'vertices'],
5550
3829
  lines: ['info', 'selection', 'data', 'box', 'vertices'],
5551
3830
  table: ['info', 'selection', 'data'],
5552
3831
  labels: ['info', 'selection', 'data', 'box', 'labels', 'location'],
@@ -5658,6 +3937,9 @@
5658
3937
  if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polyline') {
5659
3938
  return menus.lines;
5660
3939
  }
3940
+ if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polygon') {
3941
+ return menus.polygons;
3942
+ }
5661
3943
  return menus.standard;
5662
3944
  }
5663
3945
 
@@ -5730,7 +4012,9 @@
5730
4012
  }
5731
4013
 
5732
4014
  function onModeChange() {
5733
- gui.dispatchEvent('interaction_mode_change', {mode: getInteractionMode()});
4015
+ var mode = getInteractionMode();
4016
+ gui.state.interaction_mode = mode;
4017
+ gui.dispatchEvent('interaction_mode_change', {mode: mode});
5734
4018
  }
5735
4019
 
5736
4020
  // Update button highlight and selected menu item highlight (if any)
@@ -5757,7 +4041,7 @@
5757
4041
  function Model(gui) {
5758
4042
  var self = new internal.Catalog();
5759
4043
  var deleteLayer = self.deleteLayer;
5760
- utils$1.extend(self, EventDispatcher.prototype);
4044
+ utils.extend(self, EventDispatcher.prototype);
5761
4045
 
5762
4046
  // override Catalog method (so -drop command will work in web console)
5763
4047
  self.deleteLayer = function(lyr, dataset) {
@@ -5791,7 +4075,7 @@
5791
4075
  if (flags.select) {
5792
4076
  self.dispatchEvent('select', active);
5793
4077
  }
5794
- self.dispatchEvent('update', utils$1.extend({flags: flags}, active));
4078
+ self.dispatchEvent('update', utils.extend({flags: flags}, active));
5795
4079
  };
5796
4080
 
5797
4081
  self.selectLayer = function(lyr, dataset) {
@@ -5826,7 +4110,7 @@
5826
4110
  } else if (geoType == 'polygon') {
5827
4111
  test = polygonTest;
5828
4112
  } else {
5829
- error$1("Unexpected geometry type:", geoType);
4113
+ error("Unexpected geometry type:", geoType);
5830
4114
  }
5831
4115
  return test;
5832
4116
 
@@ -5893,7 +4177,7 @@
5893
4177
  for (var i=0; i<cands.length; i++) {
5894
4178
  cands[i].dist = geom.getPointToShapeDistance(x, y, cands[i].shape, arcs);
5895
4179
  }
5896
- utils$1.sortOn(cands, 'dist');
4180
+ utils.sortOn(cands, 'dist');
5897
4181
  }
5898
4182
 
5899
4183
  function pointTest(x, y) {
@@ -5919,7 +4203,7 @@
5919
4203
  }
5920
4204
  });
5921
4205
  // console.log(hitThreshold, bullseye);
5922
- return utils$1.uniq(hits); // multipoint features can register multiple hits
4206
+ return utils.uniq(hits); // multipoint features can register multiple hits
5923
4207
  }
5924
4208
 
5925
4209
  function getRadiusFunction(style) {
@@ -6126,9 +4410,9 @@
6126
4410
  };
6127
4411
  var svgData = svgTest(e); // null or a data object
6128
4412
  if (svgData) { // mouse is over an SVG symbol
6129
- utils$1.extend(data, svgData);
4413
+ utils.extend(data, svgData);
6130
4414
  // placing symbol id in front of any other hits
6131
- data.ids = utils$1.uniq([svgData.targetId].concat(data.ids));
4415
+ data.ids = utils.uniq([svgData.targetId].concat(data.ids));
6132
4416
  }
6133
4417
  data.id = data.ids.length > 0 ? data.ids[0] : -1;
6134
4418
  return data;
@@ -6161,7 +4445,7 @@
6161
4445
  }
6162
4446
 
6163
4447
  // ignore keypress if no feature is selected or user is editing text
6164
- if (pinnedId() == -1 || GUI.getInputElement()) return;
4448
+ if (pinnedId() == -1 || GUI.textIsSelected()) return;
6165
4449
 
6166
4450
  if (e.keyCode == 37 || e.keyCode == 39) {
6167
4451
  // L/R arrow keys
@@ -6229,8 +4513,8 @@
6229
4513
 
6230
4514
  self.addSelectionIds = function(ids) {
6231
4515
  turnOn('selection');
6232
- selectionIds = utils$1.uniq(selectionIds.concat(ids));
6233
- ids = utils$1.uniq(storedData.ids.concat(ids));
4516
+ selectionIds = utils.uniq(selectionIds.concat(ids));
4517
+ ids = utils.uniq(storedData.ids.concat(ids));
6234
4518
  updateSelectionState({ids: ids});
6235
4519
  };
6236
4520
 
@@ -6319,6 +4603,7 @@
6319
4603
 
6320
4604
  // Hits are re-detected on 'hover' (if hit detection is active)
6321
4605
  mouse.on('hover', function(e) {
4606
+ handlePointerEvent(e);
6322
4607
  if (storedData.pinned || !hitTest || !active) return;
6323
4608
  if (e.hover && isOverMap(e)) {
6324
4609
  // mouse is hovering directly over map area -- update hit detection
@@ -6388,7 +4673,7 @@
6388
4673
 
6389
4674
  function toggleId(id, ids) {
6390
4675
  if (ids.indexOf(id) > -1) {
6391
- return utils$1.difference(ids, [id]);
4676
+ return utils.difference(ids, [id]);
6392
4677
  }
6393
4678
  return [id].concat(ids);
6394
4679
  }
@@ -6440,9 +4725,9 @@
6440
4725
  // d: event data (may be a pointer event object, an ordinary object or null)
6441
4726
  function triggerHitEvent(type, d) {
6442
4727
  // Merge stored hit data into the event data
6443
- var eventData = utils$1.extend({mode: interactionMode}, d || {}, storedData);
4728
+ var eventData = utils.extend({mode: interactionMode}, d || {}, storedData);
6444
4729
  if (transientIds.length) {
6445
- eventData.ids = utils$1.uniq(transientIds.concat(eventData.ids || []));
4730
+ eventData.ids = utils.uniq(transientIds.concat(eventData.ids || []));
6446
4731
  }
6447
4732
  self.dispatchEvent(type, eventData);
6448
4733
  }
@@ -6576,7 +4861,7 @@
6576
4861
  }
6577
4862
  }
6578
4863
 
6579
- utils$1.inherit(Timer, EventDispatcher);
4864
+ utils.inherit(Timer, EventDispatcher);
6580
4865
 
6581
4866
  function Tween(ease) {
6582
4867
  var self = this,
@@ -6598,7 +4883,7 @@
6598
4883
  }
6599
4884
  }
6600
4885
 
6601
- utils$1.inherit(Tween, EventDispatcher);
4886
+ utils.inherit(Tween, EventDispatcher);
6602
4887
 
6603
4888
  Tween.sineInOut = function(n) {
6604
4889
  return 0.5 - Math.cos(n * Math.PI) / 2;
@@ -6666,7 +4951,7 @@
6666
4951
  update();
6667
4952
  }
6668
4953
 
6669
- utils$1.inherit(ElementPosition, EventDispatcher);
4954
+ utils.inherit(ElementPosition, EventDispatcher);
6670
4955
 
6671
4956
  function MouseWheelDirection() {
6672
4957
  var prevTime = 0;
@@ -6764,13 +5049,13 @@
6764
5049
  // Decelerate towards the end of the sustain interval (for smoother zooming)
6765
5050
  multiplier *= Tween.quadraticOut(1 - fadeFactor);
6766
5051
  }
6767
- obj = utils$1.extend({direction: wheelDirection, multiplier: multiplier}, mouse.mouseData());
5052
+ obj = utils.extend({direction: wheelDirection, multiplier: multiplier}, mouse.mouseData());
6768
5053
  self.dispatchEvent('mousewheel', obj);
6769
5054
  }
6770
5055
  }
6771
5056
  }
6772
5057
 
6773
- utils$1.inherit(MouseWheel, EventDispatcher);
5058
+ utils.inherit(MouseWheel, EventDispatcher);
6774
5059
 
6775
5060
 
6776
5061
  function MouseArea(element, pos) {
@@ -6824,7 +5109,7 @@
6824
5109
  };
6825
5110
 
6826
5111
  this.mouseData = function() {
6827
- return utils$1.extend({}, _prevEvt);
5112
+ return utils.extend({}, _prevEvt);
6828
5113
  };
6829
5114
 
6830
5115
  function onAreaDown(e) {
@@ -6883,7 +5168,7 @@
6883
5168
  dragX: evt.pageX - _downEvt.pageX,
6884
5169
  dragY: evt.pageY - _downEvt.pageY
6885
5170
  };
6886
- _self.dispatchEvent('drag', utils$1.extend(obj, evt));
5171
+ _self.dispatchEvent('drag', utils.extend(obj, evt));
6887
5172
  } else {
6888
5173
  _self.dispatchEvent('hover', evt);
6889
5174
  }
@@ -6915,7 +5200,7 @@
6915
5200
  }
6916
5201
  }
6917
5202
 
6918
- utils$1.inherit(MouseArea, EventDispatcher);
5203
+ utils.inherit(MouseArea, EventDispatcher);
6919
5204
 
6920
5205
  function initVariableClick(node, cb) {
6921
5206
  var downEvent = null;
@@ -7062,7 +5347,7 @@
7062
5347
  maxScale = 4,
7063
5348
  minTime = 100,
7064
5349
  maxTime = 800,
7065
- time = utils$1.clamp(clickTime || 200, minTime, maxTime),
5350
+ time = utils.clamp(clickTime || 200, minTime, maxTime),
7066
5351
  k = (time - minTime) / (maxTime - minTime),
7067
5352
  scale = minScale + k * (maxScale - minScale);
7068
5353
  return 1 + scale * zoomScaleMultiplier;
@@ -7289,7 +5574,7 @@
7289
5574
  rows = 0;
7290
5575
  // self.hide(); // clean up if panel is already open
7291
5576
  el.empty(); // clean up if panel is already open
7292
- utils$1.forEachProperty(rec, function(v, k) {
5577
+ utils.forEachProperty(rec, function(v, k) {
7293
5578
  var type;
7294
5579
  // missing GeoJSON fields are set to undefined on import; skip these
7295
5580
  if (v !== undefined) {
@@ -7317,7 +5602,7 @@
7317
5602
  // Some individual features can have undefined values for some or all of
7318
5603
  // their data properties (properties are set to undefined when an input JSON file
7319
5604
  // has inconsistent fields, or after force-merging layers with inconsistent fields).
7320
- el.html(utils$1.format('<div class="note">This %s is missing attribute data.</div>',
5605
+ el.html(utils.format('<div class="note">This %s is missing attribute data.</div>',
7321
5606
  table && table.getFields().length > 0 ? 'feature': 'layer'));
7322
5607
  }
7323
5608
  }
@@ -7328,7 +5613,7 @@
7328
5613
  var str = formatInspectorValue(val, type);
7329
5614
  var cell = El('tr')
7330
5615
  .appendTo(table)
7331
- .html(utils$1.format(rowHtml, key, utils$1.htmlEscape(str)))
5616
+ .html(utils.format(rowHtml, key, utils.htmlEscape(str)))
7332
5617
  .findChild('.value');
7333
5618
  setFieldClass(cell, val, type);
7334
5619
  if (editable) {
@@ -7337,7 +5622,7 @@
7337
5622
  }
7338
5623
 
7339
5624
  function setFieldClass(el, val, type) {
7340
- var isNum = type ? type == 'number' : utils$1.isNumber(val);
5625
+ var isNum = type ? type == 'number' : utils.isNumber(val);
7341
5626
  var isNully = val === undefined || val === null || val !== val;
7342
5627
  var isEmpty = val === '';
7343
5628
  el.classed('num-field', isNum);
@@ -7363,7 +5648,9 @@
7363
5648
  } else {
7364
5649
  // field content has changed
7365
5650
  strval = strval2;
5651
+ gui.dispatchEvent('data_preupdate', {FID: currId}); // for undo/redo
7366
5652
  rec[key] = val2;
5653
+ gui.dispatchEvent('data_postupdate', {FID: currId});
7367
5654
  input.value(strval);
7368
5655
  setFieldClass(el, val2, type);
7369
5656
  self.dispatchEvent('update', {field: key, value: val2, id: currId});
@@ -7375,7 +5662,7 @@
7375
5662
  function formatInspectorValue(val, type) {
7376
5663
  var str;
7377
5664
  if (type == 'date') {
7378
- str = utils$1.formatDateISO(val);
5665
+ str = utils.formatDateISO(val);
7379
5666
  } else if (type == 'object') {
7380
5667
  str = val ? JSON.stringify(val) : "";
7381
5668
  } else {
@@ -7574,11 +5861,13 @@
7574
5861
  // handle either numeric strings or numbers in fields
7575
5862
  function applyDelta(rec, key, delta) {
7576
5863
  var currVal = rec[key];
7577
- var isString = utils$1.isString(currVal);
5864
+ var isString = utils.isString(currVal);
7578
5865
  var newVal = (+currVal + delta) || 0;
7579
5866
  rec[key] = isString ? String(newVal) : newVal;
7580
5867
  }
7581
5868
 
5869
+ var snapVerticesToPoint = internal.snapVerticesToPoint;
5870
+
7582
5871
  function getDisplayCoordsById(id, layer, ext) {
7583
5872
  var coords = getPointCoordsById(id, layer);
7584
5873
  return ext.translateCoords(coords[0], coords[1]);
@@ -7599,7 +5888,7 @@
7599
5888
  }
7600
5889
 
7601
5890
 
7602
- function SymbolDragging2(gui, ext, hit) {
5891
+ function InteractiveEditor(gui, ext, hit) {
7603
5892
  // var targetTextNode; // text node currently being dragged
7604
5893
  var dragging = false;
7605
5894
  var activeRecord;
@@ -7681,6 +5970,7 @@
7681
5970
  triggerGlobalEvent('symbol_dragstart', e);
7682
5971
  startDragging();
7683
5972
  } else if (vertexEditingEnabled()) {
5973
+ onVertexDragStart(e);
7684
5974
  triggerGlobalEvent('vertex_dragstart', e);
7685
5975
  startDragging();
7686
5976
  }
@@ -7704,7 +5994,8 @@
7704
5994
  triggerGlobalEvent('label_dragend', e);
7705
5995
  stopDragging();
7706
5996
  } else if (vertexEditingEnabled()) {
7707
- onVertexDragEnd(e);
5997
+ // kludge to get dataset to recalculate internal bounding boxes
5998
+ hit.getHitTarget().arcs.transformPoints(function() {});
7708
5999
  triggerGlobalEvent('vertex_dragend', e);
7709
6000
  stopDragging();
7710
6001
  }
@@ -7712,42 +6003,62 @@
7712
6003
 
7713
6004
  hit.on('click', function(e) {
7714
6005
  if (labelEditingEnabled()) {
6006
+ var target = hit.getHitTarget();
7715
6007
  onLabelClick(e);
7716
6008
  }
7717
6009
  });
7718
6010
 
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);
6017
+
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());
6024
+ }
6025
+
6026
+
6027
+ function getVertexEventData(e) {
6028
+ return {
6029
+ FID: activeId,
6030
+ vertexIds: activeVertexIds
6031
+ };
6032
+ }
7719
6033
 
7720
6034
  function onLocationDrag(e) {
7721
6035
  var lyr = hit.getHitTarget().layer;
7722
- var p = getPointCoordsById(e.id, hit.getHitTarget().layer);
6036
+ var p = getPointCoordsById(e.id, lyr);
7723
6037
  if (!p) return;
7724
6038
  var diff = translateDeltaDisplayCoords(e.dx, e.dy, ext);
7725
6039
  p[0] += diff[0];
7726
6040
  p[1] += diff[1];
7727
- self.dispatchEvent('location_change'); // signal map to redraw
6041
+ triggerRedraw();
7728
6042
  triggerGlobalEvent('symbol_drag', e);
7729
6043
  }
7730
6044
 
7731
- function onVertexDrag(e) {
6045
+ function onVertexDragStart(e) {
7732
6046
  var target = hit.getHitTarget();
6047
+ var shp = target.layer.shapes[e.id];
7733
6048
  var p = ext.translatePixelCoords(e.x, e.y);
7734
- if (!activeVertexIds) {
7735
- activeVertexIds = internal.findNearestVertices(p, target.layer.shapes[e.id], target.arcs);
7736
- }
6049
+ activeVertexIds = internal.findNearestVertices(p, shp, target.arcs);
6050
+ activeId = e.id;
6051
+ }
6052
+
6053
+ function onVertexDrag(e) {
6054
+ var target = hit.getHitTarget();
7737
6055
  if (!activeVertexIds) return; // ignore error condition
6056
+ var p = ext.translatePixelCoords(e.x, e.y);
7738
6057
  if (gui.keyboard.shiftIsPressed()) {
7739
6058
  internal.snapPointToArcEndpoint(p, activeVertexIds, target.arcs);
7740
6059
  }
7741
- activeVertexIds.forEach(function(idx) {
7742
- internal.setVertexCoords(p[0], p[1], idx, target.arcs);
7743
- });
7744
- self.dispatchEvent('location_change'); // signal map to redraw
7745
- }
7746
-
7747
- function onVertexDragEnd(e) {
7748
- // kludge to get dataset to recalculate internal bounding boxes
7749
- hit.getHitTarget().arcs.transformPoints(function() {});
7750
- activeVertexIds = null;
6060
+ snapVerticesToPoint(activeVertexIds, p, target.arcs);
6061
+ triggerRedraw();
7751
6062
  }
7752
6063
 
7753
6064
  function onLabelClick(e) {
@@ -7760,11 +6071,19 @@
7760
6071
  }
7761
6072
  }
7762
6073
 
6074
+ function triggerRedraw() {
6075
+ gui.dispatchEvent('map-needs-refresh');
6076
+ }
6077
+
7763
6078
  function triggerGlobalEvent(type, e) {
7764
- if (e.id >= 0) {
7765
- // fire event to signal external editor that symbol coords have changed
7766
- gui.dispatchEvent(type, {FID: e.id, layer_name: hit.getHitTarget().layer.name});
7767
- }
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);
7768
6087
  }
7769
6088
 
7770
6089
  function getLabelRecordById(id) {
@@ -7798,7 +6117,7 @@
7798
6117
  var textNode;
7799
6118
  if (!dragging) return;
7800
6119
  if (e.id != activeId) {
7801
- error$1("Mismatched hit ids:", e.id, activeId);
6120
+ error("Mismatched hit ids:", e.id, activeId);
7802
6121
  }
7803
6122
  applyDelta(activeRecord, 'dx', e.dx / scale);
7804
6123
  applyDelta(activeRecord, 'dy', e.dy / scale);
@@ -7839,80 +6158,6 @@
7839
6158
  }
7840
6159
  return el.tagName == 'text' ? el : null;
7841
6160
  }
7842
-
7843
- // svg.addEventListener('mousedown', function(e) {
7844
- // var textTarget = getTextTarget(e);
7845
- // downEvt = e;
7846
- // if (!textTarget) {
7847
- // stopEditing();
7848
- // } else if (!editing) {
7849
- // // nop
7850
- // } else if (textTarget == targetTextNode) {
7851
- // startDragging();
7852
- // } else {
7853
- // startDragging();
7854
- // editTextNode(textTarget);
7855
- // }
7856
- // });
7857
-
7858
- // up event on svg
7859
- // a: currently dragging text
7860
- // -> stop dragging
7861
- // b: clicked on a text feature
7862
- // -> start editing it
7863
-
7864
-
7865
- // svg.addEventListener('mouseup', function(e) {
7866
- // var textTarget = getTextTarget(e);
7867
- // var isClick = isClickEvent(e, downEvt);
7868
- // if (isClick && textTarget && textTarget == targetTextNode &&
7869
- // activeRecord && isMultilineLabel(targetTextNode)) {
7870
- // toggleTextAlign(targetTextNode, activeRecord);
7871
- // updateSymbol();
7872
- // }
7873
- // if (dragging) {
7874
- // stopDragging();
7875
- // } else if (isClick && textTarget) {
7876
- // editTextNode(textTarget);
7877
- // }
7878
- // });
7879
-
7880
- // block dbl-click navigation when editing
7881
- // mouse.on('dblclick', function(e) {
7882
- // if (editing) e.stopPropagation();
7883
- // }, null, eventPriority);
7884
-
7885
- // mouse.on('dragstart', function(e) {
7886
- // onLabelDrag(e);
7887
- // }, null, eventPriority);
7888
-
7889
- // mouse.on('drag', function(e) {
7890
- // var scale = ext.getSymbolScale() || 1;
7891
- // onLabelDrag(e);
7892
- // if (!dragging || !activeRecord) return;
7893
- // applyDelta(activeRecord, 'dx', e.dx / scale);
7894
- // applyDelta(activeRecord, 'dy', e.dy / scale);
7895
- // if (!isMultilineLabel(targetTextNode)) {
7896
- // // update anchor position of single-line labels based on label position
7897
- // // relative to anchor point, for better placement when eventual display font is
7898
- // // different from mapshaper's font.
7899
- // updateTextAnchor(targetTextNode, activeRecord);
7900
- // }
7901
- // // updateSymbol(targetTextNode, activeRecord);
7902
- // targetTextNode = updateSymbol2(targetTextNode, activeRecord, activeId);
7903
- // }, null, eventPriority);
7904
-
7905
- // mouse.on('dragend', function(e) {
7906
- // onLabelDrag(e);
7907
- // stopDragging();
7908
- // }, null, eventPriority);
7909
-
7910
-
7911
- // function onLabelDrag(e) {
7912
- // if (dragging) {
7913
- // e.stopPropagation();
7914
- // }
7915
- // }
7916
6161
  }
7917
6162
 
7918
6163
  function startDragging() {
@@ -7923,8 +6168,7 @@
7923
6168
  dragging = false;
7924
6169
  activeId = -1;
7925
6170
  activeRecord = null;
7926
- // targetTextNode = null;
7927
- // svg.removeAttribute('class');
6171
+ activeVertexIds = null;
7928
6172
  }
7929
6173
 
7930
6174
  function isClickEvent(up, down) {
@@ -7978,7 +6222,7 @@
7978
6222
  dotSize: 2.5
7979
6223
  }, polyline: {
7980
6224
  strokeColor: black,
7981
- strokeWidth: 2.5
6225
+ strokeWidth: 2.5,
7982
6226
  }
7983
6227
  },
7984
6228
  unselectedHoverStyles = {
@@ -8040,7 +6284,7 @@
8040
6284
  }
8041
6285
 
8042
6286
  function getDefaultStyle(lyr, baseStyle) {
8043
- var style = utils$1.extend({}, baseStyle);
6287
+ var style = utils.extend({}, baseStyle);
8044
6288
  // reduce the dot size of large point layers
8045
6289
  if (lyr.geometry_type == 'point' && style.dotSize > 0) {
8046
6290
  style.dotSize *= getDotScale$1(lyr);
@@ -8106,7 +6350,7 @@
8106
6350
  var topId = o.id; // pinned id (if pinned) or hover id
8107
6351
  var topIdx = -1;
8108
6352
  var styler = function(o, i) {
8109
- utils$1.extend(o, i === topIdx ? topStyle: baseStyle);
6353
+ utils.extend(o, i === topIdx ? topStyle: baseStyle);
8110
6354
  };
8111
6355
  var baseStyle = getDefaultStyle(lyr, selectionStyles[geomType]);
8112
6356
  var topStyle;
@@ -8219,7 +6463,7 @@
8219
6463
  if (lyr.geometry_type == 'point') {
8220
6464
  return fields.indexOf('r') > -1; // require 'r' field for point symbols
8221
6465
  }
8222
- return utils$1.difference(fields, ['opacity', 'class']).length > 0;
6466
+ return utils.difference(fields, ['opacity', 'class']).length > 0;
8223
6467
  }
8224
6468
 
8225
6469
 
@@ -8292,6 +6536,7 @@
8292
6536
 
8293
6537
  this.maxScale = maxScale;
8294
6538
 
6539
+ // Display scale, e.g. meters per pixel or degrees per pixel
8295
6540
  this.getPixelSize = function() {
8296
6541
  return 1 / this.getTransform().mx;
8297
6542
  };
@@ -8375,7 +6620,7 @@
8375
6620
  }
8376
6621
 
8377
6622
  function maxAbs() {
8378
- return Math.max.apply(null, utils$1.toArray(arguments).map(Math.abs));
6623
+ return Math.max.apply(null, utils.toArray(arguments).map(Math.abs));
8379
6624
  }
8380
6625
 
8381
6626
  function limitScale(scale) {
@@ -8429,7 +6674,7 @@
8429
6674
  }
8430
6675
  }
8431
6676
 
8432
- utils$1.inherit(MapExtent, EventDispatcher);
6677
+ utils.inherit(MapExtent, EventDispatcher);
8433
6678
 
8434
6679
  // TODO: consider moving this upstream
8435
6680
  function getArcsForRendering(obj, ext) {
@@ -8483,7 +6728,10 @@
8483
6728
  } else {
8484
6729
  arcs = getArcsForRendering(obj, ext);
8485
6730
  filter = getShapeFilter(arcs, ext);
8486
- canv.drawPathShapes(layer.shapes, arcs, style, filter);
6731
+ canv.drawStyledPaths(layer.shapes, arcs, style, filter);
6732
+ if (style.vertices) {
6733
+ canv.drawVertices(layer.shapes, arcs, style, filter);
6734
+ }
8487
6735
  }
8488
6736
  }
8489
6737
 
@@ -8557,7 +6805,7 @@
8557
6805
 
8558
6806
  /*
8559
6807
  // Original function, not optimized
8560
- _self.drawPathShapes = function(shapes, arcs, style) {
6808
+ _self.drawStyledPaths = function(shapes, arcs, style) {
8561
6809
  var startPath = getPathStart(_ext),
8562
6810
  drawPath = getShapePencil(arcs, _ext),
8563
6811
  styler = style.styler || null;
@@ -8570,8 +6818,28 @@
8570
6818
  };
8571
6819
  */
8572
6820
 
6821
+ _self.drawVertices = function(shapes, arcs, style, filter) {
6822
+ var iter = new internal.ShapeIter(arcs);
6823
+ var t = getScaledTransform(_ext);
6824
+ var radius = (style.strokeWidth * 0.9 || 2.2) * GUI.getPixelRatio() * getScaledLineScale(_ext);
6825
+ var color = style.strokeColor || 'black';
6826
+ var shp;
6827
+ _ctx.beginPath();
6828
+ _ctx.fillStyle = color;
6829
+ for (var i=0; i<shapes.length; i++) {
6830
+ shp = shapes[i];
6831
+ 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);
6835
+ }
6836
+ }
6837
+ _ctx.fill();
6838
+ _ctx.closePath();
6839
+ };
6840
+
8573
6841
  // Optimized to draw paths in same-style batches (faster Canvas drawing)
8574
- _self.drawPathShapes = function(shapes, arcs, style, filter) {
6842
+ _self.drawStyledPaths = function(shapes, arcs, style, filter) {
8575
6843
  var styleIndex = {};
8576
6844
  var batchSize = 1500;
8577
6845
  var startPath = getPathStart(_ext, getScaledLineScale(_ext));
@@ -8590,7 +6858,7 @@
8590
6858
  key = getStyleKey(style);
8591
6859
  if (key in styleIndex === false) {
8592
6860
  styleIndex[key] = {
8593
- style: utils$1.defaults({}, style),
6861
+ style: utils.defaults({}, style),
8594
6862
  shapes: []
8595
6863
  };
8596
6864
  }
@@ -8699,7 +6967,7 @@
8699
6967
  }
8700
6968
  }
8701
6969
 
8702
- // TODO: consider using drawPathShapes(), which draws paths in batches
6970
+ // TODO: consider using drawStyledPaths(), which draws paths in batches
8703
6971
  // for faster Canvas rendering. Downside: changes stacking order, which
8704
6972
  // is bad if circles are graduated.
8705
6973
  _self.drawPoints = function(shapes, style) {
@@ -9064,7 +7332,7 @@
9064
7332
  var frame = ext.getFrame(); // frame should be set if we're rendering a furniture layer
9065
7333
  var obj = internal.getEmptyLayerForSVG(lyr, {});
9066
7334
  if (!frame) {
9067
- stop$1('Missing map frame data');
7335
+ stop('Missing map frame data');
9068
7336
  }
9069
7337
  obj.properties.transform = getSvgFurnitureTransform(ext);
9070
7338
  obj.properties.class = 'mapshaper-svg-furniture';
@@ -9092,7 +7360,7 @@
9092
7360
  var html = '';
9093
7361
  // generate a unique id so layer can be identified when symbols are repositioned
9094
7362
  // use it as a class name to avoid id collisions
9095
- var id = utils$1.getUniqueName();
7363
+ var id = utils.getUniqueName();
9096
7364
  var classNames = [id, 'mapshaper-svg-layer', 'mapshaper-' + type + '-layer'];
9097
7365
  g.setAttribute('class', classNames.join(' '));
9098
7366
  target.svg_id = id;
@@ -9543,7 +7811,7 @@
9543
7811
  }
9544
7812
 
9545
7813
  if (obj.tabular) {
9546
- utils$1.extend(obj, getDisplayLayerForTable(layer.data));
7814
+ utils.extend(obj, getDisplayLayerForTable(layer.data));
9547
7815
  }
9548
7816
 
9549
7817
  // dynamic reprojection (arcs were already reprojected above)
@@ -9601,7 +7869,7 @@
9601
7869
  return ids;
9602
7870
  }
9603
7871
 
9604
- utils$1.inherit(MshpMap, EventDispatcher);
7872
+ utils.inherit(MshpMap, EventDispatcher);
9605
7873
 
9606
7874
  function MshpMap(gui) {
9607
7875
  var opts = gui.options,
@@ -9639,7 +7907,7 @@
9639
7907
  _mouse.disable();
9640
7908
  });
9641
7909
 
9642
- gui.on('undo_redo', function() {
7910
+ gui.on('map-needs-refresh', function() {
9643
7911
  drawLayers();
9644
7912
  });
9645
7913
 
@@ -9695,7 +7963,7 @@
9695
7963
  this.setDisplayCRS = function(crs) {
9696
7964
  // TODO: update bounds of frame layer, if there is a frame layer
9697
7965
  var oldCRS = this.getDisplayCRS();
9698
- var newCRS = utils$1.isString(crs) ? internal.getCRS(crs) : crs;
7966
+ var newCRS = utils.isString(crs) ? internal.getCRS(crs) : crs;
9699
7967
  // TODO: handle case that old and new CRS are the same
9700
7968
  _dynamicCRS = newCRS;
9701
7969
  if (!_activeLyr) return; // stop here if no layers have been selected
@@ -9801,13 +8069,7 @@
9801
8069
  });
9802
8070
  }
9803
8071
 
9804
- if (true) { // TODO: add option to disable?
9805
- _editor = new SymbolDragging2(gui, _ext, _hit);
9806
- _editor.on('location_change', function(e) {
9807
- // TODO: look into optimizing, so only changed symbol is redrawn
9808
- drawLayers();
9809
- });
9810
- }
8072
+ _editor = new InteractiveEditor(gui, _ext, _hit);
9811
8073
 
9812
8074
  _ext.on('change', function(e) {
9813
8075
  if (e.reset) return; // don't need to redraw map here if extent has been reset
@@ -9827,7 +8089,11 @@
9827
8089
  function updateOverlayLayer(e) {
9828
8090
  var style = getOverlayStyle(_activeLyr.layer, e);
9829
8091
  if (style) {
9830
- _overlayLyr = utils$1.defaults({
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({
9831
8097
  layer: filterLayerByIds(_activeLyr.layer, style.ids),
9832
8098
  style: style
9833
8099
  }, _activeLyr);
@@ -9992,7 +8258,7 @@
9992
8258
  // kludge to hide ghosted layers when reference layers are present
9993
8259
  // TODO: consider never showing ghosted layers (which appear after
9994
8260
  // commands like dissolve and filter).
9995
- mapLayer.style = utils$1.defaults({
8261
+ mapLayer.style = utils.defaults({
9996
8262
  strokeColors: [null, mapLayer.style.strokeColors[1]]
9997
8263
  }, mapLayer.style);
9998
8264
  }
@@ -10048,7 +8314,7 @@
10048
8314
 
10049
8315
  function GuiInstance(container, opts) {
10050
8316
  var gui = new ModeSwitcher();
10051
- opts = utils$1.extend({
8317
+ opts = utils.extend({
10052
8318
  // defaults
10053
8319
  homeControl: true,
10054
8320
  zoomControl: true,
@@ -10067,6 +8333,7 @@
10067
8333
  gui.interaction = new InteractionMode(gui);
10068
8334
  gui.session = new SessionHistory(gui);
10069
8335
  gui.undo = new Undo(gui);
8336
+ gui.state = {};
10070
8337
 
10071
8338
  gui.showProgressMessage = function(msg) {
10072
8339
  if (!gui.progressMessage) {
@@ -10150,6 +8417,7 @@
10150
8417
  opts.display_all = vars['display-all'] || vars.a || !!manifest.display_all;
10151
8418
  opts.quick_view = vars['quick-view'] || vars.q || !!manifest.quick_view;
10152
8419
  opts.target = vars.target || manifest.target || null;
8420
+ opts.name = vars.name || manifest.name || null;
10153
8421
  return opts;
10154
8422
  }
10155
8423