mapshaper 0.6.74 → 0.6.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mapshaper.js CHANGED
@@ -749,7 +749,7 @@
749
749
  }
750
750
 
751
751
  function getGenericComparator(asc) {
752
- asc = asc !== false;
752
+ asc = asc !== false && asc != 'descending'; // ascending is the default
753
753
  return function(a, b) {
754
754
  var retn = 0;
755
755
  if (b == null) {
@@ -1362,13 +1362,13 @@
1362
1362
  }
1363
1363
 
1364
1364
  // Format an array of (preferably short) strings in columns for console logging.
1365
- function formatStringsAsGrid(arr) {
1365
+ function formatStringsAsGrid(arr, width) {
1366
1366
  // TODO: variable column width
1367
1367
  var longest = arr.reduce(function(len, str) {
1368
1368
  return Math.max(len, str.length);
1369
1369
  }, 0),
1370
1370
  colWidth = longest + 2,
1371
- perLine = Math.floor(80 / colWidth) || 1;
1371
+ perLine = Math.floor((width || 80) / colWidth) || 1;
1372
1372
  return arr.reduce(function(memo, name, i) {
1373
1373
  var col = i % perLine;
1374
1374
  if (i > 0 && col === 0) memo += '\n';
@@ -3720,8 +3720,6 @@
3720
3720
  }
3721
3721
  var require$1 = f;
3722
3722
 
3723
- // import { createRequire } from "module";
3724
-
3725
3723
  var iconv = require$1('iconv-lite');
3726
3724
 
3727
3725
  // import iconv from 'iconv-lite';
@@ -4289,6 +4287,10 @@
4289
4287
  return layerHasPaths(lyr) || layerHasPoints(lyr);
4290
4288
  }
4291
4289
 
4290
+ function layerIsGeometric(lyr) {
4291
+ return !!lyr.geometry_type; // only checks type, includes empty layers
4292
+ }
4293
+
4292
4294
  function layerHasPaths(lyr) {
4293
4295
  return (lyr.geometry_type == 'polygon' || lyr.geometry_type == 'polyline') &&
4294
4296
  layerHasNonNullShapes(lyr);
@@ -4552,6 +4554,7 @@
4552
4554
  getLayerDataTable: getLayerDataTable,
4553
4555
  layerHasNonNullData: layerHasNonNullData,
4554
4556
  layerHasGeometry: layerHasGeometry,
4557
+ layerIsGeometric: layerIsGeometric,
4555
4558
  layerHasPaths: layerHasPaths,
4556
4559
  layerHasPoints: layerHasPoints,
4557
4560
  layerIsRectangle: layerIsRectangle,
@@ -5493,6 +5496,8 @@
5493
5496
  initLegacyArcs(arguments[0]); // want to phase this out
5494
5497
  } else if (arguments.length == 3) {
5495
5498
  initXYData.apply(this, arguments);
5499
+ } else if (arguments.length === 0) {
5500
+ initLegacyArcs([]); // empty collection
5496
5501
  } else {
5497
5502
  error("ArcCollection() Invalid arguments");
5498
5503
  }
@@ -13184,7 +13189,6 @@
13184
13189
  return findVertexIds(p2.x, p2.y, arcs);
13185
13190
  }
13186
13191
 
13187
-
13188
13192
  function snapVerticesToPoint(ids, p, arcs, final) {
13189
13193
  var data = arcs.getVertexData();
13190
13194
  ids.forEach(function(idx) {
@@ -23046,28 +23050,30 @@ ${svg}
23046
23050
  // @samples Array of buffers containing sample text fields
23047
23051
  // TODO: Improve reliability and number of detectable encodings.
23048
23052
  function detectEncoding(samples) {
23049
- var encoding = null;
23050
- var utf8 = looksLikeUtf8(samples);
23051
- var win1252 = looksLikeWin1252(samples);
23052
- if (utf8 == 2 || utf8 > win1252) {
23053
- encoding = 'utf8';
23054
- } else if (win1252 > 0) {
23055
- encoding = 'win1252';
23056
- } else {
23057
- encoding = 'latin1'; // the original Shapefile encoding, using as an (imperfect) fallback
23058
- }
23059
-
23060
- return {
23061
- encoding: encoding,
23062
- confidence: Math.max(utf8, win1252)
23063
- };
23053
+ // score each encoding as 2 (high confidence) 1 (low confidence) or 0 (fail)
23054
+ var candidates = [{
23055
+ // latin1 is the original Shapefile encoding, using as an imperfect fallback
23056
+ // (sorts to the top only if all other encodings score 0)
23057
+ encoding: 'latin1',
23058
+ confidence: 0
23059
+ },{
23060
+ encoding: 'win1252',
23061
+ confidence: looksLikeWin1252(samples)
23062
+ }, {
23063
+ encoding: 'utf8',
23064
+ confidence: looksLikeUtf8(samples)
23065
+ }, {
23066
+ encoding: 'gb18030',
23067
+ confidence: looksLikeGB18030(samples)
23068
+ }];
23069
+ utils.sortOn(candidates, 'confidence', 'descending');
23070
+ return candidates[0];
23064
23071
  }
23065
23072
 
23066
- // Convert an array of text samples to a single string using a given encoding
23067
23073
  function decodeSamples(enc, samples) {
23068
23074
  return samples.map(function(buf) {
23069
23075
  return decodeString(buf, enc).trim();
23070
- }).join('\n');
23076
+ });
23071
23077
  }
23072
23078
 
23073
23079
  // Win1252 is the same as Latin1, except it replaces a block of control
@@ -23082,45 +23088,67 @@ ${svg}
23082
23088
  //
23083
23089
  function looksLikeWin1252(samples) {
23084
23090
  //common l.c. ascii chars
23085
- var ascii = 'abcdefghijklmnopqrstuvwxyz0123456789.()\'"?+-\n,:;/|_$% ',
23086
- // common extended + NBS (found in the wild)
23087
- extended = 'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ°–±’‘' + '\xA0',
23088
- str = decodeSamples('win1252', samples),
23089
- asciiScore = getCharScore(str, ascii),
23090
- totalScore = getCharScore(str, extended + ascii);
23091
- return totalScore > 0.98 && asciiScore >= 0.8 && 2 ||
23092
- totalScore > 0.97 && asciiScore >= 0.6 && 1 || 0;
23091
+ var commonAscii = 'abcdefghijklmnopqrstuvwxyz0123456789.()\'"?+-\n,:;/|_$% ',
23092
+ // more common extended chars + NBS (found in the wild)
23093
+ moreChars = 'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ°–±’‘' + '\xA0',
23094
+ str = decodeSamples('win1252', samples).join(''),
23095
+ commonAsciiPct = calcCharPct(str, commonAscii),
23096
+ expandedPct = calcCharPct(str, moreChars) + commonAsciiPct;
23097
+ var high = expandedPct > 0.98 && commonAsciiPct >= 0.8;
23098
+ var low = expandedPct > 0.97 && commonAsciiPct >= 0.6;
23099
+ return getScore(high, low);
23093
23100
  }
23094
23101
 
23095
- // Reject string if it contains the "replacement character" after decoding to UTF-8
23096
23102
  function looksLikeUtf8(samples) {
23097
- // Remove the byte sequence for the utf-8-encoded replacement char before decoding,
23098
- // in case the file is in utf-8, but contains some previously corrupted text.
23099
- // samples = samples.map(internal.replaceUtf8ReplacementChar);
23100
- var str = decodeSamples('utf8', samples);
23101
- var count = (str.match(/\ufffd/g) || []).length;
23102
- var score = 1 - count / str.length;
23103
- return score == 1 && 2 || score > 0.97 && 1 || 0;
23104
- }
23105
-
23106
- // function replaceUtf8ReplacementChar(buf) {
23107
- // var isCopy = false;
23108
- // for (var i=0, n=buf.length; i<n; i++) {
23109
- // // Check for UTF-8 encoded replacement char (0xEF 0xBF 0xBD)
23110
- // if (buf[i] == 0xef && i + 2 < n && buf[i+1] == 0xbf && buf[i+2] == 0xbd) {
23111
- // if (!isCopy) {
23112
- // buf = utils.createBuffer(buf);
23113
- // isCopy = true;
23114
- // }
23115
- // buf[i] = buf[i+1] = buf[i+2] = 63; // ascii question mark
23116
- // }
23117
- // }
23118
- // return buf;
23119
- // }
23103
+ // Reject string if it contains the "replacement character" after decoding
23104
+ // as utf-8
23105
+ var str = decodeSamples('utf8', samples).join('');
23106
+ var invalidPct = getInvalidPct(str);
23107
+ var high = invalidPct == 0;
23108
+ var low = invalidPct < 0.03;
23109
+ return getScore(high, low);
23110
+ }
23111
+
23112
+ function looksLikeGB18030(samples) {
23113
+ // from Jun Da's frequency table
23114
+ var commonHanZi = '的一了是不我他在人有这来个说上你们到地大着子那就也时道中出得为里下她要么去可以过和看之然后会自没小好生天而起对能还事想都心只家面样把国多又于头年手发如什开前当所无知老但见长已军从方声儿回意作话两点现很成身情十用些走经同进动己三行种向日明女正问此学太打间分因给本眼定二气力被门真法外听实其高先几笑再主将山战才口文最部第它西与全白者便相住公使东等边信像斯机光次感神却死理名重四做别叫王并水月果何位怎马常觉海张少处亲安特美呢色原直望命由候吧让应尔难关许车平师民夫书新接吗路利世比放活快总立队更花爱清五内金带工风克任至指往入空德吃表连解教思飞物电受今完林干代告兵加认通找远非性脸体轻记目令变似反南场跟必石拉士报李火且满该孩字红象即结言员房件万条提写或坐北早失离步陈乎请转近切黑深城办倒各父传音站官半男击合阿英决怕杀未形及算青黄落刚百论谁突交团度义罗始强紧敌八母钱极片化流管惊每题晚虽政兴答司妈夜越啊奇达谈武友数领朝保服曾拿则哪格尽根急语容喜求衣留双影刻制随冷九苦量备布照周故准客船江系姐争功怪星断句龙竟视界讲取古六静底精七河久绝阳识哈台确息期整伤忙娘终剑送计愿欢微您沉装敢云脚消若复收千木乐毛华集树弟皇响希诉号巴穿线汉攻呀警派刘酒雷停史阵错建足显丽另包势破亮首志观病热跑业治田冲运约暗待共院仍区害元哥围屋胡产室调类细议爷注易务众帝市摇乱密姑斗除式示睛楼造朋社持慢般皮京况块忽脑校甚查土怀福单联赶背统喝疑支血饭灵够章群威举兰游器察嘴痛铁掉宝历改推枪念参术帮党据品须居称旁退梦科岁低严引吉睡爸呼追局露维苏证村挥独节谢香亚波角案读图左掌假跳究楚余鬼钟座礼展玉肯既止守广考恩异料段尼画续米草胜存唐医程弹烟商顾招宗堂野初府激雨尚渐诗顿伯孙际沙雪板闻导致护春态基设耳简婚幸味右买演权卡继依恶庄炮亦虎州刀球需兄闪笔否烈玩啦逃排仅弄具散默景顶郑洋丝卫速侠差贵君习妇助恐救湖莫窗险顺封佛委旧印伙妹副宫洞永罪松责组防艺营班试鲁宋靠索灯介翻喊纪秘妻纸银略充戏担某杨射魔遇鱼陆级坏忘乡鲜哭费抓叶醒族纳床咱桌境列午振抱专毫店街怒托剧置秋藏赵划普岛较承脱革忍伸份免齐抗猛园临犯食架选歌按败良隐养属洛朱狂修坚压寻旅谋温探资端投泪阴换药麻施丈冰雄著绿职负短模荣遗农叹川毒吴蒋卖范禁杂富秀县祖梅谷凡刺登蓝奶缓姓牛价规篇劳质婆仙馆摆舞层狗占墙善熟验肉臣状呆圣懂袋唱值迷替归巨讨毕批镇吸森拍灭握伊杰勒卷偷奔省谓危付伦休厅预迎罢恨博亡欲悲宣标闹岸';
23115
+ var str = decodeSamples('gb18030', samples).join('');
23116
+ // Almost all the common Unicode Hanzi are in this range (along with many more uncommon ones)
23117
+ var chineseStr = str.replace(/[^\u4e00-\u9fa5]/g, '');
23118
+ var chinesePct = chineseStr.length / str.length;
23119
+ var commonAsciiStr = extractCommonAsciiChars(str);
23120
+ var commonAsciiPct = commonAsciiStr.length / str.length;
23121
+ // Some encodings get converted almost completely into valid (but mostly
23122
+ // uncommon) Chinese characters by the gb18030 converter.
23123
+ // To guard against this, we're requiring that a certain percentage of
23124
+ // characters be on a list of the most common characters.
23125
+ var commonHanZiPct = calcCharPct(chineseStr, commonHanZi);
23126
+ // check for non-convertible characters
23127
+ var invalidPct = getInvalidPct(str);
23128
+ var high = chinesePct > 0.5 && (chinesePct + commonAsciiPct) > 0.9 &&
23129
+ invalidPct === 0 && commonHanZiPct > 0.25;
23130
+ var low = chinesePct > 0.3 && (chinesePct + commonAsciiPct) > 0.8 &&
23131
+ commonHanZiPct > 0.15;
23132
+ return getScore(high, low);
23133
+ }
23134
+
23135
+ function getScore(high, low) {
23136
+ return high && 2 || low && 1 || 0;
23137
+ }
23138
+
23139
+ function getInvalidPct(str) {
23140
+ // count occurences of the "replacement" character
23141
+ var invalidCount = (str.match(/\ufffd/g) || []).length;
23142
+ return invalidCount / str.length;
23143
+ }
23144
+
23145
+ function extractCommonAsciiChars(str) {
23146
+ return str.replace(/[^a-zA-Z0-9.()'"?+\n,:;/|_$% -]/g, '');
23147
+ }
23120
23148
 
23121
23149
  // Calc percentage of chars in a string that are present in a second string
23122
23150
  // @chars String of chars to look for in @str
23123
- function getCharScore(str, chars) {
23151
+ function calcCharPct(str, chars) {
23124
23152
  var index = {},
23125
23153
  count = 0;
23126
23154
  str = str.toLowerCase();
@@ -23130,9 +23158,16 @@ ${svg}
23130
23158
  for (i=0, n=str.length; i<n; i++) {
23131
23159
  count += index[str[i]] || 0;
23132
23160
  }
23133
- return count / str.length;
23161
+ return count / str.length || 0;
23134
23162
  }
23135
23163
 
23164
+ var EncodingDetection = /*#__PURE__*/Object.freeze({
23165
+ __proto__: null,
23166
+ detectEncodingFromBOM: detectEncodingFromBOM,
23167
+ detectEncoding: detectEncoding,
23168
+ decodeSamples: decodeSamples
23169
+ });
23170
+
23136
23171
  // Convert a string containing delimited text data into a dataset object
23137
23172
  function importDelim(str, opts) {
23138
23173
  return importDelim2({content: str}, opts);
@@ -26644,25 +26679,19 @@ ${svg}
26644
26679
  var info = detectEncoding(samples);
26645
26680
  encoding = info.encoding;
26646
26681
  if (info.confidence < 2) {
26647
- msg = 'Warning: Unable to auto-detect the text encoding of a DBF file with high confidence.';
26682
+ msg = 'Warning: Unable to auto-detect the DBF file text encoding with high confidence.';
26648
26683
  msg += '\n\nDefaulting to: ' + encoding + (encoding in encodingNames ? ' (' + encodingNames[encoding] + ')' : '');
26649
- msg += '\n\nSample of how non-ascii text was imported:';
26650
- msg += '\n' + formatStringsAsGrid(decodeSamples(encoding, samples).split('\n'));
26651
- msg += decodeSamples(encoding, samples);
26684
+ msg += '\n\nSample of how non-ascii text was imported:\n';
26685
+ if (runningInBrowser()) {
26686
+ msg += '<pre>' + formatStringsAsGrid(decodeSamples(encoding, samples), 50) + '</pre>';
26687
+ } else {
26688
+ msg += formatStringsAsGrid(decodeSamples(encoding, samples));
26689
+ }
26652
26690
  msg += '\n\n' + ENCODING_PROMPT + '\n';
26653
26691
  message(msg);
26654
26692
  }
26655
26693
  }
26656
26694
 
26657
- // Show a sample of decoded text if non-ascii-range text has been found
26658
- // if (encoding && samples.length > 0) {
26659
- // msg = "Detected DBF text encoding: " + encoding + (encoding in encodingNames ? " (" + encodingNames[encoding] + ")" : "");
26660
- // message(msg);
26661
- // msg = decodeSamples(encoding, samples);
26662
- // msg = formatStringsAsGrid(msg.split('\n'));
26663
- // msg = "\nSample text containing non-ascii characters:" + (msg.length > 60 ? '\n' : '') + msg;
26664
- // verbose(msg);
26665
- // }
26666
26695
  return encoding;
26667
26696
  }
26668
26697
 
@@ -37783,7 +37812,7 @@ ${svg}
37783
37812
  filteredLyr = copyLayer(filteredLyr);
37784
37813
  }
37785
37814
 
37786
- if (opts.verbose !== false) {
37815
+ if (opts.verbose !== false && !opts.quiet) {
37787
37816
  message(utils.format('Retained %,d of %,d features', getFeatureCount(filteredLyr), n));
37788
37817
  }
37789
37818
 
@@ -37833,14 +37862,15 @@ ${svg}
37833
37862
  }
37834
37863
 
37835
37864
  function combineFilters(a, b) {
37836
- return (a && b && function(id) {
37865
+ return a && b ? function(id) {
37837
37866
  return a(id) && b(id);
37838
- }) || a || b;
37867
+ } : (a || b);
37839
37868
  }
37840
37869
 
37841
- cmd.evaluateEachFeature = function(lyr, dataset, exp, opts) {
37870
+ cmd.evaluateEachFeature = function(lyr, dataset, expArg, opts) {
37842
37871
  var n = getFeatureCount(lyr),
37843
37872
  arcs = dataset.arcs,
37873
+ exp = expArg || '',
37844
37874
  compiled, filter;
37845
37875
 
37846
37876
  var exprOpts = {
@@ -42377,6 +42407,9 @@ ${svg}
42377
42407
  candidates.forEach(function(candId) {
42378
42408
  var hit;
42379
42409
  if (candId == absId) return; // ignore self-intersections
42410
+ if (absArcId(candId) >= arcs.size()) {
42411
+ return;
42412
+ }
42380
42413
  hit = geom.getPointToPathInfo(endpoint.point[0], endpoint.point[1], [candId], arcs);
42381
42414
  if (hit && hit.distance <= maxGapLen && (!target || hit.distance < target.distance)) {
42382
42415
  target = hit;
@@ -42462,7 +42495,9 @@ ${svg}
42462
42495
  // if (opts.gap_tolerance) {
42463
42496
  //opts = utils.defaults({snap_interval: opts.gap_tolerance * 0.1}, opts);
42464
42497
  // }
42465
- addIntersectionCuts(dataset, opts);
42498
+ if (!opts.no_cuts) {
42499
+ addIntersectionCuts(dataset, opts);
42500
+ }
42466
42501
  return layers.map(function(lyr) {
42467
42502
  if (lyr.geometry_type != 'polyline') stop("Expected a polyline layer");
42468
42503
  if (opts.from_rings) {
@@ -42492,7 +42527,12 @@ ${svg}
42492
42527
  }
42493
42528
 
42494
42529
  function createPolygonLayer(lyr, dataset, opts) {
42495
- var nodes = closeUndershoots(lyr, dataset, opts);
42530
+ var nodes;
42531
+ if (opts.no_cuts) {
42532
+ nodes = new NodeCollection(dataset.arcs);
42533
+ } else {
42534
+ nodes = closeUndershoots(lyr, dataset, opts);
42535
+ }
42496
42536
  // ignore arcs that don't belong to this layer
42497
42537
  nodes.setArcFilter(getArcPresenceTest(lyr.shapes, nodes.arcs));
42498
42538
  var data = buildPolygonMosaic(nodes);
@@ -45485,7 +45525,7 @@ ${svg}
45485
45525
  });
45486
45526
  }
45487
45527
 
45488
- var version = "0.6.74";
45528
+ var version = "0.6.75";
45489
45529
 
45490
45530
  // Parse command line args into commands and run them
45491
45531
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -46162,6 +46202,7 @@ ${svg}
46162
46202
  DelimExport,
46163
46203
  DelimImport,
46164
46204
  DelimReader,
46205
+ EncodingDetection,
46165
46206
  Encodings,
46166
46207
  Explode,
46167
46208
  Export,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.6.74",
3
+ "version": "0.6.75",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
@@ -1144,10 +1144,18 @@
1144
1144
  return self;
1145
1145
  };
1146
1146
 
1147
- self.close = function() {
1148
- if (el) el.remove();
1147
+ self.close = function(action) {
1148
+ var ms = 0;
1149
+ var _el = el;
1150
+ if (action == 'fade' && _el) {
1151
+ ms = 1000;
1152
+ _el.addClass('fade-out');
1153
+ }
1149
1154
  if (_close) _close();
1150
1155
  el = _cancel = _close = null;
1156
+ setTimeout(function() {
1157
+ if (_el) _el.remove();
1158
+ }, ms);
1151
1159
  };
1152
1160
  return self;
1153
1161
  }
@@ -1753,6 +1761,9 @@
1753
1761
  }],
1754
1762
  info: {}
1755
1763
  };
1764
+ if (type == 'polygon' || type == 'point') {
1765
+ dataset.arcs = new internal.ArcCollection();
1766
+ }
1756
1767
  if (crsInfo) {
1757
1768
  internal.setDatasetCrsInfo(dataset, crsInfo);
1758
1769
  }
@@ -2426,7 +2437,10 @@
2426
2437
  }
2427
2438
 
2428
2439
  function error() {
2429
- stop.apply(null, utils$1.toArray(arguments));
2440
+ var msg = GUI.formatMessageArgs(arguments);
2441
+ console.error(msg);
2442
+ gui.alert('An unkown error occured');
2443
+ throw new Error(msg);
2430
2444
  }
2431
2445
 
2432
2446
  function message() {
@@ -2807,7 +2821,7 @@
2807
2821
  strokeWidth: 1.2,
2808
2822
  vertices: true,
2809
2823
  vertex_overlay_color: o.hit_type == 'vertex' ? violet : black,
2810
- vertex_overlay_scale: o.hit_type == 'vertex' ? 2.4 : 1.7,
2824
+ vertex_overlay_scale: o.hit_type == 'vertex' ? 2.5 : 2,
2811
2825
  vertex_overlay: o.hit_coordinates || null,
2812
2826
  selected_points: o.selected_points || null,
2813
2827
  fillColor: null
@@ -3025,6 +3039,11 @@
3025
3039
  return internal.getUnfilteredArcCoords(arcId, target.source.dataset.arcs);
3026
3040
  }
3027
3041
 
3042
+ function getLastVertexCoords(target) {
3043
+ var arcs = target.source.dataset.arcs;
3044
+ return internal.getVertexCoords(arcs.getPointCount() - 1, arcs);
3045
+ }
3046
+
3028
3047
  function getLastArcLength(target) {
3029
3048
  var arcId = target.source.dataset.arcs.size() - 1;
3030
3049
  return internal.getUnfilteredArcLength(arcId, target.source.dataset.arcs);
@@ -4920,13 +4939,10 @@
4920
4939
  return internal.getLayerSourceFile(lyr, dataset);
4921
4940
  }
4922
4941
 
4923
-
4924
4942
  function isPinnable(lyr) {
4925
- return internal.layerHasGeometry(lyr) || internal.layerHasFurniture(lyr);
4943
+ return internal.layerIsGeometric(lyr) || internal.layerHasFurniture(lyr);
4926
4944
  }
4927
4945
 
4928
-
4929
-
4930
4946
  function rowHTML(c1, c2, cname) {
4931
4947
  return utils$1.format('<div class="row%s"><div class="col1">%s</div>' +
4932
4948
  '<div class="col2">%s</div></div>', cname ? ' ' + cname : '', c1, c2);
@@ -5205,10 +5221,10 @@
5205
5221
 
5206
5222
  gui.on('path_extend', function(e) {
5207
5223
  var redo = function() {
5208
- gui.dispatchEvent('redo_path_extend', {p: e.p});
5224
+ gui.dispatchEvent('redo_path_extend', {p: e.p, shapes: e.shapes2});
5209
5225
  };
5210
5226
  var undo = function() {
5211
- gui.dispatchEvent('undo_path_extend');
5227
+ gui.dispatchEvent('undo_path_extend', {shapes: e.shapes1});
5212
5228
  };
5213
5229
  addHistoryState(undo, redo);
5214
5230
  });
@@ -5543,22 +5559,23 @@
5543
5559
  if (!o || !o.layer) {
5544
5560
  return menus.standard; // TODO: more sensible handling of missing layer
5545
5561
  }
5546
- if (!internal.layerHasGeometry(o.layer)) {
5562
+ if (!o.layer.geometry_type) {
5547
5563
  return menus.table;
5548
5564
  }
5549
5565
  if (internal.layerHasLabels(o.layer)) {
5550
5566
  return menus.labels;
5551
5567
  }
5552
- if (internal.layerHasPoints(o.layer)) {
5568
+ if (o.layer.geometry_type == 'point') {
5553
5569
  return menus.points;
5554
5570
  }
5555
- if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polyline') {
5571
+ if (o.layer.geometry_type == 'polyline') {
5556
5572
  return menus.lines;
5557
5573
  }
5558
- if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polygon') {
5574
+ if (o.layer.geometry_type == 'polygon') {
5559
5575
  return internal.layerOnlyHasRectangles(o.layer, o.dataset.arcs) ?
5560
5576
  menus.rectangles : menus.polygons;
5561
5577
  }
5578
+
5562
5579
  return menus.standard;
5563
5580
  }
5564
5581
 
@@ -5908,13 +5925,13 @@
5908
5925
  }
5909
5926
 
5910
5927
  // Format an array of (preferably short) strings in columns for console logging.
5911
- function formatStringsAsGrid(arr) {
5928
+ function formatStringsAsGrid(arr, width) {
5912
5929
  // TODO: variable column width
5913
5930
  var longest = arr.reduce(function(len, str) {
5914
5931
  return Math.max(len, str.length);
5915
5932
  }, 0),
5916
5933
  colWidth = longest + 2,
5917
- perLine = Math.floor(80 / colWidth) || 1;
5934
+ perLine = Math.floor((width || 80) / colWidth) || 1;
5918
5935
  return arr.reduce(function(memo, name, i) {
5919
5936
  var col = i % perLine;
5920
5937
  if (i > 0 && col === 0) memo += '\n';
@@ -6575,7 +6592,7 @@
6575
6592
  }
6576
6593
 
6577
6594
  function getGenericComparator(asc) {
6578
- asc = asc !== false;
6595
+ asc = asc !== false && asc != 'descending'; // ascending is the default
6579
6596
  return function(a, b) {
6580
6597
  var retn = 0;
6581
6598
  if (b == null) {
@@ -9710,12 +9727,14 @@
9710
9727
  var prevClickEvent;
9711
9728
  var prevHoverEvent;
9712
9729
  var initialArcCount = -1;
9730
+ var initialShapeCount = -1;
9713
9731
  var drawingId = -1; // feature id of path being drawn
9732
+ var sessionCount = 0;
9714
9733
  var alert;
9715
9734
  var _dragging = false;
9716
9735
 
9717
9736
  function active() {
9718
- return !!alert;
9737
+ return initialArcCount >= 0;
9719
9738
  }
9720
9739
 
9721
9740
  function dragging() {
@@ -9753,7 +9772,7 @@
9753
9772
  appendNewPath(target, e.p1, e.p2);
9754
9773
  deleteLastVertex(target); // second vertex is a placeholder
9755
9774
  gui.undo.redo(); // add next vertex in the path
9756
- gui.model.updated({arc_count: true});
9775
+ fullRedraw();
9757
9776
  });
9758
9777
 
9759
9778
  gui.on('undo_path_add', function(e) {
@@ -9769,8 +9788,28 @@
9769
9788
  } else {
9770
9789
  appendVertex$1(target, e.p);
9771
9790
  }
9791
+ if (e.shapes) {
9792
+ replaceShapes(e.shapes);
9793
+ }
9772
9794
  });
9773
9795
 
9796
+ function replaceShapes(shapes) {
9797
+ var target = hit.getHitTarget();
9798
+ var records = target.layer.data?.getRecords();
9799
+ var prevLen = target.layer.shapes.length;
9800
+ var newLen = initialShapeCount + shapes.length;
9801
+ var recordCount = records?.length || 0;
9802
+ target.layer.shapes = target.layer.shapes.slice(0, initialShapeCount).concat(shapes);
9803
+ while (records && records.length > newLen) {
9804
+ records.pop();
9805
+ }
9806
+ while (records && records.length < newLen) {
9807
+ appendNewDataRecord(target.layer);
9808
+ }
9809
+ }
9810
+
9811
+
9812
+
9774
9813
  gui.on('undo_path_extend', function(e) {
9775
9814
  var target = hit.getHitTarget();
9776
9815
  if (drawing() && prevHoverEvent) {
@@ -9782,75 +9821,70 @@
9782
9821
  if (getLastArcLength(target) < 2) {
9783
9822
  gui.undo.undo(); // remove the path
9784
9823
  }
9824
+ if (e.shapes) {
9825
+ replaceShapes(e.shapes);
9826
+ }
9785
9827
  });
9786
9828
 
9787
9829
  function turnOn() {
9788
9830
  var target = hit.getHitTarget();
9789
- var pathStr = polygonMode() ? 'closed paths' : 'paths';
9831
+ initialArcCount = target.arcs.size();
9832
+ initialShapeCount = target.layer.shapes.length;
9833
+ if (sessionCount === 0) {
9834
+ showInstructions();
9835
+ }
9836
+ sessionCount++;
9837
+ }
9838
+
9839
+ function showInstructions() {
9790
9840
  var isMac = navigator.userAgent.includes('Mac');
9791
9841
  var symbol = isMac ? '⌘' : '^';
9842
+ var pathStr = polygonMode() ? 'closed paths' : 'paths';
9792
9843
  var msg = `Instructions: Click on the map to draw ${pathStr}. Drag vertices to reshape a path. Type ${symbol}Z/${symbol}Y to undo/redo.`;
9793
- initialArcCount = hit.getHitTarget().arcs.size();
9794
- alert = showPopupAlert(msg, null, {non_blocking: true, max_width: '360px'});
9844
+ alert = showPopupAlert(msg, null, {
9845
+ non_blocking: true, max_width: '360px'});
9795
9846
  }
9796
9847
 
9797
9848
  function turnOff() {
9798
- finishPath();
9849
+ var removed = 0;
9850
+ finishCurrentPath();
9799
9851
  if (polygonMode()) {
9800
- finishPolygons();
9852
+ removed = removeOpenPolygons();
9801
9853
  }
9802
9854
  clearDrawingInfo();
9803
- alert.close();
9804
- alert = null;
9855
+ if (alert) {
9856
+ alert.close();
9857
+ alert = null;
9858
+ }
9805
9859
  initialArcCount = -1;
9860
+ initialShapeCount = -1;
9806
9861
  if (gui.interaction.getMode() == 'drawing') {
9807
9862
  // mode change was not initiated by interactive menu -- turn off interactivity
9808
9863
  gui.interaction.turnOff();
9809
9864
  }
9810
- }
9811
-
9812
- function finish() {
9813
- if (polygonMode()) {
9814
- finishPolygons();
9865
+ if (removed > 0) {
9866
+ fullRedraw();
9815
9867
  }
9816
9868
  }
9817
9869
 
9818
- function finishPolygons() {
9819
- // step1: make a polyline layer containing just newly drawn paths
9870
+ // returns number of removed shapes
9871
+ function removeOpenPolygons() {
9820
9872
  var target = hit.getHitTarget();
9821
- var newShapeCount = target.arcs.size() - initialArcCount;
9822
- if (newShapeCount <= 0) return; // no paths added
9823
- var polygonLyr = target.source.layer;
9824
- var polygonRecords = polygonLyr.data ? polygonLyr.data.getRecords() : null;
9825
- var initialShapeCount = polygonLyr.shapes.length - newShapeCount;
9826
- var templateRecord;
9827
- if (polygonRecords) {
9828
- // use one of the newly created records as a template (they should all be the same)
9829
- templateRecord = polygonRecords.pop();
9830
- polygonRecords.splice(initialShapeCount); // remove new records
9831
- }
9832
- var polylineLyr = {
9833
- geometry_type: 'polyline',
9834
- shapes: polygonLyr.shapes.splice(initialShapeCount) // move new shapes to polyline layer
9835
- };
9836
-
9837
- // step2: convert polylines to polygons
9838
- //
9839
- // create a temp dataset containing both layers (so original arcs are preserved)
9840
- var tmp = Object.assign({}, target.source.dataset);
9841
- tmp.layers = tmp.layers.concat(polylineLyr);
9842
- var outputLayers = mapshaper.cmd.polygons([polylineLyr], tmp, {});
9843
-
9844
- // step3: add new polygons to the original polygons
9845
- outputLayers[0].shapes.forEach(function(shp) {
9846
- var rec = polygonRecords ? internal.copyRecord(templateRecord) : null;
9847
- polygonLyr.shapes.push(shp);
9848
- if (polygonRecords) {
9849
- polygonRecords.push(rec);
9873
+ var arcs = target.source.dataset.arcs;
9874
+ var n = target.layer.shapes.length;
9875
+ // delete open paths (should only occur on single-arc shapes)
9876
+ for (var i=initialShapeCount; i<n; i++) {
9877
+ var shp = target.layer.shapes[i];
9878
+ if (!geom.pathIsClosed(shp[0], arcs)) {
9879
+ target.layer.shapes[i] = null;
9850
9880
  }
9851
- });
9881
+ }
9882
+ // removes polygons with wrong winding order and null geometry
9883
+ mapshaper.cmd.filterFeatures(target.layer, arcs, {remove_empty: true, quiet: true});
9884
+ return n - target.layer.shapes.length;
9885
+ }
9852
9886
 
9853
- // step4: update map
9887
+ function fullRedraw() {
9854
9888
  gui.model.updated({arc_count: true});
9855
9889
  }
9856
9890
 
@@ -9863,6 +9897,9 @@
9863
9897
 
9864
9898
  hit.on('dragstart', function(e) {
9865
9899
  if (!active() || drawing() || !hoverVertexInfo) return;
9900
+ if (alert) {
9901
+ alert.close('fade');
9902
+ }
9866
9903
  e.originalEvent.stopPropagation();
9867
9904
  _dragging = true;
9868
9905
  if (hoverVertexInfo.type == 'interpolated') {
@@ -9903,12 +9940,10 @@
9903
9940
  // double-click finishes a path (when drawing)
9904
9941
  hit.on('dblclick', function(e) {
9905
9942
  if (!active()) return;
9943
+ // double click finishes a path
9944
+ // note: if the preceding 'click' finished the path, this does not fire
9906
9945
  if (drawing()) {
9907
- // double click finishes a path
9908
- // before: dblclick is preceded by two clicks, need another vertex delete
9909
- // now: second click is suppressed
9910
- // deleteLastVertex(hit.getHitTarget());
9911
- finishPath();
9946
+ finishCurrentPath();
9912
9947
  e.originalEvent.stopPropagation(); // prevent dblclick zoom
9913
9948
  return;
9914
9949
  }
@@ -9920,7 +9955,7 @@
9920
9955
  if (!active() || dragging()) return;
9921
9956
  if (drawing()) {
9922
9957
  if (!e.overMap) {
9923
- finishPath();
9958
+ finishCurrentPath();
9924
9959
  return;
9925
9960
  }
9926
9961
  if (gui.keyboard.shiftIsPressed()) {
@@ -9948,27 +9983,27 @@
9948
9983
  if (!active()) return;
9949
9984
  if (detectDoubleClick(e)) return; // ignore second click of a dblclick
9950
9985
  var p = pixToDataCoords(e.x, e.y);
9951
- if (drawing() && hoverVertexInfo) {
9952
- // finish the path if a vertex is highlighted
9953
- p = hoverVertexInfo.point;
9954
- extendPath(p);
9955
- finishPath();
9956
- } else if (drawing()) {
9957
- extendPath(p);
9986
+ if (drawing()) {
9987
+ extendCurrentPath(hoverVertexInfo?.point || p);
9988
+ // extendCurrentPath(p); // just extend to current mouse position (not hover vertex)
9958
9989
  } else if (gui.keyboard.shiftIsPressed()) {
9959
9990
  deleteActiveVertex(e);
9960
9991
  } else {
9961
- startPath(p);
9992
+ startNewPath(p);
9993
+ if (alert) {
9994
+ alert.close('fade');
9995
+ }
9962
9996
  }
9963
9997
  prevClickEvent = e;
9964
9998
  });
9965
9999
 
9966
10000
  // esc key finishes a path
9967
10001
  gui.keyboard.on('keydown', function(e) {
9968
- if (active() && e.keyName == 'esc') {
9969
- finishPath();
10002
+ if (active() && (e.keyName == 'esc' || e.keyName == 'enter')) {
10003
+ e.stopPropagation();
10004
+ finishCurrentPath();
9970
10005
  }
9971
- });
10006
+ }, null, 10);
9972
10007
 
9973
10008
  // detect second 'click' event of a double-click action
9974
10009
  function detectDoubleClick(evt) {
@@ -10036,7 +10071,7 @@
10036
10071
  thisEvt.y = snapped.y;
10037
10072
  }
10038
10073
 
10039
- function finishPath() {
10074
+ function finishCurrentPath() {
10040
10075
  if (!drawing()) return;
10041
10076
  var target = hit.getHitTarget();
10042
10077
  if (getLastArcLength(target) <= 2) { // includes hover point
@@ -10045,27 +10080,44 @@
10045
10080
  deleteLastVertex(target);
10046
10081
  }
10047
10082
  clearDrawingInfo();
10048
- gui.model.updated({arc_count: true});
10083
+ fullRedraw();
10049
10084
  }
10050
10085
 
10051
10086
  // p: [x, y] source data coordinates
10052
- function startPath(p2) {
10087
+ function startNewPath(p2) {
10053
10088
  var target = hit.getHitTarget();
10054
- var p1 = hoverVertexInfo ? hoverVertexInfo.point : p2;
10089
+ var p1 = hoverVertexInfo?.point || p2;
10055
10090
  appendNewPath(target, p1, p2);
10056
10091
  gui.dispatchEvent('path_add', {target, p1, p2});
10057
10092
  drawingId = target.layer.shapes.length - 1;
10058
10093
  hit.setDrawingId(drawingId);
10059
10094
  }
10060
10095
 
10061
- // p: [x, y] source data coordinates
10062
- function extendPath(p) {
10096
+ // p: [x, y] source data coordinates of new point on path
10097
+ function extendCurrentPath(p) {
10063
10098
  var target = hit.getHitTarget();
10064
- if (getLastArcLength(target) >= 2) {
10099
+ var shapes1, shapes2;
10100
+ // finish the path if a vertex is selected (but not an interpolated point)
10101
+ var finish = hoverVertexInfo?.type == 'vertex';
10102
+ if (getLastArcLength(target) < 2) {
10103
+ error$1('Defective path');
10104
+ }
10105
+ if (finish && polygonMode()) {
10106
+ shapes1 = target.layer.shapes.slice(initialShapeCount);
10107
+ shapes2 = tryToClosePath(shapes1);
10108
+ }
10109
+ if (shapes2) {
10110
+ replaceShapes(shapes2);
10111
+ gui.dispatchEvent('path_extend', {target, p, shapes1, shapes2});
10112
+ clearDrawingInfo();
10113
+ fullRedraw();
10114
+
10115
+ } else {
10116
+ appendVertex$1(target, p);
10065
10117
  gui.dispatchEvent('path_extend', {target, p});
10118
+ hit.triggerChangeEvent(); // trigger overlay redraw
10066
10119
  }
10067
- appendVertex$1(target, p);
10068
- hit.triggerChangeEvent();
10120
+
10069
10121
  }
10070
10122
 
10071
10123
  // p: [x, y] source data coordinates
@@ -10160,6 +10212,55 @@
10160
10212
  return closest;
10161
10213
  }
10162
10214
 
10215
+ // shapes: shapes that have been drawn in the current session
10216
+ //
10217
+ function tryToClosePath(shapes) {
10218
+ var target = hit.getHitTarget();
10219
+ var tmpLyr = {
10220
+ geometry_type: 'polyline',
10221
+ shapes: shapes.concat()
10222
+ };
10223
+ // create a temp dataset containing tmp layer and original layers
10224
+ // (so original arcs are retained)
10225
+ var tmpDataset = Object.assign({}, target.source.dataset);
10226
+ tmpDataset.layers = tmpDataset.layers.concat(tmpLyr);
10227
+ // NOTE: added "no_cuts" option to prevent polygons function from modifying
10228
+ // arcs, which would break undo/redo and cause other problems
10229
+ var outputLyr = mapshaper.cmd.polygons([tmpLyr], tmpDataset, {no_cuts: true})[0];
10230
+ var isOpenPath = getOpenPathTest(outputLyr.shapes);
10231
+ var shapes2 = [];
10232
+ shapes.forEach(function(shp) {
10233
+ if (isOpenPath(shp)) {
10234
+ shapes2.push(shp);
10235
+ }
10236
+ });
10237
+ return shapes2.concat(outputLyr.shapes);
10238
+ }
10239
+
10240
+ // Returns a function for testing if a shape is an unclosed path, and doesn't
10241
+ // overlap with an array of polygon shapes
10242
+ // polygons: array of polygon shapes
10243
+ function getOpenPathTest(polygons) {
10244
+ var arcs = [];
10245
+ internal.forEachArcId(polygons, function(arcId) {
10246
+ if (arcId < 0) arcId = ~arcId;
10247
+ arcs.push(arcId);
10248
+ });
10249
+ return function(shp) {
10250
+ // assume that compound shapes are already polygons
10251
+ var isOpen = false;
10252
+ if (shapeHasOneFwdArc(shp)) {
10253
+ var arcId = shp[0][0];
10254
+ if (arcId < 0) arcId = ~arcId;
10255
+ isOpen = !arcs.includes(arcId);
10256
+ }
10257
+ return isOpen;
10258
+ };
10259
+ }
10260
+
10261
+ function shapeHasOneFwdArc(shp) {
10262
+ return shp.length == 1 && shp[0].length == 1 && shp[0][0] >= 0;
10263
+ }
10163
10264
  }
10164
10265
 
10165
10266
  function initPointDrawing(gui, ext, hit) {
@@ -10756,8 +10857,8 @@
10756
10857
  function getArcsForRendering(obj, ext) {
10757
10858
  var dataset = obj.source.dataset;
10758
10859
  var sourceArcs = dataset.arcs;
10759
- if (obj.geographic && dataset.displayArcs) {
10760
- return dataset.displayArcs.getScaledArcs(ext);
10860
+ if (obj.geographic && dataset.gui?.displayArcs) {
10861
+ return dataset.gui.displayArcs.getScaledArcs(ext);
10761
10862
  }
10762
10863
  return obj.arcs;
10763
10864
  }
@@ -11877,10 +11978,10 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11877
11978
  //if (!lyr.geographic || !sourceCRS) {
11878
11979
  // let getDisplayLayer() handle case of unprojectable source
11879
11980
  if (!lyr.geographic) {
11880
- return lyr;
11981
+ return;
11881
11982
  }
11882
11983
  if (lyr.dynamic_crs && internal.crsAreEqual(sourceCRS, lyr.dynamic_crs)) {
11883
- return lyr;
11984
+ return;
11884
11985
  }
11885
11986
  lyr2 = getDisplayLayer(lyr.source.layer, lyr.source.dataset, {crs: displayCRS});
11886
11987
  // kludge: copy projection-related properties to original layer
@@ -11916,7 +12017,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11916
12017
 
11917
12018
  var displayCRS = opts.crs || null;
11918
12019
  // display arcs may have been generated when another layer in the dataset was converted for display... re-use if available
11919
- var displayArcs = dataset.displayArcs || null;
12020
+ var displayArcs = dataset.gui?.displayArcs || null;
11920
12021
  var sourceCRS;
11921
12022
  var emptyArcs;
11922
12023
 
@@ -11933,7 +12034,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11933
12034
 
11934
12035
  // Assume that dataset.displayArcs is in the display CRS
11935
12036
  // (it must be deleted upstream if reprojection is needed)
11936
- if (!obj.empty && dataset.arcs && !displayArcs) {
12037
+ // if (!obj.empty && dataset.arcs && !displayArcs) {
12038
+ if (dataset.arcs && !displayArcs) {
11937
12039
  // project arcs, if needed
11938
12040
  if (needReprojectionForDisplay(sourceCRS, displayCRS)) {
11939
12041
  displayArcs = projectArcsForDisplay(dataset.arcs, sourceCRS, displayCRS);
@@ -11943,7 +12045,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11943
12045
  }
11944
12046
 
11945
12047
  enhanceArcCollectionForDisplay(displayArcs);
11946
- dataset.displayArcs = displayArcs; // stash these in the dataset for other layers to use
12048
+ dataset.gui = {displayArcs}; // stash these in the dataset for other layers to use
11947
12049
  }
11948
12050
 
11949
12051
  if (internal.layerHasFurniture(layer)) {
@@ -11955,14 +12057,14 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11955
12057
  // treating furniture layers (other than frame) as tabular for now,
11956
12058
  // so there is something to show if they are selected
11957
12059
  // obj.tabular = obj.furniture_type != 'frame';
11958
- } else if (obj.empty) {
11959
- obj.layer = {shapes: []}; // ideally we should avoid empty layers
11960
- } else if (!layer.geometry_type) {
11961
- obj.tabular = true;
11962
- } else {
12060
+ } else if (layer.geometry_type) {
11963
12061
  obj.geographic = true;
11964
12062
  obj.layer = layer;
11965
12063
  obj.arcs = displayArcs;
12064
+ } else if (!obj.empty) {
12065
+ obj.tabular = true;
12066
+ } else {
12067
+ obj.layer = {shapes: []}; // ideally we should avoid empty layers
11966
12068
  }
11967
12069
 
11968
12070
  if (obj.tabular) {
@@ -12387,13 +12489,13 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12387
12489
  updateFullBounds();
12388
12490
  };
12389
12491
 
12390
- function getGlobalStyleOptions() {
12492
+ function getGlobalStyleOptions(opts) {
12391
12493
  var mode = gui.state.interaction_mode;
12392
- return {
12494
+ return Object.assign({
12393
12495
  darkMode: !!gui.state.dark_basemap,
12394
12496
  outlineMode: mode == 'vertices',
12395
12497
  interactionMode: mode
12396
- };
12498
+ }, opts);
12397
12499
  }
12398
12500
 
12399
12501
  // Refresh map display in response to data changes, layer selection, etc.
@@ -12408,7 +12510,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12408
12510
 
12409
12511
  if (arcsMayHaveChanged(e.flags)) {
12410
12512
  // regenerate filtered arcs the next time they are needed for rendering
12411
- // delete e.dataset.displayArcs;
12513
+ // delete e.dataset.gui.displayArcs
12412
12514
  clearAllDisplayArcs();
12413
12515
 
12414
12516
  // reset simplification after projection (thresholds have changed)
@@ -12585,7 +12687,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12585
12687
 
12586
12688
  function clearAllDisplayArcs() {
12587
12689
  model.getDatasets().forEach(function(o) {
12588
- delete o.displayArcs;
12690
+ delete o.gui;
12589
12691
  });
12590
12692
  }
12591
12693
 
package/www/mapshaper.js CHANGED
@@ -749,7 +749,7 @@
749
749
  }
750
750
 
751
751
  function getGenericComparator(asc) {
752
- asc = asc !== false;
752
+ asc = asc !== false && asc != 'descending'; // ascending is the default
753
753
  return function(a, b) {
754
754
  var retn = 0;
755
755
  if (b == null) {
@@ -1362,13 +1362,13 @@
1362
1362
  }
1363
1363
 
1364
1364
  // Format an array of (preferably short) strings in columns for console logging.
1365
- function formatStringsAsGrid(arr) {
1365
+ function formatStringsAsGrid(arr, width) {
1366
1366
  // TODO: variable column width
1367
1367
  var longest = arr.reduce(function(len, str) {
1368
1368
  return Math.max(len, str.length);
1369
1369
  }, 0),
1370
1370
  colWidth = longest + 2,
1371
- perLine = Math.floor(80 / colWidth) || 1;
1371
+ perLine = Math.floor((width || 80) / colWidth) || 1;
1372
1372
  return arr.reduce(function(memo, name, i) {
1373
1373
  var col = i % perLine;
1374
1374
  if (i > 0 && col === 0) memo += '\n';
@@ -3720,8 +3720,6 @@
3720
3720
  }
3721
3721
  var require$1 = f;
3722
3722
 
3723
- // import { createRequire } from "module";
3724
-
3725
3723
  var iconv = require$1('iconv-lite');
3726
3724
 
3727
3725
  // import iconv from 'iconv-lite';
@@ -4289,6 +4287,10 @@
4289
4287
  return layerHasPaths(lyr) || layerHasPoints(lyr);
4290
4288
  }
4291
4289
 
4290
+ function layerIsGeometric(lyr) {
4291
+ return !!lyr.geometry_type; // only checks type, includes empty layers
4292
+ }
4293
+
4292
4294
  function layerHasPaths(lyr) {
4293
4295
  return (lyr.geometry_type == 'polygon' || lyr.geometry_type == 'polyline') &&
4294
4296
  layerHasNonNullShapes(lyr);
@@ -4552,6 +4554,7 @@
4552
4554
  getLayerDataTable: getLayerDataTable,
4553
4555
  layerHasNonNullData: layerHasNonNullData,
4554
4556
  layerHasGeometry: layerHasGeometry,
4557
+ layerIsGeometric: layerIsGeometric,
4555
4558
  layerHasPaths: layerHasPaths,
4556
4559
  layerHasPoints: layerHasPoints,
4557
4560
  layerIsRectangle: layerIsRectangle,
@@ -5493,6 +5496,8 @@
5493
5496
  initLegacyArcs(arguments[0]); // want to phase this out
5494
5497
  } else if (arguments.length == 3) {
5495
5498
  initXYData.apply(this, arguments);
5499
+ } else if (arguments.length === 0) {
5500
+ initLegacyArcs([]); // empty collection
5496
5501
  } else {
5497
5502
  error("ArcCollection() Invalid arguments");
5498
5503
  }
@@ -13184,7 +13189,6 @@
13184
13189
  return findVertexIds(p2.x, p2.y, arcs);
13185
13190
  }
13186
13191
 
13187
-
13188
13192
  function snapVerticesToPoint(ids, p, arcs, final) {
13189
13193
  var data = arcs.getVertexData();
13190
13194
  ids.forEach(function(idx) {
@@ -23046,28 +23050,30 @@ ${svg}
23046
23050
  // @samples Array of buffers containing sample text fields
23047
23051
  // TODO: Improve reliability and number of detectable encodings.
23048
23052
  function detectEncoding(samples) {
23049
- var encoding = null;
23050
- var utf8 = looksLikeUtf8(samples);
23051
- var win1252 = looksLikeWin1252(samples);
23052
- if (utf8 == 2 || utf8 > win1252) {
23053
- encoding = 'utf8';
23054
- } else if (win1252 > 0) {
23055
- encoding = 'win1252';
23056
- } else {
23057
- encoding = 'latin1'; // the original Shapefile encoding, using as an (imperfect) fallback
23058
- }
23059
-
23060
- return {
23061
- encoding: encoding,
23062
- confidence: Math.max(utf8, win1252)
23063
- };
23053
+ // score each encoding as 2 (high confidence) 1 (low confidence) or 0 (fail)
23054
+ var candidates = [{
23055
+ // latin1 is the original Shapefile encoding, using as an imperfect fallback
23056
+ // (sorts to the top only if all other encodings score 0)
23057
+ encoding: 'latin1',
23058
+ confidence: 0
23059
+ },{
23060
+ encoding: 'win1252',
23061
+ confidence: looksLikeWin1252(samples)
23062
+ }, {
23063
+ encoding: 'utf8',
23064
+ confidence: looksLikeUtf8(samples)
23065
+ }, {
23066
+ encoding: 'gb18030',
23067
+ confidence: looksLikeGB18030(samples)
23068
+ }];
23069
+ utils.sortOn(candidates, 'confidence', 'descending');
23070
+ return candidates[0];
23064
23071
  }
23065
23072
 
23066
- // Convert an array of text samples to a single string using a given encoding
23067
23073
  function decodeSamples(enc, samples) {
23068
23074
  return samples.map(function(buf) {
23069
23075
  return decodeString(buf, enc).trim();
23070
- }).join('\n');
23076
+ });
23071
23077
  }
23072
23078
 
23073
23079
  // Win1252 is the same as Latin1, except it replaces a block of control
@@ -23082,45 +23088,67 @@ ${svg}
23082
23088
  //
23083
23089
  function looksLikeWin1252(samples) {
23084
23090
  //common l.c. ascii chars
23085
- var ascii = 'abcdefghijklmnopqrstuvwxyz0123456789.()\'"?+-\n,:;/|_$% ',
23086
- // common extended + NBS (found in the wild)
23087
- extended = 'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ°–±’‘' + '\xA0',
23088
- str = decodeSamples('win1252', samples),
23089
- asciiScore = getCharScore(str, ascii),
23090
- totalScore = getCharScore(str, extended + ascii);
23091
- return totalScore > 0.98 && asciiScore >= 0.8 && 2 ||
23092
- totalScore > 0.97 && asciiScore >= 0.6 && 1 || 0;
23091
+ var commonAscii = 'abcdefghijklmnopqrstuvwxyz0123456789.()\'"?+-\n,:;/|_$% ',
23092
+ // more common extended chars + NBS (found in the wild)
23093
+ moreChars = 'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ°–±’‘' + '\xA0',
23094
+ str = decodeSamples('win1252', samples).join(''),
23095
+ commonAsciiPct = calcCharPct(str, commonAscii),
23096
+ expandedPct = calcCharPct(str, moreChars) + commonAsciiPct;
23097
+ var high = expandedPct > 0.98 && commonAsciiPct >= 0.8;
23098
+ var low = expandedPct > 0.97 && commonAsciiPct >= 0.6;
23099
+ return getScore(high, low);
23093
23100
  }
23094
23101
 
23095
- // Reject string if it contains the "replacement character" after decoding to UTF-8
23096
23102
  function looksLikeUtf8(samples) {
23097
- // Remove the byte sequence for the utf-8-encoded replacement char before decoding,
23098
- // in case the file is in utf-8, but contains some previously corrupted text.
23099
- // samples = samples.map(internal.replaceUtf8ReplacementChar);
23100
- var str = decodeSamples('utf8', samples);
23101
- var count = (str.match(/\ufffd/g) || []).length;
23102
- var score = 1 - count / str.length;
23103
- return score == 1 && 2 || score > 0.97 && 1 || 0;
23104
- }
23105
-
23106
- // function replaceUtf8ReplacementChar(buf) {
23107
- // var isCopy = false;
23108
- // for (var i=0, n=buf.length; i<n; i++) {
23109
- // // Check for UTF-8 encoded replacement char (0xEF 0xBF 0xBD)
23110
- // if (buf[i] == 0xef && i + 2 < n && buf[i+1] == 0xbf && buf[i+2] == 0xbd) {
23111
- // if (!isCopy) {
23112
- // buf = utils.createBuffer(buf);
23113
- // isCopy = true;
23114
- // }
23115
- // buf[i] = buf[i+1] = buf[i+2] = 63; // ascii question mark
23116
- // }
23117
- // }
23118
- // return buf;
23119
- // }
23103
+ // Reject string if it contains the "replacement character" after decoding
23104
+ // as utf-8
23105
+ var str = decodeSamples('utf8', samples).join('');
23106
+ var invalidPct = getInvalidPct(str);
23107
+ var high = invalidPct == 0;
23108
+ var low = invalidPct < 0.03;
23109
+ return getScore(high, low);
23110
+ }
23111
+
23112
+ function looksLikeGB18030(samples) {
23113
+ // from Jun Da's frequency table
23114
+ var commonHanZi = '的一了是不我他在人有这来个说上你们到地大着子那就也时道中出得为里下她要么去可以过和看之然后会自没小好生天而起对能还事想都心只家面样把国多又于头年手发如什开前当所无知老但见长已军从方声儿回意作话两点现很成身情十用些走经同进动己三行种向日明女正问此学太打间分因给本眼定二气力被门真法外听实其高先几笑再主将山战才口文最部第它西与全白者便相住公使东等边信像斯机光次感神却死理名重四做别叫王并水月果何位怎马常觉海张少处亲安特美呢色原直望命由候吧让应尔难关许车平师民夫书新接吗路利世比放活快总立队更花爱清五内金带工风克任至指往入空德吃表连解教思飞物电受今完林干代告兵加认通找远非性脸体轻记目令变似反南场跟必石拉士报李火且满该孩字红象即结言员房件万条提写或坐北早失离步陈乎请转近切黑深城办倒各父传音站官半男击合阿英决怕杀未形及算青黄落刚百论谁突交团度义罗始强紧敌八母钱极片化流管惊每题晚虽政兴答司妈夜越啊奇达谈武友数领朝保服曾拿则哪格尽根急语容喜求衣留双影刻制随冷九苦量备布照周故准客船江系姐争功怪星断句龙竟视界讲取古六静底精七河久绝阳识哈台确息期整伤忙娘终剑送计愿欢微您沉装敢云脚消若复收千木乐毛华集树弟皇响希诉号巴穿线汉攻呀警派刘酒雷停史阵错建足显丽另包势破亮首志观病热跑业治田冲运约暗待共院仍区害元哥围屋胡产室调类细议爷注易务众帝市摇乱密姑斗除式示睛楼造朋社持慢般皮京况块忽脑校甚查土怀福单联赶背统喝疑支血饭灵够章群威举兰游器察嘴痛铁掉宝历改推枪念参术帮党据品须居称旁退梦科岁低严引吉睡爸呼追局露维苏证村挥独节谢香亚波角案读图左掌假跳究楚余鬼钟座礼展玉肯既止守广考恩异料段尼画续米草胜存唐医程弹烟商顾招宗堂野初府激雨尚渐诗顿伯孙际沙雪板闻导致护春态基设耳简婚幸味右买演权卡继依恶庄炮亦虎州刀球需兄闪笔否烈玩啦逃排仅弄具散默景顶郑洋丝卫速侠差贵君习妇助恐救湖莫窗险顺封佛委旧印伙妹副宫洞永罪松责组防艺营班试鲁宋靠索灯介翻喊纪秘妻纸银略充戏担某杨射魔遇鱼陆级坏忘乡鲜哭费抓叶醒族纳床咱桌境列午振抱专毫店街怒托剧置秋藏赵划普岛较承脱革忍伸份免齐抗猛园临犯食架选歌按败良隐养属洛朱狂修坚压寻旅谋温探资端投泪阴换药麻施丈冰雄著绿职负短模荣遗农叹川毒吴蒋卖范禁杂富秀县祖梅谷凡刺登蓝奶缓姓牛价规篇劳质婆仙馆摆舞层狗占墙善熟验肉臣状呆圣懂袋唱值迷替归巨讨毕批镇吸森拍灭握伊杰勒卷偷奔省谓危付伦休厅预迎罢恨博亡欲悲宣标闹岸';
23115
+ var str = decodeSamples('gb18030', samples).join('');
23116
+ // Almost all the common Unicode Hanzi are in this range (along with many more uncommon ones)
23117
+ var chineseStr = str.replace(/[^\u4e00-\u9fa5]/g, '');
23118
+ var chinesePct = chineseStr.length / str.length;
23119
+ var commonAsciiStr = extractCommonAsciiChars(str);
23120
+ var commonAsciiPct = commonAsciiStr.length / str.length;
23121
+ // Some encodings get converted almost completely into valid (but mostly
23122
+ // uncommon) Chinese characters by the gb18030 converter.
23123
+ // To guard against this, we're requiring that a certain percentage of
23124
+ // characters be on a list of the most common characters.
23125
+ var commonHanZiPct = calcCharPct(chineseStr, commonHanZi);
23126
+ // check for non-convertible characters
23127
+ var invalidPct = getInvalidPct(str);
23128
+ var high = chinesePct > 0.5 && (chinesePct + commonAsciiPct) > 0.9 &&
23129
+ invalidPct === 0 && commonHanZiPct > 0.25;
23130
+ var low = chinesePct > 0.3 && (chinesePct + commonAsciiPct) > 0.8 &&
23131
+ commonHanZiPct > 0.15;
23132
+ return getScore(high, low);
23133
+ }
23134
+
23135
+ function getScore(high, low) {
23136
+ return high && 2 || low && 1 || 0;
23137
+ }
23138
+
23139
+ function getInvalidPct(str) {
23140
+ // count occurences of the "replacement" character
23141
+ var invalidCount = (str.match(/\ufffd/g) || []).length;
23142
+ return invalidCount / str.length;
23143
+ }
23144
+
23145
+ function extractCommonAsciiChars(str) {
23146
+ return str.replace(/[^a-zA-Z0-9.()'"?+\n,:;/|_$% -]/g, '');
23147
+ }
23120
23148
 
23121
23149
  // Calc percentage of chars in a string that are present in a second string
23122
23150
  // @chars String of chars to look for in @str
23123
- function getCharScore(str, chars) {
23151
+ function calcCharPct(str, chars) {
23124
23152
  var index = {},
23125
23153
  count = 0;
23126
23154
  str = str.toLowerCase();
@@ -23130,9 +23158,16 @@ ${svg}
23130
23158
  for (i=0, n=str.length; i<n; i++) {
23131
23159
  count += index[str[i]] || 0;
23132
23160
  }
23133
- return count / str.length;
23161
+ return count / str.length || 0;
23134
23162
  }
23135
23163
 
23164
+ var EncodingDetection = /*#__PURE__*/Object.freeze({
23165
+ __proto__: null,
23166
+ detectEncodingFromBOM: detectEncodingFromBOM,
23167
+ detectEncoding: detectEncoding,
23168
+ decodeSamples: decodeSamples
23169
+ });
23170
+
23136
23171
  // Convert a string containing delimited text data into a dataset object
23137
23172
  function importDelim(str, opts) {
23138
23173
  return importDelim2({content: str}, opts);
@@ -26644,25 +26679,19 @@ ${svg}
26644
26679
  var info = detectEncoding(samples);
26645
26680
  encoding = info.encoding;
26646
26681
  if (info.confidence < 2) {
26647
- msg = 'Warning: Unable to auto-detect the text encoding of a DBF file with high confidence.';
26682
+ msg = 'Warning: Unable to auto-detect the DBF file text encoding with high confidence.';
26648
26683
  msg += '\n\nDefaulting to: ' + encoding + (encoding in encodingNames ? ' (' + encodingNames[encoding] + ')' : '');
26649
- msg += '\n\nSample of how non-ascii text was imported:';
26650
- msg += '\n' + formatStringsAsGrid(decodeSamples(encoding, samples).split('\n'));
26651
- msg += decodeSamples(encoding, samples);
26684
+ msg += '\n\nSample of how non-ascii text was imported:\n';
26685
+ if (runningInBrowser()) {
26686
+ msg += '<pre>' + formatStringsAsGrid(decodeSamples(encoding, samples), 50) + '</pre>';
26687
+ } else {
26688
+ msg += formatStringsAsGrid(decodeSamples(encoding, samples));
26689
+ }
26652
26690
  msg += '\n\n' + ENCODING_PROMPT + '\n';
26653
26691
  message(msg);
26654
26692
  }
26655
26693
  }
26656
26694
 
26657
- // Show a sample of decoded text if non-ascii-range text has been found
26658
- // if (encoding && samples.length > 0) {
26659
- // msg = "Detected DBF text encoding: " + encoding + (encoding in encodingNames ? " (" + encodingNames[encoding] + ")" : "");
26660
- // message(msg);
26661
- // msg = decodeSamples(encoding, samples);
26662
- // msg = formatStringsAsGrid(msg.split('\n'));
26663
- // msg = "\nSample text containing non-ascii characters:" + (msg.length > 60 ? '\n' : '') + msg;
26664
- // verbose(msg);
26665
- // }
26666
26695
  return encoding;
26667
26696
  }
26668
26697
 
@@ -37783,7 +37812,7 @@ ${svg}
37783
37812
  filteredLyr = copyLayer(filteredLyr);
37784
37813
  }
37785
37814
 
37786
- if (opts.verbose !== false) {
37815
+ if (opts.verbose !== false && !opts.quiet) {
37787
37816
  message(utils.format('Retained %,d of %,d features', getFeatureCount(filteredLyr), n));
37788
37817
  }
37789
37818
 
@@ -37833,14 +37862,15 @@ ${svg}
37833
37862
  }
37834
37863
 
37835
37864
  function combineFilters(a, b) {
37836
- return (a && b && function(id) {
37865
+ return a && b ? function(id) {
37837
37866
  return a(id) && b(id);
37838
- }) || a || b;
37867
+ } : (a || b);
37839
37868
  }
37840
37869
 
37841
- cmd.evaluateEachFeature = function(lyr, dataset, exp, opts) {
37870
+ cmd.evaluateEachFeature = function(lyr, dataset, expArg, opts) {
37842
37871
  var n = getFeatureCount(lyr),
37843
37872
  arcs = dataset.arcs,
37873
+ exp = expArg || '',
37844
37874
  compiled, filter;
37845
37875
 
37846
37876
  var exprOpts = {
@@ -42377,6 +42407,9 @@ ${svg}
42377
42407
  candidates.forEach(function(candId) {
42378
42408
  var hit;
42379
42409
  if (candId == absId) return; // ignore self-intersections
42410
+ if (absArcId(candId) >= arcs.size()) {
42411
+ return;
42412
+ }
42380
42413
  hit = geom.getPointToPathInfo(endpoint.point[0], endpoint.point[1], [candId], arcs);
42381
42414
  if (hit && hit.distance <= maxGapLen && (!target || hit.distance < target.distance)) {
42382
42415
  target = hit;
@@ -42462,7 +42495,9 @@ ${svg}
42462
42495
  // if (opts.gap_tolerance) {
42463
42496
  //opts = utils.defaults({snap_interval: opts.gap_tolerance * 0.1}, opts);
42464
42497
  // }
42465
- addIntersectionCuts(dataset, opts);
42498
+ if (!opts.no_cuts) {
42499
+ addIntersectionCuts(dataset, opts);
42500
+ }
42466
42501
  return layers.map(function(lyr) {
42467
42502
  if (lyr.geometry_type != 'polyline') stop("Expected a polyline layer");
42468
42503
  if (opts.from_rings) {
@@ -42492,7 +42527,12 @@ ${svg}
42492
42527
  }
42493
42528
 
42494
42529
  function createPolygonLayer(lyr, dataset, opts) {
42495
- var nodes = closeUndershoots(lyr, dataset, opts);
42530
+ var nodes;
42531
+ if (opts.no_cuts) {
42532
+ nodes = new NodeCollection(dataset.arcs);
42533
+ } else {
42534
+ nodes = closeUndershoots(lyr, dataset, opts);
42535
+ }
42496
42536
  // ignore arcs that don't belong to this layer
42497
42537
  nodes.setArcFilter(getArcPresenceTest(lyr.shapes, nodes.arcs));
42498
42538
  var data = buildPolygonMosaic(nodes);
@@ -45485,7 +45525,7 @@ ${svg}
45485
45525
  });
45486
45526
  }
45487
45527
 
45488
- var version = "0.6.74";
45528
+ var version = "0.6.75";
45489
45529
 
45490
45530
  // Parse command line args into commands and run them
45491
45531
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -46162,6 +46202,7 @@ ${svg}
46162
46202
  DelimExport,
46163
46203
  DelimImport,
46164
46204
  DelimReader,
46205
+ EncodingDetection,
46165
46206
  Encodings,
46166
46207
  Explode,
46167
46208
  Export,
package/www/page.css CHANGED
@@ -40,6 +40,20 @@ body {
40
40
  cursor: default;
41
41
  }
42
42
 
43
+ .fade-out {
44
+ animation: fadeOut ease forwards 0.6s;
45
+ }
46
+
47
+ @keyframes fadeOut {
48
+ 0% {
49
+ opacity: 1;
50
+ }
51
+ 100% {
52
+ opacity: 0;
53
+ }
54
+ }
55
+
56
+
43
57
  .selectable,.selectable * {
44
58
  user-select: default;
45
59
  -webkit-user-select: text;