mapshaper 0.5.74 → 0.5.78
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/CHANGELOG.md +14 -0
- package/bin/mapshaper-gui +1 -1
- package/mapshaper.js +638 -177
- package/package.json +2 -2
- package/www/index.html +2 -2
- package/www/mapshaper-gui.js +2470 -5773
- package/www/mapshaper.js +638 -177
- package/www/modules.js +21 -2
- package/www/page.css +1 -1
package/mapshaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var VERSION = "0.5.
|
|
3
|
+
var VERSION = "0.5.78";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
var utils = /*#__PURE__*/Object.freeze({
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
get initializeArray () { return initializeArray; },
|
|
53
53
|
get replaceArray () { return replaceArray; },
|
|
54
54
|
get repeatString () { return repeatString; },
|
|
55
|
+
get splitLines () { return splitLines; },
|
|
55
56
|
get pluralSuffix () { return pluralSuffix; },
|
|
56
57
|
get endsWith () { return endsWith; },
|
|
57
58
|
get lpad () { return lpad; },
|
|
@@ -61,7 +62,9 @@
|
|
|
61
62
|
get rtrim () { return rtrim; },
|
|
62
63
|
get addThousandsSep () { return addThousandsSep; },
|
|
63
64
|
get numToStr () { return numToStr; },
|
|
64
|
-
get formatNumber () { return formatNumber
|
|
65
|
+
get formatNumber () { return formatNumber; },
|
|
66
|
+
get formatIntlNumber () { return formatIntlNumber; },
|
|
67
|
+
get formatNumberForDisplay () { return formatNumberForDisplay; },
|
|
65
68
|
get shuffle () { return shuffle; },
|
|
66
69
|
get sortOn () { return sortOn; },
|
|
67
70
|
get genericSort () { return genericSort; },
|
|
@@ -532,6 +535,10 @@
|
|
|
532
535
|
return str;
|
|
533
536
|
}
|
|
534
537
|
|
|
538
|
+
function splitLines(str) {
|
|
539
|
+
return str.split(/\r?\n/);
|
|
540
|
+
}
|
|
541
|
+
|
|
535
542
|
function pluralSuffix(count) {
|
|
536
543
|
return count != 1 ? 's' : '';
|
|
537
544
|
}
|
|
@@ -584,7 +591,16 @@
|
|
|
584
591
|
return decimals >= 0 ? num.toFixed(decimals) : String(num);
|
|
585
592
|
}
|
|
586
593
|
|
|
587
|
-
function formatNumber
|
|
594
|
+
function formatNumber(val) {
|
|
595
|
+
return val + '';
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function formatIntlNumber(val) {
|
|
599
|
+
var str = formatNumber(val);
|
|
600
|
+
return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function formatNumberForDisplay(num, decimals, nullStr, showPos) {
|
|
588
604
|
var fmt;
|
|
589
605
|
if (isNaN(num)) {
|
|
590
606
|
fmt = nullStr || '-';
|
|
@@ -862,7 +878,8 @@
|
|
|
862
878
|
str = str.toUpperCase();
|
|
863
879
|
}
|
|
864
880
|
else if (isNumber) {
|
|
865
|
-
str =
|
|
881
|
+
// str = formatNumberForDisplay(val, isInt ? 0 : decimals);
|
|
882
|
+
str = numToStr(val, decimals);
|
|
866
883
|
if (str[0] == '-') {
|
|
867
884
|
isNeg = true;
|
|
868
885
|
str = str.substr(1);
|
|
@@ -2967,12 +2984,33 @@
|
|
|
2967
2984
|
orient2D(cx, cy, dx, dy, bx, by) <= 0;
|
|
2968
2985
|
}
|
|
2969
2986
|
|
|
2987
|
+
// Useful for determining if a segment that intersects another segment is
|
|
2988
|
+
// entering or leaving an enclosed buffer area
|
|
2989
|
+
// returns -1 if angle of p1p2 -> p3p4 is counter-clockwise (left turn)
|
|
2990
|
+
// returns 1 if angle is clockwise
|
|
2991
|
+
// return 0 if segments are collinear
|
|
2992
|
+
function segmentTurn(p1, p2, p3, p4) {
|
|
2993
|
+
var ax = p1[0],
|
|
2994
|
+
ay = p1[1],
|
|
2995
|
+
bx = p2[0],
|
|
2996
|
+
by = p2[1],
|
|
2997
|
+
// shift p3p4 segment to start at p2
|
|
2998
|
+
dx = bx - p3[0],
|
|
2999
|
+
dy = by - p3[1],
|
|
3000
|
+
cx = p4[0] + dx,
|
|
3001
|
+
cy = p4[1] + dy,
|
|
3002
|
+
orientation = orient2D(ax, ay, bx, by, cx, cy);
|
|
3003
|
+
if (!orientation) return 0;
|
|
3004
|
+
return orientation < 0 ? 1 : -1;
|
|
3005
|
+
}
|
|
3006
|
+
|
|
2970
3007
|
var SegmentGeom = /*#__PURE__*/Object.freeze({
|
|
2971
3008
|
__proto__: null,
|
|
2972
3009
|
segmentIntersection: segmentIntersection,
|
|
2973
3010
|
findClosestPointOnSeg: findClosestPointOnSeg,
|
|
2974
3011
|
orient2D: orient2D,
|
|
2975
|
-
segmentHit: segmentHit
|
|
3012
|
+
segmentHit: segmentHit,
|
|
3013
|
+
segmentTurn: segmentTurn
|
|
2976
3014
|
});
|
|
2977
3015
|
|
|
2978
3016
|
var geom = Object.assign({}, Geom, PolygonGeom, PathGeom, SegmentGeom, PolygonCentroid);
|
|
@@ -8136,10 +8174,14 @@
|
|
|
8136
8174
|
}
|
|
8137
8175
|
|
|
8138
8176
|
function roundToDigits(n, d) {
|
|
8139
|
-
return +n.toFixed(d);
|
|
8177
|
+
return +n.toFixed(d); // string conversion makes this slow
|
|
8140
8178
|
}
|
|
8141
8179
|
|
|
8142
|
-
|
|
8180
|
+
function roundToTenths(n) {
|
|
8181
|
+
return (Math.round(n * 10)) / 10;
|
|
8182
|
+
}
|
|
8183
|
+
|
|
8184
|
+
// inc: Rounding increment (e.g. 0.001 rounds to thousandths)
|
|
8143
8185
|
function getRoundingFunction(inc) {
|
|
8144
8186
|
if (!utils.isNumber(inc) || inc === 0) {
|
|
8145
8187
|
error("Rounding increment must be a non-zero number.");
|
|
@@ -8209,6 +8251,7 @@
|
|
|
8209
8251
|
__proto__: null,
|
|
8210
8252
|
roundToSignificantDigits: roundToSignificantDigits,
|
|
8211
8253
|
roundToDigits: roundToDigits,
|
|
8254
|
+
roundToTenths: roundToTenths,
|
|
8212
8255
|
getRoundingFunction: getRoundingFunction,
|
|
8213
8256
|
getBoundsPrecisionForDisplay: getBoundsPrecisionForDisplay,
|
|
8214
8257
|
getRoundedCoordString: getRoundedCoordString,
|
|
@@ -10270,6 +10313,12 @@
|
|
|
10270
10313
|
};
|
|
10271
10314
|
}
|
|
10272
10315
|
|
|
10316
|
+
/*
|
|
10317
|
+
|
|
10318
|
+
|
|
10319
|
+
|
|
10320
|
+
*/
|
|
10321
|
+
|
|
10273
10322
|
// TODO: use polygon pathfinder shared code
|
|
10274
10323
|
function collectPolylineArcs(ids, nodes, testArc, useArc) {
|
|
10275
10324
|
var parts = [];
|
|
@@ -11839,7 +11888,6 @@
|
|
|
11839
11888
|
});
|
|
11840
11889
|
|
|
11841
11890
|
// Map positive or negative integer ids to non-negative integer ids
|
|
11842
|
-
|
|
11843
11891
|
function IdLookupIndex(n, clearable) {
|
|
11844
11892
|
var fwdIndex = new Int32Array(n);
|
|
11845
11893
|
var revIndex = new Int32Array(n);
|
|
@@ -13811,7 +13859,7 @@
|
|
|
13811
13859
|
var symbolRenderers = {};
|
|
13812
13860
|
|
|
13813
13861
|
function getTransform(xy, scale) {
|
|
13814
|
-
var str = 'translate(' + xy[0] + ' ' + xy[1] + ')';
|
|
13862
|
+
var str = 'translate(' + roundToTenths(xy[0]) + ' ' + roundToTenths(xy[1]) + ')';
|
|
13815
13863
|
if (scale && scale != 1) {
|
|
13816
13864
|
str += ' scale(' + scale + ')';
|
|
13817
13865
|
}
|
|
@@ -15539,6 +15587,139 @@ ${svg}
|
|
|
15539
15587
|
}];
|
|
15540
15588
|
}
|
|
15541
15589
|
|
|
15590
|
+
function exportRecordsAsFixedWidthString(fields, records, opts) {
|
|
15591
|
+
var rows = [], col;
|
|
15592
|
+
for (var i=0; i<fields.length; i++) {
|
|
15593
|
+
col = formatFixedWidthColumn(fields[i], records, opts);
|
|
15594
|
+
if (i === 0) {
|
|
15595
|
+
rows = col;
|
|
15596
|
+
} else for (var j=0; j<rows.length; j++) {
|
|
15597
|
+
rows[j] += ' ' + col[j];
|
|
15598
|
+
}
|
|
15599
|
+
}
|
|
15600
|
+
return rows.join('\n');
|
|
15601
|
+
}
|
|
15602
|
+
|
|
15603
|
+
function formatFixedWidthColumn(field, records, opts) {
|
|
15604
|
+
var arr = [],
|
|
15605
|
+
maxLen = field.length,
|
|
15606
|
+
n = records.length,
|
|
15607
|
+
i, val;
|
|
15608
|
+
arr.push(field);
|
|
15609
|
+
for (i=0; i<n; i++) {
|
|
15610
|
+
val = formatFixedWidthValue(records[i][field], opts);
|
|
15611
|
+
maxLen = Math.max(maxLen, val.length);
|
|
15612
|
+
arr.push(val);
|
|
15613
|
+
}
|
|
15614
|
+
for (i=0; i<arr.length; i++) {
|
|
15615
|
+
arr[i] = arr[i].padEnd(maxLen, ' ');
|
|
15616
|
+
}
|
|
15617
|
+
return arr;
|
|
15618
|
+
}
|
|
15619
|
+
|
|
15620
|
+
function formatFixedWidthValue(val, opts) {
|
|
15621
|
+
// TODO: remove duplication with mapshaper-delim-export.js
|
|
15622
|
+
var s;
|
|
15623
|
+
if (val == null) {
|
|
15624
|
+
s = '';
|
|
15625
|
+
} else if (utils.isString(val)) {
|
|
15626
|
+
s = val; // TODO: handle wide characters, newlines etc.
|
|
15627
|
+
} else if (utils.isNumber(val)) {
|
|
15628
|
+
s = opts.decimal_comma ? utils.formatIntlNumber(val) : utils.formatNumber(val);
|
|
15629
|
+
} else if (utils.isObject(val)) {
|
|
15630
|
+
s = JSON.stringify(val);
|
|
15631
|
+
} else {
|
|
15632
|
+
s = val + '';
|
|
15633
|
+
}
|
|
15634
|
+
return s;
|
|
15635
|
+
}
|
|
15636
|
+
|
|
15637
|
+
|
|
15638
|
+
function readFixedWidthRecords(reader, opts) {
|
|
15639
|
+
var str = reader.toString(opts.encoding || 'ascii');
|
|
15640
|
+
return readFixedWidthRecordsFromString(str, opts);
|
|
15641
|
+
}
|
|
15642
|
+
|
|
15643
|
+
function readFixedWidthRecordsFromString(str, ops) {
|
|
15644
|
+
var fields = parseFixedWidthInfo(str.substring(0, 2000));
|
|
15645
|
+
if (!fields) return [];
|
|
15646
|
+
var lines = utils.splitLines(str);
|
|
15647
|
+
if (lines[lines.length - 1] === '') lines.pop(); // handle newline at end of string
|
|
15648
|
+
var records = [];
|
|
15649
|
+
for (var i=1; i<lines.length; i++) {
|
|
15650
|
+
records.push(parseFixedWidthLine(lines[i], fields));
|
|
15651
|
+
}
|
|
15652
|
+
return records;
|
|
15653
|
+
}
|
|
15654
|
+
|
|
15655
|
+
function parseFixedWidthInfo(sample) {
|
|
15656
|
+
var lines = utils.splitLines(sample);
|
|
15657
|
+
if (lines.length > 2) lines.pop(); // remove possible partial line
|
|
15658
|
+
var n = getMaxLineLength(lines);
|
|
15659
|
+
var headerLine = lines[0];
|
|
15660
|
+
var colInfo = [];
|
|
15661
|
+
var colStart = 0;
|
|
15662
|
+
var inContent = false;
|
|
15663
|
+
var inHeader = false;
|
|
15664
|
+
var isContentChar, isHeaderChar, isColStart, colEnd;
|
|
15665
|
+
for (var i=0; i<=n; i++) {
|
|
15666
|
+
isHeaderChar = testContentChar(headerLine, i);
|
|
15667
|
+
isContentChar = !testEmptyCol(lines, i);
|
|
15668
|
+
isColStart = isHeaderChar && !inHeader;
|
|
15669
|
+
if (isColStart && inContent) {
|
|
15670
|
+
// all lines should have a space char in the position right before a header starts
|
|
15671
|
+
return null;
|
|
15672
|
+
}
|
|
15673
|
+
if (i == n || i > 0 && isColStart) {
|
|
15674
|
+
colEnd = i == n ? undefined : i-1;
|
|
15675
|
+
colInfo.push({
|
|
15676
|
+
name: readValue$1(headerLine, colStart, colEnd),
|
|
15677
|
+
end: colEnd,
|
|
15678
|
+
start: colStart
|
|
15679
|
+
});
|
|
15680
|
+
colStart = i;
|
|
15681
|
+
}
|
|
15682
|
+
inContent = isContentChar;
|
|
15683
|
+
inHeader = isHeaderChar;
|
|
15684
|
+
}
|
|
15685
|
+
return colInfo.length > 0 ? colInfo : null;
|
|
15686
|
+
}
|
|
15687
|
+
|
|
15688
|
+
function getMaxLineLength(lines) {
|
|
15689
|
+
var max = 0;
|
|
15690
|
+
for (var i=0; i<lines.length; i++) {
|
|
15691
|
+
max = Math.max(max, lines[i].length);
|
|
15692
|
+
}
|
|
15693
|
+
return max;
|
|
15694
|
+
}
|
|
15695
|
+
|
|
15696
|
+
function readValue$1(line, start, end) {
|
|
15697
|
+
return line.substring(start, end).trim();
|
|
15698
|
+
}
|
|
15699
|
+
|
|
15700
|
+
function parseFixedWidthLine(str, fields) {
|
|
15701
|
+
var obj = {}, field;
|
|
15702
|
+
for (var i=0; i<fields.length; i++) {
|
|
15703
|
+
field = fields[i];
|
|
15704
|
+
obj[field.name] = readValue$1(str, field.start, field.end);
|
|
15705
|
+
}
|
|
15706
|
+
return obj;
|
|
15707
|
+
}
|
|
15708
|
+
|
|
15709
|
+
function testContentChar(str, i) {
|
|
15710
|
+
return i < str.length && str[i] !== ' ';
|
|
15711
|
+
}
|
|
15712
|
+
|
|
15713
|
+
// return true iff all samples are blank at index i
|
|
15714
|
+
function testEmptyCol(samples, i) {
|
|
15715
|
+
var line;
|
|
15716
|
+
for (var j=0; j<samples.length; j++) {
|
|
15717
|
+
line = samples[j];
|
|
15718
|
+
if (testContentChar(line, i)) return false;
|
|
15719
|
+
}
|
|
15720
|
+
return true;
|
|
15721
|
+
}
|
|
15722
|
+
|
|
15542
15723
|
// Generate output content from a dataset object
|
|
15543
15724
|
function exportDelim(dataset, opts) {
|
|
15544
15725
|
var delim = getExportDelimiter(dataset.info, opts),
|
|
@@ -15560,6 +15741,9 @@ ${svg}
|
|
|
15560
15741
|
var encoding = opts.encoding || 'utf8';
|
|
15561
15742
|
var records = lyr.data.getRecords();
|
|
15562
15743
|
var fields = findFieldNames(records, opts.field_order);
|
|
15744
|
+
if (delim == ' ') {
|
|
15745
|
+
return exportRecordsAsFixedWidthString(fields, records, opts);
|
|
15746
|
+
}
|
|
15563
15747
|
var formatRow = getDelimRowFormatter(fields, delim, opts);
|
|
15564
15748
|
// exporting utf8 and ascii text as string by default (for now)
|
|
15565
15749
|
var exportAsString = encodingIsUtf8(encoding) && !opts.to_buffer &&
|
|
@@ -15617,15 +15801,6 @@ ${svg}
|
|
|
15617
15801
|
};
|
|
15618
15802
|
}
|
|
15619
15803
|
|
|
15620
|
-
function formatNumber$1(val) {
|
|
15621
|
-
return val + '';
|
|
15622
|
-
}
|
|
15623
|
-
|
|
15624
|
-
function formatIntlNumber(val) {
|
|
15625
|
-
var str = formatNumber$1(val);
|
|
15626
|
-
return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
|
|
15627
|
-
}
|
|
15628
|
-
|
|
15629
15804
|
function getDelimValueFormatter(delim, opts) {
|
|
15630
15805
|
var dquoteRxp = new RegExp('["\n\r' + delim + ']');
|
|
15631
15806
|
var decimalComma = opts && opts.decimal_comma || false;
|
|
@@ -15642,7 +15817,7 @@ ${svg}
|
|
|
15642
15817
|
} else if (utils.isString(val)) {
|
|
15643
15818
|
s = formatString(val);
|
|
15644
15819
|
} else if (utils.isNumber(val)) {
|
|
15645
|
-
s = decimalComma ? formatIntlNumber(val) : formatNumber
|
|
15820
|
+
s = decimalComma ? utils.formatIntlNumber(val) : utils.formatNumber(val);
|
|
15646
15821
|
} else if (utils.isObject(val)) {
|
|
15647
15822
|
s = formatString(JSON.stringify(val));
|
|
15648
15823
|
} else {
|
|
@@ -15685,8 +15860,6 @@ ${svg}
|
|
|
15685
15860
|
__proto__: null,
|
|
15686
15861
|
exportDelim: exportDelim,
|
|
15687
15862
|
exportLayerAsDSV: exportLayerAsDSV,
|
|
15688
|
-
formatNumber: formatNumber$1,
|
|
15689
|
-
formatIntlNumber: formatIntlNumber,
|
|
15690
15863
|
getDelimValueFormatter: getDelimValueFormatter
|
|
15691
15864
|
});
|
|
15692
15865
|
|
|
@@ -17050,8 +17223,9 @@ ${svg}
|
|
|
17050
17223
|
//
|
|
17051
17224
|
// TODO: confirm compatibility with all supported encodings
|
|
17052
17225
|
function readDelimRecords(reader, delim, optsArg) {
|
|
17226
|
+
var opts = optsArg || {};
|
|
17227
|
+
if (delim == ' ') return readFixedWidthRecords(reader, opts);
|
|
17053
17228
|
var reader2 = new Reader2(reader),
|
|
17054
|
-
opts = optsArg || {},
|
|
17055
17229
|
headerStr = readLinesAsString(reader2, getDelimHeaderLines(opts), opts.encoding),
|
|
17056
17230
|
header = parseDelimHeaderSection(headerStr, delim, opts),
|
|
17057
17231
|
convertRowArr = getRowConverter(header.import_fields),
|
|
@@ -17074,6 +17248,7 @@ ${svg}
|
|
|
17074
17248
|
// for delimiter characters and newlines. Input size is limited by the maximum
|
|
17075
17249
|
// string size.
|
|
17076
17250
|
function readDelimRecordsFromString(str, delim, opts) {
|
|
17251
|
+
if (delim == ' ') return readFixedWidthRecordsFromString(str, opts);
|
|
17077
17252
|
var header = parseDelimHeaderSection(str, delim, opts);
|
|
17078
17253
|
if (header.import_fields.length === 0 || !header.remainder) return [];
|
|
17079
17254
|
var convert = getRowConverter(header.import_fields);
|
|
@@ -17477,7 +17652,7 @@ ${svg}
|
|
|
17477
17652
|
};
|
|
17478
17653
|
}
|
|
17479
17654
|
|
|
17480
|
-
var supportedDelimiters = ['|', '\t', ',', ';'];
|
|
17655
|
+
var supportedDelimiters = ['|', '\t', ',', ';', ' '];
|
|
17481
17656
|
|
|
17482
17657
|
function isSupportedDelimiter(d) {
|
|
17483
17658
|
return utils.contains(supportedDelimiters, d);
|
|
@@ -17838,9 +18013,21 @@ ${svg}
|
|
|
17838
18013
|
}
|
|
17839
18014
|
|
|
17840
18015
|
function cleanArgv(argv) {
|
|
17841
|
-
|
|
18016
|
+
// Note: original trim caused some quoted spaces to be removed
|
|
18017
|
+
// (e.g. bash shell seems to convert [delimiter=" "] to [delimiter= ],
|
|
18018
|
+
// which then got trimmed to [delimiter=] below)
|
|
18019
|
+
//// argv = argv.map(function(s) {return s.trim();}); // trim whitespace
|
|
18020
|
+
|
|
18021
|
+
// Updated: don't trim space from tokens like [delimeter= ]
|
|
18022
|
+
argv = argv.map(function(s) {
|
|
18023
|
+
if (!/= $/.test(s)) {
|
|
18024
|
+
s = s.trimEnd();
|
|
18025
|
+
}
|
|
18026
|
+
s = s.trimStart();
|
|
18027
|
+
return s;
|
|
18028
|
+
});
|
|
17842
18029
|
argv = argv.filter(function(s) {return s !== '';}); // remove empty tokens
|
|
17843
|
-
// removing trimQuotes() call... now, strings like 'name="Meg"' will no longer
|
|
18030
|
+
// Note: removing trimQuotes() call... now, strings like 'name="Meg"' will no longer
|
|
17844
18031
|
// be parsed the same way as name=Meg and name="Meg"
|
|
17845
18032
|
//// argv = argv.map(utils.trimQuotes); // remove one level of single or dbl quotes
|
|
17846
18033
|
return argv;
|
|
@@ -18947,6 +19134,28 @@ ${svg}
|
|
|
18947
19134
|
'$ mapshaper data.json -colorizer name=getColor nodata=#eee breaks=20,40 \\\n' +
|
|
18948
19135
|
' colors=#e0f3db,#a8ddb5,#43a2ca -each \'fill = getColor(RATING)\' -o output.json');
|
|
18949
19136
|
|
|
19137
|
+
parser.command('dashlines')
|
|
19138
|
+
.describe('split lines into sections, with or without a gap')
|
|
19139
|
+
.oldAlias('split-lines')
|
|
19140
|
+
.option('dash-length', {
|
|
19141
|
+
type: 'distance',
|
|
19142
|
+
describe: 'length of split-apart lines (e.g. 200km)'
|
|
19143
|
+
})
|
|
19144
|
+
.option('gap-length', {
|
|
19145
|
+
type: 'distance',
|
|
19146
|
+
describe: 'length of gaps between dashes (default is 0)'
|
|
19147
|
+
})
|
|
19148
|
+
.option('scaled', {
|
|
19149
|
+
type: 'flag',
|
|
19150
|
+
describe: 'scale dashes and gaps to prevent partial dashes'
|
|
19151
|
+
})
|
|
19152
|
+
.option('planar', {
|
|
19153
|
+
type: 'flag',
|
|
19154
|
+
describe: 'use planar geometry'
|
|
19155
|
+
})
|
|
19156
|
+
.option('where', whereOpt)
|
|
19157
|
+
.option('target', targetOpt);
|
|
19158
|
+
|
|
18950
19159
|
parser.command('define')
|
|
18951
19160
|
// .describe('define expression variables')
|
|
18952
19161
|
.option('expression', {
|
|
@@ -19143,6 +19352,10 @@ ${svg}
|
|
|
19143
19352
|
.option('keep-shapes', {
|
|
19144
19353
|
type: 'flag'
|
|
19145
19354
|
})
|
|
19355
|
+
.option('ids', {
|
|
19356
|
+
// describe: 'filter on a list of feature ids',
|
|
19357
|
+
type: 'numbers'
|
|
19358
|
+
})
|
|
19146
19359
|
.option('cleanup', {type: 'flag'}) // TODO: document
|
|
19147
19360
|
.option('name', nameOpt)
|
|
19148
19361
|
.option('target', targetOpt)
|
|
@@ -19711,6 +19924,11 @@ ${svg}
|
|
|
19711
19924
|
DEFAULT: true,
|
|
19712
19925
|
describe: 'expression or field for grouping features and naming split layers'
|
|
19713
19926
|
})
|
|
19927
|
+
.option('ids', {
|
|
19928
|
+
// used by gui history to split on selected features
|
|
19929
|
+
// describe: 'split on a list of feature ids',
|
|
19930
|
+
type: 'numbers'
|
|
19931
|
+
})
|
|
19714
19932
|
.option('apart', {
|
|
19715
19933
|
describe: 'save output layers to independent datasets',
|
|
19716
19934
|
type: 'flag'
|
|
@@ -26263,97 +26481,6 @@ ${svg}
|
|
|
26263
26481
|
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymin, bb.xmin, bb.ymin));
|
|
26264
26482
|
}
|
|
26265
26483
|
|
|
26266
|
-
function getGeodesic(P) {
|
|
26267
|
-
if (!isLatLngCRS(P)) error('Expected an unprojected CRS');
|
|
26268
|
-
var f = P.es / (1 + Math.sqrt(P.one_es));
|
|
26269
|
-
var GeographicLib = require('mproj').internal.GeographicLib;
|
|
26270
|
-
return new GeographicLib.Geodesic.Geodesic(P.a, f);
|
|
26271
|
-
}
|
|
26272
|
-
|
|
26273
|
-
function getPlanarSegmentEndpoint(x, y, bearing, meterDist) {
|
|
26274
|
-
var rad = bearing / 180 * Math.PI;
|
|
26275
|
-
var dx = Math.sin(rad) * meterDist;
|
|
26276
|
-
var dy = Math.cos(rad) * meterDist;
|
|
26277
|
-
return [x + dx, y + dy];
|
|
26278
|
-
}
|
|
26279
|
-
|
|
26280
|
-
// source: https://github.com/mapbox/cheap-ruler/blob/master/index.js
|
|
26281
|
-
function fastGeodeticSegmentFunction(lng, lat, bearing, meterDist) {
|
|
26282
|
-
var D2R = Math.PI / 180;
|
|
26283
|
-
var cos = Math.cos(lat * D2R);
|
|
26284
|
-
var cos2 = 2 * cos * cos - 1;
|
|
26285
|
-
var cos3 = 2 * cos * cos2 - cos;
|
|
26286
|
-
var cos4 = 2 * cos * cos3 - cos2;
|
|
26287
|
-
var cos5 = 2 * cos * cos4 - cos3;
|
|
26288
|
-
var kx = (111.41513 * cos - 0.09455 * cos3 + 0.00012 * cos5) * 1000;
|
|
26289
|
-
var ky = (111.13209 - 0.56605 * cos2 + 0.0012 * cos4) * 1000;
|
|
26290
|
-
var bearingRad = bearing * D2R;
|
|
26291
|
-
var lat2 = lat + Math.cos(bearingRad) * meterDist / ky;
|
|
26292
|
-
var lng2 = lng + Math.sin(bearingRad) * meterDist / kx;
|
|
26293
|
-
return [lng2, lat2];
|
|
26294
|
-
}
|
|
26295
|
-
|
|
26296
|
-
function getGeodeticSegmentFunction(P) {
|
|
26297
|
-
if (!isLatLngCRS(P)) {
|
|
26298
|
-
return getPlanarSegmentEndpoint;
|
|
26299
|
-
}
|
|
26300
|
-
var g = getGeodesic(P);
|
|
26301
|
-
return function(lng, lat, bearing, meterDist) {
|
|
26302
|
-
var o = g.Direct(lat, lng, bearing, meterDist);
|
|
26303
|
-
var p = [o.lon2, o.lat2];
|
|
26304
|
-
return p;
|
|
26305
|
-
};
|
|
26306
|
-
}
|
|
26307
|
-
|
|
26308
|
-
function getFastGeodeticSegmentFunction(P) {
|
|
26309
|
-
// CAREFUL: this function has higher error at very large distances and at the poles
|
|
26310
|
-
// also, it wouldn't work for other planets than Earth
|
|
26311
|
-
return isLatLngCRS(P) ? fastGeodeticSegmentFunction : getPlanarSegmentEndpoint;
|
|
26312
|
-
}
|
|
26313
|
-
|
|
26314
|
-
// Useful for determining if a segment that intersects another segment is
|
|
26315
|
-
// entering or leaving an enclosed buffer area
|
|
26316
|
-
// returns -1 if angle of p1p2 -> p3p4 is counter-clockwise (left turn)
|
|
26317
|
-
// returns 1 if angle is clockwise
|
|
26318
|
-
// return 0 if segments are collinear
|
|
26319
|
-
function segmentTurn(p1, p2, p3, p4) {
|
|
26320
|
-
var ax = p1[0],
|
|
26321
|
-
ay = p1[1],
|
|
26322
|
-
bx = p2[0],
|
|
26323
|
-
by = p2[1],
|
|
26324
|
-
// shift p3p4 segment to start at p2
|
|
26325
|
-
dx = bx - p3[0],
|
|
26326
|
-
dy = by - p3[1],
|
|
26327
|
-
cx = p4[0] + dx,
|
|
26328
|
-
cy = p4[1] + dy,
|
|
26329
|
-
orientation = geom.orient2D(ax, ay, bx, by, cx, cy);
|
|
26330
|
-
if (!orientation) return 0;
|
|
26331
|
-
return orientation < 0 ? 1 : -1;
|
|
26332
|
-
}
|
|
26333
|
-
|
|
26334
|
-
function bearingDegrees(a, b, c, d) {
|
|
26335
|
-
return geom.bearing(a, b, c, d) * 180 / Math.PI;
|
|
26336
|
-
}
|
|
26337
|
-
|
|
26338
|
-
function bearingDegrees2D(a, b, c, d) {
|
|
26339
|
-
return geom.bearing2D(a, b, c, d) * 180 / Math.PI;
|
|
26340
|
-
}
|
|
26341
|
-
|
|
26342
|
-
// return function to calculate bearing of a segment in degrees
|
|
26343
|
-
function getBearingFunction(dataset) {
|
|
26344
|
-
var P = getDatasetCRS(dataset);
|
|
26345
|
-
return isLatLngCRS(P) ? bearingDegrees : bearingDegrees2D;
|
|
26346
|
-
}
|
|
26347
|
-
|
|
26348
|
-
var Geodesic = /*#__PURE__*/Object.freeze({
|
|
26349
|
-
__proto__: null,
|
|
26350
|
-
getPlanarSegmentEndpoint: getPlanarSegmentEndpoint,
|
|
26351
|
-
getGeodeticSegmentFunction: getGeodeticSegmentFunction,
|
|
26352
|
-
getFastGeodeticSegmentFunction: getFastGeodeticSegmentFunction,
|
|
26353
|
-
segmentTurn: segmentTurn,
|
|
26354
|
-
getBearingFunction: getBearingFunction
|
|
26355
|
-
});
|
|
26356
|
-
|
|
26357
26484
|
function getPolylineBufferMaker2(arcs, geod, getBearing, opts) {
|
|
26358
26485
|
var makeLeftBuffer = getPathBufferMaker2(arcs, geod, getBearing, opts);
|
|
26359
26486
|
var geomType = opts.geometry_type;
|
|
@@ -26647,6 +26774,101 @@ ${svg}
|
|
|
26647
26774
|
};
|
|
26648
26775
|
}
|
|
26649
26776
|
|
|
26777
|
+
// GeographicLib docs: https://geographiclib.sourceforge.io/html/js/
|
|
26778
|
+
// https://geographiclib.sourceforge.io/html/js/module-GeographicLib_Geodesic.Geodesic.html
|
|
26779
|
+
// https://geographiclib.sourceforge.io/html/js/tutorial-2-interface.html
|
|
26780
|
+
function getGeodesic(P) {
|
|
26781
|
+
if (!isLatLngCRS(P)) error('Expected an unprojected CRS');
|
|
26782
|
+
var f = P.es / (1 + Math.sqrt(P.one_es));
|
|
26783
|
+
var GeographicLib = require('mproj').internal.GeographicLib;
|
|
26784
|
+
return new GeographicLib.Geodesic.Geodesic(P.a, f);
|
|
26785
|
+
}
|
|
26786
|
+
|
|
26787
|
+
function interpolatePoint2D(ax, ay, bx, by, k) {
|
|
26788
|
+
var j = 1 - k;
|
|
26789
|
+
return [ax * j + bx * k, ay * j + by * k];
|
|
26790
|
+
}
|
|
26791
|
+
|
|
26792
|
+
function getInterpolationFunction(P) {
|
|
26793
|
+
var spherical = P && isLatLngCRS(P);
|
|
26794
|
+
if (!spherical) return interpolatePoint2D;
|
|
26795
|
+
var geod = getGeodesic(P);
|
|
26796
|
+
return function(lng, lat, lng2, lat2, k) {
|
|
26797
|
+
var r = geod.Inverse(lat, lng, lat2, lng2);
|
|
26798
|
+
var dist = r.s12 * k;
|
|
26799
|
+
var r2 = geod.Direct(lat, lng, r.azi1, dist);
|
|
26800
|
+
return [r2.lon2, r2.lat2];
|
|
26801
|
+
};
|
|
26802
|
+
}
|
|
26803
|
+
|
|
26804
|
+
function getPlanarSegmentEndpoint(x, y, bearing, meterDist) {
|
|
26805
|
+
var rad = bearing / 180 * Math.PI;
|
|
26806
|
+
var dx = Math.sin(rad) * meterDist;
|
|
26807
|
+
var dy = Math.cos(rad) * meterDist;
|
|
26808
|
+
return [x + dx, y + dy];
|
|
26809
|
+
}
|
|
26810
|
+
|
|
26811
|
+
// source: https://github.com/mapbox/cheap-ruler/blob/master/index.js
|
|
26812
|
+
function fastGeodeticSegmentFunction(lng, lat, bearing, meterDist) {
|
|
26813
|
+
var D2R = Math.PI / 180;
|
|
26814
|
+
var cos = Math.cos(lat * D2R);
|
|
26815
|
+
var cos2 = 2 * cos * cos - 1;
|
|
26816
|
+
var cos3 = 2 * cos * cos2 - cos;
|
|
26817
|
+
var cos4 = 2 * cos * cos3 - cos2;
|
|
26818
|
+
var cos5 = 2 * cos * cos4 - cos3;
|
|
26819
|
+
var kx = (111.41513 * cos - 0.09455 * cos3 + 0.00012 * cos5) * 1000;
|
|
26820
|
+
var ky = (111.13209 - 0.56605 * cos2 + 0.0012 * cos4) * 1000;
|
|
26821
|
+
var bearingRad = bearing * D2R;
|
|
26822
|
+
var lat2 = lat + Math.cos(bearingRad) * meterDist / ky;
|
|
26823
|
+
var lng2 = lng + Math.sin(bearingRad) * meterDist / kx;
|
|
26824
|
+
return [lng2, lat2];
|
|
26825
|
+
}
|
|
26826
|
+
|
|
26827
|
+
function getGeodeticSegmentFunction(P) {
|
|
26828
|
+
if (!isLatLngCRS(P)) {
|
|
26829
|
+
return getPlanarSegmentEndpoint;
|
|
26830
|
+
}
|
|
26831
|
+
var g = getGeodesic(P);
|
|
26832
|
+
return function(lng, lat, bearing, meterDist) {
|
|
26833
|
+
var o = g.Direct(lat, lng, bearing, meterDist);
|
|
26834
|
+
var p = [o.lon2, o.lat2];
|
|
26835
|
+
return p;
|
|
26836
|
+
};
|
|
26837
|
+
}
|
|
26838
|
+
|
|
26839
|
+
function getFastGeodeticSegmentFunction(P) {
|
|
26840
|
+
// CAREFUL: this function has higher error at very large distances and at the poles
|
|
26841
|
+
// also, it wouldn't work for other planets than Earth
|
|
26842
|
+
return isLatLngCRS(P) ? fastGeodeticSegmentFunction : getPlanarSegmentEndpoint;
|
|
26843
|
+
}
|
|
26844
|
+
|
|
26845
|
+
|
|
26846
|
+
function bearingDegrees(a, b, c, d) {
|
|
26847
|
+
return geom.bearing(a, b, c, d) * 180 / Math.PI;
|
|
26848
|
+
}
|
|
26849
|
+
|
|
26850
|
+
function bearingDegrees2D(a, b, c, d) {
|
|
26851
|
+
return geom.bearing2D(a, b, c, d) * 180 / Math.PI;
|
|
26852
|
+
}
|
|
26853
|
+
|
|
26854
|
+
// return function to calculate bearing of a segment in degrees
|
|
26855
|
+
function getBearingFunction(dataset) {
|
|
26856
|
+
var P = getDatasetCRS(dataset);
|
|
26857
|
+
return isLatLngCRS(P) ? bearingDegrees : bearingDegrees2D;
|
|
26858
|
+
}
|
|
26859
|
+
|
|
26860
|
+
var Geodesic = /*#__PURE__*/Object.freeze({
|
|
26861
|
+
__proto__: null,
|
|
26862
|
+
interpolatePoint2D: interpolatePoint2D,
|
|
26863
|
+
getInterpolationFunction: getInterpolationFunction,
|
|
26864
|
+
getPlanarSegmentEndpoint: getPlanarSegmentEndpoint,
|
|
26865
|
+
getGeodeticSegmentFunction: getGeodeticSegmentFunction,
|
|
26866
|
+
getFastGeodeticSegmentFunction: getFastGeodeticSegmentFunction,
|
|
26867
|
+
bearingDegrees: bearingDegrees,
|
|
26868
|
+
bearingDegrees2D: bearingDegrees2D,
|
|
26869
|
+
getBearingFunction: getBearingFunction
|
|
26870
|
+
});
|
|
26871
|
+
|
|
26650
26872
|
function makePolylineBuffer(lyr, dataset, opts) {
|
|
26651
26873
|
var geojson = makeShapeBufferGeoJSON(lyr, dataset, opts);
|
|
26652
26874
|
var dataset2 = importGeoJSON(geojson, {});
|
|
@@ -29682,6 +29904,178 @@ ${svg}
|
|
|
29682
29904
|
getColorizerFunction: getColorizerFunction
|
|
29683
29905
|
});
|
|
29684
29906
|
|
|
29907
|
+
function expressionUsesGeoJSON(exp) {
|
|
29908
|
+
return exp.includes('this.geojson');
|
|
29909
|
+
}
|
|
29910
|
+
|
|
29911
|
+
function getFeatureEditor(lyr, dataset) {
|
|
29912
|
+
var changed = false;
|
|
29913
|
+
var api = {};
|
|
29914
|
+
// need to copy attribute to avoid circular references if geojson is assigned
|
|
29915
|
+
// to a data property.
|
|
29916
|
+
var copy = copyLayer(lyr);
|
|
29917
|
+
var features = exportLayerAsGeoJSON(copy, dataset, {}, true);
|
|
29918
|
+
|
|
29919
|
+
api.get = function(i) {
|
|
29920
|
+
return features[i];
|
|
29921
|
+
};
|
|
29922
|
+
|
|
29923
|
+
api.set = function(feat, i) {
|
|
29924
|
+
changed = true;
|
|
29925
|
+
if (utils.isString(feat)) {
|
|
29926
|
+
feat = JSON.parse(feat);
|
|
29927
|
+
}
|
|
29928
|
+
features[i] = GeoJSON.toFeature(feat); // TODO: validate
|
|
29929
|
+
};
|
|
29930
|
+
|
|
29931
|
+
api.done = function() {
|
|
29932
|
+
if (!changed) return; // read-only expression
|
|
29933
|
+
// TODO: validate number of features, etc.
|
|
29934
|
+
var geojson = {
|
|
29935
|
+
type: 'FeatureCollection',
|
|
29936
|
+
features: features
|
|
29937
|
+
};
|
|
29938
|
+
|
|
29939
|
+
// console.log(JSON.stringify(geojson, null, 2))
|
|
29940
|
+
return importGeoJSON(geojson);
|
|
29941
|
+
};
|
|
29942
|
+
return api;
|
|
29943
|
+
}
|
|
29944
|
+
|
|
29945
|
+
cmd.dashlines = function(lyr, dataset, opts) {
|
|
29946
|
+
var crs = getDatasetCRS(dataset);
|
|
29947
|
+
var defs = getStateVar('defs');
|
|
29948
|
+
var exp = `this.geojson = splitFeature(this.geojson)`;
|
|
29949
|
+
requirePolylineLayer(lyr);
|
|
29950
|
+
defs.splitFeature = getSplitFeatureFunction(crs, opts);
|
|
29951
|
+
cmd.evaluateEachFeature(lyr, dataset, exp, opts);
|
|
29952
|
+
delete defs.splitFeature;
|
|
29953
|
+
};
|
|
29954
|
+
|
|
29955
|
+
function getSplitFeatureFunction(crs, opts) {
|
|
29956
|
+
var dashLen = opts.dash_length ? convertDistanceParam(opts.dash_length, crs) : 0;
|
|
29957
|
+
var gapLen = opts.gap_length ? convertDistanceParam(opts.gap_length, crs) : 0;
|
|
29958
|
+
if (dashLen > 0 === false) {
|
|
29959
|
+
stop('Missing required dash-length parameter');
|
|
29960
|
+
}
|
|
29961
|
+
if (gapLen >= 0 == false) {
|
|
29962
|
+
stop('Invalid gap-length option');
|
|
29963
|
+
}
|
|
29964
|
+
var splitLine = getSplitLineFunction(crs, dashLen, gapLen, opts);
|
|
29965
|
+
return function(feat) {
|
|
29966
|
+
var geom = feat.geometry;
|
|
29967
|
+
if (!geom) return feat;
|
|
29968
|
+
if (geom.type == 'LineString') {
|
|
29969
|
+
geom.type = 'MultiLineString';
|
|
29970
|
+
geom.coordinates = [geom.coordinates];
|
|
29971
|
+
}
|
|
29972
|
+
if (geom.type != 'MultiLineString') {
|
|
29973
|
+
error('Unexpected geometry:', geom.type);
|
|
29974
|
+
}
|
|
29975
|
+
geom.coordinates = geom.coordinates.reduce(function(memo, coords) {
|
|
29976
|
+
try {
|
|
29977
|
+
var parts = splitLine(coords);
|
|
29978
|
+
memo = memo.concat(parts);
|
|
29979
|
+
} catch(e) {
|
|
29980
|
+
console.error(e);
|
|
29981
|
+
throw e;
|
|
29982
|
+
}
|
|
29983
|
+
return memo;
|
|
29984
|
+
}, []);
|
|
29985
|
+
|
|
29986
|
+
return feat;
|
|
29987
|
+
};
|
|
29988
|
+
}
|
|
29989
|
+
|
|
29990
|
+
function getSplitLineFunction(crs, dashLen, gapLen, opts) {
|
|
29991
|
+
var planar = !!opts.planar;
|
|
29992
|
+
var interpolate = getInterpolationFunction(planar ? null : crs);
|
|
29993
|
+
var distance = isLatLngCRS(crs) ? greatCircleDistance : distance2D;
|
|
29994
|
+
var inDash, parts2, interval, scale;
|
|
29995
|
+
function addPart(coords) {
|
|
29996
|
+
if (inDash) parts2.push(coords);
|
|
29997
|
+
if (gapLen > 0) {
|
|
29998
|
+
inDash = !inDash;
|
|
29999
|
+
interval = scale * (inDash ? dashLen : gapLen);
|
|
30000
|
+
}
|
|
30001
|
+
}
|
|
30002
|
+
|
|
30003
|
+
return function splitLineString(coords) {
|
|
30004
|
+
var elapsedDist = 0;
|
|
30005
|
+
var p = coords[0];
|
|
30006
|
+
var coords2 = [p];
|
|
30007
|
+
var segLen, pct, prev;
|
|
30008
|
+
if (opts.scaled) {
|
|
30009
|
+
scale = scaleDashes(dashLen, gapLen, getLineLength(coords, distance));
|
|
30010
|
+
} else {
|
|
30011
|
+
scale = 1;
|
|
30012
|
+
}
|
|
30013
|
+
// init this LineString
|
|
30014
|
+
inDash = gapLen > 0 ? false : true;
|
|
30015
|
+
interval = scale * (inDash ? dashLen : gapLen);
|
|
30016
|
+
if (!inDash) {
|
|
30017
|
+
// start gapped lines with a half-gap
|
|
30018
|
+
// (a half-gap or a half-dash is probably better for rings and intersecting lines)
|
|
30019
|
+
interval *= 0.5;
|
|
30020
|
+
}
|
|
30021
|
+
parts2 = [];
|
|
30022
|
+
for (var i=1, n=coords.length; i<n; i++) {
|
|
30023
|
+
prev = p;
|
|
30024
|
+
p = coords[i];
|
|
30025
|
+
segLen = distance(prev[0], prev[1], p[0], p[1]);
|
|
30026
|
+
if (segLen <= 0) continue;
|
|
30027
|
+
while (elapsedDist + segLen >= interval) {
|
|
30028
|
+
// this segment contains a break either within it or at the far endpoint
|
|
30029
|
+
pct = (interval - elapsedDist) / segLen;
|
|
30030
|
+
if (pct > 0.999 && i == n - 1) {
|
|
30031
|
+
// snap to endpoint (so fp rounding errors don't result in a tiny
|
|
30032
|
+
// last segment)
|
|
30033
|
+
pct = 1;
|
|
30034
|
+
}
|
|
30035
|
+
if (pct < 1) {
|
|
30036
|
+
prev = interpolate(prev[0], prev[1], p[0], p[1], pct);
|
|
30037
|
+
} else {
|
|
30038
|
+
prev = p;
|
|
30039
|
+
}
|
|
30040
|
+
coords2.push(prev);
|
|
30041
|
+
addPart(coords2);
|
|
30042
|
+
// start a new part
|
|
30043
|
+
coords2 = pct < 1 ? [prev] : [];
|
|
30044
|
+
elapsedDist = 0;
|
|
30045
|
+
segLen = (1 - pct) * segLen;
|
|
30046
|
+
}
|
|
30047
|
+
coords2.push(p);
|
|
30048
|
+
elapsedDist += segLen;
|
|
30049
|
+
}
|
|
30050
|
+
if (elapsedDist > 0 && coords2.length > 1) {
|
|
30051
|
+
addPart(coords2);
|
|
30052
|
+
}
|
|
30053
|
+
return parts2;
|
|
30054
|
+
};
|
|
30055
|
+
}
|
|
30056
|
+
|
|
30057
|
+
function getLineLength(coords, distance) {
|
|
30058
|
+
var len = 0;
|
|
30059
|
+
for (var i=1, n=coords.length; i<n; i++) {
|
|
30060
|
+
len += distance(coords[i-1][0], coords[i-1][1], coords[i][0], coords[i][1]);
|
|
30061
|
+
}
|
|
30062
|
+
return len;
|
|
30063
|
+
}
|
|
30064
|
+
|
|
30065
|
+
function scaleDashes(dash, gap, len) {
|
|
30066
|
+
var dash2, gap2;
|
|
30067
|
+
var n = len / (dash + gap); // number of dashes
|
|
30068
|
+
var n1 = Math.floor(n);
|
|
30069
|
+
var n2 = Math.ceil(n);
|
|
30070
|
+
var k1 = len / (n1 * (dash + gap)); // scaled-up dashes, >1
|
|
30071
|
+
var k2 = len / (n2 * (dash + gap)); // scaled-down dashes <1
|
|
30072
|
+
var k = k2;
|
|
30073
|
+
if (k1 < 1/k2 && n1 > 0) {
|
|
30074
|
+
k = k1; // pick the smaller of the two scales
|
|
30075
|
+
}
|
|
30076
|
+
return k;
|
|
30077
|
+
}
|
|
30078
|
+
|
|
29685
30079
|
// This function creates a continuous mosaic of data values in a
|
|
29686
30080
|
// given field by assigning data from adjacent polygon features to polygons
|
|
29687
30081
|
// that contain null values.
|
|
@@ -31206,43 +31600,6 @@ ${svg}
|
|
|
31206
31600
|
}
|
|
31207
31601
|
};
|
|
31208
31602
|
|
|
31209
|
-
function expressionUsesGeoJSON(exp) {
|
|
31210
|
-
return exp.includes('this.geojson');
|
|
31211
|
-
}
|
|
31212
|
-
|
|
31213
|
-
function getFeatureEditor(lyr, dataset) {
|
|
31214
|
-
var changed = false;
|
|
31215
|
-
var api = {};
|
|
31216
|
-
// need to copy attribute to avoid circular references if geojson is assigned
|
|
31217
|
-
// to a data property.
|
|
31218
|
-
var copy = copyLayer(lyr);
|
|
31219
|
-
var features = exportLayerAsGeoJSON(copy, dataset, {}, true);
|
|
31220
|
-
|
|
31221
|
-
api.get = function(i) {
|
|
31222
|
-
return features[i];
|
|
31223
|
-
};
|
|
31224
|
-
|
|
31225
|
-
api.set = function(feat, i) {
|
|
31226
|
-
changed = true;
|
|
31227
|
-
if (utils.isString(feat)) {
|
|
31228
|
-
feat = JSON.parse(feat);
|
|
31229
|
-
}
|
|
31230
|
-
features[i] = GeoJSON.toFeature(feat); // TODO: validate
|
|
31231
|
-
};
|
|
31232
|
-
|
|
31233
|
-
api.done = function() {
|
|
31234
|
-
if (!changed) return; // read-only expression
|
|
31235
|
-
// TODO: validate number of features, etc.
|
|
31236
|
-
var geojson = {
|
|
31237
|
-
type: 'FeatureCollection',
|
|
31238
|
-
features: features
|
|
31239
|
-
};
|
|
31240
|
-
// console.log(JSON.stringify(geojson, null, 2))
|
|
31241
|
-
return importGeoJSON(geojson);
|
|
31242
|
-
};
|
|
31243
|
-
return api;
|
|
31244
|
-
}
|
|
31245
|
-
|
|
31246
31603
|
cmd.evaluateEachFeature = function(lyr, dataset, exp, opts) {
|
|
31247
31604
|
var n = getFeatureCount(lyr),
|
|
31248
31605
|
arcs = dataset.arcs,
|
|
@@ -31485,6 +31842,10 @@ ${svg}
|
|
|
31485
31842
|
filter = compileValueExpression(opts.expression, lyr, arcs);
|
|
31486
31843
|
}
|
|
31487
31844
|
|
|
31845
|
+
if (opts.ids) {
|
|
31846
|
+
filter = combineFilters(filter, getIdFilter(opts.ids));
|
|
31847
|
+
}
|
|
31848
|
+
|
|
31488
31849
|
if (opts.remove_empty) {
|
|
31489
31850
|
filter = combineFilters(filter, getNullGeometryFilter(lyr, arcs));
|
|
31490
31851
|
}
|
|
@@ -31543,6 +31904,13 @@ ${svg}
|
|
|
31543
31904
|
lyr.data = filteredRecords ? new DataTable(filteredRecords) : null;
|
|
31544
31905
|
}
|
|
31545
31906
|
|
|
31907
|
+
function getIdFilter(ids) {
|
|
31908
|
+
var set = new Set(ids);
|
|
31909
|
+
return function(i) {
|
|
31910
|
+
return set.has(i);
|
|
31911
|
+
};
|
|
31912
|
+
}
|
|
31913
|
+
|
|
31546
31914
|
function getNullGeometryFilter(lyr, arcs) {
|
|
31547
31915
|
var shapes = lyr.shapes;
|
|
31548
31916
|
if (lyr.geometry_type == 'polygon') {
|
|
@@ -31848,7 +32216,6 @@ ${svg}
|
|
|
31848
32216
|
return joinTableToLayer(targetLyr, polygonLyr.data, joinFunction, opts);
|
|
31849
32217
|
}
|
|
31850
32218
|
|
|
31851
|
-
|
|
31852
32219
|
function prepJoinLayers(targetLyr, srcLyr) {
|
|
31853
32220
|
if (!targetLyr.data) {
|
|
31854
32221
|
// create an empty data table if target layer is missing attributes
|
|
@@ -33816,10 +34183,6 @@ ${svg}
|
|
|
33816
34183
|
|
|
33817
34184
|
|
|
33818
34185
|
|
|
33819
|
-
function formatNumber(val) {
|
|
33820
|
-
return val + '';
|
|
33821
|
-
}
|
|
33822
|
-
|
|
33823
34186
|
function maxChars(arr) {
|
|
33824
34187
|
return arr.reduce(function(memo, str) {
|
|
33825
34188
|
var w = stringDisplayWidth(str);
|
|
@@ -33843,14 +34206,14 @@ ${svg}
|
|
|
33843
34206
|
}
|
|
33844
34207
|
|
|
33845
34208
|
function countIntegralChars(val) {
|
|
33846
|
-
return utils.isNumber(val) ? (formatNumber(val) + '.').indexOf('.') : 0;
|
|
34209
|
+
return utils.isNumber(val) ? (utils.formatNumber(val) + '.').indexOf('.') : 0;
|
|
33847
34210
|
}
|
|
33848
34211
|
|
|
33849
34212
|
function formatTableValue(val, integralChars) {
|
|
33850
34213
|
var str;
|
|
33851
34214
|
if (utils.isNumber(val)) {
|
|
33852
34215
|
str = utils.lpad("", integralChars - countIntegralChars(val), ' ') +
|
|
33853
|
-
formatNumber(val);
|
|
34216
|
+
utils.formatNumber(val);
|
|
33854
34217
|
} else if (utils.isString(val)) {
|
|
33855
34218
|
str = formatString(val);
|
|
33856
34219
|
} else if (utils.isDate(val)) {
|
|
@@ -34176,6 +34539,32 @@ ${svg}
|
|
|
34176
34539
|
return d;
|
|
34177
34540
|
}
|
|
34178
34541
|
|
|
34542
|
+
function findNearestVertices(p, shp, arcs) {
|
|
34543
|
+
var p2 = findNearestVertex(p[0], p[1], shp, arcs);
|
|
34544
|
+
return findVertexIds(p2.x, p2.y, arcs);
|
|
34545
|
+
}
|
|
34546
|
+
|
|
34547
|
+
// p: point to snap
|
|
34548
|
+
// ids: ids of nearby vertices, possibly including an arc endpoint
|
|
34549
|
+
function snapPointToArcEndpoint(p, ids, arcs) {
|
|
34550
|
+
var p2, p3, dx, dy;
|
|
34551
|
+
ids.forEach(function(idx) {
|
|
34552
|
+
if (vertexIsArcStart(idx, arcs)) {
|
|
34553
|
+
p2 = getVertexCoords(idx + 1, arcs);
|
|
34554
|
+
} else if (vertexIsArcEnd(idx, arcs)) {
|
|
34555
|
+
p2 = getVertexCoords(idx - 1, arcs);
|
|
34556
|
+
}
|
|
34557
|
+
});
|
|
34558
|
+
if (!p2) return;
|
|
34559
|
+
dx = p2[0] - p[0];
|
|
34560
|
+
dy = p2[1] - p[1];
|
|
34561
|
+
if (Math.abs(dx) > Math.abs(dy)) {
|
|
34562
|
+
p[1] = p2[1]; // snap y coord
|
|
34563
|
+
} else {
|
|
34564
|
+
p[0] = p2[0];
|
|
34565
|
+
}
|
|
34566
|
+
}
|
|
34567
|
+
|
|
34179
34568
|
// Find ids of vertices with identical coordinates to x,y in an ArcCollection
|
|
34180
34569
|
// Caveat: does not exclude vertices that are not visible at the
|
|
34181
34570
|
// current level of simplification.
|
|
@@ -34238,6 +34627,18 @@ ${svg}
|
|
|
34238
34627
|
return minLen < Infinity ? {x: minX, y: minY} : null;
|
|
34239
34628
|
}
|
|
34240
34629
|
|
|
34630
|
+
var VertexUtils = /*#__PURE__*/Object.freeze({
|
|
34631
|
+
__proto__: null,
|
|
34632
|
+
findNearestVertices: findNearestVertices,
|
|
34633
|
+
snapPointToArcEndpoint: snapPointToArcEndpoint,
|
|
34634
|
+
findVertexIds: findVertexIds,
|
|
34635
|
+
getVertexCoords: getVertexCoords,
|
|
34636
|
+
vertexIsArcEnd: vertexIsArcEnd,
|
|
34637
|
+
vertexIsArcStart: vertexIsArcStart,
|
|
34638
|
+
setVertexCoords: setVertexCoords,
|
|
34639
|
+
findNearestVertex: findNearestVertex
|
|
34640
|
+
});
|
|
34641
|
+
|
|
34241
34642
|
// Returns x,y coordinates of the point that is at the midpoint of each polyline feature
|
|
34242
34643
|
// Uses 2d cartesian geometry
|
|
34243
34644
|
// TODO: optionally use spherical geometry
|
|
@@ -34365,11 +34766,6 @@ ${svg}
|
|
|
34365
34766
|
});
|
|
34366
34767
|
}
|
|
34367
34768
|
|
|
34368
|
-
function interpolatePoint2D(ax, ay, bx, by, k) {
|
|
34369
|
-
var j = 1 - k;
|
|
34370
|
-
return [ax * j + bx * k, ay * j + by * k];
|
|
34371
|
-
}
|
|
34372
|
-
|
|
34373
34769
|
function interpolatePointsAlongArc(ids, arcs, interval) {
|
|
34374
34770
|
var iter = arcs.getShapeIter(ids);
|
|
34375
34771
|
var distance = arcs.isPlanar() ? geom.distance2D : geom.greatCircleDistance;
|
|
@@ -34621,6 +35017,38 @@ ${svg}
|
|
|
34621
35017
|
}
|
|
34622
35018
|
}
|
|
34623
35019
|
|
|
35020
|
+
function pointsFromPolylinesForJoin(lyr, dataset) {
|
|
35021
|
+
var shapes = lyr.shapes.map(function(shp) {
|
|
35022
|
+
return polylineToMidpoints(shp, dataset.arcs);
|
|
35023
|
+
});
|
|
35024
|
+
return {
|
|
35025
|
+
geometry_type: 'point',
|
|
35026
|
+
shapes: shapes,
|
|
35027
|
+
data: lyr.data // TODO copy if needed
|
|
35028
|
+
};
|
|
35029
|
+
}
|
|
35030
|
+
|
|
35031
|
+
function validateOpts(opts) {
|
|
35032
|
+
if (!opts.point_method) {
|
|
35033
|
+
stop('The "point-method" flag is required for polyline-polygon joins');
|
|
35034
|
+
}
|
|
35035
|
+
}
|
|
35036
|
+
|
|
35037
|
+
function joinPolylinesToPolygons(targetLyr, targetDataset, source, opts) {
|
|
35038
|
+
validateOpts(opts);
|
|
35039
|
+
var pointLyr = pointsFromPolylinesForJoin(source.layer, source.dataset);
|
|
35040
|
+
var retn = joinPointsToPolygons(targetLyr, targetDataset.arcs, pointLyr, opts);
|
|
35041
|
+
return retn;
|
|
35042
|
+
}
|
|
35043
|
+
|
|
35044
|
+
function joinPolygonsToPolylines(targetLyr, targetDataset, source, opts) {
|
|
35045
|
+
validateOpts(opts);
|
|
35046
|
+
var pointLyr = pointsFromPolylinesForJoin(targetLyr, targetDataset);
|
|
35047
|
+
var retn = joinPolygonsToPoints(pointLyr, source.layer, source.dataset.arcs, opts);
|
|
35048
|
+
targetLyr.data = pointLyr.data;
|
|
35049
|
+
return retn;
|
|
35050
|
+
}
|
|
35051
|
+
|
|
34624
35052
|
class TinyQueue {
|
|
34625
35053
|
constructor(data = [], compare = defaultCompare) {
|
|
34626
35054
|
this.data = data;
|
|
@@ -34944,7 +35372,7 @@ ${svg}
|
|
|
34944
35372
|
|
|
34945
35373
|
cmd.join = function(targetLyr, targetDataset, src, opts) {
|
|
34946
35374
|
var srcType, targetType, retn;
|
|
34947
|
-
if (!src || !src.
|
|
35375
|
+
if (!src || !src.dataset) {
|
|
34948
35376
|
stop("Missing a joinable data source");
|
|
34949
35377
|
}
|
|
34950
35378
|
if (opts.keys) {
|
|
@@ -34952,9 +35380,18 @@ ${svg}
|
|
|
34952
35380
|
if (opts.keys.length != 2) {
|
|
34953
35381
|
stop("Expected two key fields: a target field and a source field");
|
|
34954
35382
|
}
|
|
35383
|
+
if (!src.layer.data) {
|
|
35384
|
+
stop("Source layer is missing attribute data");
|
|
35385
|
+
}
|
|
34955
35386
|
retn = joinAttributesToFeatures(targetLyr, src.layer.data, opts);
|
|
34956
35387
|
} else {
|
|
34957
35388
|
// spatial join
|
|
35389
|
+
if (!src.layer.data) {
|
|
35390
|
+
// KLUDGE -- users might want to join a layer without attributes
|
|
35391
|
+
// to test for intersection... the simplest way to support this is
|
|
35392
|
+
// to add an empty data table to the source layer
|
|
35393
|
+
initDataTable(src.layer);
|
|
35394
|
+
}
|
|
34958
35395
|
requireDatasetsHaveCompatibleCRS([targetDataset, src.dataset]);
|
|
34959
35396
|
srcType = src.layer.geometry_type;
|
|
34960
35397
|
targetType = targetLyr.geometry_type;
|
|
@@ -34966,6 +35403,10 @@ ${svg}
|
|
|
34966
35403
|
retn = joinPointsToPoints(targetLyr, src.layer, getDatasetCRS(targetDataset), opts);
|
|
34967
35404
|
} else if (srcType == 'polygon' && targetType == 'polygon') {
|
|
34968
35405
|
retn = joinPolygonsToPolygons(targetLyr, targetDataset, src, opts);
|
|
35406
|
+
} else if (srcType == 'polyline' && targetType == 'polygon') {
|
|
35407
|
+
retn = joinPolylinesToPolygons(targetLyr, targetDataset, src, opts);
|
|
35408
|
+
} else if (srcType == 'polygon' && targetType == 'polyline') {
|
|
35409
|
+
retn = joinPolygonsToPolylines(targetLyr, targetDataset, src, opts);
|
|
34969
35410
|
} else {
|
|
34970
35411
|
stop(utils.format("Unable to join %s geometry to %s geometry",
|
|
34971
35412
|
srcType || 'null', targetType || 'null'));
|
|
@@ -37144,13 +37585,20 @@ ${svg}
|
|
|
37144
37585
|
|
|
37145
37586
|
// @expression: optional field name or expression
|
|
37146
37587
|
//
|
|
37147
|
-
cmd.splitLayer = function(src, expression,
|
|
37148
|
-
var
|
|
37588
|
+
cmd.splitLayer = function(src, expression, optsArg) {
|
|
37589
|
+
var opts = optsArg || {},
|
|
37590
|
+
lyr0 = opts.no_replace ? copyLayer(src) : src,
|
|
37149
37591
|
properties = lyr0.data ? lyr0.data.getRecords() : null,
|
|
37150
37592
|
shapes = lyr0.shapes,
|
|
37151
37593
|
index = {},
|
|
37152
37594
|
splitLayers = [],
|
|
37153
|
-
namer
|
|
37595
|
+
namer;
|
|
37596
|
+
|
|
37597
|
+
if (opts.ids) {
|
|
37598
|
+
namer = getIdSplitFunction(opts.ids);
|
|
37599
|
+
} else {
|
|
37600
|
+
namer = getSplitNameFunction(lyr0, expression);
|
|
37601
|
+
}
|
|
37154
37602
|
|
|
37155
37603
|
// if (splitField) {
|
|
37156
37604
|
// internal.requireDataField(lyr0, splitField);
|
|
@@ -37182,15 +37630,24 @@ ${svg}
|
|
|
37182
37630
|
return splitLayers;
|
|
37183
37631
|
};
|
|
37184
37632
|
|
|
37633
|
+
function getIdSplitFunction(ids) {
|
|
37634
|
+
var set = new Set(ids);
|
|
37635
|
+
return function(i) {
|
|
37636
|
+
return set.has(i) ? '1' : '2';
|
|
37637
|
+
};
|
|
37638
|
+
}
|
|
37639
|
+
|
|
37640
|
+
function getDefaultSplitFunction(lyr) {
|
|
37641
|
+
// if not splitting on an expression and layer is unnamed, name split-apart layers
|
|
37642
|
+
// like: split-1, split-2, ...
|
|
37643
|
+
return function(i) {
|
|
37644
|
+
return (lyr && lyr.name || 'split') + '-' + (i + 1);
|
|
37645
|
+
};
|
|
37646
|
+
}
|
|
37647
|
+
|
|
37185
37648
|
function getSplitNameFunction(lyr, exp) {
|
|
37186
37649
|
var compiled;
|
|
37187
|
-
if (!exp)
|
|
37188
|
-
// if not splitting on an expression and layer is unnamed, name split-apart layers
|
|
37189
|
-
// like: split-1, split-2, ...
|
|
37190
|
-
return function(i) {
|
|
37191
|
-
return (lyr && lyr.name || 'split') + '-' + (i + 1);
|
|
37192
|
-
};
|
|
37193
|
-
}
|
|
37650
|
+
if (!exp) return getDefaultSplitFunction(lyr);
|
|
37194
37651
|
lyr = {name: lyr.name, data: lyr.data}; // remove shape info
|
|
37195
37652
|
compiled = compileValueExpression(exp, lyr, null);
|
|
37196
37653
|
return function(i) {
|
|
@@ -37990,6 +38447,9 @@ ${svg}
|
|
|
37990
38447
|
} else if (name == 'colorizer') {
|
|
37991
38448
|
outputLayers = cmd.colorizer(opts);
|
|
37992
38449
|
|
|
38450
|
+
} else if (name == 'dashlines') {
|
|
38451
|
+
applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
|
|
38452
|
+
|
|
37993
38453
|
} else if (name == 'define') {
|
|
37994
38454
|
cmd.define(opts);
|
|
37995
38455
|
|
|
@@ -39097,7 +39557,8 @@ ${svg}
|
|
|
39097
39557
|
TopojsonImport,
|
|
39098
39558
|
Topology,
|
|
39099
39559
|
Units,
|
|
39100
|
-
SvgHatch
|
|
39560
|
+
SvgHatch,
|
|
39561
|
+
VertexUtils
|
|
39101
39562
|
);
|
|
39102
39563
|
|
|
39103
39564
|
// The entry point for the core mapshaper module
|