mapshaper 0.6.24 → 0.6.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mapshaper.js +363 -69
- package/package.json +3 -4
- package/www/index.html +7 -8
- package/www/mapshaper-gui.js +32 -5
- package/www/mapshaper.js +363 -69
- package/www/modules.js +4 -1
package/mapshaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var VERSION = "0.6.
|
|
3
|
+
var VERSION = "0.6.26";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
var utils = /*#__PURE__*/Object.freeze({
|
|
@@ -2449,6 +2449,17 @@
|
|
|
2449
2449
|
getPathCentroid: getPathCentroid
|
|
2450
2450
|
});
|
|
2451
2451
|
|
|
2452
|
+
function testSegmentBoundsIntersection(a, b, bb) {
|
|
2453
|
+
if (bb.containsPoint(a[0], a[1])) {
|
|
2454
|
+
return true;
|
|
2455
|
+
}
|
|
2456
|
+
return !!(
|
|
2457
|
+
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmin, bb.ymin, bb.xmin, bb.ymax) ||
|
|
2458
|
+
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmin, bb.ymax, bb.xmax, bb.ymax) ||
|
|
2459
|
+
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymax, bb.xmax, bb.ymin) ||
|
|
2460
|
+
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymin, bb.xmin, bb.ymin));
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2452
2463
|
// A compactness measure designed for testing electoral districts for gerrymandering.
|
|
2453
2464
|
// Returns value in [0-1] range. 1 = perfect circle, 0 = collapsed polygon
|
|
2454
2465
|
function calcPolsbyPopperCompactness(area, perimeter) {
|
|
@@ -2496,21 +2507,32 @@
|
|
|
2496
2507
|
// }, 0);
|
|
2497
2508
|
// }
|
|
2498
2509
|
|
|
2510
|
+
// test if a rectangle is completely enclosed in a planar polygon
|
|
2511
|
+
function testBoundsInPolygon(bounds, shp, arcs) {
|
|
2512
|
+
if (!shp || !testPointInPolygon(bounds.xmin, bounds.ymin, shp, arcs)) return false;
|
|
2513
|
+
var isIn = true;
|
|
2514
|
+
shp.forEach(function(ids) {
|
|
2515
|
+
forEachSegmentInPath(ids, arcs, function(a, b, xx, yy) {
|
|
2516
|
+
isIn = isIn && !testSegmentBoundsIntersection([xx[a], yy[a]], [xx[b], yy[b]], bounds);
|
|
2517
|
+
});
|
|
2518
|
+
});
|
|
2519
|
+
return isIn;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2499
2522
|
// Return true if point is inside or on boundary of a shape
|
|
2500
2523
|
//
|
|
2501
2524
|
function testPointInPolygon(x, y, shp, arcs) {
|
|
2502
2525
|
var isIn = false,
|
|
2503
2526
|
isOn = false;
|
|
2504
|
-
if (shp)
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
}
|
|
2527
|
+
if (!shp) return false;
|
|
2528
|
+
shp.forEach(function(ids) {
|
|
2529
|
+
var inRing = testPointInRing(x, y, ids, arcs);
|
|
2530
|
+
if (inRing == 1) {
|
|
2531
|
+
isIn = !isIn;
|
|
2532
|
+
} else if (inRing == -1) {
|
|
2533
|
+
isOn = true;
|
|
2534
|
+
}
|
|
2535
|
+
});
|
|
2514
2536
|
return isOn || isIn;
|
|
2515
2537
|
}
|
|
2516
2538
|
|
|
@@ -2518,6 +2540,8 @@
|
|
|
2518
2540
|
return ay + (x - ax) * (by - ay) / (bx - ax);
|
|
2519
2541
|
}
|
|
2520
2542
|
|
|
2543
|
+
|
|
2544
|
+
|
|
2521
2545
|
// Test if point (x, y) is inside, outside or on the boundary of a polygon ring
|
|
2522
2546
|
// Return 0: outside; 1: inside; -1: on boundary
|
|
2523
2547
|
//
|
|
@@ -2708,6 +2732,7 @@
|
|
|
2708
2732
|
getShapeArea: getShapeArea,
|
|
2709
2733
|
getPlanarShapeArea: getPlanarShapeArea,
|
|
2710
2734
|
getSphericalShapeArea: getSphericalShapeArea,
|
|
2735
|
+
testBoundsInPolygon: testBoundsInPolygon,
|
|
2711
2736
|
testPointInPolygon: testPointInPolygon,
|
|
2712
2737
|
testPointInRing: testPointInRing,
|
|
2713
2738
|
testRayIntersection: testRayIntersection,
|
|
@@ -7251,7 +7276,7 @@
|
|
|
7251
7276
|
let size = source.length;
|
|
7252
7277
|
let value = this ? this.unpack(source, size) : defaultUnpackr.unpack(source, size);
|
|
7253
7278
|
if (forEach) {
|
|
7254
|
-
forEach(value);
|
|
7279
|
+
if (forEach(value) === false) return;
|
|
7255
7280
|
while(position$1 < size) {
|
|
7256
7281
|
lastPosition = position$1;
|
|
7257
7282
|
if (forEach(checkedRead()) === false) {
|
|
@@ -8193,7 +8218,7 @@
|
|
|
8193
8218
|
let safeEnd;
|
|
8194
8219
|
let bundledStrings = null;
|
|
8195
8220
|
let writeStructSlots;
|
|
8196
|
-
const MAX_BUNDLE_SIZE =
|
|
8221
|
+
const MAX_BUNDLE_SIZE = 0x5500; // maximum characters such that the encoded bytes fits in 16 bits.
|
|
8197
8222
|
const hasNonLatin = /[\u0080-\uFFFF]/;
|
|
8198
8223
|
const RECORD_SYMBOL = Symbol('record-id');
|
|
8199
8224
|
class Packr extends Unpackr {
|
|
@@ -8222,7 +8247,7 @@
|
|
|
8222
8247
|
if (maxSharedStructures > 8160)
|
|
8223
8248
|
throw new Error('Maximum maxSharedStructure is 8160')
|
|
8224
8249
|
if (options.structuredClone && options.moreTypes == undefined) {
|
|
8225
|
-
|
|
8250
|
+
this.moreTypes = true;
|
|
8226
8251
|
}
|
|
8227
8252
|
let maxOwnStructures = options.maxOwnStructures;
|
|
8228
8253
|
if (maxOwnStructures == null)
|
|
@@ -8917,12 +8942,11 @@
|
|
|
8917
8942
|
if (notifySharedUpdate)
|
|
8918
8943
|
return hasSharedUpdate = true;
|
|
8919
8944
|
position = newPosition;
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
}
|
|
8925
|
-
pack(value);
|
|
8945
|
+
let startTarget = target;
|
|
8946
|
+
pack(value);
|
|
8947
|
+
if (startTarget !== target) {
|
|
8948
|
+
return { position, targetView, target }; // indicate the buffer was re-allocated
|
|
8949
|
+
}
|
|
8926
8950
|
return position;
|
|
8927
8951
|
}, this);
|
|
8928
8952
|
if (newPosition === 0) // bail and go to a msgpack object
|
|
@@ -13704,6 +13728,28 @@
|
|
|
13704
13728
|
}});
|
|
13705
13729
|
}
|
|
13706
13730
|
|
|
13731
|
+
if (hasPaths || hasPoints) {
|
|
13732
|
+
addGetters(ctx, {
|
|
13733
|
+
// TODO: count hole/s + containing ring as one part
|
|
13734
|
+
partCount: function() {
|
|
13735
|
+
var shp = lyr.shapes[_id];
|
|
13736
|
+
return shp ? shp.length : 0;
|
|
13737
|
+
},
|
|
13738
|
+
isNull: function() {
|
|
13739
|
+
return ctx.partCount === 0;
|
|
13740
|
+
},
|
|
13741
|
+
bounds: function() {
|
|
13742
|
+
return shapeBounds().toArray();
|
|
13743
|
+
},
|
|
13744
|
+
height: function() {
|
|
13745
|
+
return shapeBounds().height();
|
|
13746
|
+
},
|
|
13747
|
+
width: function() {
|
|
13748
|
+
return shapeBounds().width();
|
|
13749
|
+
}
|
|
13750
|
+
});
|
|
13751
|
+
}
|
|
13752
|
+
|
|
13707
13753
|
if (hasPaths) {
|
|
13708
13754
|
|
|
13709
13755
|
ctx.bboxContainsPoint = function(x, y) {
|
|
@@ -13729,24 +13775,10 @@
|
|
|
13729
13775
|
return rect.contains(bbox);
|
|
13730
13776
|
};
|
|
13731
13777
|
|
|
13732
|
-
|
|
13733
|
-
|
|
13734
|
-
|
|
13735
|
-
|
|
13736
|
-
},
|
|
13737
|
-
isNull: function() {
|
|
13738
|
-
return ctx.partCount === 0;
|
|
13739
|
-
},
|
|
13740
|
-
bounds: function() {
|
|
13741
|
-
return shapeBounds().toArray();
|
|
13742
|
-
},
|
|
13743
|
-
height: function() {
|
|
13744
|
-
return shapeBounds().height();
|
|
13745
|
-
},
|
|
13746
|
-
width: function() {
|
|
13747
|
-
return shapeBounds().width();
|
|
13748
|
-
}
|
|
13749
|
-
});
|
|
13778
|
+
// TODO
|
|
13779
|
+
// ctx.intersectsRectangle = function(a, b, c, d) {}; // paths... points too?
|
|
13780
|
+
// ctx.containsPoint = function(x, y) {}; // polygon only
|
|
13781
|
+
// ctx.containedByRectangle(a, b, c, d); // paths and points... how do multipart points work?
|
|
13750
13782
|
|
|
13751
13783
|
if (lyr.geometry_type == 'polyline') {
|
|
13752
13784
|
addGetters(ctx, {
|
|
@@ -13809,7 +13841,7 @@
|
|
|
13809
13841
|
}
|
|
13810
13842
|
|
|
13811
13843
|
} else if (hasPoints) {
|
|
13812
|
-
|
|
13844
|
+
|
|
13813
13845
|
Object.defineProperty(ctx, 'coordinates',
|
|
13814
13846
|
{set: function(obj) {
|
|
13815
13847
|
if (!obj || utils.isArray(obj)) {
|
|
@@ -13848,8 +13880,13 @@
|
|
|
13848
13880
|
}
|
|
13849
13881
|
|
|
13850
13882
|
function shapeBounds() {
|
|
13851
|
-
if (
|
|
13883
|
+
if (_bounds) return _bounds;
|
|
13884
|
+
if (hasPaths) {
|
|
13852
13885
|
_bounds = arcs.getMultiShapeBounds(_ids);
|
|
13886
|
+
} else if (hasPoints) {
|
|
13887
|
+
_bounds = getPointFeatureBounds(lyr.shapes[_id]);
|
|
13888
|
+
} else {
|
|
13889
|
+
_bounds = new Bounds();
|
|
13853
13890
|
}
|
|
13854
13891
|
return _bounds;
|
|
13855
13892
|
}
|
|
@@ -13857,8 +13894,8 @@
|
|
|
13857
13894
|
return function(id) {
|
|
13858
13895
|
_id = id;
|
|
13859
13896
|
// reset stored values
|
|
13897
|
+
_bounds = null;
|
|
13860
13898
|
if (hasPaths) {
|
|
13861
|
-
_bounds = null;
|
|
13862
13899
|
_centroid = null;
|
|
13863
13900
|
_innerXY = null;
|
|
13864
13901
|
_ids = lyr.shapes[id];
|
|
@@ -14263,9 +14300,9 @@
|
|
|
14263
14300
|
}
|
|
14264
14301
|
|
|
14265
14302
|
function compileCalcExpression(lyr, arcs, exp) {
|
|
14266
|
-
var rowNo = 0, colNo = 0, cols = [];
|
|
14303
|
+
var rowNo = 0, colNo = 0, recId = -1, cols = [];
|
|
14267
14304
|
var ctx1 = { // context for first phase (capturing values for each feature)
|
|
14268
|
-
count: assign,
|
|
14305
|
+
count: assign, // dummy function - first pass does nothing
|
|
14269
14306
|
sum: captureNum,
|
|
14270
14307
|
sums: capture,
|
|
14271
14308
|
average: captureNum,
|
|
@@ -14280,6 +14317,7 @@
|
|
|
14280
14317
|
max: captureNum,
|
|
14281
14318
|
mode: capture,
|
|
14282
14319
|
collect: capture,
|
|
14320
|
+
collectIds: captureId,
|
|
14283
14321
|
first: assignOnce,
|
|
14284
14322
|
every: every,
|
|
14285
14323
|
some: some,
|
|
@@ -14303,6 +14341,7 @@
|
|
|
14303
14341
|
mean: wrap(utils.mean),
|
|
14304
14342
|
mode: wrap(getMode),
|
|
14305
14343
|
collect: wrap(pass),
|
|
14344
|
+
collectIds: wrap(pass),
|
|
14306
14345
|
first: wrap(pass),
|
|
14307
14346
|
every: wrap(pass, false),
|
|
14308
14347
|
some: wrap(pass, false),
|
|
@@ -14389,6 +14428,7 @@
|
|
|
14389
14428
|
|
|
14390
14429
|
function procRecord(i) {
|
|
14391
14430
|
if (i < 0 || i >= len) error("Invalid record index");
|
|
14431
|
+
recId = i;
|
|
14392
14432
|
calc1(i);
|
|
14393
14433
|
rowNo++;
|
|
14394
14434
|
colNo = 0;
|
|
@@ -14397,6 +14437,7 @@
|
|
|
14397
14437
|
function noop() {}
|
|
14398
14438
|
|
|
14399
14439
|
function reset() {
|
|
14440
|
+
recId = -1;
|
|
14400
14441
|
rowNo = 0;
|
|
14401
14442
|
colNo = 0;
|
|
14402
14443
|
cols = [];
|
|
@@ -14438,6 +14479,10 @@
|
|
|
14438
14479
|
}
|
|
14439
14480
|
*/
|
|
14440
14481
|
|
|
14482
|
+
function captureId() {
|
|
14483
|
+
capture(recId);
|
|
14484
|
+
}
|
|
14485
|
+
|
|
14441
14486
|
function capture(val) {
|
|
14442
14487
|
var col;
|
|
14443
14488
|
if (rowNo === 0) {
|
|
@@ -24602,6 +24647,21 @@ ${svg}
|
|
|
24602
24647
|
// Experimental commands
|
|
24603
24648
|
parser.section('Experimental commands (may give unexpected results)');
|
|
24604
24649
|
|
|
24650
|
+
parser.command('add-shape')
|
|
24651
|
+
.describe('')
|
|
24652
|
+
.option('geojson', {
|
|
24653
|
+
|
|
24654
|
+
})
|
|
24655
|
+
.option('coordinates', {
|
|
24656
|
+
|
|
24657
|
+
})
|
|
24658
|
+
.option('properties', {
|
|
24659
|
+
|
|
24660
|
+
})
|
|
24661
|
+
.option('name', nameOpt)
|
|
24662
|
+
.option('target', targetOpt)
|
|
24663
|
+
.option('no-replace', noReplaceOpt);
|
|
24664
|
+
|
|
24605
24665
|
parser.command('alpha-shapes')
|
|
24606
24666
|
// .describe('convert points to alpha shapes (aka concave hulls)')
|
|
24607
24667
|
.option('interval', {
|
|
@@ -28076,6 +28136,136 @@ ${svg}
|
|
|
28076
28136
|
stashVar('input_files', job.input_files);
|
|
28077
28137
|
}
|
|
28078
28138
|
|
|
28139
|
+
cmd.addShape = addShape;
|
|
28140
|
+
|
|
28141
|
+
function addShape(targetLayers, targetDataset, opts) {
|
|
28142
|
+
if (targetLayers.length > 1) {
|
|
28143
|
+
stop('Command expects a single target layer');
|
|
28144
|
+
}
|
|
28145
|
+
var targetLyr = targetLayers[0]; // may be undefined
|
|
28146
|
+
var targetType = !opts.no_replace && targetLyr && targetLyr.geometry_type || null;
|
|
28147
|
+
var dataset = importGeoJSON(toFeature(opts, targetType));
|
|
28148
|
+
var outputLyr = mergeDatasetsIntoDataset(targetDataset, [dataset])[0];
|
|
28149
|
+
if (opts.no_replace || !targetLyr) {
|
|
28150
|
+
// create new layer
|
|
28151
|
+
setOutputLayerName(outputLyr, targetLyr && targetLyr.name, null, opts);
|
|
28152
|
+
return [outputLyr];
|
|
28153
|
+
}
|
|
28154
|
+
// merge into target layer
|
|
28155
|
+
return cmd.mergeLayers([targetLyr, outputLyr], {force: true});
|
|
28156
|
+
}
|
|
28157
|
+
|
|
28158
|
+
function toFeature(opts, geomType) {
|
|
28159
|
+
if (opts.geojson) {
|
|
28160
|
+
return parseArg(opts.geojson);
|
|
28161
|
+
}
|
|
28162
|
+
|
|
28163
|
+
var geom = opts.coordinates && parseCoordsAsGeometry(opts.coordinates) || null;
|
|
28164
|
+
|
|
28165
|
+
if (!geom) {
|
|
28166
|
+
stop('Missing required shape coordinates');
|
|
28167
|
+
}
|
|
28168
|
+
|
|
28169
|
+
if (geomType == 'point' && geom.type != 'Point') {
|
|
28170
|
+
stop('Expected point coordinates, received', geom.type);
|
|
28171
|
+
}
|
|
28172
|
+
|
|
28173
|
+
if (geomType == 'polygon' && geom.type != 'Polygon') {
|
|
28174
|
+
stop('Expected polygon coordinates, received', geom.type);
|
|
28175
|
+
}
|
|
28176
|
+
|
|
28177
|
+
if (geomType == 'polyline') {
|
|
28178
|
+
if (geom.type == 'Polygon') {
|
|
28179
|
+
geom.coordinates = geom.coordinates[0];
|
|
28180
|
+
geom.type = 'LineString';
|
|
28181
|
+
} else {
|
|
28182
|
+
stop('Expected polyline coordinates, received', geom.type);
|
|
28183
|
+
}
|
|
28184
|
+
}
|
|
28185
|
+
|
|
28186
|
+
return {
|
|
28187
|
+
type: 'Feature',
|
|
28188
|
+
properties: parseProperties(opts.properties),
|
|
28189
|
+
geometry: geom
|
|
28190
|
+
};
|
|
28191
|
+
}
|
|
28192
|
+
|
|
28193
|
+
function parseArg(obj) {
|
|
28194
|
+
return typeof obj == 'string' ? JSON.parse(obj) : obj;
|
|
28195
|
+
}
|
|
28196
|
+
|
|
28197
|
+
function parseProperties(arg) {
|
|
28198
|
+
if (!arg) return null;
|
|
28199
|
+
return parseArg(arg);
|
|
28200
|
+
}
|
|
28201
|
+
|
|
28202
|
+
function isArrayOfNumbers(arr) {
|
|
28203
|
+
return arr.length >= 2 && arr.every(utils.isNumber);
|
|
28204
|
+
}
|
|
28205
|
+
|
|
28206
|
+
function isClosedPath$1(arr) {
|
|
28207
|
+
return isArrayOfPoints(arr) && arr.length > 3 && samePoint$1(arr[0], arr[arr.length - 1]);
|
|
28208
|
+
}
|
|
28209
|
+
|
|
28210
|
+
function samePoint$1(a, b) {
|
|
28211
|
+
return a[0] == b[0] && a[1] == b[1];
|
|
28212
|
+
}
|
|
28213
|
+
|
|
28214
|
+
function isArrayOfPoints(arr) {
|
|
28215
|
+
return arr.every(isPoint);
|
|
28216
|
+
}
|
|
28217
|
+
|
|
28218
|
+
function isPoint(arr) {
|
|
28219
|
+
return arr && arr.length == 2 && isArrayOfNumbers(arr);
|
|
28220
|
+
}
|
|
28221
|
+
|
|
28222
|
+
function transposeCoords(arr) {
|
|
28223
|
+
var coords = [];
|
|
28224
|
+
for (var i=0; i<arr.length; i+=2) {
|
|
28225
|
+
coords.push([arr[i], arr[i+1]]);
|
|
28226
|
+
}
|
|
28227
|
+
if (!isArrayOfPoints(coords)) {
|
|
28228
|
+
stop('Unable to parse x,y,x,y... coordinates');
|
|
28229
|
+
}
|
|
28230
|
+
return coords;
|
|
28231
|
+
}
|
|
28232
|
+
|
|
28233
|
+
function parseCoordsAsGeometry(arg) {
|
|
28234
|
+
if (typeof arg == 'string') {
|
|
28235
|
+
arg = arg.trim();
|
|
28236
|
+
if (!arg.startsWith('[') && !arg.endsWith(']')) {
|
|
28237
|
+
arg = '[' + arg + ']';
|
|
28238
|
+
}
|
|
28239
|
+
}
|
|
28240
|
+
var arr = parseArg(arg);
|
|
28241
|
+
if (isPoint(arr)) {
|
|
28242
|
+
return {
|
|
28243
|
+
type: 'Point',
|
|
28244
|
+
coordinates: arr
|
|
28245
|
+
};
|
|
28246
|
+
}
|
|
28247
|
+
|
|
28248
|
+
if (isArrayOfNumbers(arr)) {
|
|
28249
|
+
arr = transposeCoords(arr);
|
|
28250
|
+
}
|
|
28251
|
+
|
|
28252
|
+
if (isClosedPath$1(arr)) {
|
|
28253
|
+
return {
|
|
28254
|
+
type: 'Polygon',
|
|
28255
|
+
coordinates: [arr]
|
|
28256
|
+
};
|
|
28257
|
+
}
|
|
28258
|
+
|
|
28259
|
+
if (isArrayOfPoints(arr)) {
|
|
28260
|
+
return {
|
|
28261
|
+
type: 'LineString',
|
|
28262
|
+
coordinates: arr
|
|
28263
|
+
};
|
|
28264
|
+
}
|
|
28265
|
+
|
|
28266
|
+
stop('Unable to import coordinates');
|
|
28267
|
+
}
|
|
28268
|
+
|
|
28079
28269
|
const epsilon = 1.1102230246251565e-16;
|
|
28080
28270
|
const splitter = 134217729;
|
|
28081
28271
|
const resulterrbound = (3 + 8 * epsilon) * epsilon;
|
|
@@ -29264,17 +29454,6 @@ ${svg}
|
|
|
29264
29454
|
bufferIntersection: bufferIntersection
|
|
29265
29455
|
});
|
|
29266
29456
|
|
|
29267
|
-
function testSegmentBoundsIntersection(a, b, bb) {
|
|
29268
|
-
if (bb.containsPoint(a[0], a[1])) {
|
|
29269
|
-
return true;
|
|
29270
|
-
}
|
|
29271
|
-
return !!(
|
|
29272
|
-
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmin, bb.ymin, bb.xmin, bb.ymax) ||
|
|
29273
|
-
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmin, bb.ymax, bb.xmax, bb.ymax) ||
|
|
29274
|
-
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymax, bb.xmax, bb.ymin) ||
|
|
29275
|
-
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymin, bb.xmin, bb.ymin));
|
|
29276
|
-
}
|
|
29277
|
-
|
|
29278
29457
|
function getPolylineBufferMaker2(arcs, geod, getBearing, opts) {
|
|
29279
29458
|
var makeLeftBuffer = getPathBufferMaker2(arcs, geod, getBearing, opts);
|
|
29280
29459
|
var geomType = opts.geometry_type;
|
|
@@ -32733,6 +32912,8 @@ ${svg}
|
|
|
32733
32912
|
var colorArg = opts.colors && opts.colors.length == 1 ? opts.colors[0] : null;
|
|
32734
32913
|
var colorScheme;
|
|
32735
32914
|
|
|
32915
|
+
if (method == 'blacki') return [];
|
|
32916
|
+
|
|
32736
32917
|
if (colorArg == 'random') {
|
|
32737
32918
|
if (categorical) {
|
|
32738
32919
|
return getRandomColors(n);
|
|
@@ -32802,7 +32983,7 @@ ${svg}
|
|
|
32802
32983
|
}
|
|
32803
32984
|
|
|
32804
32985
|
var sequential = ['quantile', 'nice', 'equal-interval', 'hybrid', 'breaks'];
|
|
32805
|
-
var all = ['non-adjacent', 'indexed', 'categorical'].concat(sequential);
|
|
32986
|
+
var all = ['non-adjacent', 'indexed', 'categorical', 'blacki'].concat(sequential);
|
|
32806
32987
|
|
|
32807
32988
|
function getClassifyMethod(opts, dataType) {
|
|
32808
32989
|
var method;
|
|
@@ -32836,6 +33017,79 @@ ${svg}
|
|
|
32836
33017
|
return method;
|
|
32837
33018
|
}
|
|
32838
33019
|
|
|
33020
|
+
function getBlackiClassifier(lyr, dataField) {
|
|
33021
|
+
var records = lyr.data.getRecords();
|
|
33022
|
+
// classes are integers, dataIds can be any scalar
|
|
33023
|
+
var dataToClassIndex = {};
|
|
33024
|
+
var classToDataIndex = [];
|
|
33025
|
+
var classCount = 0;
|
|
33026
|
+
|
|
33027
|
+
records.forEach(function(rec, recId) {
|
|
33028
|
+
var dataIds = rec[dataField];
|
|
33029
|
+
var thisClass = -1;
|
|
33030
|
+
var thatClass, dataId;
|
|
33031
|
+
|
|
33032
|
+
if (!Array.isArray(dataIds)) return;
|
|
33033
|
+
|
|
33034
|
+
for (var i=0; i<dataIds.length; i++) {
|
|
33035
|
+
dataId = dataIds[i];
|
|
33036
|
+
thatClass = dataId in dataToClassIndex ? dataToClassIndex[dataId] : -1;
|
|
33037
|
+
if (thatClass == -1) {
|
|
33038
|
+
if (thisClass == -1) {
|
|
33039
|
+
thisClass = classCount++;
|
|
33040
|
+
classToDataIndex[thisClass] = [dataId];
|
|
33041
|
+
}
|
|
33042
|
+
dataToClassIndex[dataId] = thisClass;
|
|
33043
|
+
|
|
33044
|
+
} else {
|
|
33045
|
+
|
|
33046
|
+
if (thisClass == -1) {
|
|
33047
|
+
thisClass = thatClass;
|
|
33048
|
+
} else if (thisClass > thatClass) {
|
|
33049
|
+
mergeClassIntoClass(thisClass, thatClass);
|
|
33050
|
+
thisClass = thatClass;
|
|
33051
|
+
} else if (thisClass < thatClass) {
|
|
33052
|
+
mergeClassIntoClass(thatClass, thisClass);
|
|
33053
|
+
}
|
|
33054
|
+
}
|
|
33055
|
+
}
|
|
33056
|
+
});
|
|
33057
|
+
|
|
33058
|
+
var remapTable = compressClassIds();
|
|
33059
|
+
var classIds = records.map(function(rec) {
|
|
33060
|
+
var ids = rec[dataField];
|
|
33061
|
+
if (!Array.isArray(ids) || ids.length === 0) return -1;
|
|
33062
|
+
// TODO: assert that ids all belong to the same class
|
|
33063
|
+
if (ids[0] in dataToClassIndex === false) {
|
|
33064
|
+
error('Internal error');
|
|
33065
|
+
}
|
|
33066
|
+
return remapTable[dataToClassIndex[ids[0]]];
|
|
33067
|
+
});
|
|
33068
|
+
|
|
33069
|
+
classToDataIndex = null;
|
|
33070
|
+
dataToClassIndex = null;
|
|
33071
|
+
|
|
33072
|
+
return function(recId) {
|
|
33073
|
+
return classIds[recId];
|
|
33074
|
+
};
|
|
33075
|
+
|
|
33076
|
+
function compressClassIds() {
|
|
33077
|
+
var newId = -1;
|
|
33078
|
+
return classToDataIndex.map(function(d, oldId) {
|
|
33079
|
+
if (d !== null) newId++;
|
|
33080
|
+
return newId;
|
|
33081
|
+
});
|
|
33082
|
+
}
|
|
33083
|
+
|
|
33084
|
+
function mergeClassIntoClass(fromId, toId) {
|
|
33085
|
+
classToDataIndex[fromId].forEach(function(dataId) {
|
|
33086
|
+
dataToClassIndex[dataId] = toId;
|
|
33087
|
+
});
|
|
33088
|
+
classToDataIndex[toId] = utils.uniq(classToDataIndex[toId].concat(classToDataIndex[fromId]));
|
|
33089
|
+
classToDataIndex[fromId] = null;
|
|
33090
|
+
}
|
|
33091
|
+
}
|
|
33092
|
+
|
|
32839
33093
|
cmd.classify = function(lyr, dataset, optsArg) {
|
|
32840
33094
|
if (!lyr.data) {
|
|
32841
33095
|
initDataTable(lyr);
|
|
@@ -32898,6 +33152,8 @@ ${svg}
|
|
|
32898
33152
|
stop('Invalid number of classes:', opts.classes, '(expected a value greater than 1)');
|
|
32899
33153
|
}
|
|
32900
33154
|
numClasses = opts.classes;
|
|
33155
|
+
} else if (method == 'blacki') {
|
|
33156
|
+
numClasses = 999; // dummy value
|
|
32901
33157
|
} else if (method == 'indexed' && dataField) {
|
|
32902
33158
|
numClasses = getIndexedClassCount(records, dataField);
|
|
32903
33159
|
} else if (opts.breaks) {
|
|
@@ -32935,6 +33191,8 @@ ${svg}
|
|
|
32935
33191
|
if (fieldType === null) {
|
|
32936
33192
|
// no valid data -- always return null value
|
|
32937
33193
|
classifyByRecordId = function() {return nullValue;};
|
|
33194
|
+
} else if (method == 'blacki') {
|
|
33195
|
+
classifyByRecordId = getBlackiClassifier(lyr, dataField);
|
|
32938
33196
|
} else if (method == 'non-adjacent') {
|
|
32939
33197
|
classifyByRecordId = getNonAdjacentClassifier(lyr, dataset, values);
|
|
32940
33198
|
} else if (method == 'indexed') {
|
|
@@ -37095,7 +37353,7 @@ ${svg}
|
|
|
37095
37353
|
editor.done();
|
|
37096
37354
|
if (!opts.debug) {
|
|
37097
37355
|
buildTopology(dataset);
|
|
37098
|
-
|
|
37356
|
+
cleanProjectedPathLayers(dataset);
|
|
37099
37357
|
}
|
|
37100
37358
|
}
|
|
37101
37359
|
|
|
@@ -37606,15 +37864,41 @@ ${svg}
|
|
|
37606
37864
|
} else {
|
|
37607
37865
|
clipData = getClippingDataset(src, dest, opts);
|
|
37608
37866
|
}
|
|
37867
|
+
// clip data to projection limits (some projections), if content exceeds the limit
|
|
37868
|
+
//
|
|
37609
37869
|
if (clipData) {
|
|
37610
|
-
|
|
37611
|
-
// the clipping shape. But how to tell?
|
|
37612
|
-
clipLayersInPlace(dataset.layers, clipData, dataset, 'clip');
|
|
37613
|
-
clipped = true;
|
|
37870
|
+
clipped = clipLayersIfNeeded(dataset, clipData);
|
|
37614
37871
|
}
|
|
37615
37872
|
return cut || clipped;
|
|
37616
37873
|
}
|
|
37617
37874
|
|
|
37875
|
+
function clipLayersIfNeeded(dataset, clipData) {
|
|
37876
|
+
// Avoid clipping layers that are fully enclosed within the projectable
|
|
37877
|
+
// coordinate space (represented by a dataset containing a single
|
|
37878
|
+
// polygon layer, @clipData). This avoids performing unnecessary intersection
|
|
37879
|
+
// tests on each line segment.
|
|
37880
|
+
var layers = dataset.layers.filter(function(lyr) {
|
|
37881
|
+
return !layerIsFullyEnclosed(lyr, dataset, clipData);
|
|
37882
|
+
});
|
|
37883
|
+
if (layers.length > 0) {
|
|
37884
|
+
clipLayersInPlace(layers, clipData, dataset, 'clip');
|
|
37885
|
+
return true;
|
|
37886
|
+
}
|
|
37887
|
+
return false;
|
|
37888
|
+
}
|
|
37889
|
+
|
|
37890
|
+
// @clipData: a dataset containing a polygon layer
|
|
37891
|
+
function layerIsFullyEnclosed(lyr, dataset, clipData) {
|
|
37892
|
+
// This test uses the layer's bounding box to represent the extent of the
|
|
37893
|
+
// layer, and can produce false negatives.
|
|
37894
|
+
var dataBounds = getLayerBounds(lyr, dataset.arcs);
|
|
37895
|
+
var enclosed = false;
|
|
37896
|
+
clipData.layers[0].shapes.forEach(function(shp, i) {
|
|
37897
|
+
enclosed = enclosed || testBoundsInPolygon(dataBounds, shp, clipData.arcs);
|
|
37898
|
+
});
|
|
37899
|
+
return enclosed;
|
|
37900
|
+
}
|
|
37901
|
+
|
|
37618
37902
|
|
|
37619
37903
|
function insertPreProjectionCuts(dataset, src, dest) {
|
|
37620
37904
|
var antimeridian = getAntimeridian(dest.lam0 * 180 / Math.PI);
|
|
@@ -37809,7 +38093,6 @@ ${svg}
|
|
|
37809
38093
|
var badArcs = 0;
|
|
37810
38094
|
var badPoints = 0;
|
|
37811
38095
|
var clipped = preProjectionClip(dataset, src, dest, opts);
|
|
37812
|
-
|
|
37813
38096
|
dataset.layers.forEach(function(lyr) {
|
|
37814
38097
|
if (layerHasPoints(lyr)) {
|
|
37815
38098
|
badPoints += projectPointLayer(lyr, proj); // v2 compatible (invalid points are removed)
|
|
@@ -37826,7 +38109,7 @@ ${svg}
|
|
|
37826
38109
|
if (clipped) {
|
|
37827
38110
|
// TODO: could more selective in cleaning clipped layers
|
|
37828
38111
|
// (probably only needed when clipped area crosses the antimeridian or includes a pole)
|
|
37829
|
-
|
|
38112
|
+
cleanProjectedPathLayers(dataset);
|
|
37830
38113
|
}
|
|
37831
38114
|
|
|
37832
38115
|
if (badArcs > 0 && !opts.quiet) {
|
|
@@ -37842,7 +38125,7 @@ ${svg}
|
|
|
37842
38125
|
// * Removes line intersections
|
|
37843
38126
|
// * TODO: what if a layer contains polygons with desired overlaps? should
|
|
37844
38127
|
// we ignore overlaps between different features?
|
|
37845
|
-
function
|
|
38128
|
+
function cleanProjectedPathLayers(dataset) {
|
|
37846
38129
|
// TODO: only clean affected polygons (cleaning all polygons can be slow)
|
|
37847
38130
|
var polygonLayers = dataset.layers.filter(lyr => lyr.geometry_type == 'polygon');
|
|
37848
38131
|
// clean options: force a topology update (by default, this only happens when
|
|
@@ -37910,7 +38193,7 @@ ${svg}
|
|
|
37910
38193
|
__proto__: null,
|
|
37911
38194
|
fetchCrsInfo: fetchCrsInfo,
|
|
37912
38195
|
projectDataset: projectDataset,
|
|
37913
|
-
|
|
38196
|
+
cleanProjectedPathLayers: cleanProjectedPathLayers,
|
|
37914
38197
|
projectPointLayer: projectPointLayer,
|
|
37915
38198
|
projectArcs: projectArcs,
|
|
37916
38199
|
projectArcs2: projectArcs2
|
|
@@ -38884,12 +39167,12 @@ ${svg}
|
|
|
38884
39167
|
function polylineToMidpoints(shp, arcs, opts) {
|
|
38885
39168
|
if (!shp) return null;
|
|
38886
39169
|
var points = shp.map(function(path) {
|
|
38887
|
-
return findPathMidpoint(path, arcs);
|
|
39170
|
+
return findPathMidpoint(path, arcs, false);
|
|
38888
39171
|
});
|
|
38889
39172
|
return points;
|
|
38890
39173
|
}
|
|
38891
39174
|
|
|
38892
|
-
function findPathMidpoint(path, arcs) {
|
|
39175
|
+
function findPathMidpoint(path, arcs, useNearestVertex) {
|
|
38893
39176
|
var halfLen = calcPathLen(path, arcs, false) / 2;
|
|
38894
39177
|
var partialLen = 0;
|
|
38895
39178
|
var p;
|
|
@@ -38906,7 +39189,11 @@ ${svg}
|
|
|
38906
39189
|
var k;
|
|
38907
39190
|
if (partialLen + segLen >= halfLen) {
|
|
38908
39191
|
k = (halfLen - partialLen) / segLen;
|
|
38909
|
-
|
|
39192
|
+
if (useNearestVertex) {
|
|
39193
|
+
k = k < 0.5 ? 0 : 1;
|
|
39194
|
+
}
|
|
39195
|
+
// p = [a + k * (c - a), b + k * (d - b)];
|
|
39196
|
+
p = [(1 - k) * a + k * c, (1 - k) * b + k * d];
|
|
38910
39197
|
}
|
|
38911
39198
|
partialLen += segLen;
|
|
38912
39199
|
});
|
|
@@ -42786,7 +43073,7 @@ ${svg}
|
|
|
42786
43073
|
name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
|
|
42787
43074
|
name == 'require' || name == 'run' || name == 'define' ||
|
|
42788
43075
|
name == 'include' || name == 'print' || name == 'comment' || name == 'if' || name == 'elif' ||
|
|
42789
|
-
name == 'else' || name == 'endif' || name == 'stop';
|
|
43076
|
+
name == 'else' || name == 'endif' || name == 'stop' || name == 'add-shape';
|
|
42790
43077
|
}
|
|
42791
43078
|
|
|
42792
43079
|
async function runCommand(command, job) {
|
|
@@ -42860,7 +43147,14 @@ ${svg}
|
|
|
42860
43147
|
source = findCommandSource(convertSourceName(opts.source, targets), job.catalog, opts);
|
|
42861
43148
|
}
|
|
42862
43149
|
|
|
42863
|
-
if (name == '
|
|
43150
|
+
if (name == 'add-shape') {
|
|
43151
|
+
if (!targetDataset) {
|
|
43152
|
+
targetDataset = {info: {}, layers: []};
|
|
43153
|
+
targetLayers = targetDataset.layers;
|
|
43154
|
+
job.catalog.addDataset(targetDataset);
|
|
43155
|
+
}
|
|
43156
|
+
outputLayers = cmd.addShape(targetLayers, targetDataset, opts);
|
|
43157
|
+
} else if (name == 'affine') {
|
|
42864
43158
|
cmd.affine(targetLayers, targetDataset, opts);
|
|
42865
43159
|
|
|
42866
43160
|
} else if (name == 'alpha-shapes') {
|