mapshaper 0.5.77 → 0.5.81
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 -1
- package/mapshaper.js +873 -409
- package/package.json +2 -2
- package/www/mapshaper-gui.js +8 -10
- package/www/mapshaper.js +873 -409
- package/www/modules.js +21 -2
package/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 = utils.rtrim(s);
|
|
18036
|
+
}
|
|
18037
|
+
s = utils.ltrim(s);
|
|
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;
|
|
@@ -19191,6 +19363,10 @@ ${svg}
|
|
|
19191
19363
|
.option('keep-shapes', {
|
|
19192
19364
|
type: 'flag'
|
|
19193
19365
|
})
|
|
19366
|
+
.option('ids', {
|
|
19367
|
+
// describe: 'filter on a list of feature ids',
|
|
19368
|
+
type: 'numbers'
|
|
19369
|
+
})
|
|
19194
19370
|
.option('cleanup', {type: 'flag'}) // TODO: document
|
|
19195
19371
|
.option('name', nameOpt)
|
|
19196
19372
|
.option('target', targetOpt)
|
|
@@ -19313,6 +19489,13 @@ ${svg}
|
|
|
19313
19489
|
})
|
|
19314
19490
|
.option('target', targetOpt);
|
|
19315
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
|
+
|
|
19316
19499
|
parser.command('inlay')
|
|
19317
19500
|
.describe('inscribe a polygon layer inside another polygon layer')
|
|
19318
19501
|
.option('source', {
|
|
@@ -19625,6 +19808,7 @@ ${svg}
|
|
|
19625
19808
|
})
|
|
19626
19809
|
.option('target', targetOpt);
|
|
19627
19810
|
|
|
19811
|
+
|
|
19628
19812
|
parser.command('simplify')
|
|
19629
19813
|
.validate(validateSimplifyOpts)
|
|
19630
19814
|
.example('Retain 10% of removable vertices\n$ mapshaper input.shp -simplify 10%')
|
|
@@ -19759,6 +19943,11 @@ ${svg}
|
|
|
19759
19943
|
DEFAULT: true,
|
|
19760
19944
|
describe: 'expression or field for grouping features and naming split layers'
|
|
19761
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
|
+
})
|
|
19762
19951
|
.option('apart', {
|
|
19763
19952
|
describe: 'save output layers to independent datasets',
|
|
19764
19953
|
type: 'flag'
|
|
@@ -19858,26 +20047,81 @@ ${svg}
|
|
|
19858
20047
|
.option('target', targetOpt);
|
|
19859
20048
|
|
|
19860
20049
|
parser.command('symbols')
|
|
19861
|
-
// .describe('
|
|
20050
|
+
// .describe('symbolize points as polygons, circles, stars or arrows')
|
|
19862
20051
|
.option('type', {
|
|
19863
|
-
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'
|
|
19864
20111
|
})
|
|
19865
20112
|
.option('stroke', {})
|
|
19866
20113
|
.option('stroke-width', {})
|
|
19867
|
-
.option('fill', {
|
|
19868
|
-
|
|
19869
|
-
|
|
20114
|
+
.option('fill', {
|
|
20115
|
+
describe: 'symbol fill color'
|
|
20116
|
+
})
|
|
19870
20117
|
.option('effect', {})
|
|
19871
|
-
.option('
|
|
19872
|
-
.option('
|
|
19873
|
-
.option('
|
|
19874
|
-
.option('
|
|
19875
|
-
.option('arrow-stem-taper', {})
|
|
19876
|
-
.option('arrow-scaling', {})
|
|
19877
|
-
.option('where', whereOpt)
|
|
19878
|
-
.option('target', targetOpt);
|
|
20118
|
+
// .option('where', whereOpt)
|
|
20119
|
+
.option('name', nameOpt)
|
|
20120
|
+
.option('target', targetOpt)
|
|
20121
|
+
.option('no-replace', noReplaceOpt);
|
|
19879
20122
|
// .option('name', nameOpt);
|
|
19880
20123
|
|
|
20124
|
+
|
|
19881
20125
|
parser.command('target')
|
|
19882
20126
|
.describe('set active layer (or layers)')
|
|
19883
20127
|
.option('target', {
|
|
@@ -20028,13 +20272,6 @@ ${svg}
|
|
|
20028
20272
|
})
|
|
20029
20273
|
.option('name', nameOpt);
|
|
20030
20274
|
|
|
20031
|
-
parser.command('include')
|
|
20032
|
-
.describe('import JS data and functions for use in JS expressions')
|
|
20033
|
-
.option('file', {
|
|
20034
|
-
DEFAULT: true,
|
|
20035
|
-
describe: 'file containing a JS object with key:value pairs to import'
|
|
20036
|
-
});
|
|
20037
|
-
|
|
20038
20275
|
parser.command('fuzzy-join')
|
|
20039
20276
|
.describe('join points to polygons, with data fill and fuzzy match')
|
|
20040
20277
|
.option('source', {
|
|
@@ -21033,12 +21270,13 @@ ${svg}
|
|
|
21033
21270
|
if (this instanceof ShpReader === false) {
|
|
21034
21271
|
return new ShpReader(shpSrc, shxSrc);
|
|
21035
21272
|
}
|
|
21036
|
-
|
|
21037
21273
|
var shpFile = utils.isString(shpSrc) ? new FileReader(shpSrc) : new BufferReader(shpSrc);
|
|
21038
21274
|
var header = parseHeader(shpFile.readToBinArray(0, 100));
|
|
21039
|
-
var
|
|
21040
|
-
var
|
|
21041
|
-
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);
|
|
21042
21280
|
var shxBin, shxFile;
|
|
21043
21281
|
|
|
21044
21282
|
if (shxSrc) {
|
|
@@ -21046,8 +21284,6 @@ ${svg}
|
|
|
21046
21284
|
shxBin = shxFile.readToBinArray(0, shxFile.size()).bigEndian();
|
|
21047
21285
|
}
|
|
21048
21286
|
|
|
21049
|
-
reset();
|
|
21050
|
-
|
|
21051
21287
|
this.header = function() {
|
|
21052
21288
|
return header;
|
|
21053
21289
|
};
|
|
@@ -21065,62 +21301,35 @@ ${svg}
|
|
|
21065
21301
|
|
|
21066
21302
|
// Iterator interface for reading shape records
|
|
21067
21303
|
this.nextShape = function() {
|
|
21068
|
-
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);
|
|
21069
21310
|
if (!shape) {
|
|
21070
|
-
|
|
21071
|
-
|
|
21072
|
-
// ne_10m_admin_0_boundary_lines_land.shp
|
|
21073
|
-
// ne_110m_admin_0_scale_rank.shp
|
|
21074
|
-
verbose("Skipped over " + skippedBytes + " non-data bytes in the .shp file.");
|
|
21075
|
-
}
|
|
21076
|
-
shpFile.close();
|
|
21077
|
-
reset();
|
|
21311
|
+
done();
|
|
21312
|
+
return null;
|
|
21078
21313
|
}
|
|
21314
|
+
recordCount++;
|
|
21079
21315
|
return shape;
|
|
21080
21316
|
};
|
|
21081
21317
|
|
|
21082
|
-
|
|
21083
|
-
|
|
21084
|
-
|
|
21085
|
-
|
|
21086
|
-
|
|
21087
|
-
|
|
21088
|
-
|
|
21089
|
-
if (offset > shpOffset) {
|
|
21090
|
-
skippedBytes += offset - shpOffset;
|
|
21091
|
-
}
|
|
21092
|
-
} else {
|
|
21093
|
-
offset = shpOffset;
|
|
21094
|
-
}
|
|
21095
|
-
shape = readShapeAtOffset(offset);
|
|
21096
|
-
if (!shape) {
|
|
21097
|
-
// Some in-the-wild .shp files contain junk bytes between records. This
|
|
21098
|
-
// is a problem if the .shx index file is not present.
|
|
21099
|
-
// Here, we try to scan past the junk to find the next record.
|
|
21100
|
-
shape = huntForNextShape(offset, expectedId);
|
|
21101
|
-
}
|
|
21102
|
-
if (shape) {
|
|
21103
|
-
if (shape.id < expectedId) {
|
|
21104
|
-
message("Found a Shapefile record with the same id as a previous record (" + shape.id + ") -- skipping.");
|
|
21105
|
-
return readNextShape();
|
|
21106
|
-
} else if (shape.id > expectedId) {
|
|
21107
|
-
stop("Shapefile contains an out-of-sequence record. Possible data corruption -- bailing.");
|
|
21108
|
-
}
|
|
21109
|
-
recordCount++;
|
|
21110
|
-
}
|
|
21111
|
-
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);
|
|
21112
21325
|
}
|
|
21113
21326
|
|
|
21114
21327
|
function done() {
|
|
21115
|
-
|
|
21116
|
-
|
|
21117
|
-
|
|
21118
|
-
|
|
21119
|
-
|
|
21120
|
-
function reset() {
|
|
21121
|
-
shpOffset = 100;
|
|
21122
|
-
skippedBytes = 0;
|
|
21123
|
-
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
|
+
}
|
|
21124
21333
|
}
|
|
21125
21334
|
|
|
21126
21335
|
function parseHeader(bin) {
|
|
@@ -21149,47 +21358,81 @@ ${svg}
|
|
|
21149
21358
|
return header;
|
|
21150
21359
|
}
|
|
21151
21360
|
|
|
21152
|
-
function readShapeAtOffset(offset) {
|
|
21153
|
-
var shape = null,
|
|
21154
|
-
recordSize, recordType, recordId, goodSize, goodType, bin;
|
|
21155
21361
|
|
|
21156
|
-
|
|
21157
|
-
|
|
21158
|
-
|
|
21159
|
-
|
|
21160
|
-
|
|
21161
|
-
|
|
21162
|
-
|
|
21163
|
-
|
|
21164
|
-
|
|
21165
|
-
|
|
21166
|
-
|
|
21167
|
-
|
|
21168
|
-
|
|
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.');
|
|
21169
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
|
|
21170
21397
|
return shape;
|
|
21171
21398
|
}
|
|
21172
21399
|
|
|
21173
|
-
//
|
|
21174
|
-
//
|
|
21175
|
-
|
|
21176
|
-
|
|
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(),
|
|
21177
21411
|
shape = null,
|
|
21178
|
-
bin, recordId, recordType,
|
|
21179
|
-
while (offset + 12 <=
|
|
21412
|
+
bin, recordId, recordType, isValidType;
|
|
21413
|
+
while (offset + 12 <= fileSize) {
|
|
21180
21414
|
bin = shpFile.readToBinArray(offset, 12);
|
|
21181
21415
|
recordId = bin.bigEndian().readUint32();
|
|
21182
21416
|
recordType = bin.littleEndian().skipBytes(4).readUint32();
|
|
21183
|
-
|
|
21184
|
-
|
|
21185
|
-
|
|
21186
|
-
|
|
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.");
|
|
21187
21431
|
}
|
|
21188
|
-
offset += 4; // try next integer position
|
|
21189
21432
|
}
|
|
21190
|
-
|
|
21191
|
-
|
|
21192
|
-
|
|
21433
|
+
if (shape && offset > start) {
|
|
21434
|
+
verbose("Skipped over " + (offset - start) + " non-data bytes in the .shp file.");
|
|
21435
|
+
}
|
|
21193
21436
|
return shape;
|
|
21194
21437
|
}
|
|
21195
21438
|
}
|
|
@@ -21198,22 +21441,6 @@ ${svg}
|
|
|
21198
21441
|
return this.header().type;
|
|
21199
21442
|
};
|
|
21200
21443
|
|
|
21201
|
-
ShpReader.prototype.getCounts = function() {
|
|
21202
|
-
var counts = {
|
|
21203
|
-
nullCount: 0,
|
|
21204
|
-
partCount: 0,
|
|
21205
|
-
shapeCount: 0,
|
|
21206
|
-
pointCount: 0
|
|
21207
|
-
};
|
|
21208
|
-
this.forEachShape(function(shp) {
|
|
21209
|
-
if (shp.isNull) counts.nullCount++;
|
|
21210
|
-
counts.pointCount += shp.pointCount;
|
|
21211
|
-
counts.partCount += shp.partCount;
|
|
21212
|
-
counts.shapeCount++;
|
|
21213
|
-
});
|
|
21214
|
-
return counts;
|
|
21215
|
-
};
|
|
21216
|
-
|
|
21217
21444
|
// Apply snapping, remove duplicate coords and clean up defective paths in a dataset
|
|
21218
21445
|
// Assumes that any CRS info has been added to the dataset
|
|
21219
21446
|
// @opts: import options
|
|
@@ -26022,7 +26249,6 @@ ${svg}
|
|
|
26022
26249
|
if (constTol) return constTol;
|
|
26023
26250
|
return constTol ? constTol : meterDist * pctOfRadius;
|
|
26024
26251
|
};
|
|
26025
|
-
|
|
26026
26252
|
}
|
|
26027
26253
|
|
|
26028
26254
|
function getBufferDistanceFunction(lyr, dataset, opts) {
|
|
@@ -27195,7 +27421,7 @@ ${svg}
|
|
|
27195
27421
|
cmd.buffer = makeBufferLayer;
|
|
27196
27422
|
|
|
27197
27423
|
function makeBufferLayer(lyr, dataset, opts) {
|
|
27198
|
-
var dataset2
|
|
27424
|
+
var dataset2;
|
|
27199
27425
|
if (lyr.geometry_type == 'point') {
|
|
27200
27426
|
dataset2 = makePointBuffer(lyr, dataset, opts);
|
|
27201
27427
|
} else if (lyr.geometry_type == 'polyline') {
|
|
@@ -27205,13 +27431,9 @@ ${svg}
|
|
|
27205
27431
|
} else {
|
|
27206
27432
|
stop("Unsupported geometry type");
|
|
27207
27433
|
}
|
|
27208
|
-
|
|
27209
|
-
lyr2 =
|
|
27210
|
-
|
|
27211
|
-
if (lyr.data && !lyr2.data) {
|
|
27212
|
-
lyr2.data = opts.no_replace ? lyr.data.clone() : lyr.data;
|
|
27213
|
-
}
|
|
27214
|
-
return outputLayers;
|
|
27434
|
+
|
|
27435
|
+
var lyr2 = mergeOutputLayerIntoDataset(lyr, dataset, dataset2, opts);
|
|
27436
|
+
return [lyr2];
|
|
27215
27437
|
}
|
|
27216
27438
|
|
|
27217
27439
|
// TODO: support three or more stops
|
|
@@ -31672,6 +31894,10 @@ ${svg}
|
|
|
31672
31894
|
filter = compileValueExpression(opts.expression, lyr, arcs);
|
|
31673
31895
|
}
|
|
31674
31896
|
|
|
31897
|
+
if (opts.ids) {
|
|
31898
|
+
filter = combineFilters(filter, getIdFilter(opts.ids));
|
|
31899
|
+
}
|
|
31900
|
+
|
|
31675
31901
|
if (opts.remove_empty) {
|
|
31676
31902
|
filter = combineFilters(filter, getNullGeometryFilter(lyr, arcs));
|
|
31677
31903
|
}
|
|
@@ -31730,6 +31956,13 @@ ${svg}
|
|
|
31730
31956
|
lyr.data = filteredRecords ? new DataTable(filteredRecords) : null;
|
|
31731
31957
|
}
|
|
31732
31958
|
|
|
31959
|
+
function getIdFilter(ids) {
|
|
31960
|
+
var set = new Set(ids);
|
|
31961
|
+
return function(i) {
|
|
31962
|
+
return set.has(i);
|
|
31963
|
+
};
|
|
31964
|
+
}
|
|
31965
|
+
|
|
31733
31966
|
function getNullGeometryFilter(lyr, arcs) {
|
|
31734
31967
|
var shapes = lyr.shapes;
|
|
31735
31968
|
if (lyr.geometry_type == 'polygon') {
|
|
@@ -33785,7 +34018,7 @@ ${svg}
|
|
|
33785
34018
|
|
|
33786
34019
|
function createMeridianPart(x, ymin, ymax) {
|
|
33787
34020
|
var coords = densifyPathByInterval([[x, ymin], [x, ymax]], precision);
|
|
33788
|
-
meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord(x)}));
|
|
34021
|
+
meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord$1(x)}));
|
|
33789
34022
|
}
|
|
33790
34023
|
|
|
33791
34024
|
function createParallel(y) {
|
|
@@ -33795,7 +34028,7 @@ ${svg}
|
|
|
33795
34028
|
}
|
|
33796
34029
|
|
|
33797
34030
|
// remove tiny offsets
|
|
33798
|
-
function roundCoord(x) {
|
|
34031
|
+
function roundCoord$1(x) {
|
|
33799
34032
|
return +x.toFixed(3) || 0;
|
|
33800
34033
|
}
|
|
33801
34034
|
|
|
@@ -34002,10 +34235,6 @@ ${svg}
|
|
|
34002
34235
|
|
|
34003
34236
|
|
|
34004
34237
|
|
|
34005
|
-
function formatNumber(val) {
|
|
34006
|
-
return val + '';
|
|
34007
|
-
}
|
|
34008
|
-
|
|
34009
34238
|
function maxChars(arr) {
|
|
34010
34239
|
return arr.reduce(function(memo, str) {
|
|
34011
34240
|
var w = stringDisplayWidth(str);
|
|
@@ -34029,14 +34258,14 @@ ${svg}
|
|
|
34029
34258
|
}
|
|
34030
34259
|
|
|
34031
34260
|
function countIntegralChars(val) {
|
|
34032
|
-
return utils.isNumber(val) ? (formatNumber(val) + '.').indexOf('.') : 0;
|
|
34261
|
+
return utils.isNumber(val) ? (utils.formatNumber(val) + '.').indexOf('.') : 0;
|
|
34033
34262
|
}
|
|
34034
34263
|
|
|
34035
34264
|
function formatTableValue(val, integralChars) {
|
|
34036
34265
|
var str;
|
|
34037
34266
|
if (utils.isNumber(val)) {
|
|
34038
34267
|
str = utils.lpad("", integralChars - countIntegralChars(val), ' ') +
|
|
34039
|
-
formatNumber(val);
|
|
34268
|
+
utils.formatNumber(val);
|
|
34040
34269
|
} else if (utils.isString(val)) {
|
|
34041
34270
|
str = formatString(val);
|
|
34042
34271
|
} else if (utils.isDate(val)) {
|
|
@@ -36450,6 +36679,99 @@ ${svg}
|
|
|
36450
36679
|
parseScalebarLabelToKm: parseScalebarLabelToKm
|
|
36451
36680
|
});
|
|
36452
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
|
+
|
|
36453
36775
|
function calcSimplifyStats(arcs, use3D) {
|
|
36454
36776
|
var distSq = use3D ? pointSegGeoDistSq : geom.pointSegDistSq,
|
|
36455
36777
|
calcAngle = use3D ? geom.signedAngleSph : geom.signedAngle,
|
|
@@ -37408,13 +37730,20 @@ ${svg}
|
|
|
37408
37730
|
|
|
37409
37731
|
// @expression: optional field name or expression
|
|
37410
37732
|
//
|
|
37411
|
-
cmd.splitLayer = function(src, expression,
|
|
37412
|
-
var
|
|
37733
|
+
cmd.splitLayer = function(src, expression, optsArg) {
|
|
37734
|
+
var opts = optsArg || {},
|
|
37735
|
+
lyr0 = opts.no_replace ? copyLayer(src) : src,
|
|
37413
37736
|
properties = lyr0.data ? lyr0.data.getRecords() : null,
|
|
37414
37737
|
shapes = lyr0.shapes,
|
|
37415
37738
|
index = {},
|
|
37416
37739
|
splitLayers = [],
|
|
37417
|
-
namer
|
|
37740
|
+
namer;
|
|
37741
|
+
|
|
37742
|
+
if (opts.ids) {
|
|
37743
|
+
namer = getIdSplitFunction(opts.ids);
|
|
37744
|
+
} else {
|
|
37745
|
+
namer = getSplitNameFunction(lyr0, expression);
|
|
37746
|
+
}
|
|
37418
37747
|
|
|
37419
37748
|
// if (splitField) {
|
|
37420
37749
|
// internal.requireDataField(lyr0, splitField);
|
|
@@ -37446,15 +37775,24 @@ ${svg}
|
|
|
37446
37775
|
return splitLayers;
|
|
37447
37776
|
};
|
|
37448
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
|
+
|
|
37449
37793
|
function getSplitNameFunction(lyr, exp) {
|
|
37450
37794
|
var compiled;
|
|
37451
|
-
if (!exp)
|
|
37452
|
-
// if not splitting on an expression and layer is unnamed, name split-apart layers
|
|
37453
|
-
// like: split-1, split-2, ...
|
|
37454
|
-
return function(i) {
|
|
37455
|
-
return (lyr && lyr.name || 'split') + '-' + (i + 1);
|
|
37456
|
-
};
|
|
37457
|
-
}
|
|
37795
|
+
if (!exp) return getDefaultSplitFunction(lyr);
|
|
37458
37796
|
lyr = {name: lyr.name, data: lyr.data}; // remove shape info
|
|
37459
37797
|
compiled = compileValueExpression(exp, lyr, null);
|
|
37460
37798
|
return function(i) {
|
|
@@ -37521,56 +37859,83 @@ ${svg}
|
|
|
37521
37859
|
});
|
|
37522
37860
|
};
|
|
37523
37861
|
|
|
37524
|
-
|
|
37525
|
-
var len = 'length' in d ? d.length : 10;
|
|
37526
|
-
var filled = 'fill' in d;
|
|
37527
|
-
return filled ? getFilledArrow(d, len) : getStickArrow(d, len);
|
|
37528
|
-
};
|
|
37862
|
+
var roundCoord = getRoundingFunction(0.01);
|
|
37529
37863
|
|
|
37530
|
-
function
|
|
37531
|
-
|
|
37532
|
-
|
|
37533
|
-
|
|
37534
|
-
|
|
37535
|
-
|
|
37536
|
-
}
|
|
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
|
+
}
|
|
37537
37871
|
}
|
|
37538
37872
|
|
|
37539
|
-
function
|
|
37540
|
-
|
|
37541
|
-
|
|
37542
|
-
|
|
37543
|
-
fill: d.fill || 'magenta'
|
|
37544
|
-
};
|
|
37873
|
+
function flipY(coords) {
|
|
37874
|
+
forEachSymbolCoord(coords, function(p) {
|
|
37875
|
+
p[1] = -p[1];
|
|
37876
|
+
});
|
|
37545
37877
|
}
|
|
37546
37878
|
|
|
37547
|
-
function
|
|
37548
|
-
|
|
37549
|
-
|
|
37550
|
-
|
|
37551
|
-
|
|
37552
|
-
}
|
|
37553
|
-
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
|
+
});
|
|
37554
37884
|
}
|
|
37555
37885
|
|
|
37556
|
-
function
|
|
37557
|
-
|
|
37558
|
-
|
|
37559
|
-
|
|
37560
|
-
|
|
37561
|
-
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
|
+
});
|
|
37562
37891
|
}
|
|
37563
37892
|
|
|
37564
|
-
function
|
|
37565
|
-
|
|
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);
|
|
37566
37930
|
}
|
|
37567
37931
|
|
|
37568
37932
|
function getStickArrowCoords(d, totalLen) {
|
|
37933
|
+
var minStemRatio = getMinStemRatio(d);
|
|
37569
37934
|
var headAngle = d['arrow-head-angle'] || 90;
|
|
37570
37935
|
var curve = d['arrow-stem-curve'] || 0;
|
|
37571
37936
|
var unscaledHeadWidth = d['arrow-head-width'] || 9;
|
|
37572
37937
|
var unscaledHeadLen = getHeadLength(unscaledHeadWidth, headAngle);
|
|
37573
|
-
var scale = getScale(totalLen, unscaledHeadLen);
|
|
37938
|
+
var scale = getScale(totalLen, unscaledHeadLen, minStemRatio);
|
|
37574
37939
|
var headWidth = unscaledHeadWidth * scale;
|
|
37575
37940
|
var headLen = unscaledHeadLen * scale;
|
|
37576
37941
|
var tip = getStickArrowTip(totalLen, curve);
|
|
@@ -37581,84 +37946,273 @@ ${svg}
|
|
|
37581
37946
|
if (!headLen) return [stem];
|
|
37582
37947
|
var head = [addPoints([-headWidth / 2, -headLen], tip), tip.concat(), addPoints([headWidth / 2, -headLen], tip)];
|
|
37583
37948
|
|
|
37584
|
-
|
|
37585
|
-
|
|
37949
|
+
rotateCoords(stem, d.rotation);
|
|
37950
|
+
rotateCoords(head, d.rotation);
|
|
37586
37951
|
return [stem, head];
|
|
37587
37952
|
}
|
|
37588
37953
|
|
|
37589
|
-
function
|
|
37590
|
-
|
|
37591
|
-
return headWidth * headRatio;
|
|
37954
|
+
function getMinStemRatio(d) {
|
|
37955
|
+
return d['arrow-min-stem'] >= 0 ? d['arrow-min-stem'] : 0.4;
|
|
37592
37956
|
}
|
|
37593
37957
|
|
|
37594
|
-
function getFilledArrowCoords(
|
|
37595
|
-
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,
|
|
37596
37962
|
unscaledStemWidth = d['arrow-stem-width'] || 2,
|
|
37597
37963
|
unscaledHeadWidth = d['arrow-head-width'] || unscaledStemWidth * 3,
|
|
37598
37964
|
unscaledHeadLen = getHeadLength(unscaledHeadWidth, headAngle),
|
|
37599
|
-
scale = getScale(totalLen, unscaledHeadLen),
|
|
37965
|
+
scale = getScale(totalLen, unscaledHeadLen, minStemRatio),
|
|
37600
37966
|
headWidth = unscaledHeadWidth * scale,
|
|
37601
37967
|
headLen = unscaledHeadLen * scale,
|
|
37602
37968
|
stemWidth = unscaledStemWidth * scale,
|
|
37603
37969
|
stemTaper = d['arrow-stem-taper'] || 0,
|
|
37604
|
-
|
|
37970
|
+
stemCurve = d['arrow-stem-curve'] || 0,
|
|
37971
|
+
stemLen = totalLen - headLen,
|
|
37972
|
+
coords;
|
|
37605
37973
|
|
|
37606
37974
|
var headDx = headWidth / 2,
|
|
37607
37975
|
stemDx = stemWidth / 2,
|
|
37608
37976
|
baseDx = stemDx * (1 - stemTaper);
|
|
37609
37977
|
|
|
37610
|
-
|
|
37978
|
+
if (unscaledHeadWidth < unscaledStemWidth) {
|
|
37979
|
+
stop('Arrow head must be at least as wide as the stem.');
|
|
37980
|
+
}
|
|
37981
|
+
|
|
37982
|
+
if (!stemCurve || Math.abs(stemCurve) > 90) {
|
|
37983
|
+
coords = [[baseDx, 0], [stemDx, stemLen], [headDx, stemLen], [0, stemLen + headLen],
|
|
37611
37984
|
[-headDx, stemLen], [-stemDx, stemLen], [-baseDx, 0], [baseDx, 0]];
|
|
37985
|
+
} else {
|
|
37986
|
+
if (direction > 0) stemCurve = -stemCurve;
|
|
37987
|
+
coords = getCurvedArrowCoords(stemLen, headLen, stemCurve, stemDx, headDx, baseDx);
|
|
37988
|
+
}
|
|
37612
37989
|
|
|
37613
|
-
|
|
37990
|
+
rotateCoords(coords, direction);
|
|
37614
37991
|
return [coords];
|
|
37615
37992
|
}
|
|
37616
37993
|
|
|
37617
|
-
|
|
37618
|
-
|
|
37619
|
-
var
|
|
37620
|
-
|
|
37621
|
-
|
|
37622
|
-
|
|
37623
|
-
|
|
37624
|
-
|
|
37994
|
+
|
|
37995
|
+
function getScale(totalLen, headLen, minStemRatio) {
|
|
37996
|
+
var maxHeadPct = 1 - minStemRatio;
|
|
37997
|
+
var headPct = headLen / totalLen;
|
|
37998
|
+
if (headPct > maxHeadPct) {
|
|
37999
|
+
return maxHeadPct / headPct;
|
|
38000
|
+
}
|
|
38001
|
+
return 1;
|
|
37625
38002
|
}
|
|
37626
38003
|
|
|
37627
|
-
|
|
37628
|
-
|
|
37629
|
-
var
|
|
37630
|
-
|
|
37631
|
-
|
|
37632
|
-
|
|
37633
|
-
|
|
37634
|
-
|
|
38004
|
+
function getStickArrowTip(totalLen, curve) {
|
|
38005
|
+
// curve/2 intersects the arrowhead at 90deg (trigonometry)
|
|
38006
|
+
var theta = Math.abs(curve/2) / 180 * Math.PI;
|
|
38007
|
+
var dx = totalLen * Math.sin(theta) * (curve > 0 ? -1 : 1);
|
|
38008
|
+
var dy = totalLen * Math.cos(theta);
|
|
38009
|
+
return [dx, dy];
|
|
38010
|
+
}
|
|
38011
|
+
|
|
38012
|
+
function addPoints(a, b) {
|
|
38013
|
+
return [a[0] + b[0], a[1] + b[1]];
|
|
38014
|
+
}
|
|
38015
|
+
|
|
38016
|
+
|
|
38017
|
+
function getHeadLength(headWidth, headAngle) {
|
|
38018
|
+
var headRatio = 1 / Math.tan(Math.PI * headAngle / 180 / 2) / 2; // length-to-width head ratio
|
|
38019
|
+
return headWidth * headRatio;
|
|
38020
|
+
}
|
|
38021
|
+
|
|
38022
|
+
function getCurvedArrowCoords(stemLen, headLen, curvature, stemDx, headDx, baseDx) {
|
|
38023
|
+
// coordinates go counter clockwise, starting from the leftmost head coordinate
|
|
38024
|
+
var theta = Math.abs(curvature) / 180 * Math.PI;
|
|
38025
|
+
var sign = curvature > 0 ? 1 : -1;
|
|
38026
|
+
var dx = stemLen * Math.sin(theta / 2) * sign;
|
|
38027
|
+
var dy = stemLen * Math.cos(theta / 2);
|
|
38028
|
+
var head = [[stemDx + dx, dy], [headDx + dx, dy],
|
|
38029
|
+
[dx, headLen + dy], [-headDx + dx, dy], [-stemDx + dx, dy]];
|
|
38030
|
+
var ax = baseDx * Math.cos(theta); // rotate arrow base
|
|
38031
|
+
var ay = baseDx * Math.sin(theta) * -sign;
|
|
38032
|
+
var leftStem = getCurvedStemCoords(-ax, -ay, -stemDx + dx, dy, theta);
|
|
38033
|
+
var rightStem = getCurvedStemCoords(ax, ay, stemDx + dx, dy, theta);
|
|
38034
|
+
var stem = leftStem.concat(rightStem.reverse());
|
|
38035
|
+
// stem.pop();
|
|
38036
|
+
return stem.concat(head);
|
|
38037
|
+
}
|
|
38038
|
+
|
|
38039
|
+
// ax, ay: point on the base
|
|
38040
|
+
// bx, by: point on the stem
|
|
38041
|
+
function getCurvedStemCoords(ax, ay, bx, by, theta0) {
|
|
38042
|
+
// case: curved side intrudes into head (because stem is too short)
|
|
38043
|
+
if (ay > by) {
|
|
38044
|
+
return [[ax * by / ay, by]];
|
|
37635
38045
|
}
|
|
37636
|
-
|
|
37637
|
-
|
|
37638
|
-
|
|
37639
|
-
|
|
37640
|
-
|
|
38046
|
+
var dx = bx - ax,
|
|
38047
|
+
dy = by - ay,
|
|
38048
|
+
dy1 = (dy * dy - dx * dx) / (2 * dy),
|
|
38049
|
+
dy2 = dy - dy1,
|
|
38050
|
+
dx2 = Math.sqrt(dx * dx + dy * dy) / 2,
|
|
38051
|
+
theta = Math.PI - Math.asin(dx2 / dy2) * 2,
|
|
38052
|
+
degrees = theta * 180 / Math.PI,
|
|
38053
|
+
radius = dy2 / Math.tan(theta / 2),
|
|
38054
|
+
leftBend = bx > ax,
|
|
38055
|
+
sign = leftBend ? 1 : -1,
|
|
38056
|
+
points = Math.round(degrees / 5) + 2,
|
|
38057
|
+
increment = theta / (points + 1),
|
|
38058
|
+
coords = [[bx, by]];
|
|
38059
|
+
|
|
38060
|
+
for (var i=1; i<= points; i++) {
|
|
38061
|
+
var phi = i * increment / 2;
|
|
38062
|
+
var sinPhi = Math.sin(phi);
|
|
38063
|
+
var cosPhi = Math.cos(phi);
|
|
38064
|
+
var c = sinPhi * radius * 2;
|
|
38065
|
+
var a = sinPhi * c;
|
|
38066
|
+
var b = cosPhi * c;
|
|
38067
|
+
coords.push([bx - a * sign, by - b]);
|
|
38068
|
+
}
|
|
38069
|
+
coords.push([ax, ay]);
|
|
38070
|
+
return coords;
|
|
38071
|
+
}
|
|
38072
|
+
|
|
38073
|
+
// sides: e.g. 5-pointed star has 10 sides
|
|
38074
|
+
// radius: distance from center to point
|
|
38075
|
+
//
|
|
38076
|
+
function getPolygonCoords(radius, opts) {
|
|
38077
|
+
var type = opts.type;
|
|
38078
|
+
var sides = +opts.sides || getDefaultSides(type);
|
|
38079
|
+
var isStar = type == 'star';
|
|
38080
|
+
if (isStar && (sides < 6 || sides % 2 !== 0)) {
|
|
38081
|
+
stop(`Invalid number of sides for a star (${sides})`);
|
|
38082
|
+
} else if (sides >= 3 === false) {
|
|
38083
|
+
stop(`Invalid number of sides (${sides})`);
|
|
38084
|
+
}
|
|
38085
|
+
var coords = [],
|
|
38086
|
+
angle = 360 / sides,
|
|
38087
|
+
b = isStar ? 1 : 0.5,
|
|
38088
|
+
theta, even, len;
|
|
38089
|
+
if (opts.orientation == 'b') {
|
|
38090
|
+
b = 0;
|
|
38091
|
+
}
|
|
38092
|
+
for (var i=0; i<sides; i++) {
|
|
38093
|
+
even = i % 2 == 0;
|
|
38094
|
+
len = radius;
|
|
38095
|
+
if (isStar && even) {
|
|
38096
|
+
len *= (opts.star_ratio || 0.5);
|
|
38097
|
+
}
|
|
38098
|
+
theta = (i + b) * angle % 360;
|
|
38099
|
+
coords.push(getPlanarSegmentEndpoint(0, 0, theta, len));
|
|
38100
|
+
}
|
|
38101
|
+
coords.push(coords[0].concat());
|
|
38102
|
+
return [coords];
|
|
38103
|
+
}
|
|
38104
|
+
|
|
38105
|
+
function getDefaultSides(type) {
|
|
38106
|
+
return {
|
|
38107
|
+
star: 10,
|
|
38108
|
+
circle: 72,
|
|
38109
|
+
triangle: 3,
|
|
38110
|
+
square: 4,
|
|
38111
|
+
pentagon: 5,
|
|
38112
|
+
hexagon: 6,
|
|
38113
|
+
heptagon: 7,
|
|
38114
|
+
octagon: 8,
|
|
38115
|
+
nonagon: 9,
|
|
38116
|
+
decagon: 10
|
|
38117
|
+
}[type] || 4;
|
|
38118
|
+
}
|
|
38119
|
+
|
|
38120
|
+
// TODO: refactor to remove duplication in mapshaper-svg-style.js
|
|
38121
|
+
cmd.symbols = function(inputLyr, dataset, opts) {
|
|
38122
|
+
requireSinglePointLayer(inputLyr);
|
|
38123
|
+
var lyr = opts.no_replace ? copyLayer(inputLyr) : inputLyr;
|
|
38124
|
+
var polygonMode = !!opts.polygons;
|
|
38125
|
+
var metersPerPx;
|
|
38126
|
+
if (polygonMode) {
|
|
38127
|
+
requireProjectedDataset(dataset);
|
|
38128
|
+
metersPerPx = opts.pixel_scale || getMetersPerPixel(lyr, dataset);
|
|
38129
|
+
}
|
|
38130
|
+
var records = getLayerDataTable(lyr).getRecords();
|
|
38131
|
+
var getSymbolData = getSymbolDataAccessor(lyr, opts);
|
|
38132
|
+
var geometries = lyr.shapes.map(function(shp, i) {
|
|
38133
|
+
if (!shp) return null;
|
|
38134
|
+
var d = getSymbolData(i);
|
|
38135
|
+
var rec = records[i] || {};
|
|
38136
|
+
var coords, size, constructor;
|
|
38137
|
+
if (d.type == 'arrow') {
|
|
38138
|
+
size = d.radius || d.length || d['arrow-length'] || d.r;
|
|
38139
|
+
constructor = getFilledArrowCoords;
|
|
38140
|
+
} else {
|
|
38141
|
+
size = d.radius || d.length || d.r;
|
|
38142
|
+
constructor = getPolygonCoords;
|
|
38143
|
+
}
|
|
38144
|
+
if (size > 0 === false) return null;
|
|
38145
|
+
coords = constructor(size, d, opts);
|
|
38146
|
+
rotateCoords(coords, +d.rotation || 0);
|
|
38147
|
+
if (!polygonMode) {
|
|
38148
|
+
flipY(coords);
|
|
38149
|
+
}
|
|
38150
|
+
if (+opts.scale) {
|
|
38151
|
+
scaleAndShiftCoords(coords, +opts.scale, [0, 0]);
|
|
38152
|
+
}
|
|
38153
|
+
if (polygonMode) {
|
|
38154
|
+
scaleAndShiftCoords(coords, metersPerPx, shp[0]);
|
|
38155
|
+
if (d.tfill) rec.fill = d.fill;
|
|
38156
|
+
return createGeometry(coords);
|
|
37641
38157
|
} else {
|
|
37642
|
-
rec['svg-symbol'] =
|
|
38158
|
+
rec['svg-symbol'] = makeSvgSymbol(coords, d);
|
|
37643
38159
|
}
|
|
37644
38160
|
});
|
|
38161
|
+
|
|
38162
|
+
var outputLyr, dataset2;
|
|
38163
|
+
if (polygonMode) {
|
|
38164
|
+
dataset2 = importGeometries(geometries, records);
|
|
38165
|
+
outputLyr = mergeOutputLayerIntoDataset(inputLyr, dataset, dataset2, opts);
|
|
38166
|
+
outputLyr.data = lyr.data;
|
|
38167
|
+
} else {
|
|
38168
|
+
outputLyr = lyr;
|
|
38169
|
+
}
|
|
38170
|
+
return [outputLyr];
|
|
37645
38171
|
};
|
|
37646
38172
|
|
|
38173
|
+
function importGeometries(geometries, records) {
|
|
38174
|
+
var features = geometries.map(function(geom, i) {
|
|
38175
|
+
var d = records[i];
|
|
38176
|
+
return {
|
|
38177
|
+
type: 'Feature',
|
|
38178
|
+
properties: records[i] || null,
|
|
38179
|
+
geometry: geom
|
|
38180
|
+
};
|
|
38181
|
+
});
|
|
38182
|
+
var geojson = {
|
|
38183
|
+
type: 'FeatureCollection',
|
|
38184
|
+
features: features
|
|
38185
|
+
};
|
|
38186
|
+
return importGeoJSON(geojson);
|
|
38187
|
+
}
|
|
38188
|
+
|
|
38189
|
+
function createGeometry(coords) {
|
|
38190
|
+
return {
|
|
38191
|
+
type: 'Polygon',
|
|
38192
|
+
coordinates: coords
|
|
38193
|
+
};
|
|
38194
|
+
}
|
|
38195
|
+
|
|
38196
|
+
function getMetersPerPixel(lyr, dataset) {
|
|
38197
|
+
|
|
38198
|
+
// TODO: handle single point, no extent
|
|
38199
|
+
var bounds = getLayerBounds(lyr);
|
|
38200
|
+
return bounds.width() / 800;
|
|
38201
|
+
}
|
|
38202
|
+
|
|
37647
38203
|
// Returns an svg-symbol data object for one symbol
|
|
37648
|
-
function
|
|
37649
|
-
|
|
37650
|
-
|
|
37651
|
-
|
|
37652
|
-
|
|
37653
|
-
|
|
37654
|
-
|
|
37655
|
-
}
|
|
37656
|
-
return f(properties);
|
|
38204
|
+
function makeSvgSymbol(coords, properties) {
|
|
38205
|
+
roundCoordsForSVG(coords);
|
|
38206
|
+
return {
|
|
38207
|
+
type: 'polygon',
|
|
38208
|
+
coordinates: coords,
|
|
38209
|
+
fill: properties.fill || 'magenta'
|
|
38210
|
+
};
|
|
37657
38211
|
}
|
|
37658
38212
|
|
|
37659
38213
|
var Symbols = /*#__PURE__*/Object.freeze({
|
|
37660
38214
|
__proto__: null,
|
|
37661
|
-
|
|
38215
|
+
makeSvgSymbol: makeSvgSymbol
|
|
37662
38216
|
});
|
|
37663
38217
|
|
|
37664
38218
|
cmd.target = function(catalog, opts) {
|
|
@@ -37804,99 +38358,6 @@ ${svg}
|
|
|
37804
38358
|
}
|
|
37805
38359
|
};
|
|
37806
38360
|
|
|
37807
|
-
cmd.shape = function(targetDataset, opts) {
|
|
37808
|
-
var geojson, dataset;
|
|
37809
|
-
if (opts.coordinates) {
|
|
37810
|
-
geojson = makeShapeFromCoords(opts);
|
|
37811
|
-
} else if (opts.type == 'circle') {
|
|
37812
|
-
geojson = makeCircle(opts);
|
|
37813
|
-
} else if (opts.type == 'rectangle' && opts.bbox) {
|
|
37814
|
-
geojson = getRectangleGeoJSON(opts);
|
|
37815
|
-
} else {
|
|
37816
|
-
stop('Missing coordinates parameter');
|
|
37817
|
-
}
|
|
37818
|
-
// TODO: project shape if targetDataset is projected
|
|
37819
|
-
dataset = importGeoJSON(geojson, {});
|
|
37820
|
-
if (opts.rotation) {
|
|
37821
|
-
rotateDatasetCoords(dataset, opts.rotation);
|
|
37822
|
-
}
|
|
37823
|
-
dataset.layers[0].name = opts.name || opts.type || 'shape';
|
|
37824
|
-
return dataset;
|
|
37825
|
-
};
|
|
37826
|
-
|
|
37827
|
-
function getRectangleGeoJSON(opts) {
|
|
37828
|
-
var bbox = opts.bbox,
|
|
37829
|
-
xmin = bbox[0],
|
|
37830
|
-
ymin = bbox[1],
|
|
37831
|
-
xmax = bbox[2],
|
|
37832
|
-
ymax = bbox[3],
|
|
37833
|
-
interval = 0.5,
|
|
37834
|
-
coords = [],
|
|
37835
|
-
type = opts.geometry == 'polyline' ? 'LineString' : 'Polygon';
|
|
37836
|
-
addSide(xmin, ymin, xmin, ymax);
|
|
37837
|
-
addSide(xmin, ymax, xmax, ymax);
|
|
37838
|
-
addSide(xmax, ymax, xmax, ymin);
|
|
37839
|
-
addSide(xmax, ymin, xmin, ymin);
|
|
37840
|
-
coords.push([xmin, ymin]);
|
|
37841
|
-
return {
|
|
37842
|
-
type: type,
|
|
37843
|
-
coordinates: type == 'Polygon' ? [coords] : coords
|
|
37844
|
-
};
|
|
37845
|
-
|
|
37846
|
-
function addSide(x1, y1, x2, y2) {
|
|
37847
|
-
var dx = x2 - x1,
|
|
37848
|
-
dy = y2 - y1,
|
|
37849
|
-
n = Math.ceil(Math.max(Math.abs(dx) / interval, Math.abs(dy) / interval)),
|
|
37850
|
-
xint = dx / n,
|
|
37851
|
-
yint = dy / n;
|
|
37852
|
-
for (var i=0; i<n; i++) {
|
|
37853
|
-
coords.push([x1 + i * xint, y1 + i * yint]);
|
|
37854
|
-
}
|
|
37855
|
-
}
|
|
37856
|
-
}
|
|
37857
|
-
|
|
37858
|
-
function makeCircle(opts) {
|
|
37859
|
-
if (opts.radius > 0 === false && opts.radius_angle > 0 === false) {
|
|
37860
|
-
stop('Missing required radius parameter.');
|
|
37861
|
-
}
|
|
37862
|
-
var cp = opts.center || [0, 0];
|
|
37863
|
-
var radius = opts.radius || getCircleRadiusFromAngle(getCRS('wgs84'), opts.radius_angle);
|
|
37864
|
-
return getCircleGeoJSON(cp, radius, null, {geometry_type : opts.geometry || 'polygon'});
|
|
37865
|
-
}
|
|
37866
|
-
|
|
37867
|
-
function makeShapeFromCoords(opts) {
|
|
37868
|
-
var coordinates = [];
|
|
37869
|
-
var offsets = opts.offsets || [];
|
|
37870
|
-
var coords = opts.coordinates;
|
|
37871
|
-
var type, i, x, y;
|
|
37872
|
-
if (coords.length >= 2 === false) {
|
|
37873
|
-
stop('Invalid coordinates parameter.');
|
|
37874
|
-
}
|
|
37875
|
-
for (i=0; i<coords.length; i+= 2) {
|
|
37876
|
-
x = coords[i];
|
|
37877
|
-
y = coords[i + 1];
|
|
37878
|
-
coordinates.push([x, y]);
|
|
37879
|
-
}
|
|
37880
|
-
for (i=0; i<offsets.length; i+=2) {
|
|
37881
|
-
x += offsets[i];
|
|
37882
|
-
y += offsets[i + 1];
|
|
37883
|
-
coordinates.push([x, y]);
|
|
37884
|
-
}
|
|
37885
|
-
if (GeoJSON.pathIsRing(coordinates)) {
|
|
37886
|
-
type = 'Polygon';
|
|
37887
|
-
} else if (opts.closed && coordinates.length >= 3) {
|
|
37888
|
-
type = 'Polygon';
|
|
37889
|
-
coordinates.push(coordinates[0]);
|
|
37890
|
-
} else {
|
|
37891
|
-
type = 'LineString';
|
|
37892
|
-
}
|
|
37893
|
-
return {
|
|
37894
|
-
type: type,
|
|
37895
|
-
coordinates: type == 'Polygon' ? [coordinates] : coordinates
|
|
37896
|
-
};
|
|
37897
|
-
|
|
37898
|
-
}
|
|
37899
|
-
|
|
37900
38361
|
cmd.variableSimplify = function(layers, dataset, opts) {
|
|
37901
38362
|
var lyr = layers[0];
|
|
37902
38363
|
var arcs = dataset.arcs;
|
|
@@ -38438,6 +38899,9 @@ ${svg}
|
|
|
38438
38899
|
} else if (name == 'shape') {
|
|
38439
38900
|
catalog.addDataset(cmd.shape(targetDataset, opts));
|
|
38440
38901
|
|
|
38902
|
+
} else if (name == 'shapes') {
|
|
38903
|
+
outputLayers = applyCommandToEachLayer(cmd.shapes, targetLayers, targetDataset, opts);
|
|
38904
|
+
|
|
38441
38905
|
} else if (name == 'simplify') {
|
|
38442
38906
|
if (opts.variable) {
|
|
38443
38907
|
cmd.variableSimplify(targetLayers, targetDataset, opts);
|
|
@@ -38467,7 +38931,7 @@ ${svg}
|
|
|
38467
38931
|
applyCommandToEachLayer(cmd.svgStyle, targetLayers, targetDataset, opts);
|
|
38468
38932
|
|
|
38469
38933
|
} else if (name == 'symbols') {
|
|
38470
|
-
applyCommandToEachLayer(cmd.symbols, targetLayers, opts);
|
|
38934
|
+
outputLayers = applyCommandToEachLayer(cmd.symbols, targetLayers, targetDataset, opts);
|
|
38471
38935
|
|
|
38472
38936
|
} else if (name == 'subdivide') {
|
|
38473
38937
|
outputLayers = applyCommandToEachLayer(cmd.subdivideLayer, targetLayers, arcs, opts.expression);
|