mapshaper 0.5.78 → 0.5.82
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 +13 -0
- package/mapshaper.js +700 -394
- package/package.json +1 -1
- package/www/mapshaper-gui.js +0 -1
- package/www/mapshaper.js +700 -394
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({
|
|
@@ -1330,9 +1330,10 @@
|
|
|
1330
1330
|
}
|
|
1331
1331
|
|
|
1332
1332
|
function logArgs(args) {
|
|
1333
|
-
if (LOGGING
|
|
1334
|
-
|
|
1335
|
-
|
|
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);
|
|
1336
1337
|
}
|
|
1337
1338
|
|
|
1338
1339
|
var Logging = /*#__PURE__*/Object.freeze({
|
|
@@ -1744,9 +1745,17 @@
|
|
|
1744
1745
|
return [xmin, ymin, xmax, ymax];
|
|
1745
1746
|
}
|
|
1746
1747
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
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;
|
|
1750
1759
|
var D2R = Math.PI / 180;
|
|
1751
1760
|
var R2D = 180 / Math.PI;
|
|
1752
1761
|
|
|
@@ -2341,15 +2350,21 @@
|
|
|
2341
2350
|
}, 0);
|
|
2342
2351
|
}
|
|
2343
2352
|
|
|
2344
|
-
function getSphericalShapeArea(shp, arcs) {
|
|
2353
|
+
function getSphericalShapeArea(shp, arcs, R) {
|
|
2345
2354
|
if (arcs.isPlanar()) {
|
|
2346
2355
|
error("[getSphericalShapeArea()] Function requires decimal degree coordinates");
|
|
2347
2356
|
}
|
|
2348
2357
|
return (shp || []).reduce(function(area, ids) {
|
|
2349
|
-
return area + getSphericalPathArea(ids, arcs);
|
|
2358
|
+
return area + getSphericalPathArea(ids, arcs, R);
|
|
2350
2359
|
}, 0);
|
|
2351
2360
|
}
|
|
2352
2361
|
|
|
2362
|
+
// export function getEllipsoidalShapeArea(shp, arcs, crs) {
|
|
2363
|
+
// return (shp || []).reduce(function(area, ids) {
|
|
2364
|
+
// return area + getEllipsoidalPathArea(ids, arcs, crs);
|
|
2365
|
+
// }, 0);
|
|
2366
|
+
// }
|
|
2367
|
+
|
|
2353
2368
|
// Return true if point is inside or on boundary of a shape
|
|
2354
2369
|
//
|
|
2355
2370
|
function testPointInPolygon(x, y, shp, arcs) {
|
|
@@ -2463,16 +2478,17 @@
|
|
|
2463
2478
|
return (arcs.isPlanar() ? getPlanarPathArea : getSphericalPathArea)(ids, arcs);
|
|
2464
2479
|
}
|
|
2465
2480
|
|
|
2466
|
-
function getSphericalPathArea(ids, arcs) {
|
|
2481
|
+
function getSphericalPathArea(ids, arcs, R) {
|
|
2467
2482
|
var iter = arcs.getShapeIter(ids);
|
|
2468
|
-
return getSphericalPathArea2(iter);
|
|
2483
|
+
return getSphericalPathArea2(iter, R);
|
|
2469
2484
|
}
|
|
2470
2485
|
|
|
2471
|
-
function getSphericalPathArea2(iter) {
|
|
2486
|
+
function getSphericalPathArea2(iter, R) {
|
|
2472
2487
|
var sum = 0,
|
|
2473
2488
|
started = false,
|
|
2474
2489
|
deg2rad = Math.PI / 180,
|
|
2475
2490
|
x, y, xp, yp;
|
|
2491
|
+
R = R || WGS84.SEMIMAJOR_AXIS;
|
|
2476
2492
|
while (iter.hasNext()) {
|
|
2477
2493
|
x = iter.x * deg2rad;
|
|
2478
2494
|
y = Math.sin(iter.y * deg2rad);
|
|
@@ -2484,7 +2500,7 @@
|
|
|
2484
2500
|
xp = x;
|
|
2485
2501
|
yp = y;
|
|
2486
2502
|
}
|
|
2487
|
-
return sum / 2 *
|
|
2503
|
+
return sum / 2 * R * R;
|
|
2488
2504
|
}
|
|
2489
2505
|
|
|
2490
2506
|
// Get path area from an array of [x, y] points
|
|
@@ -3948,7 +3964,7 @@
|
|
|
3948
3964
|
function requireSinglePointLayer(lyr, msg) {
|
|
3949
3965
|
requirePointLayer(lyr);
|
|
3950
3966
|
if (countMultiPartFeatures(lyr) > 0) {
|
|
3951
|
-
stop(msg || 'This command requires single points');
|
|
3967
|
+
stop(msg || 'This command requires single points; layer contains multi-point features.');
|
|
3952
3968
|
}
|
|
3953
3969
|
}
|
|
3954
3970
|
|
|
@@ -4003,6 +4019,7 @@
|
|
|
4003
4019
|
return opts && opts.no_replace ? {geometry_type: src.geometry_type} : src;
|
|
4004
4020
|
}
|
|
4005
4021
|
|
|
4022
|
+
//
|
|
4006
4023
|
function setOutputLayerName(dest, src, defName, opts) {
|
|
4007
4024
|
opts = opts || {};
|
|
4008
4025
|
if (opts.name) {
|
|
@@ -4079,6 +4096,7 @@
|
|
|
4079
4096
|
return counts;
|
|
4080
4097
|
}
|
|
4081
4098
|
|
|
4099
|
+
// Returns a Bounds object
|
|
4082
4100
|
function getLayerBounds(lyr, arcs) {
|
|
4083
4101
|
var bounds = null;
|
|
4084
4102
|
if (lyr.geometry_type == 'point') {
|
|
@@ -6150,9 +6168,11 @@
|
|
|
6150
6168
|
arcCount += n;
|
|
6151
6169
|
});
|
|
6152
6170
|
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6171
|
+
if (arcSources.length > 0) {
|
|
6172
|
+
mergedArcs = mergeArcs(arcSources);
|
|
6173
|
+
if (mergedArcs.size() != arcCount) {
|
|
6174
|
+
error("[mergeDatasets()] Arc indexing error");
|
|
6175
|
+
}
|
|
6156
6176
|
}
|
|
6157
6177
|
|
|
6158
6178
|
return {
|
|
@@ -6171,6 +6191,8 @@
|
|
|
6171
6191
|
}
|
|
6172
6192
|
|
|
6173
6193
|
function mergeArcs(arr) {
|
|
6194
|
+
// Returning the original causes a test to fail
|
|
6195
|
+
// if (arr.length < 2) return arr[0];
|
|
6174
6196
|
var dataArr = arr.map(function(arcs) {
|
|
6175
6197
|
if (arcs.getRetainedInterval() > 0) {
|
|
6176
6198
|
verbose("Baking-in simplification setting.");
|
|
@@ -6775,31 +6797,47 @@
|
|
|
6775
6797
|
dataset.layers = currLayers;
|
|
6776
6798
|
}
|
|
6777
6799
|
|
|
6778
|
-
// Replace a layer
|
|
6800
|
+
// Replace a layer with a layer from a second dataset
|
|
6801
|
+
// (in-place)
|
|
6779
6802
|
// (Typically, the second dataset is imported from dynamically generated GeoJSON and contains one layer)
|
|
6780
6803
|
function replaceLayerContents(lyr, dataset, dataset2) {
|
|
6804
|
+
var lyr2 = mergeOutputLayerIntoDataset(lyr, dataset, dataset2, {});
|
|
6805
|
+
if (layerHasPaths(lyr2)) {
|
|
6806
|
+
buildTopology(dataset);
|
|
6807
|
+
}
|
|
6808
|
+
}
|
|
6809
|
+
|
|
6810
|
+
function mergeOutputLayerIntoDataset(lyr, dataset, dataset2, opts) {
|
|
6781
6811
|
if (!dataset2 || dataset2.layers.length != 1) {
|
|
6782
|
-
error('Invalid
|
|
6812
|
+
error('Invalid source dataset');
|
|
6783
6813
|
}
|
|
6784
6814
|
if (dataset.layers.includes(lyr) === false) {
|
|
6785
6815
|
error('Invalid target layer');
|
|
6786
6816
|
}
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
mergeDatasetsIntoDataset(dataset, [dataset2]);
|
|
6791
|
-
}
|
|
6817
|
+
// this command returns merged layers instead of adding them to target dataset
|
|
6818
|
+
var outputLayers = mergeDatasetsIntoDataset(dataset, [dataset2]);
|
|
6819
|
+
var lyr2 = outputLayers[0];
|
|
6792
6820
|
|
|
6793
|
-
|
|
6821
|
+
// TODO: find a more reliable way of knowing when to copy data
|
|
6822
|
+
var copyData = !lyr2.data && lyr.data && getFeatureCount(lyr2) == lyr.data.size();
|
|
6794
6823
|
|
|
6795
|
-
if (
|
|
6796
|
-
|
|
6824
|
+
if (copyData) {
|
|
6825
|
+
lyr2.data = opts.no_replace ? lyr.data.clone() : lyr.data;
|
|
6797
6826
|
}
|
|
6798
|
-
if (
|
|
6799
|
-
//
|
|
6800
|
-
|
|
6801
|
-
|
|
6827
|
+
if (opts.no_replace) {
|
|
6828
|
+
// dataset.layers.push(lyr2);
|
|
6829
|
+
|
|
6830
|
+
} else {
|
|
6831
|
+
lyr2 = Object.assign(lyr, {data: null, shapes: null}, lyr2);
|
|
6832
|
+
if (layerHasPaths(lyr)) {
|
|
6833
|
+
// Remove unused arcs from replaced layer
|
|
6834
|
+
// TODO: consider using clean insead of this
|
|
6835
|
+
dissolveArcs(dataset);
|
|
6836
|
+
}
|
|
6802
6837
|
}
|
|
6838
|
+
|
|
6839
|
+
lyr2.name = opts.name || lyr2.name;
|
|
6840
|
+
return lyr2;
|
|
6803
6841
|
}
|
|
6804
6842
|
|
|
6805
6843
|
// Transform the points in a dataset in-place; don't clean up corrupted shapes
|
|
@@ -6828,6 +6866,7 @@
|
|
|
6828
6866
|
pruneArcs: pruneArcs,
|
|
6829
6867
|
replaceLayers: replaceLayers,
|
|
6830
6868
|
replaceLayerContents: replaceLayerContents,
|
|
6869
|
+
mergeOutputLayerIntoDataset: mergeOutputLayerIntoDataset,
|
|
6831
6870
|
transformPoints: transformPoints
|
|
6832
6871
|
});
|
|
6833
6872
|
|
|
@@ -9411,6 +9450,12 @@
|
|
|
9411
9450
|
area: function() {
|
|
9412
9451
|
return _isPlanar ? ctx.planarArea : geom.getSphericalShapeArea(_ids, arcs);
|
|
9413
9452
|
},
|
|
9453
|
+
// area2: function() {
|
|
9454
|
+
// return _isPlanar ? ctx.planarArea : geom.getSphericalShapeArea(_ids, arcs, WGS84.SEMIMINOR_RADIUS);
|
|
9455
|
+
// },
|
|
9456
|
+
// area3: function() {
|
|
9457
|
+
// return _isPlanar ? ctx.planarArea : geom.getSphericalShapeArea(_ids, arcs, WGS84.AUTHALIC_RADIUS);
|
|
9458
|
+
// },
|
|
9414
9459
|
perimeter: function() {
|
|
9415
9460
|
return geom.getShapePerimeter(_ids, arcs);
|
|
9416
9461
|
},
|
|
@@ -13331,14 +13376,14 @@
|
|
|
13331
13376
|
return mergeArcs([arcs, new ArcCollection(nn, xx, yy)]);
|
|
13332
13377
|
}
|
|
13333
13378
|
|
|
13334
|
-
var roundCoord$
|
|
13379
|
+
var roundCoord$2 = getRoundingFunction(0.01);
|
|
13335
13380
|
|
|
13336
13381
|
function stringifyVertex(p) {
|
|
13337
|
-
return ' ' + roundCoord$
|
|
13382
|
+
return ' ' + roundCoord$2(p[0]) + ' ' + roundCoord$2(p[1]);
|
|
13338
13383
|
}
|
|
13339
13384
|
|
|
13340
13385
|
function stringifyCP(p) {
|
|
13341
|
-
return ' ' + roundCoord$
|
|
13386
|
+
return ' ' + roundCoord$2(p[2]) + ' ' + roundCoord$2(p[3]);
|
|
13342
13387
|
}
|
|
13343
13388
|
|
|
13344
13389
|
function isCubicCtrl(p) {
|
|
@@ -13373,40 +13418,9 @@
|
|
|
13373
13418
|
stringifyCP(p2) + stringifyVertex(p2);
|
|
13374
13419
|
}
|
|
13375
13420
|
|
|
13376
|
-
function findArcCenter(p1, p2, degrees) {
|
|
13377
|
-
var p3 = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2], // midpoint betw. p1, p2
|
|
13378
|
-
tan = 1 / Math.tan(degrees / 180 * Math.PI / 2),
|
|
13379
|
-
cp = getAffineTransform(90, tan, [0, 0], p3)(p2[0], p2[1]);
|
|
13380
|
-
return cp;
|
|
13381
|
-
}
|
|
13382
|
-
|
|
13383
|
-
// export function addBezierArcControlPoints(p1, p2, degrees) {
|
|
13384
|
-
function addBezierArcControlPoints(points, degrees) {
|
|
13385
|
-
// source: https://stackoverflow.com/questions/734076/how-to-best-approximate-a-geometrical-arc-with-a-bezier-curve
|
|
13386
|
-
var p2 = points.pop(),
|
|
13387
|
-
p1 = points.pop(),
|
|
13388
|
-
cp = findArcCenter(p1, p2, degrees),
|
|
13389
|
-
xc = cp[0],
|
|
13390
|
-
yc = cp[1],
|
|
13391
|
-
ax = p1[0] - xc,
|
|
13392
|
-
ay = p1[1] - yc,
|
|
13393
|
-
bx = p2[0] - xc,
|
|
13394
|
-
by = p2[1] - yc,
|
|
13395
|
-
q1 = ax * ax + ay * ay,
|
|
13396
|
-
q2 = q1 + ax * bx + ay * by,
|
|
13397
|
-
k2 = 4/3 * (Math.sqrt(2 * q1 * q2) - q2) / (ax * by - ay * bx);
|
|
13398
|
-
|
|
13399
|
-
points.push(p1);
|
|
13400
|
-
points.push([xc + ax - k2 * ay, yc + ay + k2 * ax, 'C']);
|
|
13401
|
-
points.push([xc + bx + k2 * by, yc + by - k2 * bx, 'C']);
|
|
13402
|
-
points.push(p2);
|
|
13403
|
-
}
|
|
13404
|
-
|
|
13405
13421
|
var SvgPathUtils = /*#__PURE__*/Object.freeze({
|
|
13406
13422
|
__proto__: null,
|
|
13407
|
-
stringifyLineStringCoords: stringifyLineStringCoords
|
|
13408
|
-
findArcCenter: findArcCenter,
|
|
13409
|
-
addBezierArcControlPoints: addBezierArcControlPoints
|
|
13423
|
+
stringifyLineStringCoords: stringifyLineStringCoords
|
|
13410
13424
|
});
|
|
13411
13425
|
|
|
13412
13426
|
/* example patterns
|
|
@@ -13683,11 +13697,17 @@
|
|
|
13683
13697
|
type: null,
|
|
13684
13698
|
length: 'number', // e.g. arrow length
|
|
13685
13699
|
rotation: 'number',
|
|
13700
|
+
radius: 'number',
|
|
13701
|
+
'arrow-length': 'number',
|
|
13702
|
+
'arrow-direction': 'number',
|
|
13686
13703
|
'arrow-head-angle': 'number',
|
|
13687
13704
|
'arrow-head-width': 'number',
|
|
13688
13705
|
'arrow-stem-width': 'number',
|
|
13689
13706
|
'arrow-stem-curve': 'number', // degrees of arc
|
|
13690
13707
|
'arrow-stem-taper': 'number',
|
|
13708
|
+
'arrow-stem-length': 'number',
|
|
13709
|
+
'arrow-head-length': 'number',
|
|
13710
|
+
'arrow-min-stem': 'number',
|
|
13691
13711
|
'arrow-scaling': 'number',
|
|
13692
13712
|
effect: null // e.g. "fade"
|
|
13693
13713
|
}, stylePropertyTypes);
|
|
@@ -13729,8 +13749,8 @@
|
|
|
13729
13749
|
if (!isSupportedSvgSymbolProperty(svgName)) {
|
|
13730
13750
|
return;
|
|
13731
13751
|
}
|
|
13732
|
-
var
|
|
13733
|
-
functions[svgName] = getSymbolPropertyAccessor(
|
|
13752
|
+
var val = opts[optName];
|
|
13753
|
+
functions[svgName] = getSymbolPropertyAccessor(val, svgName, lyr);
|
|
13734
13754
|
properties.push(svgName);
|
|
13735
13755
|
});
|
|
13736
13756
|
|
|
@@ -13753,7 +13773,8 @@
|
|
|
13753
13773
|
return /[(){}.+-/*?:&|=\[]/.test(str);
|
|
13754
13774
|
}
|
|
13755
13775
|
|
|
13756
|
-
function getSymbolPropertyAccessor(
|
|
13776
|
+
function getSymbolPropertyAccessor(val, svgName, lyr) {
|
|
13777
|
+
var strVal = String(val).trim();
|
|
13757
13778
|
var typeHint = symbolPropertyTypes[svgName];
|
|
13758
13779
|
var fields = lyr.data ? lyr.data.getFields() : [];
|
|
13759
13780
|
var literalVal = null;
|
|
@@ -13855,7 +13876,6 @@
|
|
|
13855
13876
|
isSvgColor: isSvgColor
|
|
13856
13877
|
});
|
|
13857
13878
|
|
|
13858
|
-
var symbolBuilders = {};
|
|
13859
13879
|
var symbolRenderers = {};
|
|
13860
13880
|
|
|
13861
13881
|
function getTransform(xy, scale) {
|
|
@@ -13868,7 +13888,6 @@
|
|
|
13868
13888
|
|
|
13869
13889
|
var SvgCommon = /*#__PURE__*/Object.freeze({
|
|
13870
13890
|
__proto__: null,
|
|
13871
|
-
symbolBuilders: symbolBuilders,
|
|
13872
13891
|
symbolRenderers: symbolRenderers,
|
|
13873
13892
|
getTransform: getTransform
|
|
13874
13893
|
});
|
|
@@ -18021,9 +18040,9 @@ ${svg}
|
|
|
18021
18040
|
// Updated: don't trim space from tokens like [delimeter= ]
|
|
18022
18041
|
argv = argv.map(function(s) {
|
|
18023
18042
|
if (!/= $/.test(s)) {
|
|
18024
|
-
s =
|
|
18043
|
+
s = utils.rtrim(s);
|
|
18025
18044
|
}
|
|
18026
|
-
s =
|
|
18045
|
+
s = utils.ltrim(s);
|
|
18027
18046
|
return s;
|
|
18028
18047
|
});
|
|
18029
18048
|
argv = argv.filter(function(s) {return s !== '';}); // remove empty tokens
|
|
@@ -19478,6 +19497,13 @@ ${svg}
|
|
|
19478
19497
|
})
|
|
19479
19498
|
.option('target', targetOpt);
|
|
19480
19499
|
|
|
19500
|
+
parser.command('include')
|
|
19501
|
+
.describe('import JS data and functions for use in JS expressions')
|
|
19502
|
+
.option('file', {
|
|
19503
|
+
DEFAULT: true,
|
|
19504
|
+
describe: 'file containing a JS object with key:value pairs to import'
|
|
19505
|
+
});
|
|
19506
|
+
|
|
19481
19507
|
parser.command('inlay')
|
|
19482
19508
|
.describe('inscribe a polygon layer inside another polygon layer')
|
|
19483
19509
|
.option('source', {
|
|
@@ -19790,6 +19816,7 @@ ${svg}
|
|
|
19790
19816
|
})
|
|
19791
19817
|
.option('target', targetOpt);
|
|
19792
19818
|
|
|
19819
|
+
|
|
19793
19820
|
parser.command('simplify')
|
|
19794
19821
|
.validate(validateSimplifyOpts)
|
|
19795
19822
|
.example('Retain 10% of removable vertices\n$ mapshaper input.shp -simplify 10%')
|
|
@@ -20028,26 +20055,90 @@ ${svg}
|
|
|
20028
20055
|
.option('target', targetOpt);
|
|
20029
20056
|
|
|
20030
20057
|
parser.command('symbols')
|
|
20031
|
-
// .describe('
|
|
20058
|
+
// .describe('symbolize points as polygons, circles, stars or arrows')
|
|
20032
20059
|
.option('type', {
|
|
20033
|
-
describe: 'symbol type'
|
|
20060
|
+
describe: 'symbol type (e.g. star, polygon, circle, arrow)'
|
|
20061
|
+
})
|
|
20062
|
+
.option('scale', {
|
|
20063
|
+
describe: 'scale symbols by a factor',
|
|
20064
|
+
type: 'number'
|
|
20065
|
+
})
|
|
20066
|
+
.option('pixel-scale', {
|
|
20067
|
+
describe: 'symbol scale in meters-per-pixel (see polygons option)',
|
|
20068
|
+
type: 'number',
|
|
20069
|
+
})
|
|
20070
|
+
.option('polygons', {
|
|
20071
|
+
describe: 'generate symbols as polygons instead of SVG objects',
|
|
20072
|
+
type: 'flag'
|
|
20073
|
+
})
|
|
20074
|
+
.option('radius', {
|
|
20075
|
+
describe: 'distance from center to farthest point on the symbol',
|
|
20076
|
+
type: 'distance'
|
|
20077
|
+
})
|
|
20078
|
+
.option('sides', {
|
|
20079
|
+
describe: 'sides of a polygon or star symbol',
|
|
20080
|
+
type: 'number'
|
|
20081
|
+
})
|
|
20082
|
+
.option('rotation', {
|
|
20083
|
+
describe: 'rotation of symbol in degrees'
|
|
20084
|
+
})
|
|
20085
|
+
.option('orientation', {
|
|
20086
|
+
describe: 'use orientation=b for a rotated or flipped orientation'
|
|
20087
|
+
})
|
|
20088
|
+
.option('length', {
|
|
20089
|
+
// alias for arrow-length
|
|
20090
|
+
})
|
|
20091
|
+
.option('star-ratio', {
|
|
20092
|
+
describe: 'ratio of major to minor radius of star',
|
|
20093
|
+
type: 'number'
|
|
20094
|
+
})
|
|
20095
|
+
.option('arrow-length', {
|
|
20096
|
+
describe: 'length of arrows in pixels (use with type=arrow)'
|
|
20097
|
+
})
|
|
20098
|
+
.option('arrow-direction', {
|
|
20099
|
+
describe: 'angle off of vertical (-90 = left-pointing arrow)'
|
|
20100
|
+
})
|
|
20101
|
+
.option('arrow-head-angle', {
|
|
20102
|
+
describe: 'angle of tip of arrow (default is 40 degrees)'
|
|
20103
|
+
})
|
|
20104
|
+
.option('arrow-head-width', {
|
|
20105
|
+
describe: 'size of arrow head from side to side'
|
|
20106
|
+
})
|
|
20107
|
+
.option('arrow-head-length', {
|
|
20108
|
+
describe: 'length of arrow head (alternative to arrow-head-angle)'
|
|
20109
|
+
})
|
|
20110
|
+
.option('arrow-head-shape', {
|
|
20111
|
+
// describe: 'options: a b c'
|
|
20112
|
+
})
|
|
20113
|
+
.option('arrow-stem-width', {
|
|
20114
|
+
describe: 'width of stem at its widest point'
|
|
20115
|
+
})
|
|
20116
|
+
.option('arrow-stem-length', {
|
|
20117
|
+
describe: 'alternative to arrow-length'
|
|
20118
|
+
})
|
|
20119
|
+
.option('arrow-stem-taper', {
|
|
20120
|
+
describe: 'factor for tapering the width of the stem'
|
|
20121
|
+
})
|
|
20122
|
+
.option('arrow-stem-curve', {
|
|
20123
|
+
describe: 'curvature in degrees (arrows are straight by default)'
|
|
20124
|
+
})
|
|
20125
|
+
.option('arrow-min-stem', {
|
|
20126
|
+
describe: 'min ratio of stem to total length (for small arrows)',
|
|
20127
|
+
type: 'number'
|
|
20034
20128
|
})
|
|
20035
20129
|
.option('stroke', {})
|
|
20036
20130
|
.option('stroke-width', {})
|
|
20037
|
-
.option('fill', {
|
|
20038
|
-
|
|
20039
|
-
|
|
20131
|
+
.option('fill', {
|
|
20132
|
+
describe: 'symbol fill color'
|
|
20133
|
+
})
|
|
20040
20134
|
.option('effect', {})
|
|
20041
|
-
.option('
|
|
20042
|
-
.option('
|
|
20043
|
-
.option('
|
|
20044
|
-
.option('
|
|
20045
|
-
.option('arrow-stem-taper', {})
|
|
20046
|
-
.option('arrow-scaling', {})
|
|
20047
|
-
.option('where', whereOpt)
|
|
20048
|
-
.option('target', targetOpt);
|
|
20135
|
+
// .option('where', whereOpt)
|
|
20136
|
+
.option('name', nameOpt)
|
|
20137
|
+
.option('target', targetOpt)
|
|
20138
|
+
.option('no-replace', noReplaceOpt);
|
|
20049
20139
|
// .option('name', nameOpt);
|
|
20050
20140
|
|
|
20141
|
+
|
|
20051
20142
|
parser.command('target')
|
|
20052
20143
|
.describe('set active layer (or layers)')
|
|
20053
20144
|
.option('target', {
|
|
@@ -20198,13 +20289,6 @@ ${svg}
|
|
|
20198
20289
|
})
|
|
20199
20290
|
.option('name', nameOpt);
|
|
20200
20291
|
|
|
20201
|
-
parser.command('include')
|
|
20202
|
-
.describe('import JS data and functions for use in JS expressions')
|
|
20203
|
-
.option('file', {
|
|
20204
|
-
DEFAULT: true,
|
|
20205
|
-
describe: 'file containing a JS object with key:value pairs to import'
|
|
20206
|
-
});
|
|
20207
|
-
|
|
20208
20292
|
parser.command('fuzzy-join')
|
|
20209
20293
|
.describe('join points to polygons, with data fill and fuzzy match')
|
|
20210
20294
|
.option('source', {
|
|
@@ -21203,12 +21287,13 @@ ${svg}
|
|
|
21203
21287
|
if (this instanceof ShpReader === false) {
|
|
21204
21288
|
return new ShpReader(shpSrc, shxSrc);
|
|
21205
21289
|
}
|
|
21206
|
-
|
|
21207
21290
|
var shpFile = utils.isString(shpSrc) ? new FileReader(shpSrc) : new BufferReader(shpSrc);
|
|
21208
21291
|
var header = parseHeader(shpFile.readToBinArray(0, 100));
|
|
21209
|
-
var
|
|
21210
|
-
var
|
|
21211
|
-
var
|
|
21292
|
+
var shpType = header.type;
|
|
21293
|
+
var shpOffset = 100; // used when reading .shp without .shx
|
|
21294
|
+
var recordCount = 0;
|
|
21295
|
+
var badRecordNumberCount = 0;
|
|
21296
|
+
var RecordClass = new ShpRecordClass(shpType);
|
|
21212
21297
|
var shxBin, shxFile;
|
|
21213
21298
|
|
|
21214
21299
|
if (shxSrc) {
|
|
@@ -21216,8 +21301,6 @@ ${svg}
|
|
|
21216
21301
|
shxBin = shxFile.readToBinArray(0, shxFile.size()).bigEndian();
|
|
21217
21302
|
}
|
|
21218
21303
|
|
|
21219
|
-
reset();
|
|
21220
|
-
|
|
21221
21304
|
this.header = function() {
|
|
21222
21305
|
return header;
|
|
21223
21306
|
};
|
|
@@ -21235,62 +21318,35 @@ ${svg}
|
|
|
21235
21318
|
|
|
21236
21319
|
// Iterator interface for reading shape records
|
|
21237
21320
|
this.nextShape = function() {
|
|
21238
|
-
var shape
|
|
21321
|
+
var shape;
|
|
21322
|
+
if (!shpFile) {
|
|
21323
|
+
error('Tried to read from a used ShpReader');
|
|
21324
|
+
// return null; // this reader was already used
|
|
21325
|
+
}
|
|
21326
|
+
shape = readNextShape(recordCount);
|
|
21239
21327
|
if (!shape) {
|
|
21240
|
-
|
|
21241
|
-
|
|
21242
|
-
// ne_10m_admin_0_boundary_lines_land.shp
|
|
21243
|
-
// ne_110m_admin_0_scale_rank.shp
|
|
21244
|
-
verbose("Skipped over " + skippedBytes + " non-data bytes in the .shp file.");
|
|
21245
|
-
}
|
|
21246
|
-
shpFile.close();
|
|
21247
|
-
reset();
|
|
21328
|
+
done();
|
|
21329
|
+
return null;
|
|
21248
21330
|
}
|
|
21331
|
+
recordCount++;
|
|
21249
21332
|
return shape;
|
|
21250
21333
|
};
|
|
21251
21334
|
|
|
21252
|
-
|
|
21253
|
-
|
|
21254
|
-
|
|
21255
|
-
|
|
21256
|
-
|
|
21257
|
-
|
|
21258
|
-
|
|
21259
|
-
if (offset > shpOffset) {
|
|
21260
|
-
skippedBytes += offset - shpOffset;
|
|
21261
|
-
}
|
|
21262
|
-
} else {
|
|
21263
|
-
offset = shpOffset;
|
|
21264
|
-
}
|
|
21265
|
-
shape = readShapeAtOffset(offset);
|
|
21266
|
-
if (!shape) {
|
|
21267
|
-
// Some in-the-wild .shp files contain junk bytes between records. This
|
|
21268
|
-
// is a problem if the .shx index file is not present.
|
|
21269
|
-
// Here, we try to scan past the junk to find the next record.
|
|
21270
|
-
shape = huntForNextShape(offset, expectedId);
|
|
21271
|
-
}
|
|
21272
|
-
if (shape) {
|
|
21273
|
-
if (shape.id < expectedId) {
|
|
21274
|
-
message("Found a Shapefile record with the same id as a previous record (" + shape.id + ") -- skipping.");
|
|
21275
|
-
return readNextShape();
|
|
21276
|
-
} else if (shape.id > expectedId) {
|
|
21277
|
-
stop("Shapefile contains an out-of-sequence record. Possible data corruption -- bailing.");
|
|
21278
|
-
}
|
|
21279
|
-
recordCount++;
|
|
21280
|
-
}
|
|
21281
|
-
return shape || null;
|
|
21335
|
+
// Returns a shape record or null if no more shapes can be read
|
|
21336
|
+
// i: Expected 0-based index of the next record
|
|
21337
|
+
//
|
|
21338
|
+
function readNextShape(i) {
|
|
21339
|
+
return shxBin ?
|
|
21340
|
+
readIndexedShape(shpFile, shxBin, i) :
|
|
21341
|
+
readNonIndexedShape(shpFile, shpOffset, i);
|
|
21282
21342
|
}
|
|
21283
21343
|
|
|
21284
21344
|
function done() {
|
|
21285
|
-
|
|
21286
|
-
|
|
21287
|
-
|
|
21288
|
-
|
|
21289
|
-
|
|
21290
|
-
function reset() {
|
|
21291
|
-
shpOffset = 100;
|
|
21292
|
-
skippedBytes = 0;
|
|
21293
|
-
recordCount = 0;
|
|
21345
|
+
shpFile.close();
|
|
21346
|
+
shpFile = shxFile = shxBin = null;
|
|
21347
|
+
if (badRecordNumberCount > 0) {
|
|
21348
|
+
message(`Warning: ${badRecordNumberCount}/${recordCount} features have non-standard record numbers in the .shp file.`);
|
|
21349
|
+
}
|
|
21294
21350
|
}
|
|
21295
21351
|
|
|
21296
21352
|
function parseHeader(bin) {
|
|
@@ -21319,47 +21375,81 @@ ${svg}
|
|
|
21319
21375
|
return header;
|
|
21320
21376
|
}
|
|
21321
21377
|
|
|
21322
|
-
function readShapeAtOffset(offset) {
|
|
21323
|
-
var shape = null,
|
|
21324
|
-
recordSize, recordType, recordId, goodSize, goodType, bin;
|
|
21325
21378
|
|
|
21326
|
-
|
|
21327
|
-
|
|
21328
|
-
|
|
21329
|
-
|
|
21330
|
-
|
|
21331
|
-
|
|
21332
|
-
|
|
21333
|
-
|
|
21334
|
-
|
|
21335
|
-
|
|
21336
|
-
|
|
21337
|
-
|
|
21338
|
-
|
|
21379
|
+
function readShapeAtOffset(shpFile, offset) {
|
|
21380
|
+
var fileSize = shpFile.size();
|
|
21381
|
+
if (offset + 12 > fileSize) return null; // reached end-of-file
|
|
21382
|
+
var bin = shpFile.readToBinArray(offset, 12);
|
|
21383
|
+
var recordId = bin.bigEndian().readUint32();
|
|
21384
|
+
// record size is bytes in content section + 8 header bytes
|
|
21385
|
+
var recordSize = bin.readUint32() * 2 + 8;
|
|
21386
|
+
var recordType = bin.littleEndian().readUint32();
|
|
21387
|
+
var goodSize = offset + recordSize <= fileSize && recordSize >= 12;
|
|
21388
|
+
var goodType = recordType === 0 || recordType == shpType;
|
|
21389
|
+
if (!goodSize || !goodType) {
|
|
21390
|
+
return null;
|
|
21391
|
+
}
|
|
21392
|
+
bin = shpFile.readToBinArray(offset, recordSize);
|
|
21393
|
+
return new RecordClass(bin, recordSize);
|
|
21394
|
+
}
|
|
21395
|
+
|
|
21396
|
+
function readIndexedShape(shpFile, shxBin, i) {
|
|
21397
|
+
if (shxBin.size() <= 100 + i * 8) return null; // done
|
|
21398
|
+
shxBin.position(100 + i * 8);
|
|
21399
|
+
var expectedId = i + 1;
|
|
21400
|
+
var offset = shxBin.readUint32() * 2;
|
|
21401
|
+
var recLen = shxBin.readUint32() * 2; // TODO: match this to recLen in .shp
|
|
21402
|
+
var shape = readShapeAtOffset(shpFile, offset);
|
|
21403
|
+
if (!shape) {
|
|
21404
|
+
stop('Index of Shapefile record', expectedId, 'in the .shx file is invalid.');
|
|
21405
|
+
}
|
|
21406
|
+
if (shape.id != expectedId) {
|
|
21407
|
+
badRecordNumberCount++;
|
|
21408
|
+
verbose(`Warning: A feature has a different record number in .shx (${expectedId}) and .shp (${shape.id}).`);
|
|
21339
21409
|
}
|
|
21410
|
+
// TODO: consider printing verbose message if a .shp file contains garbage bytes
|
|
21411
|
+
// example files:
|
|
21412
|
+
// ne_10m_admin_0_boundary_lines_land.shp
|
|
21413
|
+
// ne_110m_admin_0_scale_rank.shp
|
|
21340
21414
|
return shape;
|
|
21341
21415
|
}
|
|
21342
21416
|
|
|
21343
|
-
//
|
|
21344
|
-
//
|
|
21345
|
-
|
|
21346
|
-
|
|
21417
|
+
// The Shapefile specification does not require records to be densely packed or
|
|
21418
|
+
// in consecutive sequence in the .shp file. This is a problem when the .shx
|
|
21419
|
+
// index file is not present.
|
|
21420
|
+
//
|
|
21421
|
+
// Here, we try to scan past any invalid content to find the next record.
|
|
21422
|
+
// Records are required to be in sequential order.
|
|
21423
|
+
//
|
|
21424
|
+
function readNonIndexedShape(shpFile, start, i) {
|
|
21425
|
+
var expectedId = i + 1, // Shapefile ids are 1-based
|
|
21426
|
+
offset = start,
|
|
21427
|
+
fileSize = shpFile.size(),
|
|
21347
21428
|
shape = null,
|
|
21348
|
-
bin, recordId, recordType,
|
|
21349
|
-
while (offset + 12 <=
|
|
21429
|
+
bin, recordId, recordType, isValidType;
|
|
21430
|
+
while (offset + 12 <= fileSize) {
|
|
21350
21431
|
bin = shpFile.readToBinArray(offset, 12);
|
|
21351
21432
|
recordId = bin.bigEndian().readUint32();
|
|
21352
21433
|
recordType = bin.littleEndian().skipBytes(4).readUint32();
|
|
21353
|
-
|
|
21354
|
-
|
|
21355
|
-
|
|
21356
|
-
|
|
21434
|
+
isValidType = recordType == shpType || recordType === 0;
|
|
21435
|
+
if (!isValidType || recordId != expectedId && recordType === 0) {
|
|
21436
|
+
offset += 4; // keep scanning -- try next integer position
|
|
21437
|
+
continue;
|
|
21438
|
+
}
|
|
21439
|
+
shape = readShapeAtOffset(shpFile, offset);
|
|
21440
|
+
if (!shape) break; // probably ran into end of file
|
|
21441
|
+
shpOffset = offset + shape.byteLength; // update
|
|
21442
|
+
if (recordId == expectedId) break; // found an apparently valid shape
|
|
21443
|
+
if (recordId < expectedId) {
|
|
21444
|
+
message("Found a Shapefile record with the same id as a previous record (" + shape.id + ") -- skipping.");
|
|
21445
|
+
offset += shape.byteLength;
|
|
21446
|
+
} else {
|
|
21447
|
+
stop("Shapefile contains an out-of-sequence record. Possible data corruption -- bailing.");
|
|
21357
21448
|
}
|
|
21358
|
-
offset += 4; // try next integer position
|
|
21359
21449
|
}
|
|
21360
|
-
|
|
21361
|
-
|
|
21362
|
-
|
|
21450
|
+
if (shape && offset > start) {
|
|
21451
|
+
verbose("Skipped over " + (offset - start) + " non-data bytes in the .shp file.");
|
|
21452
|
+
}
|
|
21363
21453
|
return shape;
|
|
21364
21454
|
}
|
|
21365
21455
|
}
|
|
@@ -21368,22 +21458,6 @@ ${svg}
|
|
|
21368
21458
|
return this.header().type;
|
|
21369
21459
|
};
|
|
21370
21460
|
|
|
21371
|
-
ShpReader.prototype.getCounts = function() {
|
|
21372
|
-
var counts = {
|
|
21373
|
-
nullCount: 0,
|
|
21374
|
-
partCount: 0,
|
|
21375
|
-
shapeCount: 0,
|
|
21376
|
-
pointCount: 0
|
|
21377
|
-
};
|
|
21378
|
-
this.forEachShape(function(shp) {
|
|
21379
|
-
if (shp.isNull) counts.nullCount++;
|
|
21380
|
-
counts.pointCount += shp.pointCount;
|
|
21381
|
-
counts.partCount += shp.partCount;
|
|
21382
|
-
counts.shapeCount++;
|
|
21383
|
-
});
|
|
21384
|
-
return counts;
|
|
21385
|
-
};
|
|
21386
|
-
|
|
21387
21461
|
// Apply snapping, remove duplicate coords and clean up defective paths in a dataset
|
|
21388
21462
|
// Assumes that any CRS info has been added to the dataset
|
|
21389
21463
|
// @opts: import options
|
|
@@ -26192,7 +26266,6 @@ ${svg}
|
|
|
26192
26266
|
if (constTol) return constTol;
|
|
26193
26267
|
return constTol ? constTol : meterDist * pctOfRadius;
|
|
26194
26268
|
};
|
|
26195
|
-
|
|
26196
26269
|
}
|
|
26197
26270
|
|
|
26198
26271
|
function getBufferDistanceFunction(lyr, dataset, opts) {
|
|
@@ -27365,7 +27438,7 @@ ${svg}
|
|
|
27365
27438
|
cmd.buffer = makeBufferLayer;
|
|
27366
27439
|
|
|
27367
27440
|
function makeBufferLayer(lyr, dataset, opts) {
|
|
27368
|
-
var dataset2
|
|
27441
|
+
var dataset2;
|
|
27369
27442
|
if (lyr.geometry_type == 'point') {
|
|
27370
27443
|
dataset2 = makePointBuffer(lyr, dataset, opts);
|
|
27371
27444
|
} else if (lyr.geometry_type == 'polyline') {
|
|
@@ -27375,13 +27448,9 @@ ${svg}
|
|
|
27375
27448
|
} else {
|
|
27376
27449
|
stop("Unsupported geometry type");
|
|
27377
27450
|
}
|
|
27378
|
-
|
|
27379
|
-
lyr2 =
|
|
27380
|
-
|
|
27381
|
-
if (lyr.data && !lyr2.data) {
|
|
27382
|
-
lyr2.data = opts.no_replace ? lyr.data.clone() : lyr.data;
|
|
27383
|
-
}
|
|
27384
|
-
return outputLayers;
|
|
27451
|
+
|
|
27452
|
+
var lyr2 = mergeOutputLayerIntoDataset(lyr, dataset, dataset2, opts);
|
|
27453
|
+
return [lyr2];
|
|
27385
27454
|
}
|
|
27386
27455
|
|
|
27387
27456
|
// TODO: support three or more stops
|
|
@@ -33966,7 +34035,7 @@ ${svg}
|
|
|
33966
34035
|
|
|
33967
34036
|
function createMeridianPart(x, ymin, ymax) {
|
|
33968
34037
|
var coords = densifyPathByInterval([[x, ymin], [x, ymax]], precision);
|
|
33969
|
-
meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord(x)}));
|
|
34038
|
+
meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord$1(x)}));
|
|
33970
34039
|
}
|
|
33971
34040
|
|
|
33972
34041
|
function createParallel(y) {
|
|
@@ -33976,7 +34045,7 @@ ${svg}
|
|
|
33976
34045
|
}
|
|
33977
34046
|
|
|
33978
34047
|
// remove tiny offsets
|
|
33979
|
-
function roundCoord(x) {
|
|
34048
|
+
function roundCoord$1(x) {
|
|
33980
34049
|
return +x.toFixed(3) || 0;
|
|
33981
34050
|
}
|
|
33982
34051
|
|
|
@@ -36627,6 +36696,99 @@ ${svg}
|
|
|
36627
36696
|
parseScalebarLabelToKm: parseScalebarLabelToKm
|
|
36628
36697
|
});
|
|
36629
36698
|
|
|
36699
|
+
cmd.shape = function(targetDataset, opts) {
|
|
36700
|
+
var geojson, dataset;
|
|
36701
|
+
if (opts.coordinates) {
|
|
36702
|
+
geojson = makeShapeFromCoords(opts);
|
|
36703
|
+
} else if (opts.type == 'circle') {
|
|
36704
|
+
geojson = makeCircle(opts);
|
|
36705
|
+
} else if (opts.type == 'rectangle' && opts.bbox) {
|
|
36706
|
+
geojson = getRectangleGeoJSON(opts);
|
|
36707
|
+
} else {
|
|
36708
|
+
stop('Missing coordinates parameter');
|
|
36709
|
+
}
|
|
36710
|
+
// TODO: project shape if targetDataset is projected
|
|
36711
|
+
dataset = importGeoJSON(geojson, {});
|
|
36712
|
+
if (opts.rotation) {
|
|
36713
|
+
rotateDatasetCoords(dataset, opts.rotation);
|
|
36714
|
+
}
|
|
36715
|
+
dataset.layers[0].name = opts.name || opts.type || 'shape';
|
|
36716
|
+
return dataset;
|
|
36717
|
+
};
|
|
36718
|
+
|
|
36719
|
+
function getRectangleGeoJSON(opts) {
|
|
36720
|
+
var bbox = opts.bbox,
|
|
36721
|
+
xmin = bbox[0],
|
|
36722
|
+
ymin = bbox[1],
|
|
36723
|
+
xmax = bbox[2],
|
|
36724
|
+
ymax = bbox[3],
|
|
36725
|
+
interval = 0.5,
|
|
36726
|
+
coords = [],
|
|
36727
|
+
type = opts.geometry == 'polyline' ? 'LineString' : 'Polygon';
|
|
36728
|
+
addSide(xmin, ymin, xmin, ymax);
|
|
36729
|
+
addSide(xmin, ymax, xmax, ymax);
|
|
36730
|
+
addSide(xmax, ymax, xmax, ymin);
|
|
36731
|
+
addSide(xmax, ymin, xmin, ymin);
|
|
36732
|
+
coords.push([xmin, ymin]);
|
|
36733
|
+
return {
|
|
36734
|
+
type: type,
|
|
36735
|
+
coordinates: type == 'Polygon' ? [coords] : coords
|
|
36736
|
+
};
|
|
36737
|
+
|
|
36738
|
+
function addSide(x1, y1, x2, y2) {
|
|
36739
|
+
var dx = x2 - x1,
|
|
36740
|
+
dy = y2 - y1,
|
|
36741
|
+
n = Math.ceil(Math.max(Math.abs(dx) / interval, Math.abs(dy) / interval)),
|
|
36742
|
+
xint = dx / n,
|
|
36743
|
+
yint = dy / n;
|
|
36744
|
+
for (var i=0; i<n; i++) {
|
|
36745
|
+
coords.push([x1 + i * xint, y1 + i * yint]);
|
|
36746
|
+
}
|
|
36747
|
+
}
|
|
36748
|
+
}
|
|
36749
|
+
|
|
36750
|
+
function makeCircle(opts) {
|
|
36751
|
+
if (opts.radius > 0 === false && opts.radius_angle > 0 === false) {
|
|
36752
|
+
stop('Missing required radius parameter.');
|
|
36753
|
+
}
|
|
36754
|
+
var cp = opts.center || [0, 0];
|
|
36755
|
+
var radius = opts.radius || getCircleRadiusFromAngle(getCRS('wgs84'), opts.radius_angle);
|
|
36756
|
+
return getCircleGeoJSON(cp, radius, null, {geometry_type : opts.geometry || 'polygon'});
|
|
36757
|
+
}
|
|
36758
|
+
|
|
36759
|
+
function makeShapeFromCoords(opts) {
|
|
36760
|
+
var coordinates = [];
|
|
36761
|
+
var offsets = opts.offsets || [];
|
|
36762
|
+
var coords = opts.coordinates;
|
|
36763
|
+
var type, i, x, y;
|
|
36764
|
+
if (coords.length >= 2 === false) {
|
|
36765
|
+
stop('Invalid coordinates parameter.');
|
|
36766
|
+
}
|
|
36767
|
+
for (i=0; i<coords.length; i+= 2) {
|
|
36768
|
+
x = coords[i];
|
|
36769
|
+
y = coords[i + 1];
|
|
36770
|
+
coordinates.push([x, y]);
|
|
36771
|
+
}
|
|
36772
|
+
for (i=0; i<offsets.length; i+=2) {
|
|
36773
|
+
x += offsets[i];
|
|
36774
|
+
y += offsets[i + 1];
|
|
36775
|
+
coordinates.push([x, y]);
|
|
36776
|
+
}
|
|
36777
|
+
if (GeoJSON.pathIsRing(coordinates)) {
|
|
36778
|
+
type = 'Polygon';
|
|
36779
|
+
} else if (opts.closed && coordinates.length >= 3) {
|
|
36780
|
+
type = 'Polygon';
|
|
36781
|
+
coordinates.push(coordinates[0]);
|
|
36782
|
+
} else {
|
|
36783
|
+
type = 'LineString';
|
|
36784
|
+
}
|
|
36785
|
+
return {
|
|
36786
|
+
type: type,
|
|
36787
|
+
coordinates: type == 'Polygon' ? [coordinates] : coordinates
|
|
36788
|
+
};
|
|
36789
|
+
|
|
36790
|
+
}
|
|
36791
|
+
|
|
36630
36792
|
function calcSimplifyStats(arcs, use3D) {
|
|
36631
36793
|
var distSq = use3D ? pointSegGeoDistSq : geom.pointSegDistSq,
|
|
36632
36794
|
calcAngle = use3D ? geom.signedAngleSph : geom.signedAngle,
|
|
@@ -37714,144 +37876,378 @@ ${svg}
|
|
|
37714
37876
|
});
|
|
37715
37877
|
};
|
|
37716
37878
|
|
|
37717
|
-
|
|
37718
|
-
var len = 'length' in d ? d.length : 10;
|
|
37719
|
-
var filled = 'fill' in d;
|
|
37720
|
-
return filled ? getFilledArrow(d, len) : getStickArrow(d, len);
|
|
37721
|
-
};
|
|
37879
|
+
var roundCoord = getRoundingFunction(0.01);
|
|
37722
37880
|
|
|
37723
|
-
function
|
|
37724
|
-
|
|
37725
|
-
|
|
37726
|
-
|
|
37727
|
-
|
|
37728
|
-
|
|
37729
|
-
}
|
|
37881
|
+
function forEachSymbolCoord(coords, cb) {
|
|
37882
|
+
var isPoint = coords && utils.isNumber(coords[0]);
|
|
37883
|
+
var isNested = !isPoint && coords && Array.isArray(coords[0]);
|
|
37884
|
+
if (isPoint) return cb(coords);
|
|
37885
|
+
for (var i=0; i<coords.length; i++) {
|
|
37886
|
+
if (isNested) forEachSymbolCoord(coords[i], cb);
|
|
37887
|
+
}
|
|
37730
37888
|
}
|
|
37731
37889
|
|
|
37732
|
-
function
|
|
37733
|
-
|
|
37734
|
-
|
|
37735
|
-
|
|
37736
|
-
fill: d.fill || 'magenta'
|
|
37737
|
-
};
|
|
37890
|
+
function flipY(coords) {
|
|
37891
|
+
forEachSymbolCoord(coords, function(p) {
|
|
37892
|
+
p[1] = -p[1];
|
|
37893
|
+
});
|
|
37738
37894
|
}
|
|
37739
37895
|
|
|
37740
|
-
function
|
|
37741
|
-
|
|
37742
|
-
|
|
37743
|
-
|
|
37744
|
-
|
|
37745
|
-
}
|
|
37746
|
-
return 1;
|
|
37896
|
+
function scaleAndShiftCoords(coords, scale, shift) {
|
|
37897
|
+
forEachSymbolCoord(coords, function(xy) {
|
|
37898
|
+
xy[0] = xy[0] * scale + shift[0];
|
|
37899
|
+
xy[1] = xy[1] * scale + shift[1];
|
|
37900
|
+
});
|
|
37747
37901
|
}
|
|
37748
37902
|
|
|
37749
|
-
function
|
|
37750
|
-
|
|
37751
|
-
|
|
37752
|
-
|
|
37753
|
-
|
|
37754
|
-
return [dx, dy];
|
|
37903
|
+
function roundCoordsForSVG(coords) {
|
|
37904
|
+
forEachSymbolCoord(coords, function(p) {
|
|
37905
|
+
p[0] = roundCoord(p[0]);
|
|
37906
|
+
p[1] = roundCoord(p[1]);
|
|
37907
|
+
});
|
|
37755
37908
|
}
|
|
37756
37909
|
|
|
37757
|
-
function
|
|
37758
|
-
|
|
37910
|
+
function rotateCoords(coords, rotation) {
|
|
37911
|
+
if (!rotation) return;
|
|
37912
|
+
var f = getAffineTransform(rotation, 1, [0, 0], [0, 0]);
|
|
37913
|
+
forEachSymbolCoord(coords, function(p) {
|
|
37914
|
+
var p2 = f(p[0], p[1]);
|
|
37915
|
+
p[0] = p2[0];
|
|
37916
|
+
p[1] = p2[1];
|
|
37917
|
+
});
|
|
37759
37918
|
}
|
|
37760
37919
|
|
|
37761
|
-
function
|
|
37762
|
-
var
|
|
37763
|
-
|
|
37764
|
-
|
|
37765
|
-
|
|
37766
|
-
|
|
37767
|
-
var headWidth = unscaledHeadWidth * scale;
|
|
37768
|
-
var headLen = unscaledHeadLen * scale;
|
|
37769
|
-
var tip = getStickArrowTip(totalLen, curve);
|
|
37770
|
-
var stem = [[0, 0], tip.concat()];
|
|
37771
|
-
if (curve) {
|
|
37772
|
-
addBezierArcControlPoints(stem, curve);
|
|
37773
|
-
}
|
|
37774
|
-
if (!headLen) return [stem];
|
|
37775
|
-
var head = [addPoints([-headWidth / 2, -headLen], tip), tip.concat(), addPoints([headWidth / 2, -headLen], tip)];
|
|
37920
|
+
function findArcCenter(p1, p2, degrees) {
|
|
37921
|
+
var p3 = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2], // midpoint betw. p1, p2
|
|
37922
|
+
tan = 1 / Math.tan(degrees / 180 * Math.PI / 2),
|
|
37923
|
+
cp = getAffineTransform(90, tan, [0, 0], p3)(p2[0], p2[1]);
|
|
37924
|
+
return cp;
|
|
37925
|
+
}
|
|
37776
37926
|
|
|
37777
|
-
|
|
37778
|
-
|
|
37779
|
-
|
|
37927
|
+
// export function addBezierArcControlPoints(p1, p2, degrees) {
|
|
37928
|
+
function addBezierArcControlPoints(points, degrees) {
|
|
37929
|
+
// source: https://stackoverflow.com/questions/734076/how-to-best-approximate-a-geometrical-arc-with-a-bezier-curve
|
|
37930
|
+
var p2 = points.pop(),
|
|
37931
|
+
p1 = points.pop(),
|
|
37932
|
+
cp = findArcCenter(p1, p2, degrees),
|
|
37933
|
+
xc = cp[0],
|
|
37934
|
+
yc = cp[1],
|
|
37935
|
+
ax = p1[0] - xc,
|
|
37936
|
+
ay = p1[1] - yc,
|
|
37937
|
+
bx = p2[0] - xc,
|
|
37938
|
+
by = p2[1] - yc,
|
|
37939
|
+
q1 = ax * ax + ay * ay,
|
|
37940
|
+
q2 = q1 + ax * bx + ay * by,
|
|
37941
|
+
k2 = 4/3 * (Math.sqrt(2 * q1 * q2) - q2) / (ax * by - ay * bx);
|
|
37942
|
+
|
|
37943
|
+
points.push(p1);
|
|
37944
|
+
points.push([xc + ax - k2 * ay, yc + ay + k2 * ax, 'C']);
|
|
37945
|
+
points.push([xc + bx + k2 * by, yc + by - k2 * bx, 'C']);
|
|
37946
|
+
points.push(p2);
|
|
37780
37947
|
}
|
|
37781
37948
|
|
|
37782
|
-
function
|
|
37783
|
-
|
|
37784
|
-
|
|
37949
|
+
// export function getStickArrowCoords(d, totalLen) {
|
|
37950
|
+
// var minStemRatio = getMinStemRatio(d);
|
|
37951
|
+
// var headAngle = d['arrow-head-angle'] || 90;
|
|
37952
|
+
// var curve = d['arrow-stem-curve'] || 0;
|
|
37953
|
+
// var unscaledHeadWidth = d['arrow-head-width'] || 9;
|
|
37954
|
+
// var unscaledHeadLen = getHeadLength(unscaledHeadWidth, headAngle);
|
|
37955
|
+
// var scale = getScale(totalLen, unscaledHeadLen, minStemRatio);
|
|
37956
|
+
// var headWidth = unscaledHeadWidth * scale;
|
|
37957
|
+
// var headLen = unscaledHeadLen * scale;
|
|
37958
|
+
// var tip = getStickArrowTip(totalLen, curve);
|
|
37959
|
+
// var stem = [[0, 0], tip.concat()];
|
|
37960
|
+
// if (curve) {
|
|
37961
|
+
// addBezierArcControlPoints(stem, curve);
|
|
37962
|
+
// }
|
|
37963
|
+
// if (!headLen) return [stem];
|
|
37964
|
+
// var head = [addPoints([-headWidth / 2, -headLen], tip), tip.concat(), addPoints([headWidth / 2, -headLen], tip)];
|
|
37965
|
+
|
|
37966
|
+
// rotateCoords(stem, d.rotation);
|
|
37967
|
+
// rotateCoords(head, d.rotation);
|
|
37968
|
+
// return [stem, head];
|
|
37969
|
+
// }
|
|
37970
|
+
|
|
37971
|
+
function getMinStemRatio(d) {
|
|
37972
|
+
return d['arrow-min-stem'] >= 0 ? d['arrow-min-stem'] : 0.4;
|
|
37785
37973
|
}
|
|
37786
37974
|
|
|
37787
|
-
function getFilledArrowCoords(d
|
|
37788
|
-
var
|
|
37975
|
+
function getFilledArrowCoords(d) {
|
|
37976
|
+
var totalLen = d['arrow-length'] || d.radius || d.length || d.r || 0,
|
|
37977
|
+
direction = d.rotation || d['arrow-direction'] || 0,
|
|
37789
37978
|
unscaledStemWidth = d['arrow-stem-width'] || 2,
|
|
37790
37979
|
unscaledHeadWidth = d['arrow-head-width'] || unscaledStemWidth * 3,
|
|
37791
|
-
unscaledHeadLen = getHeadLength(unscaledHeadWidth,
|
|
37792
|
-
scale = getScale(totalLen, unscaledHeadLen), // scale down small arrows
|
|
37793
|
-
headWidth = unscaledHeadWidth * scale,
|
|
37794
|
-
headLen = unscaledHeadLen * scale,
|
|
37795
|
-
stemWidth = unscaledStemWidth * scale,
|
|
37980
|
+
unscaledHeadLen = getHeadLength(unscaledHeadWidth, d),
|
|
37796
37981
|
stemTaper = d['arrow-stem-taper'] || 0,
|
|
37797
|
-
|
|
37982
|
+
stemCurve = d['arrow-stem-curve'] || 0;
|
|
37983
|
+
|
|
37984
|
+
var headLen, headWidth, stemLen, stemWidth;
|
|
37985
|
+
|
|
37986
|
+
var scale = 1;
|
|
37987
|
+
|
|
37988
|
+
if (totalLen > 0) {
|
|
37989
|
+
scale = getScale(totalLen, unscaledHeadLen, getMinStemRatio(d));
|
|
37990
|
+
headWidth = unscaledHeadWidth * scale;
|
|
37991
|
+
headLen = unscaledHeadLen * scale;
|
|
37992
|
+
stemWidth = unscaledStemWidth * scale;
|
|
37993
|
+
stemLen = totalLen - headLen;
|
|
37994
|
+
|
|
37995
|
+
} else {
|
|
37996
|
+
headWidth = unscaledHeadWidth;
|
|
37997
|
+
headLen = unscaledHeadLen;
|
|
37998
|
+
stemWidth = unscaledStemWidth;
|
|
37999
|
+
stemLen = d['arrow-stem-length'] || 0;
|
|
38000
|
+
totalLen = headLen + stemLen;
|
|
38001
|
+
}
|
|
38002
|
+
|
|
38003
|
+
if (totalLen > 0 === false) return null;
|
|
38004
|
+
|
|
38005
|
+
var coords;
|
|
37798
38006
|
|
|
37799
38007
|
var headDx = headWidth / 2,
|
|
37800
38008
|
stemDx = stemWidth / 2,
|
|
37801
38009
|
baseDx = stemDx * (1 - stemTaper);
|
|
37802
38010
|
|
|
37803
|
-
|
|
38011
|
+
if (unscaledHeadWidth < unscaledStemWidth) {
|
|
38012
|
+
stop('Arrow head must be at least as wide as the stem.');
|
|
38013
|
+
}
|
|
38014
|
+
|
|
38015
|
+
if (!stemCurve || Math.abs(stemCurve) > 90) {
|
|
38016
|
+
coords = [[baseDx, 0], [stemDx, stemLen], [headDx, stemLen], [0, stemLen + headLen],
|
|
37804
38017
|
[-headDx, stemLen], [-stemDx, stemLen], [-baseDx, 0], [baseDx, 0]];
|
|
38018
|
+
} else {
|
|
38019
|
+
if (direction > 0) stemCurve = -stemCurve;
|
|
38020
|
+
coords = getCurvedArrowCoords(stemLen, headLen, stemCurve, stemDx, headDx, baseDx);
|
|
38021
|
+
}
|
|
37805
38022
|
|
|
37806
|
-
|
|
38023
|
+
rotateCoords(coords, direction);
|
|
37807
38024
|
return [coords];
|
|
37808
38025
|
}
|
|
37809
38026
|
|
|
37810
|
-
|
|
37811
|
-
|
|
37812
|
-
var
|
|
37813
|
-
|
|
37814
|
-
|
|
37815
|
-
|
|
37816
|
-
|
|
37817
|
-
|
|
38027
|
+
|
|
38028
|
+
function getScale(totalLen, headLen, minStemRatio) {
|
|
38029
|
+
var maxHeadPct = 1 - minStemRatio;
|
|
38030
|
+
var headPct = headLen / totalLen;
|
|
38031
|
+
if (headPct > maxHeadPct) {
|
|
38032
|
+
return maxHeadPct / headPct;
|
|
38033
|
+
}
|
|
38034
|
+
return 1;
|
|
37818
38035
|
}
|
|
37819
38036
|
|
|
37820
|
-
//
|
|
37821
|
-
|
|
37822
|
-
|
|
37823
|
-
|
|
37824
|
-
|
|
37825
|
-
|
|
37826
|
-
|
|
37827
|
-
|
|
38037
|
+
// function getStickArrowTip(totalLen, curve) {
|
|
38038
|
+
// // curve/2 intersects the arrowhead at 90deg (trigonometry)
|
|
38039
|
+
// var theta = Math.abs(curve/2) / 180 * Math.PI;
|
|
38040
|
+
// var dx = totalLen * Math.sin(theta) * (curve > 0 ? -1 : 1);
|
|
38041
|
+
// var dy = totalLen * Math.cos(theta);
|
|
38042
|
+
// return [dx, dy];
|
|
38043
|
+
// }
|
|
38044
|
+
|
|
38045
|
+
function addPoints(a, b) {
|
|
38046
|
+
return [a[0] + b[0], a[1] + b[1]];
|
|
38047
|
+
}
|
|
38048
|
+
|
|
38049
|
+
|
|
38050
|
+
function getHeadLength(headWidth, d) {
|
|
38051
|
+
var headLength = d['arrow-head-length'];
|
|
38052
|
+
if (headLength > 0) return headLength;
|
|
38053
|
+
var headAngle = d['arrow-head-angle'] || 40;
|
|
38054
|
+
var headRatio = 1 / Math.tan(Math.PI * headAngle / 180 / 2) / 2; // length-to-width head ratio
|
|
38055
|
+
return headWidth * headRatio;
|
|
38056
|
+
}
|
|
38057
|
+
|
|
38058
|
+
function getCurvedArrowCoords(stemLen, headLen, curvature, stemDx, headDx, baseDx) {
|
|
38059
|
+
// coordinates go counter clockwise, starting from the leftmost head coordinate
|
|
38060
|
+
var theta = Math.abs(curvature) / 180 * Math.PI;
|
|
38061
|
+
var sign = curvature > 0 ? 1 : -1;
|
|
38062
|
+
var dx = stemLen * Math.sin(theta / 2) * sign;
|
|
38063
|
+
var dy = stemLen * Math.cos(theta / 2);
|
|
38064
|
+
var head = [[stemDx + dx, dy], [headDx + dx, dy],
|
|
38065
|
+
[dx, headLen + dy], [-headDx + dx, dy], [-stemDx + dx, dy]];
|
|
38066
|
+
var ax = baseDx * Math.cos(theta); // rotate arrow base
|
|
38067
|
+
var ay = baseDx * Math.sin(theta) * -sign;
|
|
38068
|
+
var leftStem = getCurvedStemCoords(-ax, -ay, -stemDx + dx, dy, theta);
|
|
38069
|
+
var rightStem = getCurvedStemCoords(ax, ay, stemDx + dx, dy, theta);
|
|
38070
|
+
var stem = leftStem.concat(rightStem.reverse());
|
|
38071
|
+
// stem.pop();
|
|
38072
|
+
return stem.concat(head);
|
|
38073
|
+
}
|
|
38074
|
+
|
|
38075
|
+
// ax, ay: point on the base
|
|
38076
|
+
// bx, by: point on the stem
|
|
38077
|
+
function getCurvedStemCoords(ax, ay, bx, by, theta0) {
|
|
38078
|
+
// case: curved side intrudes into head (because stem is too short)
|
|
38079
|
+
if (ay > by) {
|
|
38080
|
+
return [[ax * by / ay, by]];
|
|
37828
38081
|
}
|
|
37829
|
-
|
|
37830
|
-
|
|
37831
|
-
|
|
37832
|
-
|
|
37833
|
-
|
|
38082
|
+
var dx = bx - ax,
|
|
38083
|
+
dy = by - ay,
|
|
38084
|
+
dy1 = (dy * dy - dx * dx) / (2 * dy),
|
|
38085
|
+
dy2 = dy - dy1,
|
|
38086
|
+
dx2 = Math.sqrt(dx * dx + dy * dy) / 2,
|
|
38087
|
+
theta = Math.PI - Math.asin(dx2 / dy2) * 2,
|
|
38088
|
+
degrees = theta * 180 / Math.PI,
|
|
38089
|
+
radius = dy2 / Math.tan(theta / 2),
|
|
38090
|
+
leftBend = bx > ax,
|
|
38091
|
+
sign = leftBend ? 1 : -1,
|
|
38092
|
+
points = Math.round(degrees / 5) + 2,
|
|
38093
|
+
increment = theta / (points + 1),
|
|
38094
|
+
coords = [[bx, by]];
|
|
38095
|
+
|
|
38096
|
+
for (var i=1; i<= points; i++) {
|
|
38097
|
+
var phi = i * increment / 2;
|
|
38098
|
+
var sinPhi = Math.sin(phi);
|
|
38099
|
+
var cosPhi = Math.cos(phi);
|
|
38100
|
+
var c = sinPhi * radius * 2;
|
|
38101
|
+
var a = sinPhi * c;
|
|
38102
|
+
var b = cosPhi * c;
|
|
38103
|
+
coords.push([bx - a * sign, by - b]);
|
|
38104
|
+
}
|
|
38105
|
+
coords.push([ax, ay]);
|
|
38106
|
+
return coords;
|
|
38107
|
+
}
|
|
38108
|
+
|
|
38109
|
+
// sides: e.g. 5-pointed star has 10 sides
|
|
38110
|
+
// radius: distance from center to point
|
|
38111
|
+
//
|
|
38112
|
+
function getPolygonCoords(opts) {
|
|
38113
|
+
var radius = opts.radius || opts.length || opts.r;
|
|
38114
|
+
if (radius > 0 === false) return null;
|
|
38115
|
+
var type = opts.type;
|
|
38116
|
+
var sides = +opts.sides || getDefaultSides(type);
|
|
38117
|
+
var isStar = type == 'star';
|
|
38118
|
+
if (isStar && (sides < 6 || sides % 2 !== 0)) {
|
|
38119
|
+
stop(`Invalid number of sides for a star (${sides})`);
|
|
38120
|
+
} else if (sides >= 3 === false) {
|
|
38121
|
+
stop(`Invalid number of sides (${sides})`);
|
|
38122
|
+
}
|
|
38123
|
+
var coords = [],
|
|
38124
|
+
angle = 360 / sides,
|
|
38125
|
+
b = isStar ? 1 : 0.5,
|
|
38126
|
+
theta, even, len;
|
|
38127
|
+
if (opts.orientation == 'b') {
|
|
38128
|
+
b = 0;
|
|
38129
|
+
}
|
|
38130
|
+
for (var i=0; i<sides; i++) {
|
|
38131
|
+
even = i % 2 == 0;
|
|
38132
|
+
len = radius;
|
|
38133
|
+
if (isStar && even) {
|
|
38134
|
+
len *= (opts.star_ratio || 0.5);
|
|
38135
|
+
}
|
|
38136
|
+
theta = (i + b) * angle % 360;
|
|
38137
|
+
coords.push(getPlanarSegmentEndpoint(0, 0, theta, len));
|
|
38138
|
+
}
|
|
38139
|
+
coords.push(coords[0].concat());
|
|
38140
|
+
return [coords];
|
|
38141
|
+
}
|
|
38142
|
+
|
|
38143
|
+
function getDefaultSides(type) {
|
|
38144
|
+
return {
|
|
38145
|
+
star: 10,
|
|
38146
|
+
circle: 72,
|
|
38147
|
+
triangle: 3,
|
|
38148
|
+
square: 4,
|
|
38149
|
+
pentagon: 5,
|
|
38150
|
+
hexagon: 6,
|
|
38151
|
+
heptagon: 7,
|
|
38152
|
+
octagon: 8,
|
|
38153
|
+
nonagon: 9,
|
|
38154
|
+
decagon: 10
|
|
38155
|
+
}[type] || 4;
|
|
38156
|
+
}
|
|
38157
|
+
|
|
38158
|
+
// TODO: refactor to remove duplication in mapshaper-svg-style.js
|
|
38159
|
+
cmd.symbols = function(inputLyr, dataset, opts) {
|
|
38160
|
+
requireSinglePointLayer(inputLyr);
|
|
38161
|
+
var lyr = opts.no_replace ? copyLayer(inputLyr) : inputLyr;
|
|
38162
|
+
var polygonMode = !!opts.polygons;
|
|
38163
|
+
var metersPerPx;
|
|
38164
|
+
if (polygonMode) {
|
|
38165
|
+
requireProjectedDataset(dataset);
|
|
38166
|
+
metersPerPx = opts.pixel_scale || getMetersPerPixel(lyr, dataset);
|
|
38167
|
+
}
|
|
38168
|
+
var records = getLayerDataTable(lyr).getRecords();
|
|
38169
|
+
var getSymbolData = getSymbolDataAccessor(lyr, opts);
|
|
38170
|
+
var geometries = lyr.shapes.map(function(shp, i) {
|
|
38171
|
+
if (!shp) return null;
|
|
38172
|
+
var d = getSymbolData(i);
|
|
38173
|
+
var rec = records[i] || {};
|
|
38174
|
+
var coords;
|
|
38175
|
+
if (d.type == 'arrow') {
|
|
38176
|
+
coords = getFilledArrowCoords(d);
|
|
38177
|
+
} else {
|
|
38178
|
+
coords = getPolygonCoords(d);
|
|
38179
|
+
}
|
|
38180
|
+
if (!coords) return null;
|
|
38181
|
+
rotateCoords(coords, +d.rotation || 0);
|
|
38182
|
+
if (!polygonMode) {
|
|
38183
|
+
flipY(coords);
|
|
38184
|
+
}
|
|
38185
|
+
if (+opts.scale) {
|
|
38186
|
+
scaleAndShiftCoords(coords, +opts.scale, [0, 0]);
|
|
38187
|
+
}
|
|
38188
|
+
if (polygonMode) {
|
|
38189
|
+
scaleAndShiftCoords(coords, metersPerPx, shp[0]);
|
|
38190
|
+
if (d.tfill) rec.fill = d.fill;
|
|
38191
|
+
return createGeometry(coords);
|
|
37834
38192
|
} else {
|
|
37835
|
-
rec['svg-symbol'] =
|
|
38193
|
+
rec['svg-symbol'] = makeSvgSymbol(coords, d);
|
|
37836
38194
|
}
|
|
37837
38195
|
});
|
|
38196
|
+
|
|
38197
|
+
var outputLyr, dataset2;
|
|
38198
|
+
if (polygonMode) {
|
|
38199
|
+
dataset2 = importGeometries(geometries, records);
|
|
38200
|
+
outputLyr = mergeOutputLayerIntoDataset(inputLyr, dataset, dataset2, opts);
|
|
38201
|
+
outputLyr.data = lyr.data;
|
|
38202
|
+
} else {
|
|
38203
|
+
outputLyr = lyr;
|
|
38204
|
+
}
|
|
38205
|
+
return [outputLyr];
|
|
37838
38206
|
};
|
|
37839
38207
|
|
|
38208
|
+
function importGeometries(geometries, records) {
|
|
38209
|
+
var features = geometries.map(function(geom, i) {
|
|
38210
|
+
var d = records[i];
|
|
38211
|
+
return {
|
|
38212
|
+
type: 'Feature',
|
|
38213
|
+
properties: records[i] || null,
|
|
38214
|
+
geometry: geom
|
|
38215
|
+
};
|
|
38216
|
+
});
|
|
38217
|
+
var geojson = {
|
|
38218
|
+
type: 'FeatureCollection',
|
|
38219
|
+
features: features
|
|
38220
|
+
};
|
|
38221
|
+
return importGeoJSON(geojson);
|
|
38222
|
+
}
|
|
38223
|
+
|
|
38224
|
+
function createGeometry(coords) {
|
|
38225
|
+
return {
|
|
38226
|
+
type: 'Polygon',
|
|
38227
|
+
coordinates: coords
|
|
38228
|
+
};
|
|
38229
|
+
}
|
|
38230
|
+
|
|
38231
|
+
function getMetersPerPixel(lyr, dataset) {
|
|
38232
|
+
|
|
38233
|
+
// TODO: handle single point, no extent
|
|
38234
|
+
var bounds = getLayerBounds(lyr);
|
|
38235
|
+
return bounds.width() / 800;
|
|
38236
|
+
}
|
|
38237
|
+
|
|
37840
38238
|
// Returns an svg-symbol data object for one symbol
|
|
37841
|
-
function
|
|
37842
|
-
|
|
37843
|
-
|
|
37844
|
-
|
|
37845
|
-
|
|
37846
|
-
|
|
37847
|
-
|
|
37848
|
-
}
|
|
37849
|
-
return f(properties);
|
|
38239
|
+
function makeSvgSymbol(coords, properties) {
|
|
38240
|
+
roundCoordsForSVG(coords);
|
|
38241
|
+
return {
|
|
38242
|
+
type: 'polygon',
|
|
38243
|
+
coordinates: coords,
|
|
38244
|
+
fill: properties.fill || 'magenta'
|
|
38245
|
+
};
|
|
37850
38246
|
}
|
|
37851
38247
|
|
|
37852
38248
|
var Symbols = /*#__PURE__*/Object.freeze({
|
|
37853
38249
|
__proto__: null,
|
|
37854
|
-
|
|
38250
|
+
makeSvgSymbol: makeSvgSymbol
|
|
37855
38251
|
});
|
|
37856
38252
|
|
|
37857
38253
|
cmd.target = function(catalog, opts) {
|
|
@@ -37997,99 +38393,6 @@ ${svg}
|
|
|
37997
38393
|
}
|
|
37998
38394
|
};
|
|
37999
38395
|
|
|
38000
|
-
cmd.shape = function(targetDataset, opts) {
|
|
38001
|
-
var geojson, dataset;
|
|
38002
|
-
if (opts.coordinates) {
|
|
38003
|
-
geojson = makeShapeFromCoords(opts);
|
|
38004
|
-
} else if (opts.type == 'circle') {
|
|
38005
|
-
geojson = makeCircle(opts);
|
|
38006
|
-
} else if (opts.type == 'rectangle' && opts.bbox) {
|
|
38007
|
-
geojson = getRectangleGeoJSON(opts);
|
|
38008
|
-
} else {
|
|
38009
|
-
stop('Missing coordinates parameter');
|
|
38010
|
-
}
|
|
38011
|
-
// TODO: project shape if targetDataset is projected
|
|
38012
|
-
dataset = importGeoJSON(geojson, {});
|
|
38013
|
-
if (opts.rotation) {
|
|
38014
|
-
rotateDatasetCoords(dataset, opts.rotation);
|
|
38015
|
-
}
|
|
38016
|
-
dataset.layers[0].name = opts.name || opts.type || 'shape';
|
|
38017
|
-
return dataset;
|
|
38018
|
-
};
|
|
38019
|
-
|
|
38020
|
-
function getRectangleGeoJSON(opts) {
|
|
38021
|
-
var bbox = opts.bbox,
|
|
38022
|
-
xmin = bbox[0],
|
|
38023
|
-
ymin = bbox[1],
|
|
38024
|
-
xmax = bbox[2],
|
|
38025
|
-
ymax = bbox[3],
|
|
38026
|
-
interval = 0.5,
|
|
38027
|
-
coords = [],
|
|
38028
|
-
type = opts.geometry == 'polyline' ? 'LineString' : 'Polygon';
|
|
38029
|
-
addSide(xmin, ymin, xmin, ymax);
|
|
38030
|
-
addSide(xmin, ymax, xmax, ymax);
|
|
38031
|
-
addSide(xmax, ymax, xmax, ymin);
|
|
38032
|
-
addSide(xmax, ymin, xmin, ymin);
|
|
38033
|
-
coords.push([xmin, ymin]);
|
|
38034
|
-
return {
|
|
38035
|
-
type: type,
|
|
38036
|
-
coordinates: type == 'Polygon' ? [coords] : coords
|
|
38037
|
-
};
|
|
38038
|
-
|
|
38039
|
-
function addSide(x1, y1, x2, y2) {
|
|
38040
|
-
var dx = x2 - x1,
|
|
38041
|
-
dy = y2 - y1,
|
|
38042
|
-
n = Math.ceil(Math.max(Math.abs(dx) / interval, Math.abs(dy) / interval)),
|
|
38043
|
-
xint = dx / n,
|
|
38044
|
-
yint = dy / n;
|
|
38045
|
-
for (var i=0; i<n; i++) {
|
|
38046
|
-
coords.push([x1 + i * xint, y1 + i * yint]);
|
|
38047
|
-
}
|
|
38048
|
-
}
|
|
38049
|
-
}
|
|
38050
|
-
|
|
38051
|
-
function makeCircle(opts) {
|
|
38052
|
-
if (opts.radius > 0 === false && opts.radius_angle > 0 === false) {
|
|
38053
|
-
stop('Missing required radius parameter.');
|
|
38054
|
-
}
|
|
38055
|
-
var cp = opts.center || [0, 0];
|
|
38056
|
-
var radius = opts.radius || getCircleRadiusFromAngle(getCRS('wgs84'), opts.radius_angle);
|
|
38057
|
-
return getCircleGeoJSON(cp, radius, null, {geometry_type : opts.geometry || 'polygon'});
|
|
38058
|
-
}
|
|
38059
|
-
|
|
38060
|
-
function makeShapeFromCoords(opts) {
|
|
38061
|
-
var coordinates = [];
|
|
38062
|
-
var offsets = opts.offsets || [];
|
|
38063
|
-
var coords = opts.coordinates;
|
|
38064
|
-
var type, i, x, y;
|
|
38065
|
-
if (coords.length >= 2 === false) {
|
|
38066
|
-
stop('Invalid coordinates parameter.');
|
|
38067
|
-
}
|
|
38068
|
-
for (i=0; i<coords.length; i+= 2) {
|
|
38069
|
-
x = coords[i];
|
|
38070
|
-
y = coords[i + 1];
|
|
38071
|
-
coordinates.push([x, y]);
|
|
38072
|
-
}
|
|
38073
|
-
for (i=0; i<offsets.length; i+=2) {
|
|
38074
|
-
x += offsets[i];
|
|
38075
|
-
y += offsets[i + 1];
|
|
38076
|
-
coordinates.push([x, y]);
|
|
38077
|
-
}
|
|
38078
|
-
if (GeoJSON.pathIsRing(coordinates)) {
|
|
38079
|
-
type = 'Polygon';
|
|
38080
|
-
} else if (opts.closed && coordinates.length >= 3) {
|
|
38081
|
-
type = 'Polygon';
|
|
38082
|
-
coordinates.push(coordinates[0]);
|
|
38083
|
-
} else {
|
|
38084
|
-
type = 'LineString';
|
|
38085
|
-
}
|
|
38086
|
-
return {
|
|
38087
|
-
type: type,
|
|
38088
|
-
coordinates: type == 'Polygon' ? [coordinates] : coordinates
|
|
38089
|
-
};
|
|
38090
|
-
|
|
38091
|
-
}
|
|
38092
|
-
|
|
38093
38396
|
cmd.variableSimplify = function(layers, dataset, opts) {
|
|
38094
38397
|
var lyr = layers[0];
|
|
38095
38398
|
var arcs = dataset.arcs;
|
|
@@ -38631,6 +38934,9 @@ ${svg}
|
|
|
38631
38934
|
} else if (name == 'shape') {
|
|
38632
38935
|
catalog.addDataset(cmd.shape(targetDataset, opts));
|
|
38633
38936
|
|
|
38937
|
+
} else if (name == 'shapes') {
|
|
38938
|
+
outputLayers = applyCommandToEachLayer(cmd.shapes, targetLayers, targetDataset, opts);
|
|
38939
|
+
|
|
38634
38940
|
} else if (name == 'simplify') {
|
|
38635
38941
|
if (opts.variable) {
|
|
38636
38942
|
cmd.variableSimplify(targetLayers, targetDataset, opts);
|
|
@@ -38660,7 +38966,7 @@ ${svg}
|
|
|
38660
38966
|
applyCommandToEachLayer(cmd.svgStyle, targetLayers, targetDataset, opts);
|
|
38661
38967
|
|
|
38662
38968
|
} else if (name == 'symbols') {
|
|
38663
|
-
applyCommandToEachLayer(cmd.symbols, targetLayers, opts);
|
|
38969
|
+
outputLayers = applyCommandToEachLayer(cmd.symbols, targetLayers, targetDataset, opts);
|
|
38664
38970
|
|
|
38665
38971
|
} else if (name == 'subdivide') {
|
|
38666
38972
|
outputLayers = applyCommandToEachLayer(cmd.subdivideLayer, targetLayers, arcs, opts.expression);
|