mapshaper 0.5.76 → 0.5.80
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 +1110 -573
- package/package.json +2 -2
- package/www/mapshaper-gui.js +9 -9
- package/www/mapshaper.js +1110 -573
- package/www/modules.js +21 -2
package/www/mapshaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var VERSION = "0.5.
|
|
3
|
+
var VERSION = "0.5.79";
|
|
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);
|
|
@@ -1313,9 +1330,10 @@
|
|
|
1313
1330
|
}
|
|
1314
1331
|
|
|
1315
1332
|
function logArgs(args) {
|
|
1316
|
-
if (LOGGING
|
|
1317
|
-
|
|
1318
|
-
|
|
1333
|
+
if (!LOGGING || getStateVar('QUIET') || !utils.isArrayLike(args)) return;
|
|
1334
|
+
var msg = formatLogArgs(args);
|
|
1335
|
+
if (STDOUT) console.log(msg);
|
|
1336
|
+
else console.error(msg);
|
|
1319
1337
|
}
|
|
1320
1338
|
|
|
1321
1339
|
var Logging = /*#__PURE__*/Object.freeze({
|
|
@@ -1727,9 +1745,17 @@
|
|
|
1727
1745
|
return [xmin, ymin, xmax, ymax];
|
|
1728
1746
|
}
|
|
1729
1747
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1748
|
+
var WGS84 = {
|
|
1749
|
+
// https://en.wikipedia.org/wiki/Earth_radius
|
|
1750
|
+
SEMIMAJOR_AXIS: 6378137,
|
|
1751
|
+
SEMIMINOR_AXIS: 6356752.3142,
|
|
1752
|
+
AUTHALIC_RADIUS: 6371007.2,
|
|
1753
|
+
VOLUMETRIC_RADIUS: 6371000.8
|
|
1754
|
+
};
|
|
1755
|
+
|
|
1756
|
+
// TODO: remove this constant, use actual data from dataset CRS,
|
|
1757
|
+
// also consider using ellipsoidal formulas where greater accuracy might be important.
|
|
1758
|
+
var R$1 = WGS84.SEMIMAJOR_AXIS;
|
|
1733
1759
|
var D2R = Math.PI / 180;
|
|
1734
1760
|
var R2D = 180 / Math.PI;
|
|
1735
1761
|
|
|
@@ -2324,12 +2350,12 @@
|
|
|
2324
2350
|
}, 0);
|
|
2325
2351
|
}
|
|
2326
2352
|
|
|
2327
|
-
function getSphericalShapeArea(shp, arcs) {
|
|
2353
|
+
function getSphericalShapeArea(shp, arcs, R) {
|
|
2328
2354
|
if (arcs.isPlanar()) {
|
|
2329
2355
|
error("[getSphericalShapeArea()] Function requires decimal degree coordinates");
|
|
2330
2356
|
}
|
|
2331
2357
|
return (shp || []).reduce(function(area, ids) {
|
|
2332
|
-
return area + getSphericalPathArea(ids, arcs);
|
|
2358
|
+
return area + getSphericalPathArea(ids, arcs, R);
|
|
2333
2359
|
}, 0);
|
|
2334
2360
|
}
|
|
2335
2361
|
|
|
@@ -2446,16 +2472,17 @@
|
|
|
2446
2472
|
return (arcs.isPlanar() ? getPlanarPathArea : getSphericalPathArea)(ids, arcs);
|
|
2447
2473
|
}
|
|
2448
2474
|
|
|
2449
|
-
function getSphericalPathArea(ids, arcs) {
|
|
2475
|
+
function getSphericalPathArea(ids, arcs, R) {
|
|
2450
2476
|
var iter = arcs.getShapeIter(ids);
|
|
2451
|
-
return getSphericalPathArea2(iter);
|
|
2477
|
+
return getSphericalPathArea2(iter, R);
|
|
2452
2478
|
}
|
|
2453
2479
|
|
|
2454
|
-
function getSphericalPathArea2(iter) {
|
|
2480
|
+
function getSphericalPathArea2(iter, R) {
|
|
2455
2481
|
var sum = 0,
|
|
2456
2482
|
started = false,
|
|
2457
2483
|
deg2rad = Math.PI / 180,
|
|
2458
2484
|
x, y, xp, yp;
|
|
2485
|
+
R = R || WGS84.SEMIMAJOR_AXIS;
|
|
2459
2486
|
while (iter.hasNext()) {
|
|
2460
2487
|
x = iter.x * deg2rad;
|
|
2461
2488
|
y = Math.sin(iter.y * deg2rad);
|
|
@@ -2467,7 +2494,7 @@
|
|
|
2467
2494
|
xp = x;
|
|
2468
2495
|
yp = y;
|
|
2469
2496
|
}
|
|
2470
|
-
return sum / 2 *
|
|
2497
|
+
return sum / 2 * R * R;
|
|
2471
2498
|
}
|
|
2472
2499
|
|
|
2473
2500
|
// Get path area from an array of [x, y] points
|
|
@@ -3931,7 +3958,7 @@
|
|
|
3931
3958
|
function requireSinglePointLayer(lyr, msg) {
|
|
3932
3959
|
requirePointLayer(lyr);
|
|
3933
3960
|
if (countMultiPartFeatures(lyr) > 0) {
|
|
3934
|
-
stop(msg || 'This command requires single points');
|
|
3961
|
+
stop(msg || 'This command requires single points; layer contains multi-point features.');
|
|
3935
3962
|
}
|
|
3936
3963
|
}
|
|
3937
3964
|
|
|
@@ -3986,6 +4013,7 @@
|
|
|
3986
4013
|
return opts && opts.no_replace ? {geometry_type: src.geometry_type} : src;
|
|
3987
4014
|
}
|
|
3988
4015
|
|
|
4016
|
+
//
|
|
3989
4017
|
function setOutputLayerName(dest, src, defName, opts) {
|
|
3990
4018
|
opts = opts || {};
|
|
3991
4019
|
if (opts.name) {
|
|
@@ -4062,6 +4090,7 @@
|
|
|
4062
4090
|
return counts;
|
|
4063
4091
|
}
|
|
4064
4092
|
|
|
4093
|
+
// Returns a Bounds object
|
|
4065
4094
|
function getLayerBounds(lyr, arcs) {
|
|
4066
4095
|
var bounds = null;
|
|
4067
4096
|
if (lyr.geometry_type == 'point') {
|
|
@@ -6133,9 +6162,11 @@
|
|
|
6133
6162
|
arcCount += n;
|
|
6134
6163
|
});
|
|
6135
6164
|
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6165
|
+
if (arcSources.length > 0) {
|
|
6166
|
+
mergedArcs = mergeArcs(arcSources);
|
|
6167
|
+
if (mergedArcs.size() != arcCount) {
|
|
6168
|
+
error("[mergeDatasets()] Arc indexing error");
|
|
6169
|
+
}
|
|
6139
6170
|
}
|
|
6140
6171
|
|
|
6141
6172
|
return {
|
|
@@ -6154,6 +6185,8 @@
|
|
|
6154
6185
|
}
|
|
6155
6186
|
|
|
6156
6187
|
function mergeArcs(arr) {
|
|
6188
|
+
// Returning the original causes a test to fail
|
|
6189
|
+
// if (arr.length < 2) return arr[0];
|
|
6157
6190
|
var dataArr = arr.map(function(arcs) {
|
|
6158
6191
|
if (arcs.getRetainedInterval() > 0) {
|
|
6159
6192
|
verbose("Baking-in simplification setting.");
|
|
@@ -6758,31 +6791,47 @@
|
|
|
6758
6791
|
dataset.layers = currLayers;
|
|
6759
6792
|
}
|
|
6760
6793
|
|
|
6761
|
-
// Replace a layer
|
|
6794
|
+
// Replace a layer with a layer from a second dataset
|
|
6795
|
+
// (in-place)
|
|
6762
6796
|
// (Typically, the second dataset is imported from dynamically generated GeoJSON and contains one layer)
|
|
6763
6797
|
function replaceLayerContents(lyr, dataset, dataset2) {
|
|
6798
|
+
var lyr2 = mergeOutputLayerIntoDataset(lyr, dataset, dataset2, {});
|
|
6799
|
+
if (layerHasPaths(lyr2)) {
|
|
6800
|
+
buildTopology(dataset);
|
|
6801
|
+
}
|
|
6802
|
+
}
|
|
6803
|
+
|
|
6804
|
+
function mergeOutputLayerIntoDataset(lyr, dataset, dataset2, opts) {
|
|
6764
6805
|
if (!dataset2 || dataset2.layers.length != 1) {
|
|
6765
|
-
error('Invalid
|
|
6806
|
+
error('Invalid source dataset');
|
|
6766
6807
|
}
|
|
6767
6808
|
if (dataset.layers.includes(lyr) === false) {
|
|
6768
6809
|
error('Invalid target layer');
|
|
6769
6810
|
}
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
mergeDatasetsIntoDataset(dataset, [dataset2]);
|
|
6774
|
-
}
|
|
6811
|
+
// this command returns merged layers instead of adding them to target dataset
|
|
6812
|
+
var outputLayers = mergeDatasetsIntoDataset(dataset, [dataset2]);
|
|
6813
|
+
var lyr2 = outputLayers[0];
|
|
6775
6814
|
|
|
6776
|
-
|
|
6815
|
+
// TODO: find a more reliable way of knowing when to copy data
|
|
6816
|
+
var copyData = !lyr2.data && lyr.data && getFeatureCount(lyr2) == lyr.data.size();
|
|
6777
6817
|
|
|
6778
|
-
if (
|
|
6779
|
-
|
|
6818
|
+
if (copyData) {
|
|
6819
|
+
lyr2.data = opts.no_replace ? lyr.data.clone() : lyr.data;
|
|
6780
6820
|
}
|
|
6781
|
-
if (
|
|
6782
|
-
//
|
|
6783
|
-
|
|
6784
|
-
|
|
6821
|
+
if (opts.no_replace) {
|
|
6822
|
+
// dataset.layers.push(lyr2);
|
|
6823
|
+
|
|
6824
|
+
} else {
|
|
6825
|
+
lyr2 = Object.assign(lyr, {data: null, shapes: null}, lyr2);
|
|
6826
|
+
if (layerHasPaths(lyr)) {
|
|
6827
|
+
// Remove unused arcs from replaced layer
|
|
6828
|
+
// TODO: consider using clean insead of this
|
|
6829
|
+
dissolveArcs(dataset);
|
|
6830
|
+
}
|
|
6785
6831
|
}
|
|
6832
|
+
|
|
6833
|
+
lyr2.name = opts.name || lyr2.name;
|
|
6834
|
+
return lyr2;
|
|
6786
6835
|
}
|
|
6787
6836
|
|
|
6788
6837
|
// Transform the points in a dataset in-place; don't clean up corrupted shapes
|
|
@@ -6811,6 +6860,7 @@
|
|
|
6811
6860
|
pruneArcs: pruneArcs,
|
|
6812
6861
|
replaceLayers: replaceLayers,
|
|
6813
6862
|
replaceLayerContents: replaceLayerContents,
|
|
6863
|
+
mergeOutputLayerIntoDataset: mergeOutputLayerIntoDataset,
|
|
6814
6864
|
transformPoints: transformPoints
|
|
6815
6865
|
});
|
|
6816
6866
|
|
|
@@ -9394,6 +9444,12 @@
|
|
|
9394
9444
|
area: function() {
|
|
9395
9445
|
return _isPlanar ? ctx.planarArea : geom.getSphericalShapeArea(_ids, arcs);
|
|
9396
9446
|
},
|
|
9447
|
+
// area2: function() {
|
|
9448
|
+
// return _isPlanar ? ctx.planarArea : geom.getSphericalShapeArea(_ids, arcs, WGS84.SEMIMINOR_RADIUS);
|
|
9449
|
+
// },
|
|
9450
|
+
// area3: function() {
|
|
9451
|
+
// return _isPlanar ? ctx.planarArea : geom.getSphericalShapeArea(_ids, arcs, WGS84.AUTHALIC_RADIUS);
|
|
9452
|
+
// },
|
|
9397
9453
|
perimeter: function() {
|
|
9398
9454
|
return geom.getShapePerimeter(_ids, arcs);
|
|
9399
9455
|
},
|
|
@@ -10296,6 +10352,12 @@
|
|
|
10296
10352
|
};
|
|
10297
10353
|
}
|
|
10298
10354
|
|
|
10355
|
+
/*
|
|
10356
|
+
|
|
10357
|
+
|
|
10358
|
+
|
|
10359
|
+
*/
|
|
10360
|
+
|
|
10299
10361
|
// TODO: use polygon pathfinder shared code
|
|
10300
10362
|
function collectPolylineArcs(ids, nodes, testArc, useArc) {
|
|
10301
10363
|
var parts = [];
|
|
@@ -11865,7 +11927,6 @@
|
|
|
11865
11927
|
});
|
|
11866
11928
|
|
|
11867
11929
|
// Map positive or negative integer ids to non-negative integer ids
|
|
11868
|
-
|
|
11869
11930
|
function IdLookupIndex(n, clearable) {
|
|
11870
11931
|
var fwdIndex = new Int32Array(n);
|
|
11871
11932
|
var revIndex = new Int32Array(n);
|
|
@@ -13309,14 +13370,14 @@
|
|
|
13309
13370
|
return mergeArcs([arcs, new ArcCollection(nn, xx, yy)]);
|
|
13310
13371
|
}
|
|
13311
13372
|
|
|
13312
|
-
var roundCoord$
|
|
13373
|
+
var roundCoord$2 = getRoundingFunction(0.01);
|
|
13313
13374
|
|
|
13314
13375
|
function stringifyVertex(p) {
|
|
13315
|
-
return ' ' + roundCoord$
|
|
13376
|
+
return ' ' + roundCoord$2(p[0]) + ' ' + roundCoord$2(p[1]);
|
|
13316
13377
|
}
|
|
13317
13378
|
|
|
13318
13379
|
function stringifyCP(p) {
|
|
13319
|
-
return ' ' + roundCoord$
|
|
13380
|
+
return ' ' + roundCoord$2(p[2]) + ' ' + roundCoord$2(p[3]);
|
|
13320
13381
|
}
|
|
13321
13382
|
|
|
13322
13383
|
function isCubicCtrl(p) {
|
|
@@ -13351,40 +13412,9 @@
|
|
|
13351
13412
|
stringifyCP(p2) + stringifyVertex(p2);
|
|
13352
13413
|
}
|
|
13353
13414
|
|
|
13354
|
-
function findArcCenter(p1, p2, degrees) {
|
|
13355
|
-
var p3 = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2], // midpoint betw. p1, p2
|
|
13356
|
-
tan = 1 / Math.tan(degrees / 180 * Math.PI / 2),
|
|
13357
|
-
cp = getAffineTransform(90, tan, [0, 0], p3)(p2[0], p2[1]);
|
|
13358
|
-
return cp;
|
|
13359
|
-
}
|
|
13360
|
-
|
|
13361
|
-
// export function addBezierArcControlPoints(p1, p2, degrees) {
|
|
13362
|
-
function addBezierArcControlPoints(points, degrees) {
|
|
13363
|
-
// source: https://stackoverflow.com/questions/734076/how-to-best-approximate-a-geometrical-arc-with-a-bezier-curve
|
|
13364
|
-
var p2 = points.pop(),
|
|
13365
|
-
p1 = points.pop(),
|
|
13366
|
-
cp = findArcCenter(p1, p2, degrees),
|
|
13367
|
-
xc = cp[0],
|
|
13368
|
-
yc = cp[1],
|
|
13369
|
-
ax = p1[0] - xc,
|
|
13370
|
-
ay = p1[1] - yc,
|
|
13371
|
-
bx = p2[0] - xc,
|
|
13372
|
-
by = p2[1] - yc,
|
|
13373
|
-
q1 = ax * ax + ay * ay,
|
|
13374
|
-
q2 = q1 + ax * bx + ay * by,
|
|
13375
|
-
k2 = 4/3 * (Math.sqrt(2 * q1 * q2) - q2) / (ax * by - ay * bx);
|
|
13376
|
-
|
|
13377
|
-
points.push(p1);
|
|
13378
|
-
points.push([xc + ax - k2 * ay, yc + ay + k2 * ax, 'C']);
|
|
13379
|
-
points.push([xc + bx + k2 * by, yc + by - k2 * bx, 'C']);
|
|
13380
|
-
points.push(p2);
|
|
13381
|
-
}
|
|
13382
|
-
|
|
13383
13415
|
var SvgPathUtils = /*#__PURE__*/Object.freeze({
|
|
13384
13416
|
__proto__: null,
|
|
13385
|
-
stringifyLineStringCoords: stringifyLineStringCoords
|
|
13386
|
-
findArcCenter: findArcCenter,
|
|
13387
|
-
addBezierArcControlPoints: addBezierArcControlPoints
|
|
13417
|
+
stringifyLineStringCoords: stringifyLineStringCoords
|
|
13388
13418
|
});
|
|
13389
13419
|
|
|
13390
13420
|
/* example patterns
|
|
@@ -13661,11 +13691,15 @@
|
|
|
13661
13691
|
type: null,
|
|
13662
13692
|
length: 'number', // e.g. arrow length
|
|
13663
13693
|
rotation: 'number',
|
|
13694
|
+
radius: 'number',
|
|
13695
|
+
'arrow-length': 'number',
|
|
13696
|
+
'arrow-direction': 'number',
|
|
13664
13697
|
'arrow-head-angle': 'number',
|
|
13665
13698
|
'arrow-head-width': 'number',
|
|
13666
13699
|
'arrow-stem-width': 'number',
|
|
13667
13700
|
'arrow-stem-curve': 'number', // degrees of arc
|
|
13668
13701
|
'arrow-stem-taper': 'number',
|
|
13702
|
+
'arrow-min-stem': 'number',
|
|
13669
13703
|
'arrow-scaling': 'number',
|
|
13670
13704
|
effect: null // e.g. "fade"
|
|
13671
13705
|
}, stylePropertyTypes);
|
|
@@ -13707,8 +13741,8 @@
|
|
|
13707
13741
|
if (!isSupportedSvgSymbolProperty(svgName)) {
|
|
13708
13742
|
return;
|
|
13709
13743
|
}
|
|
13710
|
-
var
|
|
13711
|
-
functions[svgName] = getSymbolPropertyAccessor(
|
|
13744
|
+
var val = opts[optName];
|
|
13745
|
+
functions[svgName] = getSymbolPropertyAccessor(val, svgName, lyr);
|
|
13712
13746
|
properties.push(svgName);
|
|
13713
13747
|
});
|
|
13714
13748
|
|
|
@@ -13731,7 +13765,8 @@
|
|
|
13731
13765
|
return /[(){}.+-/*?:&|=\[]/.test(str);
|
|
13732
13766
|
}
|
|
13733
13767
|
|
|
13734
|
-
function getSymbolPropertyAccessor(
|
|
13768
|
+
function getSymbolPropertyAccessor(val, svgName, lyr) {
|
|
13769
|
+
var strVal = String(val).trim();
|
|
13735
13770
|
var typeHint = symbolPropertyTypes[svgName];
|
|
13736
13771
|
var fields = lyr.data ? lyr.data.getFields() : [];
|
|
13737
13772
|
var literalVal = null;
|
|
@@ -13833,7 +13868,6 @@
|
|
|
13833
13868
|
isSvgColor: isSvgColor
|
|
13834
13869
|
});
|
|
13835
13870
|
|
|
13836
|
-
var symbolBuilders = {};
|
|
13837
13871
|
var symbolRenderers = {};
|
|
13838
13872
|
|
|
13839
13873
|
function getTransform(xy, scale) {
|
|
@@ -13846,7 +13880,6 @@
|
|
|
13846
13880
|
|
|
13847
13881
|
var SvgCommon = /*#__PURE__*/Object.freeze({
|
|
13848
13882
|
__proto__: null,
|
|
13849
|
-
symbolBuilders: symbolBuilders,
|
|
13850
13883
|
symbolRenderers: symbolRenderers,
|
|
13851
13884
|
getTransform: getTransform
|
|
13852
13885
|
});
|
|
@@ -15565,6 +15598,139 @@ ${svg}
|
|
|
15565
15598
|
}];
|
|
15566
15599
|
}
|
|
15567
15600
|
|
|
15601
|
+
function exportRecordsAsFixedWidthString(fields, records, opts) {
|
|
15602
|
+
var rows = [], col;
|
|
15603
|
+
for (var i=0; i<fields.length; i++) {
|
|
15604
|
+
col = formatFixedWidthColumn(fields[i], records, opts);
|
|
15605
|
+
if (i === 0) {
|
|
15606
|
+
rows = col;
|
|
15607
|
+
} else for (var j=0; j<rows.length; j++) {
|
|
15608
|
+
rows[j] += ' ' + col[j];
|
|
15609
|
+
}
|
|
15610
|
+
}
|
|
15611
|
+
return rows.join('\n');
|
|
15612
|
+
}
|
|
15613
|
+
|
|
15614
|
+
function formatFixedWidthColumn(field, records, opts) {
|
|
15615
|
+
var arr = [],
|
|
15616
|
+
maxLen = field.length,
|
|
15617
|
+
n = records.length,
|
|
15618
|
+
i, val;
|
|
15619
|
+
arr.push(field);
|
|
15620
|
+
for (i=0; i<n; i++) {
|
|
15621
|
+
val = formatFixedWidthValue(records[i][field], opts);
|
|
15622
|
+
maxLen = Math.max(maxLen, val.length);
|
|
15623
|
+
arr.push(val);
|
|
15624
|
+
}
|
|
15625
|
+
for (i=0; i<arr.length; i++) {
|
|
15626
|
+
arr[i] = arr[i].padEnd(maxLen, ' ');
|
|
15627
|
+
}
|
|
15628
|
+
return arr;
|
|
15629
|
+
}
|
|
15630
|
+
|
|
15631
|
+
function formatFixedWidthValue(val, opts) {
|
|
15632
|
+
// TODO: remove duplication with mapshaper-delim-export.js
|
|
15633
|
+
var s;
|
|
15634
|
+
if (val == null) {
|
|
15635
|
+
s = '';
|
|
15636
|
+
} else if (utils.isString(val)) {
|
|
15637
|
+
s = val; // TODO: handle wide characters, newlines etc.
|
|
15638
|
+
} else if (utils.isNumber(val)) {
|
|
15639
|
+
s = opts.decimal_comma ? utils.formatIntlNumber(val) : utils.formatNumber(val);
|
|
15640
|
+
} else if (utils.isObject(val)) {
|
|
15641
|
+
s = JSON.stringify(val);
|
|
15642
|
+
} else {
|
|
15643
|
+
s = val + '';
|
|
15644
|
+
}
|
|
15645
|
+
return s;
|
|
15646
|
+
}
|
|
15647
|
+
|
|
15648
|
+
|
|
15649
|
+
function readFixedWidthRecords(reader, opts) {
|
|
15650
|
+
var str = reader.toString(opts.encoding || 'ascii');
|
|
15651
|
+
return readFixedWidthRecordsFromString(str, opts);
|
|
15652
|
+
}
|
|
15653
|
+
|
|
15654
|
+
function readFixedWidthRecordsFromString(str, ops) {
|
|
15655
|
+
var fields = parseFixedWidthInfo(str.substring(0, 2000));
|
|
15656
|
+
if (!fields) return [];
|
|
15657
|
+
var lines = utils.splitLines(str);
|
|
15658
|
+
if (lines[lines.length - 1] === '') lines.pop(); // handle newline at end of string
|
|
15659
|
+
var records = [];
|
|
15660
|
+
for (var i=1; i<lines.length; i++) {
|
|
15661
|
+
records.push(parseFixedWidthLine(lines[i], fields));
|
|
15662
|
+
}
|
|
15663
|
+
return records;
|
|
15664
|
+
}
|
|
15665
|
+
|
|
15666
|
+
function parseFixedWidthInfo(sample) {
|
|
15667
|
+
var lines = utils.splitLines(sample);
|
|
15668
|
+
if (lines.length > 2) lines.pop(); // remove possible partial line
|
|
15669
|
+
var n = getMaxLineLength(lines);
|
|
15670
|
+
var headerLine = lines[0];
|
|
15671
|
+
var colInfo = [];
|
|
15672
|
+
var colStart = 0;
|
|
15673
|
+
var inContent = false;
|
|
15674
|
+
var inHeader = false;
|
|
15675
|
+
var isContentChar, isHeaderChar, isColStart, colEnd;
|
|
15676
|
+
for (var i=0; i<=n; i++) {
|
|
15677
|
+
isHeaderChar = testContentChar(headerLine, i);
|
|
15678
|
+
isContentChar = !testEmptyCol(lines, i);
|
|
15679
|
+
isColStart = isHeaderChar && !inHeader;
|
|
15680
|
+
if (isColStart && inContent) {
|
|
15681
|
+
// all lines should have a space char in the position right before a header starts
|
|
15682
|
+
return null;
|
|
15683
|
+
}
|
|
15684
|
+
if (i == n || i > 0 && isColStart) {
|
|
15685
|
+
colEnd = i == n ? undefined : i-1;
|
|
15686
|
+
colInfo.push({
|
|
15687
|
+
name: readValue$1(headerLine, colStart, colEnd),
|
|
15688
|
+
end: colEnd,
|
|
15689
|
+
start: colStart
|
|
15690
|
+
});
|
|
15691
|
+
colStart = i;
|
|
15692
|
+
}
|
|
15693
|
+
inContent = isContentChar;
|
|
15694
|
+
inHeader = isHeaderChar;
|
|
15695
|
+
}
|
|
15696
|
+
return colInfo.length > 0 ? colInfo : null;
|
|
15697
|
+
}
|
|
15698
|
+
|
|
15699
|
+
function getMaxLineLength(lines) {
|
|
15700
|
+
var max = 0;
|
|
15701
|
+
for (var i=0; i<lines.length; i++) {
|
|
15702
|
+
max = Math.max(max, lines[i].length);
|
|
15703
|
+
}
|
|
15704
|
+
return max;
|
|
15705
|
+
}
|
|
15706
|
+
|
|
15707
|
+
function readValue$1(line, start, end) {
|
|
15708
|
+
return line.substring(start, end).trim();
|
|
15709
|
+
}
|
|
15710
|
+
|
|
15711
|
+
function parseFixedWidthLine(str, fields) {
|
|
15712
|
+
var obj = {}, field;
|
|
15713
|
+
for (var i=0; i<fields.length; i++) {
|
|
15714
|
+
field = fields[i];
|
|
15715
|
+
obj[field.name] = readValue$1(str, field.start, field.end);
|
|
15716
|
+
}
|
|
15717
|
+
return obj;
|
|
15718
|
+
}
|
|
15719
|
+
|
|
15720
|
+
function testContentChar(str, i) {
|
|
15721
|
+
return i < str.length && str[i] !== ' ';
|
|
15722
|
+
}
|
|
15723
|
+
|
|
15724
|
+
// return true iff all samples are blank at index i
|
|
15725
|
+
function testEmptyCol(samples, i) {
|
|
15726
|
+
var line;
|
|
15727
|
+
for (var j=0; j<samples.length; j++) {
|
|
15728
|
+
line = samples[j];
|
|
15729
|
+
if (testContentChar(line, i)) return false;
|
|
15730
|
+
}
|
|
15731
|
+
return true;
|
|
15732
|
+
}
|
|
15733
|
+
|
|
15568
15734
|
// Generate output content from a dataset object
|
|
15569
15735
|
function exportDelim(dataset, opts) {
|
|
15570
15736
|
var delim = getExportDelimiter(dataset.info, opts),
|
|
@@ -15586,6 +15752,9 @@ ${svg}
|
|
|
15586
15752
|
var encoding = opts.encoding || 'utf8';
|
|
15587
15753
|
var records = lyr.data.getRecords();
|
|
15588
15754
|
var fields = findFieldNames(records, opts.field_order);
|
|
15755
|
+
if (delim == ' ') {
|
|
15756
|
+
return exportRecordsAsFixedWidthString(fields, records, opts);
|
|
15757
|
+
}
|
|
15589
15758
|
var formatRow = getDelimRowFormatter(fields, delim, opts);
|
|
15590
15759
|
// exporting utf8 and ascii text as string by default (for now)
|
|
15591
15760
|
var exportAsString = encodingIsUtf8(encoding) && !opts.to_buffer &&
|
|
@@ -15643,15 +15812,6 @@ ${svg}
|
|
|
15643
15812
|
};
|
|
15644
15813
|
}
|
|
15645
15814
|
|
|
15646
|
-
function formatNumber$1(val) {
|
|
15647
|
-
return val + '';
|
|
15648
|
-
}
|
|
15649
|
-
|
|
15650
|
-
function formatIntlNumber(val) {
|
|
15651
|
-
var str = formatNumber$1(val);
|
|
15652
|
-
return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
|
|
15653
|
-
}
|
|
15654
|
-
|
|
15655
15815
|
function getDelimValueFormatter(delim, opts) {
|
|
15656
15816
|
var dquoteRxp = new RegExp('["\n\r' + delim + ']');
|
|
15657
15817
|
var decimalComma = opts && opts.decimal_comma || false;
|
|
@@ -15668,7 +15828,7 @@ ${svg}
|
|
|
15668
15828
|
} else if (utils.isString(val)) {
|
|
15669
15829
|
s = formatString(val);
|
|
15670
15830
|
} else if (utils.isNumber(val)) {
|
|
15671
|
-
s = decimalComma ? formatIntlNumber(val) : formatNumber
|
|
15831
|
+
s = decimalComma ? utils.formatIntlNumber(val) : utils.formatNumber(val);
|
|
15672
15832
|
} else if (utils.isObject(val)) {
|
|
15673
15833
|
s = formatString(JSON.stringify(val));
|
|
15674
15834
|
} else {
|
|
@@ -15711,8 +15871,6 @@ ${svg}
|
|
|
15711
15871
|
__proto__: null,
|
|
15712
15872
|
exportDelim: exportDelim,
|
|
15713
15873
|
exportLayerAsDSV: exportLayerAsDSV,
|
|
15714
|
-
formatNumber: formatNumber$1,
|
|
15715
|
-
formatIntlNumber: formatIntlNumber,
|
|
15716
15874
|
getDelimValueFormatter: getDelimValueFormatter
|
|
15717
15875
|
});
|
|
15718
15876
|
|
|
@@ -17076,8 +17234,9 @@ ${svg}
|
|
|
17076
17234
|
//
|
|
17077
17235
|
// TODO: confirm compatibility with all supported encodings
|
|
17078
17236
|
function readDelimRecords(reader, delim, optsArg) {
|
|
17237
|
+
var opts = optsArg || {};
|
|
17238
|
+
if (delim == ' ') return readFixedWidthRecords(reader, opts);
|
|
17079
17239
|
var reader2 = new Reader2(reader),
|
|
17080
|
-
opts = optsArg || {},
|
|
17081
17240
|
headerStr = readLinesAsString(reader2, getDelimHeaderLines(opts), opts.encoding),
|
|
17082
17241
|
header = parseDelimHeaderSection(headerStr, delim, opts),
|
|
17083
17242
|
convertRowArr = getRowConverter(header.import_fields),
|
|
@@ -17100,6 +17259,7 @@ ${svg}
|
|
|
17100
17259
|
// for delimiter characters and newlines. Input size is limited by the maximum
|
|
17101
17260
|
// string size.
|
|
17102
17261
|
function readDelimRecordsFromString(str, delim, opts) {
|
|
17262
|
+
if (delim == ' ') return readFixedWidthRecordsFromString(str, opts);
|
|
17103
17263
|
var header = parseDelimHeaderSection(str, delim, opts);
|
|
17104
17264
|
if (header.import_fields.length === 0 || !header.remainder) return [];
|
|
17105
17265
|
var convert = getRowConverter(header.import_fields);
|
|
@@ -17503,7 +17663,7 @@ ${svg}
|
|
|
17503
17663
|
};
|
|
17504
17664
|
}
|
|
17505
17665
|
|
|
17506
|
-
var supportedDelimiters = ['|', '\t', ',', ';'];
|
|
17666
|
+
var supportedDelimiters = ['|', '\t', ',', ';', ' '];
|
|
17507
17667
|
|
|
17508
17668
|
function isSupportedDelimiter(d) {
|
|
17509
17669
|
return utils.contains(supportedDelimiters, d);
|
|
@@ -17864,9 +18024,21 @@ ${svg}
|
|
|
17864
18024
|
}
|
|
17865
18025
|
|
|
17866
18026
|
function cleanArgv(argv) {
|
|
17867
|
-
|
|
18027
|
+
// Note: original trim caused some quoted spaces to be removed
|
|
18028
|
+
// (e.g. bash shell seems to convert [delimiter=" "] to [delimiter= ],
|
|
18029
|
+
// which then got trimmed to [delimiter=] below)
|
|
18030
|
+
//// argv = argv.map(function(s) {return s.trim();}); // trim whitespace
|
|
18031
|
+
|
|
18032
|
+
// Updated: don't trim space from tokens like [delimeter= ]
|
|
18033
|
+
argv = argv.map(function(s) {
|
|
18034
|
+
if (!/= $/.test(s)) {
|
|
18035
|
+
s = s.trimEnd();
|
|
18036
|
+
}
|
|
18037
|
+
s = s.trimStart();
|
|
18038
|
+
return s;
|
|
18039
|
+
});
|
|
17868
18040
|
argv = argv.filter(function(s) {return s !== '';}); // remove empty tokens
|
|
17869
|
-
// removing trimQuotes() call... now, strings like 'name="Meg"' will no longer
|
|
18041
|
+
// Note: removing trimQuotes() call... now, strings like 'name="Meg"' will no longer
|
|
17870
18042
|
// be parsed the same way as name=Meg and name="Meg"
|
|
17871
18043
|
//// argv = argv.map(utils.trimQuotes); // remove one level of single or dbl quotes
|
|
17872
18044
|
return argv;
|
|
@@ -18973,6 +19145,28 @@ ${svg}
|
|
|
18973
19145
|
'$ mapshaper data.json -colorizer name=getColor nodata=#eee breaks=20,40 \\\n' +
|
|
18974
19146
|
' colors=#e0f3db,#a8ddb5,#43a2ca -each \'fill = getColor(RATING)\' -o output.json');
|
|
18975
19147
|
|
|
19148
|
+
parser.command('dashlines')
|
|
19149
|
+
.describe('split lines into sections, with or without a gap')
|
|
19150
|
+
.oldAlias('split-lines')
|
|
19151
|
+
.option('dash-length', {
|
|
19152
|
+
type: 'distance',
|
|
19153
|
+
describe: 'length of split-apart lines (e.g. 200km)'
|
|
19154
|
+
})
|
|
19155
|
+
.option('gap-length', {
|
|
19156
|
+
type: 'distance',
|
|
19157
|
+
describe: 'length of gaps between dashes (default is 0)'
|
|
19158
|
+
})
|
|
19159
|
+
.option('scaled', {
|
|
19160
|
+
type: 'flag',
|
|
19161
|
+
describe: 'scale dashes and gaps to prevent partial dashes'
|
|
19162
|
+
})
|
|
19163
|
+
.option('planar', {
|
|
19164
|
+
type: 'flag',
|
|
19165
|
+
describe: 'use planar geometry'
|
|
19166
|
+
})
|
|
19167
|
+
.option('where', whereOpt)
|
|
19168
|
+
.option('target', targetOpt);
|
|
19169
|
+
|
|
18976
19170
|
parser.command('define')
|
|
18977
19171
|
// .describe('define expression variables')
|
|
18978
19172
|
.option('expression', {
|
|
@@ -19169,6 +19363,10 @@ ${svg}
|
|
|
19169
19363
|
.option('keep-shapes', {
|
|
19170
19364
|
type: 'flag'
|
|
19171
19365
|
})
|
|
19366
|
+
.option('ids', {
|
|
19367
|
+
// describe: 'filter on a list of feature ids',
|
|
19368
|
+
type: 'numbers'
|
|
19369
|
+
})
|
|
19172
19370
|
.option('cleanup', {type: 'flag'}) // TODO: document
|
|
19173
19371
|
.option('name', nameOpt)
|
|
19174
19372
|
.option('target', targetOpt)
|
|
@@ -19291,6 +19489,13 @@ ${svg}
|
|
|
19291
19489
|
})
|
|
19292
19490
|
.option('target', targetOpt);
|
|
19293
19491
|
|
|
19492
|
+
parser.command('include')
|
|
19493
|
+
.describe('import JS data and functions for use in JS expressions')
|
|
19494
|
+
.option('file', {
|
|
19495
|
+
DEFAULT: true,
|
|
19496
|
+
describe: 'file containing a JS object with key:value pairs to import'
|
|
19497
|
+
});
|
|
19498
|
+
|
|
19294
19499
|
parser.command('inlay')
|
|
19295
19500
|
.describe('inscribe a polygon layer inside another polygon layer')
|
|
19296
19501
|
.option('source', {
|
|
@@ -19603,6 +19808,7 @@ ${svg}
|
|
|
19603
19808
|
})
|
|
19604
19809
|
.option('target', targetOpt);
|
|
19605
19810
|
|
|
19811
|
+
|
|
19606
19812
|
parser.command('simplify')
|
|
19607
19813
|
.validate(validateSimplifyOpts)
|
|
19608
19814
|
.example('Retain 10% of removable vertices\n$ mapshaper input.shp -simplify 10%')
|
|
@@ -19737,6 +19943,11 @@ ${svg}
|
|
|
19737
19943
|
DEFAULT: true,
|
|
19738
19944
|
describe: 'expression or field for grouping features and naming split layers'
|
|
19739
19945
|
})
|
|
19946
|
+
.option('ids', {
|
|
19947
|
+
// used by gui history to split on selected features
|
|
19948
|
+
// describe: 'split on a list of feature ids',
|
|
19949
|
+
type: 'numbers'
|
|
19950
|
+
})
|
|
19740
19951
|
.option('apart', {
|
|
19741
19952
|
describe: 'save output layers to independent datasets',
|
|
19742
19953
|
type: 'flag'
|
|
@@ -19744,23 +19955,6 @@ ${svg}
|
|
|
19744
19955
|
.option('target', targetOpt)
|
|
19745
19956
|
.option('no-replace', noReplaceOpt);
|
|
19746
19957
|
|
|
19747
|
-
parser.command('split-lines')
|
|
19748
|
-
// .describe('divide lines into sections')
|
|
19749
|
-
.option('dash-length', {
|
|
19750
|
-
type: 'distance',
|
|
19751
|
-
describe: 'length of split-apart lines'
|
|
19752
|
-
})
|
|
19753
|
-
.option('gap-length', {
|
|
19754
|
-
type: 'distance',
|
|
19755
|
-
describe: 'length of gap between segments'
|
|
19756
|
-
})
|
|
19757
|
-
.option('planar', {
|
|
19758
|
-
type: 'flag',
|
|
19759
|
-
describe: 'use planar geometry'
|
|
19760
|
-
})
|
|
19761
|
-
.option('where', whereOpt)
|
|
19762
|
-
.option('target', targetOpt);
|
|
19763
|
-
|
|
19764
19958
|
parser.command('split-on-grid')
|
|
19765
19959
|
.describe('split features into separate layers using a grid')
|
|
19766
19960
|
.validate(validateGridOpts)
|
|
@@ -19853,26 +20047,81 @@ ${svg}
|
|
|
19853
20047
|
.option('target', targetOpt);
|
|
19854
20048
|
|
|
19855
20049
|
parser.command('symbols')
|
|
19856
|
-
// .describe('
|
|
20050
|
+
// .describe('symbolize points as polygons, circles, stars or arrows')
|
|
19857
20051
|
.option('type', {
|
|
19858
|
-
describe: 'symbol type'
|
|
20052
|
+
describe: 'symbol type (e.g. star, polygon, circle, arrow)'
|
|
20053
|
+
})
|
|
20054
|
+
.option('scale', {
|
|
20055
|
+
describe: 'scale symbols by a factor',
|
|
20056
|
+
type: 'number'
|
|
20057
|
+
})
|
|
20058
|
+
.option('pixel-scale', {
|
|
20059
|
+
describe: 'symbol scale in meters-per-pixel (see polygons option)',
|
|
20060
|
+
type: 'number',
|
|
20061
|
+
})
|
|
20062
|
+
.option('polygons', {
|
|
20063
|
+
describe: 'generate symbols as polygons instead of SVG objects',
|
|
20064
|
+
type: 'flag'
|
|
20065
|
+
})
|
|
20066
|
+
.option('radius', {
|
|
20067
|
+
describe: 'distance from center to farthest point on the symbol',
|
|
20068
|
+
type: 'distance'
|
|
20069
|
+
})
|
|
20070
|
+
.option('sides', {
|
|
20071
|
+
describe: 'sides of a polygon or star symbol',
|
|
20072
|
+
type: 'number'
|
|
20073
|
+
})
|
|
20074
|
+
.option('rotation', {
|
|
20075
|
+
describe: 'rotation of symbol in degrees'
|
|
20076
|
+
})
|
|
20077
|
+
.option('orientation', {
|
|
20078
|
+
describe: 'use orientation=b for a rotated or flipped orientation'
|
|
20079
|
+
})
|
|
20080
|
+
.option('length', {
|
|
20081
|
+
// alias for arrow-length
|
|
20082
|
+
})
|
|
20083
|
+
.option('star-ratio', {
|
|
20084
|
+
describe: 'ratio of major to minor radius of star',
|
|
20085
|
+
type: 'number'
|
|
20086
|
+
})
|
|
20087
|
+
.option('arrow-length', {
|
|
20088
|
+
describe: 'length of arrows in pixels (use with type=arrow)'
|
|
20089
|
+
})
|
|
20090
|
+
.option('arrow-direction', {
|
|
20091
|
+
describe: 'angle off of vertical (-90 = left-pointing arrow)'
|
|
20092
|
+
})
|
|
20093
|
+
.option('arrow-head-angle', {
|
|
20094
|
+
describe: 'angle of tip of arrow (default is 40 degrees)'
|
|
20095
|
+
})
|
|
20096
|
+
.option('arrow-head-width', {
|
|
20097
|
+
describe: 'size of arrow head from side to side'
|
|
20098
|
+
})
|
|
20099
|
+
.option('arrow-stem-width', {
|
|
20100
|
+
describe: 'width of stem at its widest point'
|
|
20101
|
+
})
|
|
20102
|
+
.option('arrow-stem-taper', {
|
|
20103
|
+
describe: 'factor for tapering the width of the stem'
|
|
20104
|
+
})
|
|
20105
|
+
.option('arrow-stem-curve', {
|
|
20106
|
+
describe: 'curvature in degrees (arrows are straight by default)'
|
|
20107
|
+
})
|
|
20108
|
+
.option('arrow-min-stem', {
|
|
20109
|
+
describe: 'min ratio of stem to total length (for small arrows)',
|
|
20110
|
+
type: 'number'
|
|
19859
20111
|
})
|
|
19860
20112
|
.option('stroke', {})
|
|
19861
20113
|
.option('stroke-width', {})
|
|
19862
|
-
.option('fill', {
|
|
19863
|
-
|
|
19864
|
-
|
|
20114
|
+
.option('fill', {
|
|
20115
|
+
describe: 'symbol fill color'
|
|
20116
|
+
})
|
|
19865
20117
|
.option('effect', {})
|
|
19866
|
-
.option('
|
|
19867
|
-
.option('
|
|
19868
|
-
.option('
|
|
19869
|
-
.option('
|
|
19870
|
-
.option('arrow-stem-taper', {})
|
|
19871
|
-
.option('arrow-scaling', {})
|
|
19872
|
-
.option('where', whereOpt)
|
|
19873
|
-
.option('target', targetOpt);
|
|
20118
|
+
// .option('where', whereOpt)
|
|
20119
|
+
.option('name', nameOpt)
|
|
20120
|
+
.option('target', targetOpt)
|
|
20121
|
+
.option('no-replace', noReplaceOpt);
|
|
19874
20122
|
// .option('name', nameOpt);
|
|
19875
20123
|
|
|
20124
|
+
|
|
19876
20125
|
parser.command('target')
|
|
19877
20126
|
.describe('set active layer (or layers)')
|
|
19878
20127
|
.option('target', {
|
|
@@ -20023,13 +20272,6 @@ ${svg}
|
|
|
20023
20272
|
})
|
|
20024
20273
|
.option('name', nameOpt);
|
|
20025
20274
|
|
|
20026
|
-
parser.command('include')
|
|
20027
|
-
.describe('import JS data and functions for use in JS expressions')
|
|
20028
|
-
.option('file', {
|
|
20029
|
-
DEFAULT: true,
|
|
20030
|
-
describe: 'file containing a JS object with key:value pairs to import'
|
|
20031
|
-
});
|
|
20032
|
-
|
|
20033
20275
|
parser.command('fuzzy-join')
|
|
20034
20276
|
.describe('join points to polygons, with data fill and fuzzy match')
|
|
20035
20277
|
.option('source', {
|
|
@@ -21028,12 +21270,13 @@ ${svg}
|
|
|
21028
21270
|
if (this instanceof ShpReader === false) {
|
|
21029
21271
|
return new ShpReader(shpSrc, shxSrc);
|
|
21030
21272
|
}
|
|
21031
|
-
|
|
21032
21273
|
var shpFile = utils.isString(shpSrc) ? new FileReader(shpSrc) : new BufferReader(shpSrc);
|
|
21033
21274
|
var header = parseHeader(shpFile.readToBinArray(0, 100));
|
|
21034
|
-
var
|
|
21035
|
-
var
|
|
21036
|
-
var
|
|
21275
|
+
var shpType = header.type;
|
|
21276
|
+
var shpOffset = 100; // used when reading .shp without .shx
|
|
21277
|
+
var recordCount = 0;
|
|
21278
|
+
var badRecordNumberCount = 0;
|
|
21279
|
+
var RecordClass = new ShpRecordClass(shpType);
|
|
21037
21280
|
var shxBin, shxFile;
|
|
21038
21281
|
|
|
21039
21282
|
if (shxSrc) {
|
|
@@ -21041,8 +21284,6 @@ ${svg}
|
|
|
21041
21284
|
shxBin = shxFile.readToBinArray(0, shxFile.size()).bigEndian();
|
|
21042
21285
|
}
|
|
21043
21286
|
|
|
21044
|
-
reset();
|
|
21045
|
-
|
|
21046
21287
|
this.header = function() {
|
|
21047
21288
|
return header;
|
|
21048
21289
|
};
|
|
@@ -21060,62 +21301,35 @@ ${svg}
|
|
|
21060
21301
|
|
|
21061
21302
|
// Iterator interface for reading shape records
|
|
21062
21303
|
this.nextShape = function() {
|
|
21063
|
-
var shape
|
|
21304
|
+
var shape;
|
|
21305
|
+
if (!shpFile) {
|
|
21306
|
+
error('Tried to read from a used ShpReader');
|
|
21307
|
+
// return null; // this reader was already used
|
|
21308
|
+
}
|
|
21309
|
+
shape = readNextShape(recordCount);
|
|
21064
21310
|
if (!shape) {
|
|
21065
|
-
|
|
21066
|
-
|
|
21067
|
-
// ne_10m_admin_0_boundary_lines_land.shp
|
|
21068
|
-
// ne_110m_admin_0_scale_rank.shp
|
|
21069
|
-
verbose("Skipped over " + skippedBytes + " non-data bytes in the .shp file.");
|
|
21070
|
-
}
|
|
21071
|
-
shpFile.close();
|
|
21072
|
-
reset();
|
|
21311
|
+
done();
|
|
21312
|
+
return null;
|
|
21073
21313
|
}
|
|
21314
|
+
recordCount++;
|
|
21074
21315
|
return shape;
|
|
21075
21316
|
};
|
|
21076
21317
|
|
|
21077
|
-
|
|
21078
|
-
|
|
21079
|
-
|
|
21080
|
-
|
|
21081
|
-
|
|
21082
|
-
|
|
21083
|
-
|
|
21084
|
-
if (offset > shpOffset) {
|
|
21085
|
-
skippedBytes += offset - shpOffset;
|
|
21086
|
-
}
|
|
21087
|
-
} else {
|
|
21088
|
-
offset = shpOffset;
|
|
21089
|
-
}
|
|
21090
|
-
shape = readShapeAtOffset(offset);
|
|
21091
|
-
if (!shape) {
|
|
21092
|
-
// Some in-the-wild .shp files contain junk bytes between records. This
|
|
21093
|
-
// is a problem if the .shx index file is not present.
|
|
21094
|
-
// Here, we try to scan past the junk to find the next record.
|
|
21095
|
-
shape = huntForNextShape(offset, expectedId);
|
|
21096
|
-
}
|
|
21097
|
-
if (shape) {
|
|
21098
|
-
if (shape.id < expectedId) {
|
|
21099
|
-
message("Found a Shapefile record with the same id as a previous record (" + shape.id + ") -- skipping.");
|
|
21100
|
-
return readNextShape();
|
|
21101
|
-
} else if (shape.id > expectedId) {
|
|
21102
|
-
stop("Shapefile contains an out-of-sequence record. Possible data corruption -- bailing.");
|
|
21103
|
-
}
|
|
21104
|
-
recordCount++;
|
|
21105
|
-
}
|
|
21106
|
-
return shape || null;
|
|
21318
|
+
// Returns a shape record or null if no more shapes can be read
|
|
21319
|
+
// i: Expected 0-based index of the next record
|
|
21320
|
+
//
|
|
21321
|
+
function readNextShape(i) {
|
|
21322
|
+
return shxBin ?
|
|
21323
|
+
readIndexedShape(shpFile, shxBin, i) :
|
|
21324
|
+
readNonIndexedShape(shpFile, shpOffset, i);
|
|
21107
21325
|
}
|
|
21108
21326
|
|
|
21109
21327
|
function done() {
|
|
21110
|
-
|
|
21111
|
-
|
|
21112
|
-
|
|
21113
|
-
|
|
21114
|
-
|
|
21115
|
-
function reset() {
|
|
21116
|
-
shpOffset = 100;
|
|
21117
|
-
skippedBytes = 0;
|
|
21118
|
-
recordCount = 0;
|
|
21328
|
+
shpFile.close();
|
|
21329
|
+
shpFile = shxFile = shxBin = null;
|
|
21330
|
+
if (badRecordNumberCount > 0) {
|
|
21331
|
+
message(`Warning: ${badRecordNumberCount}/${recordCount} features have non-standard record numbers in the .shp file.`);
|
|
21332
|
+
}
|
|
21119
21333
|
}
|
|
21120
21334
|
|
|
21121
21335
|
function parseHeader(bin) {
|
|
@@ -21144,47 +21358,81 @@ ${svg}
|
|
|
21144
21358
|
return header;
|
|
21145
21359
|
}
|
|
21146
21360
|
|
|
21147
|
-
function readShapeAtOffset(offset) {
|
|
21148
|
-
var shape = null,
|
|
21149
|
-
recordSize, recordType, recordId, goodSize, goodType, bin;
|
|
21150
21361
|
|
|
21151
|
-
|
|
21152
|
-
|
|
21153
|
-
|
|
21154
|
-
|
|
21155
|
-
|
|
21156
|
-
|
|
21157
|
-
|
|
21158
|
-
|
|
21159
|
-
|
|
21160
|
-
|
|
21161
|
-
|
|
21162
|
-
|
|
21163
|
-
|
|
21362
|
+
function readShapeAtOffset(shpFile, offset) {
|
|
21363
|
+
var fileSize = shpFile.size();
|
|
21364
|
+
if (offset + 12 > fileSize) return null; // reached end-of-file
|
|
21365
|
+
var bin = shpFile.readToBinArray(offset, 12);
|
|
21366
|
+
var recordId = bin.bigEndian().readUint32();
|
|
21367
|
+
// record size is bytes in content section + 8 header bytes
|
|
21368
|
+
var recordSize = bin.readUint32() * 2 + 8;
|
|
21369
|
+
var recordType = bin.littleEndian().readUint32();
|
|
21370
|
+
var goodSize = offset + recordSize <= fileSize && recordSize >= 12;
|
|
21371
|
+
var goodType = recordType === 0 || recordType == shpType;
|
|
21372
|
+
if (!goodSize || !goodType) {
|
|
21373
|
+
return null;
|
|
21374
|
+
}
|
|
21375
|
+
bin = shpFile.readToBinArray(offset, recordSize);
|
|
21376
|
+
return new RecordClass(bin, recordSize);
|
|
21377
|
+
}
|
|
21378
|
+
|
|
21379
|
+
function readIndexedShape(shpFile, shxBin, i) {
|
|
21380
|
+
if (shxBin.size() <= 100 + i * 8) return null; // done
|
|
21381
|
+
shxBin.position(100 + i * 8);
|
|
21382
|
+
var expectedId = i + 1;
|
|
21383
|
+
var offset = shxBin.readUint32() * 2;
|
|
21384
|
+
var recLen = shxBin.readUint32() * 2; // TODO: match this to recLen in .shp
|
|
21385
|
+
var shape = readShapeAtOffset(shpFile, offset);
|
|
21386
|
+
if (!shape) {
|
|
21387
|
+
stop('Index of Shapefile record', expectedId, 'in the .shx file is invalid.');
|
|
21164
21388
|
}
|
|
21389
|
+
if (shape.id != expectedId) {
|
|
21390
|
+
badRecordNumberCount++;
|
|
21391
|
+
verbose(`Warning: A feature has a different record number in .shx (${expectedId}) and .shp (${shape.id}).`);
|
|
21392
|
+
}
|
|
21393
|
+
// TODO: consider printing verbose message if a .shp file contains garbage bytes
|
|
21394
|
+
// example files:
|
|
21395
|
+
// ne_10m_admin_0_boundary_lines_land.shp
|
|
21396
|
+
// ne_110m_admin_0_scale_rank.shp
|
|
21165
21397
|
return shape;
|
|
21166
21398
|
}
|
|
21167
21399
|
|
|
21168
|
-
//
|
|
21169
|
-
//
|
|
21170
|
-
|
|
21171
|
-
|
|
21400
|
+
// The Shapefile specification does not require records to be densely packed or
|
|
21401
|
+
// in consecutive sequence in the .shp file. This is a problem when the .shx
|
|
21402
|
+
// index file is not present.
|
|
21403
|
+
//
|
|
21404
|
+
// Here, we try to scan past any invalid content to find the next record.
|
|
21405
|
+
// Records are required to be in sequential order.
|
|
21406
|
+
//
|
|
21407
|
+
function readNonIndexedShape(shpFile, start, i) {
|
|
21408
|
+
var expectedId = i + 1, // Shapefile ids are 1-based
|
|
21409
|
+
offset = start,
|
|
21410
|
+
fileSize = shpFile.size(),
|
|
21172
21411
|
shape = null,
|
|
21173
|
-
bin, recordId, recordType,
|
|
21174
|
-
while (offset + 12 <=
|
|
21412
|
+
bin, recordId, recordType, isValidType;
|
|
21413
|
+
while (offset + 12 <= fileSize) {
|
|
21175
21414
|
bin = shpFile.readToBinArray(offset, 12);
|
|
21176
21415
|
recordId = bin.bigEndian().readUint32();
|
|
21177
21416
|
recordType = bin.littleEndian().skipBytes(4).readUint32();
|
|
21178
|
-
|
|
21179
|
-
|
|
21180
|
-
|
|
21181
|
-
|
|
21417
|
+
isValidType = recordType == shpType || recordType === 0;
|
|
21418
|
+
if (!isValidType || recordId != expectedId && recordType === 0) {
|
|
21419
|
+
offset += 4; // keep scanning -- try next integer position
|
|
21420
|
+
continue;
|
|
21421
|
+
}
|
|
21422
|
+
shape = readShapeAtOffset(shpFile, offset);
|
|
21423
|
+
if (!shape) break; // probably ran into end of file
|
|
21424
|
+
shpOffset = offset + shape.byteLength; // update
|
|
21425
|
+
if (recordId == expectedId) break; // found an apparently valid shape
|
|
21426
|
+
if (recordId < expectedId) {
|
|
21427
|
+
message("Found a Shapefile record with the same id as a previous record (" + shape.id + ") -- skipping.");
|
|
21428
|
+
offset += shape.byteLength;
|
|
21429
|
+
} else {
|
|
21430
|
+
stop("Shapefile contains an out-of-sequence record. Possible data corruption -- bailing.");
|
|
21182
21431
|
}
|
|
21183
|
-
offset += 4; // try next integer position
|
|
21184
21432
|
}
|
|
21185
|
-
|
|
21186
|
-
|
|
21187
|
-
|
|
21433
|
+
if (shape && offset > start) {
|
|
21434
|
+
verbose("Skipped over " + (offset - start) + " non-data bytes in the .shp file.");
|
|
21435
|
+
}
|
|
21188
21436
|
return shape;
|
|
21189
21437
|
}
|
|
21190
21438
|
}
|
|
@@ -21193,22 +21441,6 @@ ${svg}
|
|
|
21193
21441
|
return this.header().type;
|
|
21194
21442
|
};
|
|
21195
21443
|
|
|
21196
|
-
ShpReader.prototype.getCounts = function() {
|
|
21197
|
-
var counts = {
|
|
21198
|
-
nullCount: 0,
|
|
21199
|
-
partCount: 0,
|
|
21200
|
-
shapeCount: 0,
|
|
21201
|
-
pointCount: 0
|
|
21202
|
-
};
|
|
21203
|
-
this.forEachShape(function(shp) {
|
|
21204
|
-
if (shp.isNull) counts.nullCount++;
|
|
21205
|
-
counts.pointCount += shp.pointCount;
|
|
21206
|
-
counts.partCount += shp.partCount;
|
|
21207
|
-
counts.shapeCount++;
|
|
21208
|
-
});
|
|
21209
|
-
return counts;
|
|
21210
|
-
};
|
|
21211
|
-
|
|
21212
21444
|
// Apply snapping, remove duplicate coords and clean up defective paths in a dataset
|
|
21213
21445
|
// Assumes that any CRS info has been added to the dataset
|
|
21214
21446
|
// @opts: import options
|
|
@@ -26017,7 +26249,6 @@ ${svg}
|
|
|
26017
26249
|
if (constTol) return constTol;
|
|
26018
26250
|
return constTol ? constTol : meterDist * pctOfRadius;
|
|
26019
26251
|
};
|
|
26020
|
-
|
|
26021
26252
|
}
|
|
26022
26253
|
|
|
26023
26254
|
function getBufferDistanceFunction(lyr, dataset, opts) {
|
|
@@ -27190,7 +27421,7 @@ ${svg}
|
|
|
27190
27421
|
cmd.buffer = makeBufferLayer;
|
|
27191
27422
|
|
|
27192
27423
|
function makeBufferLayer(lyr, dataset, opts) {
|
|
27193
|
-
var dataset2
|
|
27424
|
+
var dataset2;
|
|
27194
27425
|
if (lyr.geometry_type == 'point') {
|
|
27195
27426
|
dataset2 = makePointBuffer(lyr, dataset, opts);
|
|
27196
27427
|
} else if (lyr.geometry_type == 'polyline') {
|
|
@@ -27200,13 +27431,9 @@ ${svg}
|
|
|
27200
27431
|
} else {
|
|
27201
27432
|
stop("Unsupported geometry type");
|
|
27202
27433
|
}
|
|
27203
|
-
|
|
27204
|
-
lyr2 =
|
|
27205
|
-
|
|
27206
|
-
if (lyr.data && !lyr2.data) {
|
|
27207
|
-
lyr2.data = opts.no_replace ? lyr.data.clone() : lyr.data;
|
|
27208
|
-
}
|
|
27209
|
-
return outputLayers;
|
|
27434
|
+
|
|
27435
|
+
var lyr2 = mergeOutputLayerIntoDataset(lyr, dataset, dataset2, opts);
|
|
27436
|
+
return [lyr2];
|
|
27210
27437
|
}
|
|
27211
27438
|
|
|
27212
27439
|
// TODO: support three or more stops
|
|
@@ -29729,6 +29956,178 @@ ${svg}
|
|
|
29729
29956
|
getColorizerFunction: getColorizerFunction
|
|
29730
29957
|
});
|
|
29731
29958
|
|
|
29959
|
+
function expressionUsesGeoJSON(exp) {
|
|
29960
|
+
return exp.includes('this.geojson');
|
|
29961
|
+
}
|
|
29962
|
+
|
|
29963
|
+
function getFeatureEditor(lyr, dataset) {
|
|
29964
|
+
var changed = false;
|
|
29965
|
+
var api = {};
|
|
29966
|
+
// need to copy attribute to avoid circular references if geojson is assigned
|
|
29967
|
+
// to a data property.
|
|
29968
|
+
var copy = copyLayer(lyr);
|
|
29969
|
+
var features = exportLayerAsGeoJSON(copy, dataset, {}, true);
|
|
29970
|
+
|
|
29971
|
+
api.get = function(i) {
|
|
29972
|
+
return features[i];
|
|
29973
|
+
};
|
|
29974
|
+
|
|
29975
|
+
api.set = function(feat, i) {
|
|
29976
|
+
changed = true;
|
|
29977
|
+
if (utils.isString(feat)) {
|
|
29978
|
+
feat = JSON.parse(feat);
|
|
29979
|
+
}
|
|
29980
|
+
features[i] = GeoJSON.toFeature(feat); // TODO: validate
|
|
29981
|
+
};
|
|
29982
|
+
|
|
29983
|
+
api.done = function() {
|
|
29984
|
+
if (!changed) return; // read-only expression
|
|
29985
|
+
// TODO: validate number of features, etc.
|
|
29986
|
+
var geojson = {
|
|
29987
|
+
type: 'FeatureCollection',
|
|
29988
|
+
features: features
|
|
29989
|
+
};
|
|
29990
|
+
|
|
29991
|
+
// console.log(JSON.stringify(geojson, null, 2))
|
|
29992
|
+
return importGeoJSON(geojson);
|
|
29993
|
+
};
|
|
29994
|
+
return api;
|
|
29995
|
+
}
|
|
29996
|
+
|
|
29997
|
+
cmd.dashlines = function(lyr, dataset, opts) {
|
|
29998
|
+
var crs = getDatasetCRS(dataset);
|
|
29999
|
+
var defs = getStateVar('defs');
|
|
30000
|
+
var exp = `this.geojson = splitFeature(this.geojson)`;
|
|
30001
|
+
requirePolylineLayer(lyr);
|
|
30002
|
+
defs.splitFeature = getSplitFeatureFunction(crs, opts);
|
|
30003
|
+
cmd.evaluateEachFeature(lyr, dataset, exp, opts);
|
|
30004
|
+
delete defs.splitFeature;
|
|
30005
|
+
};
|
|
30006
|
+
|
|
30007
|
+
function getSplitFeatureFunction(crs, opts) {
|
|
30008
|
+
var dashLen = opts.dash_length ? convertDistanceParam(opts.dash_length, crs) : 0;
|
|
30009
|
+
var gapLen = opts.gap_length ? convertDistanceParam(opts.gap_length, crs) : 0;
|
|
30010
|
+
if (dashLen > 0 === false) {
|
|
30011
|
+
stop('Missing required dash-length parameter');
|
|
30012
|
+
}
|
|
30013
|
+
if (gapLen >= 0 == false) {
|
|
30014
|
+
stop('Invalid gap-length option');
|
|
30015
|
+
}
|
|
30016
|
+
var splitLine = getSplitLineFunction(crs, dashLen, gapLen, opts);
|
|
30017
|
+
return function(feat) {
|
|
30018
|
+
var geom = feat.geometry;
|
|
30019
|
+
if (!geom) return feat;
|
|
30020
|
+
if (geom.type == 'LineString') {
|
|
30021
|
+
geom.type = 'MultiLineString';
|
|
30022
|
+
geom.coordinates = [geom.coordinates];
|
|
30023
|
+
}
|
|
30024
|
+
if (geom.type != 'MultiLineString') {
|
|
30025
|
+
error('Unexpected geometry:', geom.type);
|
|
30026
|
+
}
|
|
30027
|
+
geom.coordinates = geom.coordinates.reduce(function(memo, coords) {
|
|
30028
|
+
try {
|
|
30029
|
+
var parts = splitLine(coords);
|
|
30030
|
+
memo = memo.concat(parts);
|
|
30031
|
+
} catch(e) {
|
|
30032
|
+
console.error(e);
|
|
30033
|
+
throw e;
|
|
30034
|
+
}
|
|
30035
|
+
return memo;
|
|
30036
|
+
}, []);
|
|
30037
|
+
|
|
30038
|
+
return feat;
|
|
30039
|
+
};
|
|
30040
|
+
}
|
|
30041
|
+
|
|
30042
|
+
function getSplitLineFunction(crs, dashLen, gapLen, opts) {
|
|
30043
|
+
var planar = !!opts.planar;
|
|
30044
|
+
var interpolate = getInterpolationFunction(planar ? null : crs);
|
|
30045
|
+
var distance = isLatLngCRS(crs) ? greatCircleDistance : distance2D;
|
|
30046
|
+
var inDash, parts2, interval, scale;
|
|
30047
|
+
function addPart(coords) {
|
|
30048
|
+
if (inDash) parts2.push(coords);
|
|
30049
|
+
if (gapLen > 0) {
|
|
30050
|
+
inDash = !inDash;
|
|
30051
|
+
interval = scale * (inDash ? dashLen : gapLen);
|
|
30052
|
+
}
|
|
30053
|
+
}
|
|
30054
|
+
|
|
30055
|
+
return function splitLineString(coords) {
|
|
30056
|
+
var elapsedDist = 0;
|
|
30057
|
+
var p = coords[0];
|
|
30058
|
+
var coords2 = [p];
|
|
30059
|
+
var segLen, pct, prev;
|
|
30060
|
+
if (opts.scaled) {
|
|
30061
|
+
scale = scaleDashes(dashLen, gapLen, getLineLength(coords, distance));
|
|
30062
|
+
} else {
|
|
30063
|
+
scale = 1;
|
|
30064
|
+
}
|
|
30065
|
+
// init this LineString
|
|
30066
|
+
inDash = gapLen > 0 ? false : true;
|
|
30067
|
+
interval = scale * (inDash ? dashLen : gapLen);
|
|
30068
|
+
if (!inDash) {
|
|
30069
|
+
// start gapped lines with a half-gap
|
|
30070
|
+
// (a half-gap or a half-dash is probably better for rings and intersecting lines)
|
|
30071
|
+
interval *= 0.5;
|
|
30072
|
+
}
|
|
30073
|
+
parts2 = [];
|
|
30074
|
+
for (var i=1, n=coords.length; i<n; i++) {
|
|
30075
|
+
prev = p;
|
|
30076
|
+
p = coords[i];
|
|
30077
|
+
segLen = distance(prev[0], prev[1], p[0], p[1]);
|
|
30078
|
+
if (segLen <= 0) continue;
|
|
30079
|
+
while (elapsedDist + segLen >= interval) {
|
|
30080
|
+
// this segment contains a break either within it or at the far endpoint
|
|
30081
|
+
pct = (interval - elapsedDist) / segLen;
|
|
30082
|
+
if (pct > 0.999 && i == n - 1) {
|
|
30083
|
+
// snap to endpoint (so fp rounding errors don't result in a tiny
|
|
30084
|
+
// last segment)
|
|
30085
|
+
pct = 1;
|
|
30086
|
+
}
|
|
30087
|
+
if (pct < 1) {
|
|
30088
|
+
prev = interpolate(prev[0], prev[1], p[0], p[1], pct);
|
|
30089
|
+
} else {
|
|
30090
|
+
prev = p;
|
|
30091
|
+
}
|
|
30092
|
+
coords2.push(prev);
|
|
30093
|
+
addPart(coords2);
|
|
30094
|
+
// start a new part
|
|
30095
|
+
coords2 = pct < 1 ? [prev] : [];
|
|
30096
|
+
elapsedDist = 0;
|
|
30097
|
+
segLen = (1 - pct) * segLen;
|
|
30098
|
+
}
|
|
30099
|
+
coords2.push(p);
|
|
30100
|
+
elapsedDist += segLen;
|
|
30101
|
+
}
|
|
30102
|
+
if (elapsedDist > 0 && coords2.length > 1) {
|
|
30103
|
+
addPart(coords2);
|
|
30104
|
+
}
|
|
30105
|
+
return parts2;
|
|
30106
|
+
};
|
|
30107
|
+
}
|
|
30108
|
+
|
|
30109
|
+
function getLineLength(coords, distance) {
|
|
30110
|
+
var len = 0;
|
|
30111
|
+
for (var i=1, n=coords.length; i<n; i++) {
|
|
30112
|
+
len += distance(coords[i-1][0], coords[i-1][1], coords[i][0], coords[i][1]);
|
|
30113
|
+
}
|
|
30114
|
+
return len;
|
|
30115
|
+
}
|
|
30116
|
+
|
|
30117
|
+
function scaleDashes(dash, gap, len) {
|
|
30118
|
+
var dash2, gap2;
|
|
30119
|
+
var n = len / (dash + gap); // number of dashes
|
|
30120
|
+
var n1 = Math.floor(n);
|
|
30121
|
+
var n2 = Math.ceil(n);
|
|
30122
|
+
var k1 = len / (n1 * (dash + gap)); // scaled-up dashes, >1
|
|
30123
|
+
var k2 = len / (n2 * (dash + gap)); // scaled-down dashes <1
|
|
30124
|
+
var k = k2;
|
|
30125
|
+
if (k1 < 1/k2 && n1 > 0) {
|
|
30126
|
+
k = k1; // pick the smaller of the two scales
|
|
30127
|
+
}
|
|
30128
|
+
return k;
|
|
30129
|
+
}
|
|
30130
|
+
|
|
29732
30131
|
// This function creates a continuous mosaic of data values in a
|
|
29733
30132
|
// given field by assigning data from adjacent polygon features to polygons
|
|
29734
30133
|
// that contain null values.
|
|
@@ -31253,44 +31652,6 @@ ${svg}
|
|
|
31253
31652
|
}
|
|
31254
31653
|
};
|
|
31255
31654
|
|
|
31256
|
-
function expressionUsesGeoJSON(exp) {
|
|
31257
|
-
return exp.includes('this.geojson');
|
|
31258
|
-
}
|
|
31259
|
-
|
|
31260
|
-
function getFeatureEditor(lyr, dataset) {
|
|
31261
|
-
var changed = false;
|
|
31262
|
-
var api = {};
|
|
31263
|
-
// need to copy attribute to avoid circular references if geojson is assigned
|
|
31264
|
-
// to a data property.
|
|
31265
|
-
var copy = copyLayer(lyr);
|
|
31266
|
-
var features = exportLayerAsGeoJSON(copy, dataset, {}, true);
|
|
31267
|
-
|
|
31268
|
-
api.get = function(i) {
|
|
31269
|
-
return features[i];
|
|
31270
|
-
};
|
|
31271
|
-
|
|
31272
|
-
api.set = function(feat, i) {
|
|
31273
|
-
changed = true;
|
|
31274
|
-
if (utils.isString(feat)) {
|
|
31275
|
-
feat = JSON.parse(feat);
|
|
31276
|
-
}
|
|
31277
|
-
features[i] = GeoJSON.toFeature(feat); // TODO: validate
|
|
31278
|
-
};
|
|
31279
|
-
|
|
31280
|
-
api.done = function() {
|
|
31281
|
-
if (!changed) return; // read-only expression
|
|
31282
|
-
// TODO: validate number of features, etc.
|
|
31283
|
-
var geojson = {
|
|
31284
|
-
type: 'FeatureCollection',
|
|
31285
|
-
features: features
|
|
31286
|
-
};
|
|
31287
|
-
|
|
31288
|
-
// console.log(JSON.stringify(geojson, null, 2))
|
|
31289
|
-
return importGeoJSON(geojson);
|
|
31290
|
-
};
|
|
31291
|
-
return api;
|
|
31292
|
-
}
|
|
31293
|
-
|
|
31294
31655
|
cmd.evaluateEachFeature = function(lyr, dataset, exp, opts) {
|
|
31295
31656
|
var n = getFeatureCount(lyr),
|
|
31296
31657
|
arcs = dataset.arcs,
|
|
@@ -31533,6 +31894,10 @@ ${svg}
|
|
|
31533
31894
|
filter = compileValueExpression(opts.expression, lyr, arcs);
|
|
31534
31895
|
}
|
|
31535
31896
|
|
|
31897
|
+
if (opts.ids) {
|
|
31898
|
+
filter = combineFilters(filter, getIdFilter(opts.ids));
|
|
31899
|
+
}
|
|
31900
|
+
|
|
31536
31901
|
if (opts.remove_empty) {
|
|
31537
31902
|
filter = combineFilters(filter, getNullGeometryFilter(lyr, arcs));
|
|
31538
31903
|
}
|
|
@@ -31591,6 +31956,13 @@ ${svg}
|
|
|
31591
31956
|
lyr.data = filteredRecords ? new DataTable(filteredRecords) : null;
|
|
31592
31957
|
}
|
|
31593
31958
|
|
|
31959
|
+
function getIdFilter(ids) {
|
|
31960
|
+
var set = new Set(ids);
|
|
31961
|
+
return function(i) {
|
|
31962
|
+
return set.has(i);
|
|
31963
|
+
};
|
|
31964
|
+
}
|
|
31965
|
+
|
|
31594
31966
|
function getNullGeometryFilter(lyr, arcs) {
|
|
31595
31967
|
var shapes = lyr.shapes;
|
|
31596
31968
|
if (lyr.geometry_type == 'polygon') {
|
|
@@ -31896,7 +32268,6 @@ ${svg}
|
|
|
31896
32268
|
return joinTableToLayer(targetLyr, polygonLyr.data, joinFunction, opts);
|
|
31897
32269
|
}
|
|
31898
32270
|
|
|
31899
|
-
|
|
31900
32271
|
function prepJoinLayers(targetLyr, srcLyr) {
|
|
31901
32272
|
if (!targetLyr.data) {
|
|
31902
32273
|
// create an empty data table if target layer is missing attributes
|
|
@@ -33647,7 +34018,7 @@ ${svg}
|
|
|
33647
34018
|
|
|
33648
34019
|
function createMeridianPart(x, ymin, ymax) {
|
|
33649
34020
|
var coords = densifyPathByInterval([[x, ymin], [x, ymax]], precision);
|
|
33650
|
-
meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord(x)}));
|
|
34021
|
+
meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord$1(x)}));
|
|
33651
34022
|
}
|
|
33652
34023
|
|
|
33653
34024
|
function createParallel(y) {
|
|
@@ -33657,7 +34028,7 @@ ${svg}
|
|
|
33657
34028
|
}
|
|
33658
34029
|
|
|
33659
34030
|
// remove tiny offsets
|
|
33660
|
-
function roundCoord(x) {
|
|
34031
|
+
function roundCoord$1(x) {
|
|
33661
34032
|
return +x.toFixed(3) || 0;
|
|
33662
34033
|
}
|
|
33663
34034
|
|
|
@@ -33864,10 +34235,6 @@ ${svg}
|
|
|
33864
34235
|
|
|
33865
34236
|
|
|
33866
34237
|
|
|
33867
|
-
function formatNumber(val) {
|
|
33868
|
-
return val + '';
|
|
33869
|
-
}
|
|
33870
|
-
|
|
33871
34238
|
function maxChars(arr) {
|
|
33872
34239
|
return arr.reduce(function(memo, str) {
|
|
33873
34240
|
var w = stringDisplayWidth(str);
|
|
@@ -33891,14 +34258,14 @@ ${svg}
|
|
|
33891
34258
|
}
|
|
33892
34259
|
|
|
33893
34260
|
function countIntegralChars(val) {
|
|
33894
|
-
return utils.isNumber(val) ? (formatNumber(val) + '.').indexOf('.') : 0;
|
|
34261
|
+
return utils.isNumber(val) ? (utils.formatNumber(val) + '.').indexOf('.') : 0;
|
|
33895
34262
|
}
|
|
33896
34263
|
|
|
33897
34264
|
function formatTableValue(val, integralChars) {
|
|
33898
34265
|
var str;
|
|
33899
34266
|
if (utils.isNumber(val)) {
|
|
33900
34267
|
str = utils.lpad("", integralChars - countIntegralChars(val), ' ') +
|
|
33901
|
-
formatNumber(val);
|
|
34268
|
+
utils.formatNumber(val);
|
|
33902
34269
|
} else if (utils.isString(val)) {
|
|
33903
34270
|
str = formatString(val);
|
|
33904
34271
|
} else if (utils.isDate(val)) {
|
|
@@ -34702,6 +35069,38 @@ ${svg}
|
|
|
34702
35069
|
}
|
|
34703
35070
|
}
|
|
34704
35071
|
|
|
35072
|
+
function pointsFromPolylinesForJoin(lyr, dataset) {
|
|
35073
|
+
var shapes = lyr.shapes.map(function(shp) {
|
|
35074
|
+
return polylineToMidpoints(shp, dataset.arcs);
|
|
35075
|
+
});
|
|
35076
|
+
return {
|
|
35077
|
+
geometry_type: 'point',
|
|
35078
|
+
shapes: shapes,
|
|
35079
|
+
data: lyr.data // TODO copy if needed
|
|
35080
|
+
};
|
|
35081
|
+
}
|
|
35082
|
+
|
|
35083
|
+
function validateOpts(opts) {
|
|
35084
|
+
if (!opts.point_method) {
|
|
35085
|
+
stop('The "point-method" flag is required for polyline-polygon joins');
|
|
35086
|
+
}
|
|
35087
|
+
}
|
|
35088
|
+
|
|
35089
|
+
function joinPolylinesToPolygons(targetLyr, targetDataset, source, opts) {
|
|
35090
|
+
validateOpts(opts);
|
|
35091
|
+
var pointLyr = pointsFromPolylinesForJoin(source.layer, source.dataset);
|
|
35092
|
+
var retn = joinPointsToPolygons(targetLyr, targetDataset.arcs, pointLyr, opts);
|
|
35093
|
+
return retn;
|
|
35094
|
+
}
|
|
35095
|
+
|
|
35096
|
+
function joinPolygonsToPolylines(targetLyr, targetDataset, source, opts) {
|
|
35097
|
+
validateOpts(opts);
|
|
35098
|
+
var pointLyr = pointsFromPolylinesForJoin(targetLyr, targetDataset);
|
|
35099
|
+
var retn = joinPolygonsToPoints(pointLyr, source.layer, source.dataset.arcs, opts);
|
|
35100
|
+
targetLyr.data = pointLyr.data;
|
|
35101
|
+
return retn;
|
|
35102
|
+
}
|
|
35103
|
+
|
|
34705
35104
|
class TinyQueue {
|
|
34706
35105
|
constructor(data = [], compare = defaultCompare) {
|
|
34707
35106
|
this.data = data;
|
|
@@ -35025,7 +35424,7 @@ ${svg}
|
|
|
35025
35424
|
|
|
35026
35425
|
cmd.join = function(targetLyr, targetDataset, src, opts) {
|
|
35027
35426
|
var srcType, targetType, retn;
|
|
35028
|
-
if (!src || !src.
|
|
35427
|
+
if (!src || !src.dataset) {
|
|
35029
35428
|
stop("Missing a joinable data source");
|
|
35030
35429
|
}
|
|
35031
35430
|
if (opts.keys) {
|
|
@@ -35033,9 +35432,18 @@ ${svg}
|
|
|
35033
35432
|
if (opts.keys.length != 2) {
|
|
35034
35433
|
stop("Expected two key fields: a target field and a source field");
|
|
35035
35434
|
}
|
|
35435
|
+
if (!src.layer.data) {
|
|
35436
|
+
stop("Source layer is missing attribute data");
|
|
35437
|
+
}
|
|
35036
35438
|
retn = joinAttributesToFeatures(targetLyr, src.layer.data, opts);
|
|
35037
35439
|
} else {
|
|
35038
35440
|
// spatial join
|
|
35441
|
+
if (!src.layer.data) {
|
|
35442
|
+
// KLUDGE -- users might want to join a layer without attributes
|
|
35443
|
+
// to test for intersection... the simplest way to support this is
|
|
35444
|
+
// to add an empty data table to the source layer
|
|
35445
|
+
initDataTable(src.layer);
|
|
35446
|
+
}
|
|
35039
35447
|
requireDatasetsHaveCompatibleCRS([targetDataset, src.dataset]);
|
|
35040
35448
|
srcType = src.layer.geometry_type;
|
|
35041
35449
|
targetType = targetLyr.geometry_type;
|
|
@@ -35047,6 +35455,10 @@ ${svg}
|
|
|
35047
35455
|
retn = joinPointsToPoints(targetLyr, src.layer, getDatasetCRS(targetDataset), opts);
|
|
35048
35456
|
} else if (srcType == 'polygon' && targetType == 'polygon') {
|
|
35049
35457
|
retn = joinPolygonsToPolygons(targetLyr, targetDataset, src, opts);
|
|
35458
|
+
} else if (srcType == 'polyline' && targetType == 'polygon') {
|
|
35459
|
+
retn = joinPolylinesToPolygons(targetLyr, targetDataset, src, opts);
|
|
35460
|
+
} else if (srcType == 'polygon' && targetType == 'polyline') {
|
|
35461
|
+
retn = joinPolygonsToPolylines(targetLyr, targetDataset, src, opts);
|
|
35050
35462
|
} else {
|
|
35051
35463
|
stop(utils.format("Unable to join %s geometry to %s geometry",
|
|
35052
35464
|
srcType || 'null', targetType || 'null'));
|
|
@@ -36267,6 +36679,99 @@ ${svg}
|
|
|
36267
36679
|
parseScalebarLabelToKm: parseScalebarLabelToKm
|
|
36268
36680
|
});
|
|
36269
36681
|
|
|
36682
|
+
cmd.shape = function(targetDataset, opts) {
|
|
36683
|
+
var geojson, dataset;
|
|
36684
|
+
if (opts.coordinates) {
|
|
36685
|
+
geojson = makeShapeFromCoords(opts);
|
|
36686
|
+
} else if (opts.type == 'circle') {
|
|
36687
|
+
geojson = makeCircle(opts);
|
|
36688
|
+
} else if (opts.type == 'rectangle' && opts.bbox) {
|
|
36689
|
+
geojson = getRectangleGeoJSON(opts);
|
|
36690
|
+
} else {
|
|
36691
|
+
stop('Missing coordinates parameter');
|
|
36692
|
+
}
|
|
36693
|
+
// TODO: project shape if targetDataset is projected
|
|
36694
|
+
dataset = importGeoJSON(geojson, {});
|
|
36695
|
+
if (opts.rotation) {
|
|
36696
|
+
rotateDatasetCoords(dataset, opts.rotation);
|
|
36697
|
+
}
|
|
36698
|
+
dataset.layers[0].name = opts.name || opts.type || 'shape';
|
|
36699
|
+
return dataset;
|
|
36700
|
+
};
|
|
36701
|
+
|
|
36702
|
+
function getRectangleGeoJSON(opts) {
|
|
36703
|
+
var bbox = opts.bbox,
|
|
36704
|
+
xmin = bbox[0],
|
|
36705
|
+
ymin = bbox[1],
|
|
36706
|
+
xmax = bbox[2],
|
|
36707
|
+
ymax = bbox[3],
|
|
36708
|
+
interval = 0.5,
|
|
36709
|
+
coords = [],
|
|
36710
|
+
type = opts.geometry == 'polyline' ? 'LineString' : 'Polygon';
|
|
36711
|
+
addSide(xmin, ymin, xmin, ymax);
|
|
36712
|
+
addSide(xmin, ymax, xmax, ymax);
|
|
36713
|
+
addSide(xmax, ymax, xmax, ymin);
|
|
36714
|
+
addSide(xmax, ymin, xmin, ymin);
|
|
36715
|
+
coords.push([xmin, ymin]);
|
|
36716
|
+
return {
|
|
36717
|
+
type: type,
|
|
36718
|
+
coordinates: type == 'Polygon' ? [coords] : coords
|
|
36719
|
+
};
|
|
36720
|
+
|
|
36721
|
+
function addSide(x1, y1, x2, y2) {
|
|
36722
|
+
var dx = x2 - x1,
|
|
36723
|
+
dy = y2 - y1,
|
|
36724
|
+
n = Math.ceil(Math.max(Math.abs(dx) / interval, Math.abs(dy) / interval)),
|
|
36725
|
+
xint = dx / n,
|
|
36726
|
+
yint = dy / n;
|
|
36727
|
+
for (var i=0; i<n; i++) {
|
|
36728
|
+
coords.push([x1 + i * xint, y1 + i * yint]);
|
|
36729
|
+
}
|
|
36730
|
+
}
|
|
36731
|
+
}
|
|
36732
|
+
|
|
36733
|
+
function makeCircle(opts) {
|
|
36734
|
+
if (opts.radius > 0 === false && opts.radius_angle > 0 === false) {
|
|
36735
|
+
stop('Missing required radius parameter.');
|
|
36736
|
+
}
|
|
36737
|
+
var cp = opts.center || [0, 0];
|
|
36738
|
+
var radius = opts.radius || getCircleRadiusFromAngle(getCRS('wgs84'), opts.radius_angle);
|
|
36739
|
+
return getCircleGeoJSON(cp, radius, null, {geometry_type : opts.geometry || 'polygon'});
|
|
36740
|
+
}
|
|
36741
|
+
|
|
36742
|
+
function makeShapeFromCoords(opts) {
|
|
36743
|
+
var coordinates = [];
|
|
36744
|
+
var offsets = opts.offsets || [];
|
|
36745
|
+
var coords = opts.coordinates;
|
|
36746
|
+
var type, i, x, y;
|
|
36747
|
+
if (coords.length >= 2 === false) {
|
|
36748
|
+
stop('Invalid coordinates parameter.');
|
|
36749
|
+
}
|
|
36750
|
+
for (i=0; i<coords.length; i+= 2) {
|
|
36751
|
+
x = coords[i];
|
|
36752
|
+
y = coords[i + 1];
|
|
36753
|
+
coordinates.push([x, y]);
|
|
36754
|
+
}
|
|
36755
|
+
for (i=0; i<offsets.length; i+=2) {
|
|
36756
|
+
x += offsets[i];
|
|
36757
|
+
y += offsets[i + 1];
|
|
36758
|
+
coordinates.push([x, y]);
|
|
36759
|
+
}
|
|
36760
|
+
if (GeoJSON.pathIsRing(coordinates)) {
|
|
36761
|
+
type = 'Polygon';
|
|
36762
|
+
} else if (opts.closed && coordinates.length >= 3) {
|
|
36763
|
+
type = 'Polygon';
|
|
36764
|
+
coordinates.push(coordinates[0]);
|
|
36765
|
+
} else {
|
|
36766
|
+
type = 'LineString';
|
|
36767
|
+
}
|
|
36768
|
+
return {
|
|
36769
|
+
type: type,
|
|
36770
|
+
coordinates: type == 'Polygon' ? [coordinates] : coordinates
|
|
36771
|
+
};
|
|
36772
|
+
|
|
36773
|
+
}
|
|
36774
|
+
|
|
36270
36775
|
function calcSimplifyStats(arcs, use3D) {
|
|
36271
36776
|
var distSq = use3D ? pointSegGeoDistSq : geom.pointSegDistSq,
|
|
36272
36777
|
calcAngle = use3D ? geom.signedAngleSph : geom.signedAngle,
|
|
@@ -37225,13 +37730,20 @@ ${svg}
|
|
|
37225
37730
|
|
|
37226
37731
|
// @expression: optional field name or expression
|
|
37227
37732
|
//
|
|
37228
|
-
cmd.splitLayer = function(src, expression,
|
|
37229
|
-
var
|
|
37733
|
+
cmd.splitLayer = function(src, expression, optsArg) {
|
|
37734
|
+
var opts = optsArg || {},
|
|
37735
|
+
lyr0 = opts.no_replace ? copyLayer(src) : src,
|
|
37230
37736
|
properties = lyr0.data ? lyr0.data.getRecords() : null,
|
|
37231
37737
|
shapes = lyr0.shapes,
|
|
37232
37738
|
index = {},
|
|
37233
37739
|
splitLayers = [],
|
|
37234
|
-
namer
|
|
37740
|
+
namer;
|
|
37741
|
+
|
|
37742
|
+
if (opts.ids) {
|
|
37743
|
+
namer = getIdSplitFunction(opts.ids);
|
|
37744
|
+
} else {
|
|
37745
|
+
namer = getSplitNameFunction(lyr0, expression);
|
|
37746
|
+
}
|
|
37235
37747
|
|
|
37236
37748
|
// if (splitField) {
|
|
37237
37749
|
// internal.requireDataField(lyr0, splitField);
|
|
@@ -37263,15 +37775,24 @@ ${svg}
|
|
|
37263
37775
|
return splitLayers;
|
|
37264
37776
|
};
|
|
37265
37777
|
|
|
37778
|
+
function getIdSplitFunction(ids) {
|
|
37779
|
+
var set = new Set(ids);
|
|
37780
|
+
return function(i) {
|
|
37781
|
+
return set.has(i) ? '1' : '2';
|
|
37782
|
+
};
|
|
37783
|
+
}
|
|
37784
|
+
|
|
37785
|
+
function getDefaultSplitFunction(lyr) {
|
|
37786
|
+
// if not splitting on an expression and layer is unnamed, name split-apart layers
|
|
37787
|
+
// like: split-1, split-2, ...
|
|
37788
|
+
return function(i) {
|
|
37789
|
+
return (lyr && lyr.name || 'split') + '-' + (i + 1);
|
|
37790
|
+
};
|
|
37791
|
+
}
|
|
37792
|
+
|
|
37266
37793
|
function getSplitNameFunction(lyr, exp) {
|
|
37267
37794
|
var compiled;
|
|
37268
|
-
if (!exp)
|
|
37269
|
-
// if not splitting on an expression and layer is unnamed, name split-apart layers
|
|
37270
|
-
// like: split-1, split-2, ...
|
|
37271
|
-
return function(i) {
|
|
37272
|
-
return (lyr && lyr.name || 'split') + '-' + (i + 1);
|
|
37273
|
-
};
|
|
37274
|
-
}
|
|
37795
|
+
if (!exp) return getDefaultSplitFunction(lyr);
|
|
37275
37796
|
lyr = {name: lyr.name, data: lyr.data}; // remove shape info
|
|
37276
37797
|
compiled = compileValueExpression(exp, lyr, null);
|
|
37277
37798
|
return function(i) {
|
|
@@ -37310,111 +37831,6 @@ ${svg}
|
|
|
37310
37831
|
getSplitNameFunction: getSplitNameFunction
|
|
37311
37832
|
});
|
|
37312
37833
|
|
|
37313
|
-
cmd.splitLines = function(lyr, dataset, opts) {
|
|
37314
|
-
var crs = getDatasetCRS(dataset);
|
|
37315
|
-
requirePolylineLayer(lyr);
|
|
37316
|
-
var splitFeature = getSplitFeatureFunction(crs, opts);
|
|
37317
|
-
|
|
37318
|
-
// TODO: remove duplication with mapshaper-each.js
|
|
37319
|
-
var editor = getFeatureEditor(lyr, dataset);
|
|
37320
|
-
var exprOpts = {
|
|
37321
|
-
geojson_editor: editor,
|
|
37322
|
-
context: {splitFeature}
|
|
37323
|
-
};
|
|
37324
|
-
var exp = `this.geojson = splitFeature(this.geojson)`;
|
|
37325
|
-
|
|
37326
|
-
var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, exprOpts);
|
|
37327
|
-
var n = getFeatureCount(lyr);
|
|
37328
|
-
var filter;
|
|
37329
|
-
if (opts && opts.where) {
|
|
37330
|
-
filter = compileValueExpression(opts.where, lyr, dataset.arcs);
|
|
37331
|
-
}
|
|
37332
|
-
for (var i=0; i<n; i++) {
|
|
37333
|
-
if (!filter || filter(i)) {
|
|
37334
|
-
compiled(i);
|
|
37335
|
-
}
|
|
37336
|
-
}
|
|
37337
|
-
replaceLayerContents(lyr, dataset, editor.done());
|
|
37338
|
-
};
|
|
37339
|
-
|
|
37340
|
-
function getSplitFeatureFunction(crs, opts) {
|
|
37341
|
-
var dashLen = opts.dash_length ? convertDistanceParam(opts.dash_length, crs) : 0;
|
|
37342
|
-
var gapLen = opts.gap_length ? convertDistanceParam(opts.gap_length, crs) : 0;
|
|
37343
|
-
if (dashLen > 0 === false) {
|
|
37344
|
-
stop('Missing required segment-length parameter');
|
|
37345
|
-
}
|
|
37346
|
-
if (gapLen >= 0 == false) {
|
|
37347
|
-
stop('Invalid gap-length option');
|
|
37348
|
-
}
|
|
37349
|
-
var splitLine = getSplitLineFunction(crs, dashLen, gapLen, !!opts.planar);
|
|
37350
|
-
return function(feat) {
|
|
37351
|
-
var geom = feat.geometry;
|
|
37352
|
-
if (!geom) return feat;
|
|
37353
|
-
if (geom.type == 'LineString') {
|
|
37354
|
-
geom.type = 'MultiLineString';
|
|
37355
|
-
geom.coordinates = [geom.coordinates];
|
|
37356
|
-
}
|
|
37357
|
-
if (geom.type != 'MultiLineString') {
|
|
37358
|
-
error('Unexpected geometry:', geom.type);
|
|
37359
|
-
}
|
|
37360
|
-
geom.coordinates = geom.coordinates.reduce(function(memo, coords) {
|
|
37361
|
-
try {
|
|
37362
|
-
var parts = splitLine(coords);
|
|
37363
|
-
memo = memo.concat(parts);
|
|
37364
|
-
} catch(e) {
|
|
37365
|
-
console.error(e);
|
|
37366
|
-
throw e;
|
|
37367
|
-
}
|
|
37368
|
-
return memo;
|
|
37369
|
-
}, []);
|
|
37370
|
-
|
|
37371
|
-
return feat;
|
|
37372
|
-
};
|
|
37373
|
-
}
|
|
37374
|
-
|
|
37375
|
-
function getSplitLineFunction(crs, dashLen, gapLen, planar) {
|
|
37376
|
-
var interpolate = getInterpolationFunction(planar ? null : crs);
|
|
37377
|
-
var distance = isLatLngCRS(crs) ? greatCircleDistance : distance2D;
|
|
37378
|
-
var inDash, parts2, interval;
|
|
37379
|
-
function addPart(coords) {
|
|
37380
|
-
if (inDash) parts2.push(coords);
|
|
37381
|
-
if (gapLen > 0) {
|
|
37382
|
-
inDash = !inDash;
|
|
37383
|
-
interval = inDash ? dashLen : gapLen;
|
|
37384
|
-
}
|
|
37385
|
-
}
|
|
37386
|
-
return function splitLineString(coords) {
|
|
37387
|
-
var elapsedDist = 0;
|
|
37388
|
-
var p = coords[0];
|
|
37389
|
-
var coords2 = [p];
|
|
37390
|
-
var segLen, k, prev;
|
|
37391
|
-
// init this LineString
|
|
37392
|
-
inDash = true;
|
|
37393
|
-
parts2 = [];
|
|
37394
|
-
interval = gapLen;
|
|
37395
|
-
for (var i=1, n=coords.length; i<n; i++) {
|
|
37396
|
-
prev = p;
|
|
37397
|
-
p = coords[i];
|
|
37398
|
-
segLen = distance(prev[0], prev[1], p[0], p[1]);
|
|
37399
|
-
while (elapsedDist + segLen >= interval) {
|
|
37400
|
-
k = (interval - elapsedDist) / segLen;
|
|
37401
|
-
prev = interpolate(prev[0], prev[1], p[0], p[1], k);
|
|
37402
|
-
elapsedDist = 0;
|
|
37403
|
-
coords2.push(prev);
|
|
37404
|
-
addPart(coords2);
|
|
37405
|
-
coords2 = [prev];
|
|
37406
|
-
segLen = distance(prev[0], prev[1], p[0], p[1]);
|
|
37407
|
-
}
|
|
37408
|
-
coords2.push(p);
|
|
37409
|
-
elapsedDist += segLen;
|
|
37410
|
-
}
|
|
37411
|
-
if (elapsedDist > 0 && coords2.length > 1) {
|
|
37412
|
-
addPart(coords2);
|
|
37413
|
-
}
|
|
37414
|
-
return parts2;
|
|
37415
|
-
};
|
|
37416
|
-
}
|
|
37417
|
-
|
|
37418
37834
|
cmd.svgStyle = function(lyr, dataset, opts) {
|
|
37419
37835
|
var filter;
|
|
37420
37836
|
if (!lyr.data) {
|
|
@@ -37443,56 +37859,83 @@ ${svg}
|
|
|
37443
37859
|
});
|
|
37444
37860
|
};
|
|
37445
37861
|
|
|
37446
|
-
|
|
37447
|
-
var len = 'length' in d ? d.length : 10;
|
|
37448
|
-
var filled = 'fill' in d;
|
|
37449
|
-
return filled ? getFilledArrow(d, len) : getStickArrow(d, len);
|
|
37450
|
-
};
|
|
37862
|
+
var roundCoord = getRoundingFunction(0.01);
|
|
37451
37863
|
|
|
37452
|
-
function
|
|
37453
|
-
|
|
37454
|
-
|
|
37455
|
-
|
|
37456
|
-
|
|
37457
|
-
|
|
37458
|
-
}
|
|
37864
|
+
function forEachSymbolCoord(coords, cb) {
|
|
37865
|
+
var isPoint = coords && utils.isNumber(coords[0]);
|
|
37866
|
+
var isNested = !isPoint && coords && Array.isArray(coords[0]);
|
|
37867
|
+
if (isPoint) return cb(coords);
|
|
37868
|
+
for (var i=0; i<coords.length; i++) {
|
|
37869
|
+
if (isNested) forEachSymbolCoord(coords[i], cb);
|
|
37870
|
+
}
|
|
37459
37871
|
}
|
|
37460
37872
|
|
|
37461
|
-
function
|
|
37462
|
-
|
|
37463
|
-
|
|
37464
|
-
|
|
37465
|
-
fill: d.fill || 'magenta'
|
|
37466
|
-
};
|
|
37873
|
+
function flipY(coords) {
|
|
37874
|
+
forEachSymbolCoord(coords, function(p) {
|
|
37875
|
+
p[1] = -p[1];
|
|
37876
|
+
});
|
|
37467
37877
|
}
|
|
37468
37878
|
|
|
37469
|
-
function
|
|
37470
|
-
|
|
37471
|
-
|
|
37472
|
-
|
|
37473
|
-
|
|
37474
|
-
}
|
|
37475
|
-
return 1;
|
|
37879
|
+
function scaleAndShiftCoords(coords, scale, shift) {
|
|
37880
|
+
forEachSymbolCoord(coords, function(xy) {
|
|
37881
|
+
xy[0] = xy[0] * scale + shift[0];
|
|
37882
|
+
xy[1] = xy[1] * scale + shift[1];
|
|
37883
|
+
});
|
|
37476
37884
|
}
|
|
37477
37885
|
|
|
37478
|
-
function
|
|
37479
|
-
|
|
37480
|
-
|
|
37481
|
-
|
|
37482
|
-
|
|
37483
|
-
return [dx, dy];
|
|
37886
|
+
function roundCoordsForSVG(coords) {
|
|
37887
|
+
forEachSymbolCoord(coords, function(p) {
|
|
37888
|
+
p[0] = roundCoord(p[0]);
|
|
37889
|
+
p[1] = roundCoord(p[1]);
|
|
37890
|
+
});
|
|
37484
37891
|
}
|
|
37485
37892
|
|
|
37486
|
-
function
|
|
37487
|
-
|
|
37893
|
+
function rotateCoords(coords, rotation) {
|
|
37894
|
+
if (!rotation) return;
|
|
37895
|
+
var f = getAffineTransform(rotation, 1, [0, 0], [0, 0]);
|
|
37896
|
+
forEachSymbolCoord(coords, function(p) {
|
|
37897
|
+
var p2 = f(p[0], p[1]);
|
|
37898
|
+
p[0] = p2[0];
|
|
37899
|
+
p[1] = p2[1];
|
|
37900
|
+
});
|
|
37901
|
+
}
|
|
37902
|
+
|
|
37903
|
+
function findArcCenter(p1, p2, degrees) {
|
|
37904
|
+
var p3 = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2], // midpoint betw. p1, p2
|
|
37905
|
+
tan = 1 / Math.tan(degrees / 180 * Math.PI / 2),
|
|
37906
|
+
cp = getAffineTransform(90, tan, [0, 0], p3)(p2[0], p2[1]);
|
|
37907
|
+
return cp;
|
|
37908
|
+
}
|
|
37909
|
+
|
|
37910
|
+
// export function addBezierArcControlPoints(p1, p2, degrees) {
|
|
37911
|
+
function addBezierArcControlPoints(points, degrees) {
|
|
37912
|
+
// source: https://stackoverflow.com/questions/734076/how-to-best-approximate-a-geometrical-arc-with-a-bezier-curve
|
|
37913
|
+
var p2 = points.pop(),
|
|
37914
|
+
p1 = points.pop(),
|
|
37915
|
+
cp = findArcCenter(p1, p2, degrees),
|
|
37916
|
+
xc = cp[0],
|
|
37917
|
+
yc = cp[1],
|
|
37918
|
+
ax = p1[0] - xc,
|
|
37919
|
+
ay = p1[1] - yc,
|
|
37920
|
+
bx = p2[0] - xc,
|
|
37921
|
+
by = p2[1] - yc,
|
|
37922
|
+
q1 = ax * ax + ay * ay,
|
|
37923
|
+
q2 = q1 + ax * bx + ay * by,
|
|
37924
|
+
k2 = 4/3 * (Math.sqrt(2 * q1 * q2) - q2) / (ax * by - ay * bx);
|
|
37925
|
+
|
|
37926
|
+
points.push(p1);
|
|
37927
|
+
points.push([xc + ax - k2 * ay, yc + ay + k2 * ax, 'C']);
|
|
37928
|
+
points.push([xc + bx + k2 * by, yc + by - k2 * bx, 'C']);
|
|
37929
|
+
points.push(p2);
|
|
37488
37930
|
}
|
|
37489
37931
|
|
|
37490
37932
|
function getStickArrowCoords(d, totalLen) {
|
|
37933
|
+
var minStemRatio = getMinStemRatio(d);
|
|
37491
37934
|
var headAngle = d['arrow-head-angle'] || 90;
|
|
37492
37935
|
var curve = d['arrow-stem-curve'] || 0;
|
|
37493
37936
|
var unscaledHeadWidth = d['arrow-head-width'] || 9;
|
|
37494
37937
|
var unscaledHeadLen = getHeadLength(unscaledHeadWidth, headAngle);
|
|
37495
|
-
var scale = getScale(totalLen, unscaledHeadLen);
|
|
37938
|
+
var scale = getScale(totalLen, unscaledHeadLen, minStemRatio);
|
|
37496
37939
|
var headWidth = unscaledHeadWidth * scale;
|
|
37497
37940
|
var headLen = unscaledHeadLen * scale;
|
|
37498
37941
|
var tip = getStickArrowTip(totalLen, curve);
|
|
@@ -37503,84 +37946,268 @@ ${svg}
|
|
|
37503
37946
|
if (!headLen) return [stem];
|
|
37504
37947
|
var head = [addPoints([-headWidth / 2, -headLen], tip), tip.concat(), addPoints([headWidth / 2, -headLen], tip)];
|
|
37505
37948
|
|
|
37506
|
-
|
|
37507
|
-
|
|
37949
|
+
rotateCoords(stem, d.rotation);
|
|
37950
|
+
rotateCoords(head, d.rotation);
|
|
37508
37951
|
return [stem, head];
|
|
37509
37952
|
}
|
|
37510
37953
|
|
|
37511
|
-
function
|
|
37512
|
-
|
|
37513
|
-
return headWidth * headRatio;
|
|
37954
|
+
function getMinStemRatio(d) {
|
|
37955
|
+
return d['arrow-min-stem'] >= 0 ? d['arrow-min-stem'] : 0.4;
|
|
37514
37956
|
}
|
|
37515
37957
|
|
|
37516
|
-
function getFilledArrowCoords(
|
|
37517
|
-
var
|
|
37958
|
+
function getFilledArrowCoords(totalLen, d) {
|
|
37959
|
+
var minStemRatio = getMinStemRatio(d),
|
|
37960
|
+
headAngle = d['arrow-head-angle'] || 40,
|
|
37961
|
+
direction = d.rotation || d['arrow-direction'] || 0,
|
|
37518
37962
|
unscaledStemWidth = d['arrow-stem-width'] || 2,
|
|
37519
37963
|
unscaledHeadWidth = d['arrow-head-width'] || unscaledStemWidth * 3,
|
|
37520
37964
|
unscaledHeadLen = getHeadLength(unscaledHeadWidth, headAngle),
|
|
37521
|
-
scale = getScale(totalLen, unscaledHeadLen),
|
|
37965
|
+
scale = getScale(totalLen, unscaledHeadLen, minStemRatio),
|
|
37522
37966
|
headWidth = unscaledHeadWidth * scale,
|
|
37523
37967
|
headLen = unscaledHeadLen * scale,
|
|
37524
37968
|
stemWidth = unscaledStemWidth * scale,
|
|
37525
37969
|
stemTaper = d['arrow-stem-taper'] || 0,
|
|
37970
|
+
stemCurve = d['arrow-stem-curve'] || 0,
|
|
37526
37971
|
stemLen = totalLen - headLen;
|
|
37527
37972
|
|
|
37528
37973
|
var headDx = headWidth / 2,
|
|
37529
37974
|
stemDx = stemWidth / 2,
|
|
37530
37975
|
baseDx = stemDx * (1 - stemTaper);
|
|
37531
37976
|
|
|
37532
|
-
var coords
|
|
37977
|
+
var coords;
|
|
37978
|
+
|
|
37979
|
+
if (!stemCurve || Math.abs(stemCurve) > 90) {
|
|
37980
|
+
coords = [[baseDx, 0], [stemDx, stemLen], [headDx, stemLen], [0, stemLen + headLen],
|
|
37533
37981
|
[-headDx, stemLen], [-stemDx, stemLen], [-baseDx, 0], [baseDx, 0]];
|
|
37982
|
+
} else {
|
|
37983
|
+
if (direction > 0) stemCurve = -stemCurve;
|
|
37984
|
+
coords = getCurvedArrowCoords(stemLen, headLen, stemCurve, stemDx, headDx, baseDx);
|
|
37985
|
+
}
|
|
37534
37986
|
|
|
37535
|
-
|
|
37987
|
+
rotateCoords(coords, direction);
|
|
37536
37988
|
return [coords];
|
|
37537
37989
|
}
|
|
37538
37990
|
|
|
37539
|
-
|
|
37540
|
-
|
|
37541
|
-
var
|
|
37542
|
-
|
|
37543
|
-
|
|
37544
|
-
|
|
37545
|
-
|
|
37546
|
-
|
|
37991
|
+
|
|
37992
|
+
function getScale(totalLen, headLen, minStemRatio) {
|
|
37993
|
+
var maxHeadPct = 1 - minStemRatio;
|
|
37994
|
+
var headPct = headLen / totalLen;
|
|
37995
|
+
if (headPct > maxHeadPct) {
|
|
37996
|
+
return maxHeadPct / headPct;
|
|
37997
|
+
}
|
|
37998
|
+
return 1;
|
|
37547
37999
|
}
|
|
37548
38000
|
|
|
37549
|
-
|
|
37550
|
-
|
|
37551
|
-
var
|
|
37552
|
-
|
|
37553
|
-
|
|
37554
|
-
|
|
37555
|
-
|
|
37556
|
-
|
|
38001
|
+
function getStickArrowTip(totalLen, curve) {
|
|
38002
|
+
// curve/2 intersects the arrowhead at 90deg (trigonometry)
|
|
38003
|
+
var theta = Math.abs(curve/2) / 180 * Math.PI;
|
|
38004
|
+
var dx = totalLen * Math.sin(theta) * (curve > 0 ? -1 : 1);
|
|
38005
|
+
var dy = totalLen * Math.cos(theta);
|
|
38006
|
+
return [dx, dy];
|
|
38007
|
+
}
|
|
38008
|
+
|
|
38009
|
+
function addPoints(a, b) {
|
|
38010
|
+
return [a[0] + b[0], a[1] + b[1]];
|
|
38011
|
+
}
|
|
38012
|
+
|
|
38013
|
+
|
|
38014
|
+
function getHeadLength(headWidth, headAngle) {
|
|
38015
|
+
var headRatio = 1 / Math.tan(Math.PI * headAngle / 180 / 2) / 2; // length-to-width head ratio
|
|
38016
|
+
return headWidth * headRatio;
|
|
38017
|
+
}
|
|
38018
|
+
|
|
38019
|
+
function getCurvedArrowCoords(stemLen, headLen, curvature, stemDx, headDx, baseDx) {
|
|
38020
|
+
// coordinates go counter clockwise, starting from the leftmost head coordinate
|
|
38021
|
+
var theta = Math.abs(curvature) / 180 * Math.PI;
|
|
38022
|
+
var sign = curvature > 0 ? 1 : -1;
|
|
38023
|
+
var dx = stemLen * Math.sin(theta / 2) * sign;
|
|
38024
|
+
var dy = stemLen * Math.cos(theta / 2);
|
|
38025
|
+
var head = [[stemDx + dx, dy], [headDx + dx, dy],
|
|
38026
|
+
[dx, headLen + dy], [-headDx + dx, dy], [-stemDx + dx, dy]];
|
|
38027
|
+
var ax = baseDx * Math.cos(theta); // rotate arrow base
|
|
38028
|
+
var ay = baseDx * Math.sin(theta) * -sign;
|
|
38029
|
+
var leftStem = getCurvedStemCoords(-ax, -ay, -stemDx + dx, dy, theta);
|
|
38030
|
+
var rightStem = getCurvedStemCoords(ax, ay, stemDx + dx, dy, theta);
|
|
38031
|
+
// if (stemTaper == 1) leftStem.pop();
|
|
38032
|
+
var stem = leftStem.concat(rightStem.reverse());
|
|
38033
|
+
stem.pop();
|
|
38034
|
+
return stem.concat(head);
|
|
38035
|
+
}
|
|
38036
|
+
|
|
38037
|
+
// ax, ay: point on the base
|
|
38038
|
+
// bx, by: point on the stem
|
|
38039
|
+
function getCurvedStemCoords(ax, ay, bx, by, theta0) {
|
|
38040
|
+
var dx = bx - ax,
|
|
38041
|
+
dy = by - ay,
|
|
38042
|
+
dy1 = (dy * dy - dx * dx) / (2 * dy),
|
|
38043
|
+
dy2 = dy - dy1,
|
|
38044
|
+
dx2 = Math.sqrt(dx * dx + dy * dy) / 2,
|
|
38045
|
+
theta = Math.PI - Math.asin(dx2 / dy2) * 2,
|
|
38046
|
+
degrees = theta * 180 / Math.PI,
|
|
38047
|
+
radius = dy2 / Math.tan(theta / 2),
|
|
38048
|
+
leftBend = bx > ax,
|
|
38049
|
+
sign = leftBend ? 1 : -1,
|
|
38050
|
+
points = Math.round(degrees / 5) + 2,
|
|
38051
|
+
// points = theta > 2 && 7 || theta > 1 && 6 || 5,
|
|
38052
|
+
increment = theta / (points + 1);
|
|
38053
|
+
|
|
38054
|
+
var coords = [[bx, by]];
|
|
38055
|
+
for (var i=1; i<= points; i++) {
|
|
38056
|
+
var phi = i * increment / 2;
|
|
38057
|
+
var sinPhi = Math.sin(phi);
|
|
38058
|
+
var cosPhi = Math.cos(phi);
|
|
38059
|
+
var c = sinPhi * radius * 2;
|
|
38060
|
+
var a = sinPhi * c;
|
|
38061
|
+
var b = cosPhi * c;
|
|
38062
|
+
coords.push([bx - a * sign, by - b]);
|
|
38063
|
+
}
|
|
38064
|
+
coords.push([ax, ay]);
|
|
38065
|
+
return coords;
|
|
38066
|
+
}
|
|
38067
|
+
|
|
38068
|
+
// sides: e.g. 5-pointed star has 10 sides
|
|
38069
|
+
// radius: distance from center to point
|
|
38070
|
+
//
|
|
38071
|
+
function getPolygonCoords(radius, opts) {
|
|
38072
|
+
var type = opts.type;
|
|
38073
|
+
var sides = +opts.sides || getDefaultSides(type);
|
|
38074
|
+
var isStar = type == 'star';
|
|
38075
|
+
if (isStar && (sides < 6 || sides % 2 !== 0)) {
|
|
38076
|
+
stop(`Invalid number of sides for a star (${sides})`);
|
|
38077
|
+
} else if (sides >= 3 === false) {
|
|
38078
|
+
stop(`Invalid number of sides (${sides})`);
|
|
37557
38079
|
}
|
|
37558
|
-
|
|
37559
|
-
|
|
37560
|
-
|
|
37561
|
-
|
|
37562
|
-
|
|
38080
|
+
var coords = [],
|
|
38081
|
+
angle = 360 / sides,
|
|
38082
|
+
b = isStar ? 1 : 0.5,
|
|
38083
|
+
theta, even, len;
|
|
38084
|
+
if (opts.orientation == 'b') {
|
|
38085
|
+
b = 0;
|
|
38086
|
+
}
|
|
38087
|
+
for (var i=0; i<sides; i++) {
|
|
38088
|
+
even = i % 2 == 0;
|
|
38089
|
+
len = radius;
|
|
38090
|
+
if (isStar && even) {
|
|
38091
|
+
len *= (opts.star_ratio || 0.5);
|
|
38092
|
+
}
|
|
38093
|
+
theta = (i + b) * angle % 360;
|
|
38094
|
+
coords.push(getPlanarSegmentEndpoint(0, 0, theta, len));
|
|
38095
|
+
}
|
|
38096
|
+
coords.push(coords[0].concat());
|
|
38097
|
+
return [coords];
|
|
38098
|
+
}
|
|
38099
|
+
|
|
38100
|
+
function getDefaultSides(type) {
|
|
38101
|
+
return {
|
|
38102
|
+
star: 10,
|
|
38103
|
+
circle: 72,
|
|
38104
|
+
triangle: 3,
|
|
38105
|
+
square: 4,
|
|
38106
|
+
pentagon: 5,
|
|
38107
|
+
hexagon: 6,
|
|
38108
|
+
heptagon: 7,
|
|
38109
|
+
octagon: 8,
|
|
38110
|
+
nonagon: 9,
|
|
38111
|
+
decagon: 10
|
|
38112
|
+
}[type] || 4;
|
|
38113
|
+
}
|
|
38114
|
+
|
|
38115
|
+
// TODO: refactor to remove duplication in mapshaper-svg-style.js
|
|
38116
|
+
cmd.symbols = function(inputLyr, dataset, opts) {
|
|
38117
|
+
requireSinglePointLayer(inputLyr);
|
|
38118
|
+
var lyr = opts.no_replace ? copyLayer(inputLyr) : inputLyr;
|
|
38119
|
+
var polygonMode = !!opts.polygons;
|
|
38120
|
+
var metersPerPx;
|
|
38121
|
+
if (polygonMode) {
|
|
38122
|
+
requireProjectedDataset(dataset);
|
|
38123
|
+
metersPerPx = opts.pixel_scale || getMetersPerPixel(lyr, dataset);
|
|
38124
|
+
}
|
|
38125
|
+
var records = getLayerDataTable(lyr).getRecords();
|
|
38126
|
+
var getSymbolData = getSymbolDataAccessor(lyr, opts);
|
|
38127
|
+
var geometries = lyr.shapes.map(function(shp, i) {
|
|
38128
|
+
if (!shp) return null;
|
|
38129
|
+
var d = getSymbolData(i);
|
|
38130
|
+
var rec = records[i] || {};
|
|
38131
|
+
var coords, size, constructor;
|
|
38132
|
+
if (d.type == 'arrow') {
|
|
38133
|
+
size = d.radius || d.length || d['arrow-length'] || d.r;
|
|
38134
|
+
constructor = getFilledArrowCoords;
|
|
37563
38135
|
} else {
|
|
37564
|
-
|
|
38136
|
+
size = d.radius || d.length || d.r;
|
|
38137
|
+
constructor = getPolygonCoords;
|
|
38138
|
+
}
|
|
38139
|
+
if (size > 0 === false) return null;
|
|
38140
|
+
coords = constructor(size, d, opts);
|
|
38141
|
+
rotateCoords(coords, +d.rotation || 0);
|
|
38142
|
+
if (!polygonMode) {
|
|
38143
|
+
flipY(coords);
|
|
38144
|
+
}
|
|
38145
|
+
if (+opts.scale) {
|
|
38146
|
+
scaleAndShiftCoords(coords, +opts.scale, [0, 0]);
|
|
38147
|
+
}
|
|
38148
|
+
if (polygonMode) {
|
|
38149
|
+
scaleAndShiftCoords(coords, metersPerPx, shp[0]);
|
|
38150
|
+
if (d.tfill) rec.fill = d.fill;
|
|
38151
|
+
return createGeometry(coords);
|
|
38152
|
+
} else {
|
|
38153
|
+
rec['svg-symbol'] = makeSvgSymbol(coords, d);
|
|
37565
38154
|
}
|
|
37566
38155
|
});
|
|
38156
|
+
|
|
38157
|
+
var outputLyr, dataset2;
|
|
38158
|
+
if (polygonMode) {
|
|
38159
|
+
dataset2 = importGeometries(geometries, records);
|
|
38160
|
+
outputLyr = mergeOutputLayerIntoDataset(inputLyr, dataset, dataset2, opts);
|
|
38161
|
+
outputLyr.data = lyr.data;
|
|
38162
|
+
} else {
|
|
38163
|
+
outputLyr = lyr;
|
|
38164
|
+
}
|
|
38165
|
+
return [outputLyr];
|
|
37567
38166
|
};
|
|
37568
38167
|
|
|
38168
|
+
function importGeometries(geometries, records) {
|
|
38169
|
+
var features = geometries.map(function(geom, i) {
|
|
38170
|
+
var d = records[i];
|
|
38171
|
+
return {
|
|
38172
|
+
type: 'Feature',
|
|
38173
|
+
properties: records[i] || null,
|
|
38174
|
+
geometry: geom
|
|
38175
|
+
};
|
|
38176
|
+
});
|
|
38177
|
+
var geojson = {
|
|
38178
|
+
type: 'FeatureCollection',
|
|
38179
|
+
features: features
|
|
38180
|
+
};
|
|
38181
|
+
return importGeoJSON(geojson);
|
|
38182
|
+
}
|
|
38183
|
+
|
|
38184
|
+
function createGeometry(coords) {
|
|
38185
|
+
return {
|
|
38186
|
+
type: 'Polygon',
|
|
38187
|
+
coordinates: coords
|
|
38188
|
+
};
|
|
38189
|
+
}
|
|
38190
|
+
|
|
38191
|
+
function getMetersPerPixel(lyr, dataset) {
|
|
38192
|
+
|
|
38193
|
+
// TODO: handle single point, no extent
|
|
38194
|
+
var bounds = getLayerBounds(lyr);
|
|
38195
|
+
return bounds.width() / 800;
|
|
38196
|
+
}
|
|
38197
|
+
|
|
37569
38198
|
// Returns an svg-symbol data object for one symbol
|
|
37570
|
-
function
|
|
37571
|
-
|
|
37572
|
-
|
|
37573
|
-
|
|
37574
|
-
|
|
37575
|
-
|
|
37576
|
-
|
|
37577
|
-
}
|
|
37578
|
-
return f(properties);
|
|
38199
|
+
function makeSvgSymbol(coords, properties) {
|
|
38200
|
+
roundCoordsForSVG(coords);
|
|
38201
|
+
return {
|
|
38202
|
+
type: 'polygon',
|
|
38203
|
+
coordinates: coords,
|
|
38204
|
+
fill: properties.fill || 'magenta'
|
|
38205
|
+
};
|
|
37579
38206
|
}
|
|
37580
38207
|
|
|
37581
38208
|
var Symbols = /*#__PURE__*/Object.freeze({
|
|
37582
38209
|
__proto__: null,
|
|
37583
|
-
|
|
38210
|
+
makeSvgSymbol: makeSvgSymbol
|
|
37584
38211
|
});
|
|
37585
38212
|
|
|
37586
38213
|
cmd.target = function(catalog, opts) {
|
|
@@ -37726,99 +38353,6 @@ ${svg}
|
|
|
37726
38353
|
}
|
|
37727
38354
|
};
|
|
37728
38355
|
|
|
37729
|
-
cmd.shape = function(targetDataset, opts) {
|
|
37730
|
-
var geojson, dataset;
|
|
37731
|
-
if (opts.coordinates) {
|
|
37732
|
-
geojson = makeShapeFromCoords(opts);
|
|
37733
|
-
} else if (opts.type == 'circle') {
|
|
37734
|
-
geojson = makeCircle(opts);
|
|
37735
|
-
} else if (opts.type == 'rectangle' && opts.bbox) {
|
|
37736
|
-
geojson = getRectangleGeoJSON(opts);
|
|
37737
|
-
} else {
|
|
37738
|
-
stop('Missing coordinates parameter');
|
|
37739
|
-
}
|
|
37740
|
-
// TODO: project shape if targetDataset is projected
|
|
37741
|
-
dataset = importGeoJSON(geojson, {});
|
|
37742
|
-
if (opts.rotation) {
|
|
37743
|
-
rotateDatasetCoords(dataset, opts.rotation);
|
|
37744
|
-
}
|
|
37745
|
-
dataset.layers[0].name = opts.name || opts.type || 'shape';
|
|
37746
|
-
return dataset;
|
|
37747
|
-
};
|
|
37748
|
-
|
|
37749
|
-
function getRectangleGeoJSON(opts) {
|
|
37750
|
-
var bbox = opts.bbox,
|
|
37751
|
-
xmin = bbox[0],
|
|
37752
|
-
ymin = bbox[1],
|
|
37753
|
-
xmax = bbox[2],
|
|
37754
|
-
ymax = bbox[3],
|
|
37755
|
-
interval = 0.5,
|
|
37756
|
-
coords = [],
|
|
37757
|
-
type = opts.geometry == 'polyline' ? 'LineString' : 'Polygon';
|
|
37758
|
-
addSide(xmin, ymin, xmin, ymax);
|
|
37759
|
-
addSide(xmin, ymax, xmax, ymax);
|
|
37760
|
-
addSide(xmax, ymax, xmax, ymin);
|
|
37761
|
-
addSide(xmax, ymin, xmin, ymin);
|
|
37762
|
-
coords.push([xmin, ymin]);
|
|
37763
|
-
return {
|
|
37764
|
-
type: type,
|
|
37765
|
-
coordinates: type == 'Polygon' ? [coords] : coords
|
|
37766
|
-
};
|
|
37767
|
-
|
|
37768
|
-
function addSide(x1, y1, x2, y2) {
|
|
37769
|
-
var dx = x2 - x1,
|
|
37770
|
-
dy = y2 - y1,
|
|
37771
|
-
n = Math.ceil(Math.max(Math.abs(dx) / interval, Math.abs(dy) / interval)),
|
|
37772
|
-
xint = dx / n,
|
|
37773
|
-
yint = dy / n;
|
|
37774
|
-
for (var i=0; i<n; i++) {
|
|
37775
|
-
coords.push([x1 + i * xint, y1 + i * yint]);
|
|
37776
|
-
}
|
|
37777
|
-
}
|
|
37778
|
-
}
|
|
37779
|
-
|
|
37780
|
-
function makeCircle(opts) {
|
|
37781
|
-
if (opts.radius > 0 === false && opts.radius_angle > 0 === false) {
|
|
37782
|
-
stop('Missing required radius parameter.');
|
|
37783
|
-
}
|
|
37784
|
-
var cp = opts.center || [0, 0];
|
|
37785
|
-
var radius = opts.radius || getCircleRadiusFromAngle(getCRS('wgs84'), opts.radius_angle);
|
|
37786
|
-
return getCircleGeoJSON(cp, radius, null, {geometry_type : opts.geometry || 'polygon'});
|
|
37787
|
-
}
|
|
37788
|
-
|
|
37789
|
-
function makeShapeFromCoords(opts) {
|
|
37790
|
-
var coordinates = [];
|
|
37791
|
-
var offsets = opts.offsets || [];
|
|
37792
|
-
var coords = opts.coordinates;
|
|
37793
|
-
var type, i, x, y;
|
|
37794
|
-
if (coords.length >= 2 === false) {
|
|
37795
|
-
stop('Invalid coordinates parameter.');
|
|
37796
|
-
}
|
|
37797
|
-
for (i=0; i<coords.length; i+= 2) {
|
|
37798
|
-
x = coords[i];
|
|
37799
|
-
y = coords[i + 1];
|
|
37800
|
-
coordinates.push([x, y]);
|
|
37801
|
-
}
|
|
37802
|
-
for (i=0; i<offsets.length; i+=2) {
|
|
37803
|
-
x += offsets[i];
|
|
37804
|
-
y += offsets[i + 1];
|
|
37805
|
-
coordinates.push([x, y]);
|
|
37806
|
-
}
|
|
37807
|
-
if (GeoJSON.pathIsRing(coordinates)) {
|
|
37808
|
-
type = 'Polygon';
|
|
37809
|
-
} else if (opts.closed && coordinates.length >= 3) {
|
|
37810
|
-
type = 'Polygon';
|
|
37811
|
-
coordinates.push(coordinates[0]);
|
|
37812
|
-
} else {
|
|
37813
|
-
type = 'LineString';
|
|
37814
|
-
}
|
|
37815
|
-
return {
|
|
37816
|
-
type: type,
|
|
37817
|
-
coordinates: type == 'Polygon' ? [coordinates] : coordinates
|
|
37818
|
-
};
|
|
37819
|
-
|
|
37820
|
-
}
|
|
37821
|
-
|
|
37822
38356
|
cmd.variableSimplify = function(layers, dataset, opts) {
|
|
37823
38357
|
var lyr = layers[0];
|
|
37824
38358
|
var arcs = dataset.arcs;
|
|
@@ -38176,6 +38710,9 @@ ${svg}
|
|
|
38176
38710
|
} else if (name == 'colorizer') {
|
|
38177
38711
|
outputLayers = cmd.colorizer(opts);
|
|
38178
38712
|
|
|
38713
|
+
} else if (name == 'dashlines') {
|
|
38714
|
+
applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
|
|
38715
|
+
|
|
38179
38716
|
} else if (name == 'define') {
|
|
38180
38717
|
cmd.define(opts);
|
|
38181
38718
|
|
|
@@ -38357,6 +38894,9 @@ ${svg}
|
|
|
38357
38894
|
} else if (name == 'shape') {
|
|
38358
38895
|
catalog.addDataset(cmd.shape(targetDataset, opts));
|
|
38359
38896
|
|
|
38897
|
+
} else if (name == 'shapes') {
|
|
38898
|
+
outputLayers = applyCommandToEachLayer(cmd.shapes, targetLayers, targetDataset, opts);
|
|
38899
|
+
|
|
38360
38900
|
} else if (name == 'simplify') {
|
|
38361
38901
|
if (opts.variable) {
|
|
38362
38902
|
cmd.variableSimplify(targetLayers, targetDataset, opts);
|
|
@@ -38376,9 +38916,6 @@ ${svg}
|
|
|
38376
38916
|
} else if (name == 'split') {
|
|
38377
38917
|
outputLayers = applyCommandToEachLayer(cmd.splitLayer, targetLayers, opts.expression, opts);
|
|
38378
38918
|
|
|
38379
|
-
} else if (name == 'split-lines') {
|
|
38380
|
-
applyCommandToEachLayer(cmd.splitLines, targetLayers, targetDataset, opts);
|
|
38381
|
-
|
|
38382
38919
|
} else if (name == 'split-on-grid') {
|
|
38383
38920
|
outputLayers = applyCommandToEachLayer(cmd.splitLayerOnGrid, targetLayers, arcs, opts);
|
|
38384
38921
|
|
|
@@ -38389,7 +38926,7 @@ ${svg}
|
|
|
38389
38926
|
applyCommandToEachLayer(cmd.svgStyle, targetLayers, targetDataset, opts);
|
|
38390
38927
|
|
|
38391
38928
|
} else if (name == 'symbols') {
|
|
38392
|
-
applyCommandToEachLayer(cmd.symbols, targetLayers, opts);
|
|
38929
|
+
outputLayers = applyCommandToEachLayer(cmd.symbols, targetLayers, targetDataset, opts);
|
|
38393
38930
|
|
|
38394
38931
|
} else if (name == 'subdivide') {
|
|
38395
38932
|
outputLayers = applyCommandToEachLayer(cmd.subdivideLayer, targetLayers, arcs, opts.expression);
|