mapshaper 0.6.117 → 0.6.119
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/README.md +12 -0
- package/mapshaper.js +1228 -288
- package/package.json +1 -1
- package/www/donate.html +215 -0
- package/www/index.html +5 -1
- package/www/mapshaper-gui.js +214 -17
- package/www/mapshaper.js +1228 -288
- package/www/page.css +17 -0
package/www/mapshaper.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
get expandoBuffer () { return expandoBuffer; },
|
|
20
20
|
get extend () { return extend$1; },
|
|
21
21
|
get extendBuffer () { return extendBuffer; },
|
|
22
|
-
get find () { return find; },
|
|
22
|
+
get find () { return find$1; },
|
|
23
23
|
get findMedian () { return findMedian; },
|
|
24
24
|
get findQuantile () { return findQuantile; },
|
|
25
25
|
get findRankByValue () { return findRankByValue; },
|
|
@@ -442,7 +442,7 @@
|
|
|
442
442
|
}, true);
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
-
function find(arr, test, ctx) {
|
|
445
|
+
function find$1(arr, test, ctx) {
|
|
446
446
|
var matches = arr.filter(test, ctx);
|
|
447
447
|
return matches.length === 0 ? null : matches[0];
|
|
448
448
|
}
|
|
@@ -3208,6 +3208,25 @@
|
|
|
3208
3208
|
|
|
3209
3209
|
//import { findCrossIntersection_big } from '../geom/mapshaper-segment-geom-big';
|
|
3210
3210
|
|
|
3211
|
+
// Ad-hoc counters (reset by segmentIntersectionStatsReset, inspected by
|
|
3212
|
+
// segmentIntersectionStats). Cheap when unused, so safe to leave in.
|
|
3213
|
+
var STATS = {
|
|
3214
|
+
calls: 0, // segmentIntersection() invocations
|
|
3215
|
+
touches: 0, // pairs that returned a T-touch result
|
|
3216
|
+
endpointHits: 0, // pairs that returned null via testEndpointHit
|
|
3217
|
+
crossCandidates: 0, // pairs that reached findCrossIntersection()
|
|
3218
|
+
crossRejectedFast: 0,// rejected by segmentHit_fast early-out
|
|
3219
|
+
crossRobust: 0, // useRobustCross() === true, ran BigInt math
|
|
3220
|
+
crossFast: 0, // useRobustCross() === false, ran fp math
|
|
3221
|
+
crossNull: 0 // findCrossIntersection returned null (e.g. collinear)
|
|
3222
|
+
};
|
|
3223
|
+
function segmentIntersectionStatsReset() {
|
|
3224
|
+
for (var k in STATS) STATS[k] = 0;
|
|
3225
|
+
}
|
|
3226
|
+
function segmentIntersectionStats() {
|
|
3227
|
+
return Object.assign({}, STATS);
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3211
3230
|
// Find the intersection between two 2D segments
|
|
3212
3231
|
// Returns 0, 1 or 2 [x, y] locations as null, [x, y], or [x1, y1, x2, y2]
|
|
3213
3232
|
// Special cases:
|
|
@@ -3217,6 +3236,7 @@
|
|
|
3217
3236
|
// is counted as an intersection (there will be either one or two)
|
|
3218
3237
|
//
|
|
3219
3238
|
function segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy, epsArg) {
|
|
3239
|
+
STATS.calls++;
|
|
3220
3240
|
// Use a small tolerance interval, so collinear segments and T-intersections
|
|
3221
3241
|
// are detected (floating point rounding often causes exact functions to fail)
|
|
3222
3242
|
var eps = epsArg > 0 ? epsArg :
|
|
@@ -3230,8 +3250,10 @@
|
|
|
3230
3250
|
// segments that share an endpoint. Two touches indicates overlapping
|
|
3231
3251
|
// collinear segments that do not share an endpoint.
|
|
3232
3252
|
touches = findPointSegTouches(epsSq, ax, ay, bx, by, cx, cy, dx, dy);
|
|
3253
|
+
if (touches) STATS.touches++;
|
|
3233
3254
|
// Ignore endpoint-only intersections
|
|
3234
3255
|
if (!touches && testEndpointHit(epsSq, ax, ay, bx, by, cx, cy, dx, dy)) {
|
|
3256
|
+
STATS.endpointHits++;
|
|
3235
3257
|
return null;
|
|
3236
3258
|
}
|
|
3237
3259
|
// Detect cross intersection
|
|
@@ -3243,6 +3265,7 @@
|
|
|
3243
3265
|
}
|
|
3244
3266
|
|
|
3245
3267
|
function findCrossIntersection(ax, ay, bx, by, cx, cy, dx, dy, eps) {
|
|
3268
|
+
STATS.crossCandidates++;
|
|
3246
3269
|
var p;
|
|
3247
3270
|
// The normal-precision hit function works for all inputs when eps > 0 because
|
|
3248
3271
|
// the geometries that cause the ordinary function fails are detected as
|
|
@@ -3250,8 +3273,10 @@
|
|
|
3250
3273
|
// data samples that were tested).
|
|
3251
3274
|
//
|
|
3252
3275
|
if (eps > 0 && !segmentHit_fast(ax, ay, bx, by, cx, cy, dx, dy)) {
|
|
3276
|
+
STATS.crossRejectedFast++;
|
|
3253
3277
|
return null;
|
|
3254
3278
|
} else if (eps === 0 && !segmentHit_robust(ax, ay, bx, by, cx, cy, dx, dy)) {
|
|
3279
|
+
STATS.crossRejectedFast++;
|
|
3255
3280
|
return null;
|
|
3256
3281
|
}
|
|
3257
3282
|
|
|
@@ -3260,17 +3285,13 @@
|
|
|
3260
3285
|
// the positional error within a small interval (e.g. 50% of eps)
|
|
3261
3286
|
//
|
|
3262
3287
|
if (useRobustCross(ax, ay, bx, by, cx, cy, dx, dy)) {
|
|
3288
|
+
STATS.crossRobust++;
|
|
3263
3289
|
p = findCrossIntersection_robust(ax, ay, bx, by, cx, cy, dx, dy);
|
|
3264
|
-
// var p2 = findCrossIntersection_big(ax, ay, bx, by, cx, cy, dx, dy);
|
|
3265
|
-
// var dx = p[0] - p2[0];
|
|
3266
|
-
// var dy = p[1] - p2[1];
|
|
3267
|
-
// if (dx != 0 || dy != 0) {
|
|
3268
|
-
// console.log(dx, dy)
|
|
3269
|
-
// }
|
|
3270
3290
|
} else {
|
|
3291
|
+
STATS.crossFast++;
|
|
3271
3292
|
p = findCrossIntersection_fast(ax, ay, bx, by, cx, cy, dx, dy);
|
|
3272
3293
|
}
|
|
3273
|
-
if (!p) return null;
|
|
3294
|
+
if (!p) { STATS.crossNull++; return null; }
|
|
3274
3295
|
|
|
3275
3296
|
// Snap p to a vertex if very close to one
|
|
3276
3297
|
// This avoids tiny segments caused by T-intersection overshoots and prevents
|
|
@@ -3590,6 +3611,8 @@
|
|
|
3590
3611
|
orient2D_robust: orient2D_robust,
|
|
3591
3612
|
segmentHit_fast: segmentHit_fast,
|
|
3592
3613
|
segmentIntersection: segmentIntersection,
|
|
3614
|
+
segmentIntersectionStats: segmentIntersectionStats,
|
|
3615
|
+
segmentIntersectionStatsReset: segmentIntersectionStatsReset,
|
|
3593
3616
|
segmentTurn: segmentTurn
|
|
3594
3617
|
});
|
|
3595
3618
|
|
|
@@ -6399,41 +6422,52 @@
|
|
|
6399
6422
|
};
|
|
6400
6423
|
}
|
|
6401
6424
|
|
|
6402
|
-
// Used for building topology
|
|
6425
|
+
// Used for building topology.
|
|
6426
|
+
//
|
|
6427
|
+
// Every arc is stored as a reference into some source coordinate arrays
|
|
6428
|
+
// (srcXX/srcYY) plus an inclusive [start..end] index range. The caller
|
|
6429
|
+
// decides which buffer to hand in: the shared xx/yy for normal arcs, or
|
|
6430
|
+
// a freshly-allocated merged buffer for split/wrap-around arcs. ArcIndex
|
|
6431
|
+
// doesn't care which — no sentinels, no special cases in its own code.
|
|
6403
6432
|
//
|
|
6404
6433
|
function ArcIndex(pointCount) {
|
|
6405
6434
|
var hashTableSize = Math.floor(pointCount * 0.25 + 1),
|
|
6406
6435
|
hash = getXYHash(hashTableSize),
|
|
6407
6436
|
hashTable = new Int32Array(hashTableSize),
|
|
6408
6437
|
chainIds = [],
|
|
6409
|
-
|
|
6438
|
+
arcSrcXX = [],
|
|
6439
|
+
arcSrcYY = [],
|
|
6440
|
+
arcStart = [],
|
|
6441
|
+
arcEnd = [],
|
|
6410
6442
|
arcPoints = 0;
|
|
6411
6443
|
|
|
6412
|
-
|
|
6444
|
+
initializeArray(hashTable, -1);
|
|
6413
6445
|
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6446
|
+
// Register an arc whose coordinates are `srcXX[start..end]`, `srcYY[start..end]`
|
|
6447
|
+
// (inclusive). Returns the new arc id.
|
|
6448
|
+
this.addArc = function(srcXX, srcYY, start, end) {
|
|
6449
|
+
var arcId = arcSrcXX.length,
|
|
6450
|
+
key = hash(srcXX[end], srcYY[end]);
|
|
6451
|
+
chainIds.push(hashTable[key]);
|
|
6419
6452
|
hashTable[key] = arcId;
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6453
|
+
arcSrcXX.push(srcXX);
|
|
6454
|
+
arcSrcYY.push(srcYY);
|
|
6455
|
+
arcStart.push(start);
|
|
6456
|
+
arcEnd.push(end);
|
|
6457
|
+
arcPoints += end - start + 1;
|
|
6423
6458
|
return arcId;
|
|
6424
6459
|
};
|
|
6425
6460
|
|
|
6426
|
-
// Look for a previously generated arc with the same sequence of coords, but
|
|
6427
|
-
// opposite direction. (This program uses the convention of CW for
|
|
6428
|
-
//
|
|
6461
|
+
// Look for a previously generated arc with the same sequence of coords, but
|
|
6462
|
+
// in the opposite direction. (This program uses the convention of CW for
|
|
6463
|
+
// space-enclosing rings, CCW for holes, so coincident boundaries should
|
|
6464
|
+
// contain the same points in reverse sequence.)
|
|
6429
6465
|
//
|
|
6430
6466
|
this.findDuplicateArc = function(xx, yy, start, end, getNext, getPrev) {
|
|
6431
|
-
// First, look for a reverse match
|
|
6432
6467
|
var arcId = findArcNeighbor(xx, yy, start, end, getNext);
|
|
6433
6468
|
if (arcId === null) {
|
|
6434
|
-
// Look for forward match
|
|
6435
|
-
//
|
|
6436
|
-
// Shapefiles sometimes have duplicate paths)
|
|
6469
|
+
// Look for forward match (abnormal topology, but we accept it because
|
|
6470
|
+
// in-the-wild Shapefiles sometimes have duplicate paths).
|
|
6437
6471
|
arcId = findArcNeighbor(xx, yy, end, start, getPrev);
|
|
6438
6472
|
} else {
|
|
6439
6473
|
arcId = ~arcId;
|
|
@@ -6445,17 +6479,14 @@
|
|
|
6445
6479
|
var next = getNext(start),
|
|
6446
6480
|
key = hash(xx[start], yy[start]),
|
|
6447
6481
|
arcId = hashTable[key],
|
|
6448
|
-
|
|
6449
|
-
|
|
6482
|
+
sx, sy, s, e;
|
|
6450
6483
|
while (arcId != -1) {
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
//
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
if (arcX[0] === xx[end] && arcX[len-1] === xx[start] && arcX[len-2] === xx[next] &&
|
|
6458
|
-
arcY[0] === yy[end] && arcY[len-1] === yy[start] && arcY[len-2] === yy[next]) {
|
|
6484
|
+
sx = arcSrcXX[arcId]; sy = arcSrcYY[arcId];
|
|
6485
|
+
s = arcStart[arcId]; e = arcEnd[arcId];
|
|
6486
|
+
// Check endpoints and one segment. A more rigorous match would compare
|
|
6487
|
+
// every segment, but that's slower and this is sufficient in practice.
|
|
6488
|
+
if (sx[s] === xx[end] && sx[e] === xx[start] && sx[e - 1] === xx[next] &&
|
|
6489
|
+
sy[s] === yy[end] && sy[e] === yy[start] && sy[e - 1] === yy[next]) {
|
|
6459
6490
|
return arcId;
|
|
6460
6491
|
}
|
|
6461
6492
|
arcId = chainIds[arcId];
|
|
@@ -6464,22 +6495,31 @@
|
|
|
6464
6495
|
}
|
|
6465
6496
|
|
|
6466
6497
|
this.getVertexData = function() {
|
|
6467
|
-
var
|
|
6468
|
-
|
|
6469
|
-
|
|
6498
|
+
var arcCount = arcSrcXX.length,
|
|
6499
|
+
destXX = new Float64Array(arcPoints),
|
|
6500
|
+
destYY = new Float64Array(arcPoints),
|
|
6501
|
+
nn = new Uint32Array(arcCount),
|
|
6470
6502
|
copied = 0,
|
|
6471
|
-
|
|
6472
|
-
for (
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6503
|
+
sx, sy, s, e, len, i;
|
|
6504
|
+
for (i = 0; i < arcCount; i++) {
|
|
6505
|
+
sx = arcSrcXX[i]; sy = arcSrcYY[i];
|
|
6506
|
+
s = arcStart[i]; e = arcEnd[i];
|
|
6507
|
+
len = e - s + 1;
|
|
6508
|
+
if (sx.subarray) {
|
|
6509
|
+
destXX.set(sx.subarray(s, e + 1), copied);
|
|
6510
|
+
destYY.set(sy.subarray(s, e + 1), copied);
|
|
6511
|
+
} else {
|
|
6512
|
+
for (var k = 0; k < len; k++) {
|
|
6513
|
+
destXX[copied + k] = sx[s + k];
|
|
6514
|
+
destYY[copied + k] = sy[s + k];
|
|
6515
|
+
}
|
|
6516
|
+
}
|
|
6477
6517
|
nn[i] = len;
|
|
6478
6518
|
copied += len;
|
|
6479
6519
|
}
|
|
6480
6520
|
return {
|
|
6481
|
-
xx:
|
|
6482
|
-
yy:
|
|
6521
|
+
xx: destXX,
|
|
6522
|
+
yy: destYY,
|
|
6483
6523
|
nn: nn
|
|
6484
6524
|
};
|
|
6485
6525
|
};
|
|
@@ -6578,18 +6618,15 @@
|
|
|
6578
6618
|
var pointCount = xx.length,
|
|
6579
6619
|
chainIds = initPointChains(xx, yy),
|
|
6580
6620
|
pathIds = initPathIds(pointCount, nn),
|
|
6621
|
+
pathIsRing = initPathIsRing(nn, xx, yy),
|
|
6622
|
+
isNode = computeIsNode(nn, xx, yy, chainIds, pathIds, pathIsRing),
|
|
6581
6623
|
index = new ArcIndex(pointCount),
|
|
6582
|
-
slice = usingTypedArrays() ? xx.subarray : Array.prototype.slice,
|
|
6583
6624
|
paths, retn;
|
|
6584
6625
|
paths = convertPaths(nn);
|
|
6585
6626
|
retn = index.getVertexData();
|
|
6586
6627
|
retn.paths = paths;
|
|
6587
6628
|
return retn;
|
|
6588
6629
|
|
|
6589
|
-
function usingTypedArrays() {
|
|
6590
|
-
return !!(xx.subarray && yy.subarray);
|
|
6591
|
-
}
|
|
6592
|
-
|
|
6593
6630
|
function convertPaths(nn) {
|
|
6594
6631
|
var paths = [],
|
|
6595
6632
|
pointId = 0,
|
|
@@ -6602,28 +6639,28 @@
|
|
|
6602
6639
|
return paths;
|
|
6603
6640
|
}
|
|
6604
6641
|
|
|
6642
|
+
// Fast neighbour lookups using the precomputed pathIsRing cache.
|
|
6643
|
+
// Used by findDuplicateArc (via addEdge/addSplitEdge) and by addRing.
|
|
6605
6644
|
function nextPoint(id) {
|
|
6606
6645
|
var partId = pathIds[id],
|
|
6607
6646
|
nextId = id + 1;
|
|
6608
6647
|
if (nextId < pointCount && pathIds[nextId] === partId) {
|
|
6609
|
-
return
|
|
6648
|
+
return nextId;
|
|
6610
6649
|
}
|
|
6611
|
-
|
|
6612
|
-
return sameXY(id, id - len + 1) ? id - len + 2 : -1;
|
|
6650
|
+
return pathIsRing[partId] ? id - nn[partId] + 2 : -1;
|
|
6613
6651
|
}
|
|
6614
6652
|
|
|
6615
6653
|
function prevPoint(id) {
|
|
6616
6654
|
var partId = pathIds[id],
|
|
6617
6655
|
prevId = id - 1;
|
|
6618
6656
|
if (prevId >= 0 && pathIds[prevId] === partId) {
|
|
6619
|
-
return
|
|
6657
|
+
return prevId;
|
|
6620
6658
|
}
|
|
6621
|
-
|
|
6622
|
-
return sameXY(id, id + len - 1) ? id + len - 2 : -1;
|
|
6659
|
+
return pathIsRing[partId] ? id + nn[partId] - 2 : -1;
|
|
6623
6660
|
}
|
|
6624
6661
|
|
|
6625
|
-
function
|
|
6626
|
-
return
|
|
6662
|
+
function pointIsArcEndpoint(id) {
|
|
6663
|
+
return isNode[id] === 1;
|
|
6627
6664
|
}
|
|
6628
6665
|
|
|
6629
6666
|
// Convert a non-topological path to one or more topological arcs
|
|
@@ -6666,49 +6703,9 @@
|
|
|
6666
6703
|
return arcIds;
|
|
6667
6704
|
}
|
|
6668
6705
|
|
|
6669
|
-
// Test if a point @id is an endpoint of a topological path
|
|
6670
|
-
function pointIsArcEndpoint(id) {
|
|
6671
|
-
var id2 = chainIds[id],
|
|
6672
|
-
prev = prevPoint(id),
|
|
6673
|
-
next = nextPoint(id),
|
|
6674
|
-
prev2, next2;
|
|
6675
|
-
if (prev == -1 || next == -1) {
|
|
6676
|
-
// @id is an endpoint if it is the start or end of an open path
|
|
6677
|
-
return true;
|
|
6678
|
-
}
|
|
6679
|
-
while (id != id2) {
|
|
6680
|
-
prev2 = prevPoint(id2);
|
|
6681
|
-
next2 = nextPoint(id2);
|
|
6682
|
-
if (prev2 == -1 || next2 == -1 || brokenEdge(prev, next, prev2, next2)) {
|
|
6683
|
-
// there is a discontinuity at @id -- point is arc endpoint
|
|
6684
|
-
return true;
|
|
6685
|
-
}
|
|
6686
|
-
id2 = chainIds[id2];
|
|
6687
|
-
}
|
|
6688
|
-
return false;
|
|
6689
|
-
}
|
|
6690
|
-
|
|
6691
|
-
// a and b are two vertices with the same x, y coordinates
|
|
6692
|
-
// test if the segments on either side of them are also identical
|
|
6693
|
-
function brokenEdge(aprev, anext, bprev, bnext) {
|
|
6694
|
-
var apx = xx[aprev],
|
|
6695
|
-
anx = xx[anext],
|
|
6696
|
-
bpx = xx[bprev],
|
|
6697
|
-
bnx = xx[bnext],
|
|
6698
|
-
apy = yy[aprev],
|
|
6699
|
-
any = yy[anext],
|
|
6700
|
-
bpy = yy[bprev],
|
|
6701
|
-
bny = yy[bnext];
|
|
6702
|
-
if (apx == bnx && anx == bpx && apy == bny && any == bpy ||
|
|
6703
|
-
apx == bpx && anx == bnx && apy == bpy && any == bny) {
|
|
6704
|
-
return false;
|
|
6705
|
-
}
|
|
6706
|
-
return true;
|
|
6707
|
-
}
|
|
6708
|
-
|
|
6709
6706
|
function mergeArcParts(src, startId, endId, startId2, endId2) {
|
|
6710
6707
|
var len = endId - startId + endId2 - startId2 + 2,
|
|
6711
|
-
ArrayClass =
|
|
6708
|
+
ArrayClass = src.subarray ? Float64Array : Array,
|
|
6712
6709
|
dest = new ArrayClass(len),
|
|
6713
6710
|
j = 0, i;
|
|
6714
6711
|
for (i=startId; i <= endId; i++) {
|
|
@@ -6723,8 +6720,12 @@
|
|
|
6723
6720
|
function addSplitEdge(start1, end1, start2, end2) {
|
|
6724
6721
|
var arcId = index.findDuplicateArc(xx, yy, start1, end2, nextPoint, prevPoint);
|
|
6725
6722
|
if (arcId === null) {
|
|
6726
|
-
|
|
6727
|
-
|
|
6723
|
+
// Coordinates for a split (wrap-around) edge don't form a contiguous
|
|
6724
|
+
// slice of xx/yy, so we build a standalone buffer and hand it to the
|
|
6725
|
+
// index as the arc's source.
|
|
6726
|
+
var mx = mergeArcParts(xx, start1, end1, start2, end2),
|
|
6727
|
+
my = mergeArcParts(yy, start1, end1, start2, end2);
|
|
6728
|
+
arcId = index.addArc(mx, my, 0, mx.length - 1);
|
|
6728
6729
|
}
|
|
6729
6730
|
return arcId;
|
|
6730
6731
|
}
|
|
@@ -6733,8 +6734,7 @@
|
|
|
6733
6734
|
// search for a matching edge that has already been generated
|
|
6734
6735
|
var arcId = index.findDuplicateArc(xx, yy, start, end, nextPoint, prevPoint);
|
|
6735
6736
|
if (arcId === null) {
|
|
6736
|
-
arcId = index.addArc(
|
|
6737
|
-
slice.call(yy, start, end + 1));
|
|
6737
|
+
arcId = index.addArc(xx, yy, start, end);
|
|
6738
6738
|
}
|
|
6739
6739
|
return arcId;
|
|
6740
6740
|
}
|
|
@@ -6777,6 +6777,87 @@
|
|
|
6777
6777
|
return pathIds;
|
|
6778
6778
|
}
|
|
6779
6779
|
|
|
6780
|
+
// Per-path flag: 1 if the path is a closed ring (first vertex coincides with
|
|
6781
|
+
// last vertex), else 0. Computed once so that prevPoint()/nextPoint() don't
|
|
6782
|
+
// need to call sameXY() at path boundaries.
|
|
6783
|
+
//
|
|
6784
|
+
function initPathIsRing(nn, xx, yy) {
|
|
6785
|
+
var pathCount = nn.length,
|
|
6786
|
+
pathIsRing = new Uint8Array(pathCount),
|
|
6787
|
+
pstart = 0, len;
|
|
6788
|
+
for (var p = 0; p < pathCount; p++) {
|
|
6789
|
+
len = nn[p];
|
|
6790
|
+
if (len > 1 &&
|
|
6791
|
+
xx[pstart] === xx[pstart + len - 1] &&
|
|
6792
|
+
yy[pstart] === yy[pstart + len - 1]) {
|
|
6793
|
+
pathIsRing[p] = 1;
|
|
6794
|
+
}
|
|
6795
|
+
pstart += len;
|
|
6796
|
+
}
|
|
6797
|
+
return pathIsRing;
|
|
6798
|
+
}
|
|
6799
|
+
|
|
6800
|
+
// Decide, for every point, whether it is a topological node (an arc
|
|
6801
|
+
// endpoint). Being a node is a property of the entire coincident-point
|
|
6802
|
+
// chain: all points sharing a location agree on the answer. So we walk
|
|
6803
|
+
// each chain at most once — O(n) total — instead of doing the walk
|
|
6804
|
+
// independently at every point (O(n * K), quadratic per chain).
|
|
6805
|
+
//
|
|
6806
|
+
// A chain's points are nodes iff:
|
|
6807
|
+
// - any member has a missing neighbour (open-path endpoint), or
|
|
6808
|
+
// - two members disagree on the unordered pair of neighbour coords.
|
|
6809
|
+
//
|
|
6810
|
+
function computeIsNode(nn, xx, yy, chainIds, pathIds, pathIsRing) {
|
|
6811
|
+
var n = xx.length,
|
|
6812
|
+
isNode = new Uint8Array(n),
|
|
6813
|
+
done = new Uint8Array(n);
|
|
6814
|
+
|
|
6815
|
+
function nextPoint(id) {
|
|
6816
|
+
var part = pathIds[id], nid = id + 1;
|
|
6817
|
+
if (nid < n && pathIds[nid] === part) return nid;
|
|
6818
|
+
return pathIsRing[part] ? id - nn[part] + 2 : -1;
|
|
6819
|
+
}
|
|
6820
|
+
|
|
6821
|
+
function prevPoint(id) {
|
|
6822
|
+
var part = pathIds[id], pid = id - 1;
|
|
6823
|
+
if (pid >= 0 && pathIds[pid] === part) return pid;
|
|
6824
|
+
return pathIsRing[part] ? id + nn[part] - 2 : -1;
|
|
6825
|
+
}
|
|
6826
|
+
|
|
6827
|
+
for (var i = 0; i < n; i++) {
|
|
6828
|
+
if (done[i]) continue;
|
|
6829
|
+
var result = chainIsBroken(i);
|
|
6830
|
+
var id = i;
|
|
6831
|
+
do {
|
|
6832
|
+
isNode[id] = result;
|
|
6833
|
+
done[id] = 1;
|
|
6834
|
+
id = chainIds[id];
|
|
6835
|
+
} while (id !== i);
|
|
6836
|
+
}
|
|
6837
|
+
return isNode;
|
|
6838
|
+
|
|
6839
|
+
// Returns 1 if the chain containing `start` has any broken neighbour
|
|
6840
|
+
// signature (i.e. all members are nodes), else 0.
|
|
6841
|
+
function chainIsBroken(start) {
|
|
6842
|
+
var prev = prevPoint(start),
|
|
6843
|
+
next = nextPoint(start);
|
|
6844
|
+
if (prev === -1 || next === -1) return 1;
|
|
6845
|
+
var refPX = xx[prev], refPY = yy[prev],
|
|
6846
|
+
refNX = xx[next], refNY = yy[next];
|
|
6847
|
+
var id = chainIds[start];
|
|
6848
|
+
while (id !== start) {
|
|
6849
|
+
var p = prevPoint(id), q = nextPoint(id);
|
|
6850
|
+
if (p === -1 || q === -1) return 1;
|
|
6851
|
+
var px = xx[p], py = yy[p], qx = xx[q], qy = yy[q];
|
|
6852
|
+
var fwd = px === refPX && py === refPY && qx === refNX && qy === refNY;
|
|
6853
|
+
var rev = px === refNX && py === refNY && qx === refPX && qy === refPY;
|
|
6854
|
+
if (!fwd && !rev) return 1;
|
|
6855
|
+
id = chainIds[id];
|
|
6856
|
+
}
|
|
6857
|
+
return 0;
|
|
6858
|
+
}
|
|
6859
|
+
}
|
|
6860
|
+
|
|
6780
6861
|
function replaceArcIds(src, replacements) {
|
|
6781
6862
|
return src.map(function(shape) {
|
|
6782
6863
|
return replaceArcsInShape(shape, replacements);
|
|
@@ -7262,15 +7343,201 @@
|
|
|
7262
7343
|
};
|
|
7263
7344
|
}
|
|
7264
7345
|
|
|
7346
|
+
// Lightweight hierarchical profiler.
|
|
7347
|
+
// Intended for ad-hoc performance work on hot pipelines (e.g. addIntersectionCuts).
|
|
7348
|
+
//
|
|
7349
|
+
// Usage:
|
|
7350
|
+
// import { profileStart, profileEnd, profileWrap, ... } from './utils/mapshaper-profile';
|
|
7351
|
+
// profileStart('phase'); doWork(); profileEnd('phase');
|
|
7352
|
+
// var result = profileWrap('phase', () => doWork());
|
|
7353
|
+
//
|
|
7354
|
+
// When disabled (the default) every call short-circuits in ~one comparison; safe
|
|
7355
|
+
// to leave in hot code paths. Enable from the CLI with the `-profile` command,
|
|
7356
|
+
// from JS with enableProfiling(), or by setting the MAPSHAPER_PROFILE env var.
|
|
7357
|
+
//
|
|
7358
|
+
// The profiler tracks a stack of currently-open phases so calls can nest. Each
|
|
7359
|
+
// unique stack path accumulates ms-elapsed and a call count. profileReport()
|
|
7360
|
+
// returns a flat array; formatProfileReport() pretty-prints a tree.
|
|
7361
|
+
|
|
7362
|
+
var ENABLED = false;
|
|
7363
|
+
var ROOT = makeNode('<root>');
|
|
7364
|
+
var STACK = [ROOT];
|
|
7365
|
+
var WALL_START = 0;
|
|
7366
|
+
|
|
7367
|
+
function makeNode(label) {
|
|
7368
|
+
return {
|
|
7369
|
+
label: label,
|
|
7370
|
+
totalMs: 0,
|
|
7371
|
+
selfMs: 0,
|
|
7372
|
+
calls: 0,
|
|
7373
|
+
childMsAccum: 0,
|
|
7374
|
+
children: Object.create(null)
|
|
7375
|
+
};
|
|
7376
|
+
}
|
|
7377
|
+
|
|
7378
|
+
function nowMs() {
|
|
7379
|
+
if (typeof process !== 'undefined' && process.hrtime && process.hrtime.bigint) {
|
|
7380
|
+
// bigint -> number division: precision down to 1 microsecond is fine for our needs
|
|
7381
|
+
return Number(process.hrtime.bigint()) / 1e6;
|
|
7382
|
+
}
|
|
7383
|
+
if (typeof performance !== 'undefined' && performance.now) {
|
|
7384
|
+
return performance.now();
|
|
7385
|
+
}
|
|
7386
|
+
return Date.now();
|
|
7387
|
+
}
|
|
7388
|
+
|
|
7389
|
+
function enableProfiling() {
|
|
7390
|
+
ENABLED = true;
|
|
7391
|
+
WALL_START = nowMs();
|
|
7392
|
+
}
|
|
7393
|
+
|
|
7394
|
+
function disableProfiling() {
|
|
7395
|
+
ENABLED = false;
|
|
7396
|
+
}
|
|
7397
|
+
|
|
7398
|
+
function profileEnabled() {
|
|
7399
|
+
return ENABLED;
|
|
7400
|
+
}
|
|
7401
|
+
|
|
7402
|
+
function profileReset() {
|
|
7403
|
+
ROOT = makeNode('<root>');
|
|
7404
|
+
STACK = [ROOT];
|
|
7405
|
+
WALL_START = ENABLED ? nowMs() : 0;
|
|
7406
|
+
}
|
|
7407
|
+
|
|
7408
|
+
// Open a new phase. Cheap (~one branch) when disabled.
|
|
7409
|
+
function profileStart(label) {
|
|
7410
|
+
if (!ENABLED) return;
|
|
7411
|
+
var parent = STACK[STACK.length - 1];
|
|
7412
|
+
var node = parent.children[label];
|
|
7413
|
+
if (!node) {
|
|
7414
|
+
node = makeNode(label);
|
|
7415
|
+
parent.children[label] = node;
|
|
7416
|
+
}
|
|
7417
|
+
node._t0 = nowMs();
|
|
7418
|
+
STACK.push(node);
|
|
7419
|
+
}
|
|
7420
|
+
|
|
7421
|
+
function profileEnd(label) {
|
|
7422
|
+
if (!ENABLED) return;
|
|
7423
|
+
var node = STACK[STACK.length - 1];
|
|
7424
|
+
if (label && node.label !== label) {
|
|
7425
|
+
// Mismatched labels usually mean an early return forgot to call profileEnd().
|
|
7426
|
+
// Walk up the stack until we find a match, closing the intervening frames.
|
|
7427
|
+
while (STACK.length > 1 && STACK[STACK.length - 1].label !== label) {
|
|
7428
|
+
profileEnd(STACK[STACK.length - 1].label);
|
|
7429
|
+
}
|
|
7430
|
+
node = STACK[STACK.length - 1];
|
|
7431
|
+
if (node.label !== label) return; // give up rather than throw
|
|
7432
|
+
}
|
|
7433
|
+
var elapsed = nowMs() - node._t0;
|
|
7434
|
+
node._t0 = 0;
|
|
7435
|
+
node.totalMs += elapsed;
|
|
7436
|
+
node.calls += 1;
|
|
7437
|
+
STACK.pop();
|
|
7438
|
+
var parent = STACK[STACK.length - 1];
|
|
7439
|
+
parent.childMsAccum += elapsed;
|
|
7440
|
+
}
|
|
7441
|
+
|
|
7442
|
+
// Wrap a function call. Re-throws if the callback throws but still closes the
|
|
7443
|
+
// phase so the stack stays consistent.
|
|
7444
|
+
function profileWrap(label, fn) {
|
|
7445
|
+
if (!ENABLED) return fn();
|
|
7446
|
+
profileStart(label);
|
|
7447
|
+
try {
|
|
7448
|
+
return fn();
|
|
7449
|
+
} finally {
|
|
7450
|
+
profileEnd(label);
|
|
7451
|
+
}
|
|
7452
|
+
}
|
|
7453
|
+
|
|
7454
|
+
// Build a flat tree report: array of {depth, label, totalMs, selfMs, calls}.
|
|
7455
|
+
function profileReport() {
|
|
7456
|
+
var rows = [];
|
|
7457
|
+
function visit(node, depth) {
|
|
7458
|
+
if (depth > 0) {
|
|
7459
|
+
rows.push({
|
|
7460
|
+
depth: depth - 1,
|
|
7461
|
+
label: node.label,
|
|
7462
|
+
totalMs: node.totalMs,
|
|
7463
|
+
selfMs: Math.max(0, node.totalMs - node.childMsAccum),
|
|
7464
|
+
calls: node.calls
|
|
7465
|
+
});
|
|
7466
|
+
}
|
|
7467
|
+
var keys = Object.keys(node.children);
|
|
7468
|
+
keys.sort(function(a, b) {
|
|
7469
|
+
return node.children[b].totalMs - node.children[a].totalMs;
|
|
7470
|
+
});
|
|
7471
|
+
for (var i = 0; i < keys.length; i++) {
|
|
7472
|
+
visit(node.children[keys[i]], depth + 1);
|
|
7473
|
+
}
|
|
7474
|
+
}
|
|
7475
|
+
visit(ROOT, 0);
|
|
7476
|
+
return rows;
|
|
7477
|
+
}
|
|
7478
|
+
|
|
7479
|
+
function profileWallElapsedMs() {
|
|
7480
|
+
return ENABLED && WALL_START ? nowMs() - WALL_START : 0;
|
|
7481
|
+
}
|
|
7482
|
+
|
|
7483
|
+
// Pretty-print the current report as a column-aligned tree.
|
|
7484
|
+
function formatProfileReport(opts) {
|
|
7485
|
+
var rows = profileReport();
|
|
7486
|
+
if (rows.length === 0) return '(profile is empty)';
|
|
7487
|
+
opts = opts || {};
|
|
7488
|
+
var indent = ' ';
|
|
7489
|
+
var lines = [];
|
|
7490
|
+
// header
|
|
7491
|
+
lines.push(['phase', 'total ms', 'self ms', 'calls'].join('\t'));
|
|
7492
|
+
for (var i = 0; i < rows.length; i++) {
|
|
7493
|
+
var r = rows[i];
|
|
7494
|
+
var label = '';
|
|
7495
|
+
for (var j = 0; j < r.depth; j++) label += indent;
|
|
7496
|
+
label += r.label;
|
|
7497
|
+
lines.push([
|
|
7498
|
+
label,
|
|
7499
|
+
r.totalMs.toFixed(2),
|
|
7500
|
+
r.selfMs.toFixed(2),
|
|
7501
|
+
String(r.calls)
|
|
7502
|
+
].join('\t'));
|
|
7503
|
+
}
|
|
7504
|
+
if (opts.includeWall) {
|
|
7505
|
+
lines.push('');
|
|
7506
|
+
lines.push('wall elapsed ms: ' + profileWallElapsedMs().toFixed(2));
|
|
7507
|
+
}
|
|
7508
|
+
return lines.join('\n');
|
|
7509
|
+
}
|
|
7510
|
+
|
|
7511
|
+
// Honour an env var so users (and CI harnesses) can opt in without code changes.
|
|
7512
|
+
if (typeof process !== 'undefined' && process.env && process.env.MAPSHAPER_PROFILE) {
|
|
7513
|
+
enableProfiling();
|
|
7514
|
+
}
|
|
7515
|
+
|
|
7516
|
+
var Profile = /*#__PURE__*/Object.freeze({
|
|
7517
|
+
__proto__: null,
|
|
7518
|
+
disableProfiling: disableProfiling,
|
|
7519
|
+
enableProfiling: enableProfiling,
|
|
7520
|
+
formatProfileReport: formatProfileReport,
|
|
7521
|
+
profileEnabled: profileEnabled,
|
|
7522
|
+
profileEnd: profileEnd,
|
|
7523
|
+
profileReport: profileReport,
|
|
7524
|
+
profileReset: profileReset,
|
|
7525
|
+
profileStart: profileStart,
|
|
7526
|
+
profileWallElapsedMs: profileWallElapsedMs,
|
|
7527
|
+
profileWrap: profileWrap
|
|
7528
|
+
});
|
|
7529
|
+
|
|
7265
7530
|
// Dissolve arcs that can be merged without affecting topology of layers
|
|
7266
7531
|
// remove arcs that are not referenced by any layer; remap arc ids
|
|
7267
7532
|
// in layers. (dataset.arcs is replaced).
|
|
7268
7533
|
function dissolveArcs(dataset) {
|
|
7534
|
+
profileStart('dissolveArcs.body');
|
|
7269
7535
|
var arcs = dataset.arcs,
|
|
7270
7536
|
layers = dataset.layers.filter(layerHasPaths);
|
|
7271
7537
|
|
|
7272
7538
|
if (!arcs || !layers.length) {
|
|
7273
7539
|
dataset.arcs = null;
|
|
7540
|
+
profileEnd('dissolveArcs.body');
|
|
7274
7541
|
return;
|
|
7275
7542
|
}
|
|
7276
7543
|
|
|
@@ -7280,14 +7547,17 @@
|
|
|
7280
7547
|
arcIndex = new Int32Array(arcs.size()), // maps old arc ids to new ids
|
|
7281
7548
|
arcStatus = new Uint8Array(arcs.size());
|
|
7282
7549
|
// arcStatus: 0 = unvisited, 1 = dropped, 2 = remapped, 3 = remapped + reversed
|
|
7550
|
+
profileStart('dissolveArcs.translatePaths');
|
|
7283
7551
|
layers.forEach(function(lyr) {
|
|
7284
|
-
// modify copies of the original shapes; original shapes should be unmodified
|
|
7285
|
-
// (need to test this)
|
|
7286
7552
|
lyr.shapes = lyr.shapes.map(function(shape, i) {
|
|
7287
7553
|
return editShapeParts(shape && shape.concat(), translatePath);
|
|
7288
7554
|
});
|
|
7289
7555
|
});
|
|
7556
|
+
profileEnd('dissolveArcs.translatePaths');
|
|
7557
|
+
profileStart('dissolveArcs.dissolveArcCollection');
|
|
7290
7558
|
dataset.arcs = dissolveArcCollection(arcs, newArcs, totalPoints);
|
|
7559
|
+
profileEnd('dissolveArcs.dissolveArcCollection');
|
|
7560
|
+
profileEnd('dissolveArcs.body');
|
|
7291
7561
|
|
|
7292
7562
|
function translatePath(path) {
|
|
7293
7563
|
var pointCount = 0;
|
|
@@ -12449,14 +12719,22 @@
|
|
|
12449
12719
|
|
|
12450
12720
|
function testPointInRing(p, cand) {
|
|
12451
12721
|
if (!cand.bounds.containsPoint(p[0], p[1])) return false;
|
|
12452
|
-
if (!cand.index
|
|
12453
|
-
// index
|
|
12454
|
-
//
|
|
12455
|
-
|
|
12722
|
+
if (!cand.index) {
|
|
12723
|
+
// Build a per-ring scanline index when (a) the ring is large relative to
|
|
12724
|
+
// the dataset (likely involved in many hit tests anyway) or (b) the same
|
|
12725
|
+
// ring has already been tested at least once, indicating it lies in a
|
|
12726
|
+
// hot spot for repeated point-in-polygon queries (e.g. coastal counties
|
|
12727
|
+
// tested by every offshore-island CCW ring during mosaic enclosure
|
|
12728
|
+
// detection). Building on the second hit avoids paying the index build
|
|
12729
|
+
// cost for rings that are tested only once.
|
|
12730
|
+
if (cand._tested || cand.bounds.area() > totalArea * 0.01) {
|
|
12731
|
+
cand.index = new PolygonIndex([cand.ids], arcs);
|
|
12732
|
+
} else {
|
|
12733
|
+
cand._tested = true;
|
|
12734
|
+
return geom.testPointInRing(p[0], p[1], cand.ids, arcs);
|
|
12735
|
+
}
|
|
12456
12736
|
}
|
|
12457
|
-
return cand.index
|
|
12458
|
-
cand.index.pointInPolygon(p[0], p[1]) :
|
|
12459
|
-
geom.testPointInRing(p[0], p[1], cand.ids, arcs);
|
|
12737
|
+
return cand.index.pointInPolygon(p[0], p[1]);
|
|
12460
12738
|
}
|
|
12461
12739
|
|
|
12462
12740
|
//
|
|
@@ -13476,6 +13754,7 @@
|
|
|
13476
13754
|
|
|
13477
13755
|
function singleStripeId(y) {return 0;}
|
|
13478
13756
|
// Count segments in each stripe
|
|
13757
|
+
profileStart('stripeSetup');
|
|
13479
13758
|
arcs.forEachSegment(function(id1, id2, xx, yy) {
|
|
13480
13759
|
var s1 = stripeId(yy[id1]),
|
|
13481
13760
|
s2 = stripeId(yy[id2]);
|
|
@@ -13512,8 +13791,10 @@
|
|
|
13512
13791
|
s1 += s2 > s1 ? 1 : -1;
|
|
13513
13792
|
}
|
|
13514
13793
|
});
|
|
13794
|
+
profileEnd('stripeSetup');
|
|
13515
13795
|
|
|
13516
13796
|
// Detect intersections among segments in each stripe.
|
|
13797
|
+
profileStart('intersectSegments');
|
|
13517
13798
|
var raw = arcs.getVertexData(),
|
|
13518
13799
|
intersections = [],
|
|
13519
13800
|
arr;
|
|
@@ -13523,7 +13804,11 @@
|
|
|
13523
13804
|
intersections.push(arr[j]);
|
|
13524
13805
|
}
|
|
13525
13806
|
}
|
|
13526
|
-
|
|
13807
|
+
profileEnd('intersectSegments');
|
|
13808
|
+
profileStart('dedupIntersections');
|
|
13809
|
+
var deduped = dedupIntersections(intersections, opts.unique ? getUniqueIntersectionKey : null);
|
|
13810
|
+
profileEnd('dedupIntersections');
|
|
13811
|
+
return deduped;
|
|
13527
13812
|
}
|
|
13528
13813
|
|
|
13529
13814
|
|
|
@@ -13536,22 +13821,42 @@
|
|
|
13536
13821
|
|
|
13537
13822
|
|
|
13538
13823
|
function dedupIntersections(arr, keyFunction) {
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
var
|
|
13543
|
-
|
|
13544
|
-
|
|
13824
|
+
if (keyFunction) {
|
|
13825
|
+
var index = new Map();
|
|
13826
|
+
var out = [];
|
|
13827
|
+
for (var i = 0, n = arr.length; i < n; i++) {
|
|
13828
|
+
var o = arr[i];
|
|
13829
|
+
var k = keyFunction(o);
|
|
13830
|
+
if (index.has(k)) continue;
|
|
13831
|
+
index.set(k, true);
|
|
13832
|
+
out.push(o);
|
|
13545
13833
|
}
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13834
|
+
return out;
|
|
13835
|
+
}
|
|
13836
|
+
// Default key is the pair of segments (a, b). Each segment is a vertex id
|
|
13837
|
+
// pair where the second id is either equal to or one greater than the first
|
|
13838
|
+
// (see formatIntersectingSegment). Pack each segment into a single number
|
|
13839
|
+
// (id * 2 + isMidSegment) and use a two-level Map keyed by integers, which
|
|
13840
|
+
// avoids the per-call string allocation of the previous Array#join key.
|
|
13841
|
+
var outer = new Map();
|
|
13842
|
+
var result = [];
|
|
13843
|
+
for (var ii = 0, nn = arr.length; ii < nn; ii++) {
|
|
13844
|
+
var o2 = arr[ii];
|
|
13845
|
+
var a = o2.a, b = o2.b;
|
|
13846
|
+
var ak = a[0] * 2 + (a[0] === a[1] ? 0 : 1);
|
|
13847
|
+
var bk = b[0] * 2 + (b[0] === b[1] ? 0 : 1);
|
|
13848
|
+
var inner = outer.get(ak);
|
|
13849
|
+
if (inner) {
|
|
13850
|
+
if (inner.has(bk)) continue;
|
|
13851
|
+
inner.add(bk);
|
|
13852
|
+
} else {
|
|
13853
|
+
inner = new Set();
|
|
13854
|
+
inner.add(bk);
|
|
13855
|
+
outer.set(ak, inner);
|
|
13856
|
+
}
|
|
13857
|
+
result.push(o2);
|
|
13858
|
+
}
|
|
13859
|
+
return result;
|
|
13555
13860
|
}
|
|
13556
13861
|
|
|
13557
13862
|
function getUniqueIntersectionKey(o) {
|
|
@@ -16945,16 +17250,26 @@
|
|
|
16945
17250
|
// Remove any unused arcs from the dataset's ArcCollection.
|
|
16946
17251
|
// Return a NodeCollection
|
|
16947
17252
|
function cleanArcReferences(dataset) {
|
|
17253
|
+
profileStart('NodeCollection#1');
|
|
16948
17254
|
var nodes = new NodeCollection(dataset.arcs);
|
|
17255
|
+
profileEnd('NodeCollection#1');
|
|
17256
|
+
profileStart('findDuplicateArcs');
|
|
16949
17257
|
var map = findDuplicateArcs(nodes);
|
|
17258
|
+
profileEnd('findDuplicateArcs');
|
|
16950
17259
|
var dropCount;
|
|
16951
17260
|
if (map) {
|
|
17261
|
+
profileStart('replaceIndexedArcIds');
|
|
16952
17262
|
replaceIndexedArcIds(dataset, map);
|
|
17263
|
+
profileEnd('replaceIndexedArcIds');
|
|
16953
17264
|
}
|
|
17265
|
+
profileStart('deleteUnusedArcs');
|
|
16954
17266
|
dropCount = deleteUnusedArcs(dataset);
|
|
17267
|
+
profileEnd('deleteUnusedArcs');
|
|
16955
17268
|
if (dropCount > 0) {
|
|
16956
17269
|
// rebuild nodes if arcs have changed
|
|
17270
|
+
profileStart('NodeCollection#2');
|
|
16957
17271
|
nodes = new NodeCollection(dataset.arcs);
|
|
17272
|
+
profileEnd('NodeCollection#2');
|
|
16958
17273
|
}
|
|
16959
17274
|
return nodes;
|
|
16960
17275
|
}
|
|
@@ -17014,11 +17329,13 @@
|
|
|
17014
17329
|
// and re-index the paths of all the layers that reference the arc collection.
|
|
17015
17330
|
// (in-place)
|
|
17016
17331
|
function addIntersectionCuts(dataset, _opts) {
|
|
17332
|
+
profileStart('addIntersectionCuts');
|
|
17017
17333
|
var opts = _opts || {};
|
|
17018
17334
|
var arcs = dataset.arcs;
|
|
17019
17335
|
var arcBounds = arcs && arcs.getBounds();
|
|
17020
17336
|
var snapDist, nodes;
|
|
17021
17337
|
if (!arcBounds || !arcBounds.hasBounds()) {
|
|
17338
|
+
profileEnd('addIntersectionCuts');
|
|
17022
17339
|
return new NodeCollection([]);
|
|
17023
17340
|
}
|
|
17024
17341
|
|
|
@@ -17033,46 +17350,74 @@
|
|
|
17033
17350
|
|
|
17034
17351
|
// bake-in any simplification (bug fix; before, -simplify followed by dissolve2
|
|
17035
17352
|
// used to reset simplification)
|
|
17353
|
+
profileStart('flatten');
|
|
17036
17354
|
arcs.flatten();
|
|
17355
|
+
profileEnd('flatten');
|
|
17037
17356
|
|
|
17038
17357
|
var changed = snapAndCut(dataset, snapDist);
|
|
17039
17358
|
|
|
17040
17359
|
// Detect topology again if coordinates have changed
|
|
17041
17360
|
if (changed || opts.rebuild_topology) {
|
|
17361
|
+
profileStart('buildTopology');
|
|
17042
17362
|
buildTopology(dataset);
|
|
17363
|
+
profileEnd('buildTopology');
|
|
17043
17364
|
}
|
|
17044
17365
|
// Remove degenerate shapes
|
|
17045
17366
|
// Without this step, pathfinder function would encounter dead ends.
|
|
17367
|
+
profileStart('cleanShapes');
|
|
17046
17368
|
dataset.layers.forEach(function(lyr) {
|
|
17047
17369
|
if (layerHasPaths(lyr)) {
|
|
17048
17370
|
cleanShapes(lyr.shapes, arcs, lyr.geometry_type);
|
|
17049
17371
|
}
|
|
17050
17372
|
});
|
|
17373
|
+
profileEnd('cleanShapes');
|
|
17051
17374
|
// Further clean-up -- remove duplicate and unused arcs, etc.
|
|
17375
|
+
profileStart('cleanArcReferences');
|
|
17052
17376
|
nodes = cleanArcReferences(dataset);
|
|
17377
|
+
profileEnd('cleanArcReferences');
|
|
17378
|
+
profileEnd('addIntersectionCuts');
|
|
17053
17379
|
return nodes;
|
|
17054
17380
|
}
|
|
17055
17381
|
|
|
17056
17382
|
function snapAndCut(dataset, snapDist) {
|
|
17383
|
+
profileStart('snapAndCut');
|
|
17057
17384
|
var arcs = dataset.arcs;
|
|
17058
17385
|
var cutOpts = snapDist > 0 ? {tolerance: snapDist} : {tolerance: 0};
|
|
17059
17386
|
var coordsHaveChanged = false;
|
|
17060
|
-
var snapCount, dupeCount, cutCount;
|
|
17387
|
+
var snapCount = 0, dupeCount, cutCount;
|
|
17061
17388
|
var maxLoops = 4, loopCount = 0;
|
|
17389
|
+
// After pass 1, snap & dedup can be skipped IF pass 1 snap moved nothing:
|
|
17390
|
+
// cuts inserted exact intersection coordinates that match either an existing
|
|
17391
|
+
// vertex (handled by formatIntersectingSegment) or a fresh point. The only
|
|
17392
|
+
// way a fresh cut point could need re-snapping is if it falls within snap
|
|
17393
|
+
// interval of a third (unrelated) vertex by coincidence; in clean data that
|
|
17394
|
+
// didn't happen on pass 1 either, so we skip the redundant O(V log V) sort.
|
|
17395
|
+
var skipFollowupSnap;
|
|
17062
17396
|
|
|
17063
|
-
// snap + cut until no more cuts are needed
|
|
17064
17397
|
do {
|
|
17065
17398
|
loopCount++;
|
|
17066
17399
|
cutCount = 0;
|
|
17067
|
-
|
|
17068
|
-
|
|
17400
|
+
skipFollowupSnap = loopCount > 1 && snapCount === 0;
|
|
17401
|
+
if (!skipFollowupSnap) {
|
|
17402
|
+
profileStart('snap');
|
|
17403
|
+
snapCount = snapCoordsByInterval(arcs, snapDist);
|
|
17404
|
+
profileEnd('snap');
|
|
17405
|
+
profileStart('dedupCoords');
|
|
17406
|
+
dupeCount = arcs.dedupCoords();
|
|
17407
|
+
profileEnd('dedupCoords');
|
|
17408
|
+
} else {
|
|
17409
|
+
snapCount = 0;
|
|
17410
|
+
dupeCount = 0;
|
|
17411
|
+
}
|
|
17069
17412
|
if (snapCount > 0 || cutCount > 0 || loopCount == 1) {
|
|
17070
|
-
|
|
17413
|
+
profileStart('cutPathsAtIntersections');
|
|
17071
17414
|
cutCount = cutPathsAtIntersections(dataset, cutOpts);
|
|
17415
|
+
profileEnd('cutPathsAtIntersections');
|
|
17072
17416
|
}
|
|
17073
17417
|
coordsHaveChanged |= (snapCount + dupeCount + cutCount) > 0;
|
|
17074
|
-
debug("[snapAndCut] pass:", loopCount, "snaps:", snapCount, "dupes:", dupeCount, "cuts:", cutCount);
|
|
17418
|
+
debug("[snapAndCut] pass:", loopCount, "snaps:", snapCount, "dupes:", dupeCount, "cuts:", cutCount, skipFollowupSnap ? "(skipped snap)" : "");
|
|
17075
17419
|
} while (loopCount < maxLoops && cutCount > 0);
|
|
17420
|
+
profileEnd('snapAndCut');
|
|
17076
17421
|
|
|
17077
17422
|
// should this be added?
|
|
17078
17423
|
// if (cutCount > 0 && loopCount == maxLoops) {
|
|
@@ -17137,9 +17482,13 @@
|
|
|
17137
17482
|
// Divides a collection of arcs at points where arc paths cross each other
|
|
17138
17483
|
// Returns array for remapping arc ids
|
|
17139
17484
|
function divideArcs(arcs, opts) {
|
|
17485
|
+
profileStart('findClippingPoints');
|
|
17140
17486
|
var points = findClippingPoints(arcs, opts);
|
|
17487
|
+
profileEnd('findClippingPoints');
|
|
17141
17488
|
// TODO: avoid the following if no points need to be added
|
|
17489
|
+
profileStart('insertCutPoints');
|
|
17142
17490
|
var map = insertCutPoints(points, arcs);
|
|
17491
|
+
profileEnd('insertCutPoints');
|
|
17143
17492
|
// segment-point intersections currently create duplicate points
|
|
17144
17493
|
// TODO: consider dedup in a later cleanup pass?
|
|
17145
17494
|
// arcs.dedupCoords();
|
|
@@ -17147,9 +17496,14 @@
|
|
|
17147
17496
|
}
|
|
17148
17497
|
|
|
17149
17498
|
function findClippingPoints(arcs, opts) {
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17499
|
+
profileStart('findSegmentIntersections');
|
|
17500
|
+
var intersections = findSegmentIntersections(arcs, opts);
|
|
17501
|
+
profileEnd('findSegmentIntersections');
|
|
17502
|
+
profileStart('convertIntersectionsToCutPoints');
|
|
17503
|
+
var data = arcs.getVertexData();
|
|
17504
|
+
var points = convertIntersectionsToCutPoints(intersections, data.xx, data.yy);
|
|
17505
|
+
profileEnd('convertIntersectionsToCutPoints');
|
|
17506
|
+
return points;
|
|
17153
17507
|
}
|
|
17154
17508
|
|
|
17155
17509
|
// Inserts array of cutting points into an ArcCollection
|
|
@@ -17163,9 +17517,24 @@
|
|
|
17163
17517
|
i1 = 0,
|
|
17164
17518
|
nn1 = [],
|
|
17165
17519
|
srcArcTotal = arcs.size(),
|
|
17166
|
-
map = new Uint32Array(srcArcTotal)
|
|
17167
|
-
|
|
17168
|
-
|
|
17520
|
+
map = new Uint32Array(srcArcTotal);
|
|
17521
|
+
profileStart('sortCutPoints');
|
|
17522
|
+
var sorted = sortCutPoints(unfilteredPoints, xx0, yy0);
|
|
17523
|
+
profileEnd('sortCutPoints');
|
|
17524
|
+
profileStart('filterSortedCutPoints');
|
|
17525
|
+
var points = filterSortedCutPoints(sorted, arcs);
|
|
17526
|
+
profileEnd('filterSortedCutPoints');
|
|
17527
|
+
// Skip the full xx/yy rewrite when nothing actually needs cutting.
|
|
17528
|
+
// map[k] stores the destination arc-id at which source arc k begins; with no
|
|
17529
|
+
// cuts, source arc k becomes destination arc k (map is the identity).
|
|
17530
|
+
if (points.length === 0) {
|
|
17531
|
+
for (var k = 0; k < srcArcTotal; k++) {
|
|
17532
|
+
map[k] = k;
|
|
17533
|
+
}
|
|
17534
|
+
return map;
|
|
17535
|
+
}
|
|
17536
|
+
profileStart('rewriteVertexData');
|
|
17537
|
+
var destPointTotal = arcs.getPointCount() + points.length * 2,
|
|
17169
17538
|
xx1 = new Float64Array(destPointTotal),
|
|
17170
17539
|
yy1 = new Float64Array(destPointTotal),
|
|
17171
17540
|
n0, n1, arcLen, p;
|
|
@@ -17207,6 +17576,7 @@
|
|
|
17207
17576
|
|
|
17208
17577
|
if (i1 != destPointTotal) error("[insertCutPoints()] Counting error");
|
|
17209
17578
|
arcs.updateVertexData(nn1, xx1, yy1, null);
|
|
17579
|
+
profileEnd('rewriteVertexData');
|
|
17210
17580
|
return map;
|
|
17211
17581
|
}
|
|
17212
17582
|
|
|
@@ -17249,14 +17619,35 @@
|
|
|
17249
17619
|
// Insertion order: ascending id of first endpoint of containing segment and
|
|
17250
17620
|
// ascending distance from same endpoint.
|
|
17251
17621
|
function sortCutPoints(points, xx, yy) {
|
|
17252
|
-
points.
|
|
17253
|
-
|
|
17254
|
-
|
|
17255
|
-
|
|
17256
|
-
|
|
17257
|
-
|
|
17258
|
-
|
|
17259
|
-
|
|
17622
|
+
var len = points.length;
|
|
17623
|
+
// Precompute the sort key once per point. The comparator below would
|
|
17624
|
+
// otherwise do 2*log(n) distanceSq evaluations per element. Distances are
|
|
17625
|
+
// kept in a parallel array so we don't mutate the caller's point objects.
|
|
17626
|
+
var dists = xx ? new Float64Array(len) : null;
|
|
17627
|
+
if (dists) {
|
|
17628
|
+
for (var k = 0; k < len; k++) {
|
|
17629
|
+
var p = points[k];
|
|
17630
|
+
var dx = p.x - xx[p.i];
|
|
17631
|
+
var dy = p.y - yy[p.i];
|
|
17632
|
+
dists[k] = dx * dx + dy * dy;
|
|
17633
|
+
}
|
|
17634
|
+
// Decorate-sort-undecorate: attach (point, dist) tuples, sort by (i, dist),
|
|
17635
|
+
// then write back. Avoids closing over `dists` lookup-by-identity.
|
|
17636
|
+
var tagged = new Array(len);
|
|
17637
|
+
for (var t = 0; t < len; t++) {
|
|
17638
|
+
tagged[t] = [points[t], dists[t]];
|
|
17639
|
+
}
|
|
17640
|
+
tagged.sort(function(a, b) {
|
|
17641
|
+
return a[0].i - b[0].i || a[1] - b[1];
|
|
17642
|
+
});
|
|
17643
|
+
for (var u = 0; u < len; u++) {
|
|
17644
|
+
points[u] = tagged[u][0];
|
|
17645
|
+
}
|
|
17646
|
+
} else {
|
|
17647
|
+
points.sort(function(a, b) {
|
|
17648
|
+
return a.i - b.i;
|
|
17649
|
+
});
|
|
17650
|
+
}
|
|
17260
17651
|
return points;
|
|
17261
17652
|
}
|
|
17262
17653
|
|
|
@@ -17368,31 +17759,32 @@
|
|
|
17368
17759
|
//
|
|
17369
17760
|
function buildPolygonMosaic(nodes) {
|
|
17370
17761
|
T$1.start();
|
|
17371
|
-
|
|
17372
|
-
// the ring finding operation). This modifies @nodes -- a side effect.
|
|
17762
|
+
profileStart('bpm.detachAcyclicArcs');
|
|
17373
17763
|
nodes.detachAcyclicArcs();
|
|
17764
|
+
profileEnd('bpm.detachAcyclicArcs');
|
|
17765
|
+
profileStart('bpm.findMosaicRings');
|
|
17374
17766
|
var data = findMosaicRings(nodes);
|
|
17767
|
+
profileEnd('bpm.findMosaicRings');
|
|
17375
17768
|
|
|
17376
|
-
// Process CW rings: these are indivisible space-enclosing boundaries of mosaic tiles
|
|
17377
17769
|
var mosaic = data.cw.map(function(ring) {return [ring];});
|
|
17378
17770
|
debug('Find mosaic rings', T$1.stop());
|
|
17379
17771
|
T$1.start();
|
|
17380
17772
|
|
|
17381
|
-
// Process CCW rings: these are either holes or enclosure
|
|
17382
|
-
// TODO: optimize -- testing CCW path of every island is costly
|
|
17383
17773
|
var enclosures = [];
|
|
17384
|
-
|
|
17774
|
+
profileStart('bpm.PathIndex');
|
|
17775
|
+
var index = new PathIndex(mosaic, nodes.arcs);
|
|
17776
|
+
profileEnd('bpm.PathIndex');
|
|
17777
|
+
profileStart('bpm.findEnclosingForCCW');
|
|
17385
17778
|
data.ccw.forEach(function(ring) {
|
|
17386
17779
|
var id = index.findSmallestEnclosingPolygon(ring);
|
|
17387
17780
|
if (id > -1) {
|
|
17388
|
-
// Enclosed CCW rings are holes in the enclosing mosaic tile
|
|
17389
17781
|
mosaic[id].push(ring);
|
|
17390
17782
|
} else {
|
|
17391
|
-
// Non-enclosed CCW rings are outer boundaries -- add to enclosures layer
|
|
17392
17783
|
reversePath(ring);
|
|
17393
17784
|
enclosures.push([ring]);
|
|
17394
17785
|
}
|
|
17395
17786
|
});
|
|
17787
|
+
profileEnd('bpm.findEnclosingForCCW');
|
|
17396
17788
|
debug(utils.format("Detect holes (holes: %d, enclosures: %d)", data.ccw.length - enclosures.length, enclosures.length), T$1.stop());
|
|
17397
17789
|
|
|
17398
17790
|
return {mosaic: mosaic, enclosures: enclosures, lostArcs: data.lostArcs};
|
|
@@ -17692,23 +18084,22 @@
|
|
|
17692
18084
|
});
|
|
17693
18085
|
|
|
17694
18086
|
function MosaicIndex(lyr, nodes, optsArg) {
|
|
18087
|
+
profileStart('MosaicIndex.ctor');
|
|
17695
18088
|
var opts = optsArg || {};
|
|
17696
18089
|
var shapes = lyr.shapes;
|
|
18090
|
+
profileStart('mi.buildPolygonMosaic');
|
|
17697
18091
|
var mosaic = buildPolygonMosaic(nodes).mosaic;
|
|
17698
|
-
|
|
18092
|
+
profileEnd('mi.buildPolygonMosaic');
|
|
18093
|
+
profileStart('mi.ShapeArcIndex');
|
|
17699
18094
|
var arcTileIndex = new ShapeArcIndex(mosaic, nodes.arcs);
|
|
17700
|
-
|
|
18095
|
+
profileEnd('mi.ShapeArcIndex');
|
|
17701
18096
|
var fetchedTileIndex = new IdTestIndex(mosaic.length);
|
|
17702
|
-
// bidirection index of tile ids <=> shape ids
|
|
17703
18097
|
var tileShapeIndex = new TileShapeIndex(mosaic, opts);
|
|
17704
|
-
|
|
18098
|
+
profileStart('mi.PolygonTiler.ctor');
|
|
17705
18099
|
var shapeTiler = new PolygonTiler(mosaic, arcTileIndex, nodes, opts);
|
|
18100
|
+
profileEnd('mi.PolygonTiler.ctor');
|
|
17706
18101
|
var weightFunction = null;
|
|
17707
18102
|
if (!opts.simple && opts.flat) {
|
|
17708
|
-
// opts.simple is an optimization when dissolving everything into one polygon
|
|
17709
|
-
// using -dissolve2. In this situation, we don't need a weight function.
|
|
17710
|
-
// Otherwise, if polygons are being dissolved into multiple groups,
|
|
17711
|
-
// we use a function to assign tiles in overlapping areas to a single shape.
|
|
17712
18103
|
weightFunction = getOverlapPriorityFunction(lyr.shapes, nodes.arcs, opts.overlap_rule);
|
|
17713
18104
|
}
|
|
17714
18105
|
this.mosaic = mosaic;
|
|
@@ -17716,16 +18107,19 @@
|
|
|
17716
18107
|
this.getSourceIdsByTileId = tileShapeIndex.getShapeIdsByTileId; // expose for -mosaic command
|
|
17717
18108
|
this.getTileIdsByShapeId = tileShapeIndex.getTileIdsByShapeId;
|
|
17718
18109
|
|
|
17719
|
-
|
|
18110
|
+
profileStart('mi.assignTilesToShapes');
|
|
17720
18111
|
shapes.forEach(function(shp, shapeId) {
|
|
17721
18112
|
var tileIds = shapeTiler.getTilesInShape(shp, shapeId);
|
|
17722
18113
|
tileShapeIndex.indexTileIdsByShapeId(shapeId, tileIds, weightFunction);
|
|
17723
18114
|
});
|
|
18115
|
+
profileEnd('mi.assignTilesToShapes');
|
|
17724
18116
|
|
|
17725
|
-
// ensure each tile is assigned to only one shape
|
|
17726
18117
|
if (opts.flat) {
|
|
18118
|
+
profileStart('mi.tileShapeIndex.flatten');
|
|
17727
18119
|
tileShapeIndex.flatten();
|
|
18120
|
+
profileEnd('mi.tileShapeIndex.flatten');
|
|
17728
18121
|
}
|
|
18122
|
+
profileEnd('MosaicIndex.ctor');
|
|
17729
18123
|
|
|
17730
18124
|
// fill gaps
|
|
17731
18125
|
// (assumes that tiles have been allocated to shapes and mosaic has been flattened)
|
|
@@ -17905,22 +18299,30 @@
|
|
|
17905
18299
|
}
|
|
17906
18300
|
|
|
17907
18301
|
function dissolvePolygonGroups2(groups, lyr, dataset, opts) {
|
|
18302
|
+
profileStart('dissolvePolygonGroups2');
|
|
18303
|
+
profileStart('dpg2.NodeCollection');
|
|
17908
18304
|
var arcFilter = getArcPresenceTest(lyr.shapes, dataset.arcs);
|
|
17909
18305
|
var nodes = new NodeCollection(dataset.arcs, arcFilter);
|
|
18306
|
+
profileEnd('dpg2.NodeCollection');
|
|
17910
18307
|
var mosaicOpts = {
|
|
17911
18308
|
flat: !opts.allow_overlaps,
|
|
17912
18309
|
simple: groups.length == 1,
|
|
17913
18310
|
overlap_rule: opts.overlap_rule
|
|
17914
18311
|
};
|
|
18312
|
+
profileStart('dpg2.MosaicIndex');
|
|
17915
18313
|
var mosaicIndex = new MosaicIndex(lyr, nodes, mosaicOpts);
|
|
18314
|
+
profileEnd('dpg2.MosaicIndex');
|
|
17916
18315
|
// gap fill doesn't work yet with overlapping shapes
|
|
17917
18316
|
var fillGaps = !opts.allow_overlaps && (opts.sliver_control || opts.gap_fill_area);
|
|
17918
18317
|
var cleanupData, filterData;
|
|
17919
18318
|
if (fillGaps) {
|
|
18319
|
+
profileStart('dpg2.removeGaps');
|
|
17920
18320
|
var sliverOpts = utils.extend({sliver_control: 1}, opts);
|
|
17921
18321
|
filterData = getSliverFilter(lyr, dataset, sliverOpts);
|
|
17922
18322
|
cleanupData = mosaicIndex.removeGaps(filterData.filter);
|
|
18323
|
+
profileEnd('dpg2.removeGaps');
|
|
17923
18324
|
}
|
|
18325
|
+
profileStart('dpg2.dissolveTiles');
|
|
17924
18326
|
var pathfind = getRingIntersector(mosaicIndex.nodes);
|
|
17925
18327
|
var dissolvedShapes = groups.map(function(shapeIds) {
|
|
17926
18328
|
var tiles = mosaicIndex.getTilesByShapeIds(shapeIds);
|
|
@@ -17931,14 +18333,16 @@
|
|
|
17931
18333
|
}
|
|
17932
18334
|
return dissolveTileGroup2(tiles, pathfind);
|
|
17933
18335
|
});
|
|
17934
|
-
|
|
17935
|
-
|
|
18336
|
+
profileEnd('dpg2.dissolveTiles');
|
|
18337
|
+
profileStart('dpg2.fixTangentHoles');
|
|
17936
18338
|
dissolvedShapes = fixTangentHoles(dissolvedShapes, pathfind);
|
|
18339
|
+
profileEnd('dpg2.fixTangentHoles');
|
|
17937
18340
|
|
|
17938
18341
|
if (fillGaps && !opts.quiet) {
|
|
17939
18342
|
var msg = getGapRemovalMessage(cleanupData.removed, cleanupData.remaining, filterData.label);
|
|
17940
18343
|
if (msg) message(msg);
|
|
17941
18344
|
}
|
|
18345
|
+
profileEnd('dissolvePolygonGroups2');
|
|
17942
18346
|
return dissolvedShapes;
|
|
17943
18347
|
}
|
|
17944
18348
|
|
|
@@ -18158,40 +18562,52 @@
|
|
|
18158
18562
|
cmd.cleanLayers = cleanLayers;
|
|
18159
18563
|
|
|
18160
18564
|
function cleanLayers(layers, dataset, optsArg) {
|
|
18565
|
+
profileStart('cleanLayers');
|
|
18161
18566
|
var opts = optsArg || {};
|
|
18162
18567
|
var deepClean = !opts.only_arcs;
|
|
18163
18568
|
var pathClean = utils.some(layers, layerHasPaths);
|
|
18164
18569
|
var nodes;
|
|
18165
18570
|
if (opts.debug) {
|
|
18166
18571
|
addIntersectionCuts(dataset, opts);
|
|
18572
|
+
profileEnd('cleanLayers');
|
|
18167
18573
|
return;
|
|
18168
18574
|
}
|
|
18169
18575
|
layers.forEach(function(lyr) {
|
|
18170
18576
|
if (!layerHasGeometry(lyr)) return;
|
|
18171
18577
|
if (lyr.geometry_type == 'polygon' && opts.rewind) {
|
|
18578
|
+
profileStart('rewindPolygons');
|
|
18172
18579
|
rewindPolygons(lyr, dataset.arcs);
|
|
18580
|
+
profileEnd('rewindPolygons');
|
|
18173
18581
|
}
|
|
18174
18582
|
if (deepClean) {
|
|
18175
18583
|
if (!nodes) {
|
|
18176
18584
|
nodes = addIntersectionCuts(dataset, opts);
|
|
18177
18585
|
}
|
|
18178
18586
|
if (lyr.geometry_type == 'polygon') {
|
|
18587
|
+
profileStart('cleanPolygonLayerGeometry');
|
|
18179
18588
|
cleanPolygonLayerGeometry(lyr, dataset, opts);
|
|
18589
|
+
profileEnd('cleanPolygonLayerGeometry');
|
|
18180
18590
|
} else if (lyr.geometry_type == 'polyline') {
|
|
18591
|
+
profileStart('cleanPolylineLayerGeometry');
|
|
18181
18592
|
cleanPolylineLayerGeometry(lyr, dataset);
|
|
18593
|
+
profileEnd('cleanPolylineLayerGeometry');
|
|
18182
18594
|
} else if (lyr.geometry_type == 'point') {
|
|
18183
18595
|
cleanPointLayerGeometry(lyr);
|
|
18184
18596
|
}
|
|
18185
18597
|
}
|
|
18186
18598
|
if (!opts.allow_empty) {
|
|
18599
|
+
profileStart('filterFeatures');
|
|
18187
18600
|
cmd.filterFeatures(lyr, dataset.arcs, {remove_empty: true, verbose: opts.verbose});
|
|
18601
|
+
profileEnd('filterFeatures');
|
|
18188
18602
|
}
|
|
18189
18603
|
});
|
|
18190
18604
|
|
|
18191
18605
|
if (!opts.no_arc_dissolve && pathClean && dataset.arcs) {
|
|
18192
|
-
|
|
18606
|
+
profileStart('dissolveArcs');
|
|
18193
18607
|
dissolveArcs(dataset);
|
|
18608
|
+
profileEnd('dissolveArcs');
|
|
18194
18609
|
}
|
|
18610
|
+
profileEnd('cleanLayers');
|
|
18195
18611
|
}
|
|
18196
18612
|
|
|
18197
18613
|
function cleanPolygonLayerGeometry(lyr, dataset, opts) {
|
|
@@ -18724,13 +19140,13 @@
|
|
|
18724
19140
|
// default is true iff layers contain attributes
|
|
18725
19141
|
return utils.some(layers, function(lyr) {
|
|
18726
19142
|
var fields = lyr.data ? lyr.data.getFields() : [];
|
|
18727
|
-
var haveData = useFeatureProperties(fields, opts);
|
|
19143
|
+
var haveData = useFeatureProperties$1(fields, opts);
|
|
18728
19144
|
var haveId = !!getIdField(fields, opts);
|
|
18729
19145
|
return haveData || haveId;
|
|
18730
19146
|
});
|
|
18731
19147
|
}
|
|
18732
19148
|
|
|
18733
|
-
function useFeatureProperties(fields, opts) {
|
|
19149
|
+
function useFeatureProperties$1(fields, opts) {
|
|
18734
19150
|
return !(opts.drop_table || opts.cut_table || fields.length === 0 ||
|
|
18735
19151
|
fields.length == 1 && fields[0] == GeoJSON.ID_FIELD);
|
|
18736
19152
|
}
|
|
@@ -18739,7 +19155,7 @@
|
|
|
18739
19155
|
var fields = table ? table.getFields() : [],
|
|
18740
19156
|
idField = getIdField(fields, opts),
|
|
18741
19157
|
properties, records;
|
|
18742
|
-
if (!useFeatureProperties(fields, opts)) {
|
|
19158
|
+
if (!useFeatureProperties$1(fields, opts)) {
|
|
18743
19159
|
return null;
|
|
18744
19160
|
}
|
|
18745
19161
|
records = table.getRecords();
|
|
@@ -24500,12 +24916,10 @@ ${svg}
|
|
|
24500
24916
|
}
|
|
24501
24917
|
|
|
24502
24918
|
async function exportLayerToGeoPackage(lyr, dataset, gpkg, opts) {
|
|
24503
|
-
var
|
|
24919
|
+
var fields, columns, tableName, targetSrs, state;
|
|
24504
24920
|
if (!lyr.geometry_type) return;
|
|
24505
|
-
|
|
24506
|
-
|
|
24507
|
-
if (features.length === 0) return;
|
|
24508
|
-
fields = inferFieldTypes(features);
|
|
24921
|
+
fields = inferFieldTypesFromLayer(lyr, dataset, opts);
|
|
24922
|
+
if (!fields) return;
|
|
24509
24923
|
tableName = getTableName(lyr.name);
|
|
24510
24924
|
columns = fields.map(function(field) {
|
|
24511
24925
|
return {
|
|
@@ -24522,8 +24936,11 @@ ${svg}
|
|
|
24522
24936
|
// GeoJSON is EPSG:4326 and reprojects unless given EPSG:4326 metadata.
|
|
24523
24937
|
// Mapshaper output coords are already in target CRS, so suppress reprojection.
|
|
24524
24938
|
var srs = getSourceSrsWithoutReprojection(featureDao && featureDao.srs, targetSrs);
|
|
24525
|
-
|
|
24526
|
-
|
|
24939
|
+
state = initLayerFeatureExportState(lyr, dataset, opts);
|
|
24940
|
+
var featureIndex = 0;
|
|
24941
|
+
for (var i = 0; i < state.count; i++) {
|
|
24942
|
+
var feat = exportFeatureAtIndex(state, i);
|
|
24943
|
+
if (!feat || !feat.geometry) continue;
|
|
24527
24944
|
var normalized = normalizeFeature(feat, fields);
|
|
24528
24945
|
try {
|
|
24529
24946
|
await gpkg.addGeoJSONFeatureToGeoPackageWithFeatureDaoAndSrs(
|
|
@@ -24533,8 +24950,9 @@ ${svg}
|
|
|
24533
24950
|
);
|
|
24534
24951
|
} catch (e) {
|
|
24535
24952
|
throw Error('GeoPackage insert failed at layer "' + tableName +
|
|
24536
|
-
'", feature ' +
|
|
24953
|
+
'", feature ' + featureIndex + ': ' + e.message);
|
|
24537
24954
|
}
|
|
24955
|
+
featureIndex++;
|
|
24538
24956
|
}
|
|
24539
24957
|
}
|
|
24540
24958
|
|
|
@@ -24561,9 +24979,11 @@ ${svg}
|
|
|
24561
24979
|
stop$1('Unable to export GeoPackage bytes');
|
|
24562
24980
|
}
|
|
24563
24981
|
|
|
24564
|
-
function
|
|
24982
|
+
function inferFieldTypesFromLayer(lyr, dataset, opts) {
|
|
24983
|
+
var sawFeature = false;
|
|
24565
24984
|
var index = {};
|
|
24566
|
-
|
|
24985
|
+
forEachLayerExportFeature(lyr, dataset, opts, function(feat) {
|
|
24986
|
+
sawFeature = true;
|
|
24567
24987
|
var props = feat.properties || {};
|
|
24568
24988
|
Object.keys(props).forEach(function(name) {
|
|
24569
24989
|
if (!isSupportedPropertyName(name)) return;
|
|
@@ -24577,11 +24997,79 @@ ${svg}
|
|
|
24577
24997
|
}
|
|
24578
24998
|
});
|
|
24579
24999
|
});
|
|
25000
|
+
if (!sawFeature) return null;
|
|
24580
25001
|
return Object.keys(index).map(function(name) {
|
|
24581
25002
|
return {name: name, type: index[name]};
|
|
24582
25003
|
});
|
|
24583
25004
|
}
|
|
24584
25005
|
|
|
25006
|
+
function forEachLayerExportFeature(lyr, dataset, opts, cb) {
|
|
25007
|
+
var state = initLayerFeatureExportState(lyr, dataset, opts);
|
|
25008
|
+
var featureIndex = 0;
|
|
25009
|
+
for (var i = 0; i < state.count; i++) {
|
|
25010
|
+
var feature = exportFeatureAtIndex(state, i);
|
|
25011
|
+
if (!feature || !feature.geometry) continue;
|
|
25012
|
+
cb(feature, featureIndex++, i);
|
|
25013
|
+
}
|
|
25014
|
+
}
|
|
25015
|
+
|
|
25016
|
+
function initLayerFeatureExportState(lyr, dataset, opts) {
|
|
25017
|
+
var records = lyr.data ? lyr.data.getRecords() : null;
|
|
25018
|
+
var fields = lyr.data ? lyr.data.getFields() : [];
|
|
25019
|
+
var shapes = lyr.shapes || null;
|
|
25020
|
+
if (records && shapes && records.length !== shapes.length) {
|
|
25021
|
+
stop$1('Mismatch between number of properties and number of shapes');
|
|
25022
|
+
}
|
|
25023
|
+
return {
|
|
25024
|
+
lyr: lyr,
|
|
25025
|
+
dataset: dataset,
|
|
25026
|
+
opts: opts || {},
|
|
25027
|
+
fields: fields,
|
|
25028
|
+
records: records,
|
|
25029
|
+
shapes: shapes,
|
|
25030
|
+
count: Math.max(records ? records.length : 0, shapes ? shapes.length : 0),
|
|
25031
|
+
idField: getIdField(fields, opts || {}),
|
|
25032
|
+
useProps: useFeatureProperties(fields, opts || {})
|
|
25033
|
+
};
|
|
25034
|
+
}
|
|
25035
|
+
|
|
25036
|
+
function exportFeatureAtIndex(state, i) {
|
|
25037
|
+
var shape = state.shapes ? state.shapes[i] : null;
|
|
25038
|
+
var geom = shape ? GeoJSON.exporters[state.lyr.geometry_type](shape, state.dataset.arcs, state.opts) : null;
|
|
25039
|
+
var rec = state.records ? state.records[i] : null;
|
|
25040
|
+
var props = null;
|
|
25041
|
+
if (state.useProps && rec) {
|
|
25042
|
+
if (state.idField == GeoJSON.ID_FIELD) {
|
|
25043
|
+
props = Object.assign({}, rec);
|
|
25044
|
+
delete props[state.idField];
|
|
25045
|
+
} else {
|
|
25046
|
+
props = rec;
|
|
25047
|
+
}
|
|
25048
|
+
}
|
|
25049
|
+
var feat = GeoJSON.toFeature(geom, props);
|
|
25050
|
+
if (state.idField && rec) {
|
|
25051
|
+
feat.id = state.idField in rec ? rec[state.idField] : null;
|
|
25052
|
+
}
|
|
25053
|
+
if (state.opts.no_null_props && !feat.properties) {
|
|
25054
|
+
feat.properties = {};
|
|
25055
|
+
}
|
|
25056
|
+
if (Array.isArray(state.opts.hoist) && feat.properties) {
|
|
25057
|
+
feat.properties = Object.assign({}, feat.properties);
|
|
25058
|
+
state.opts.hoist.forEach(function(field) {
|
|
25059
|
+
if (Object.prototype.hasOwnProperty.call(feat.properties, field)) {
|
|
25060
|
+
feat[field] = feat.properties[field];
|
|
25061
|
+
delete feat.properties[field];
|
|
25062
|
+
}
|
|
25063
|
+
});
|
|
25064
|
+
}
|
|
25065
|
+
return feat;
|
|
25066
|
+
}
|
|
25067
|
+
|
|
25068
|
+
function useFeatureProperties(fields, opts) {
|
|
25069
|
+
return !(opts.drop_table || opts.cut_table || fields.length === 0 ||
|
|
25070
|
+
fields.length == 1 && fields[0] == GeoJSON.ID_FIELD);
|
|
25071
|
+
}
|
|
25072
|
+
|
|
24585
25073
|
function inferValueType(value) {
|
|
24586
25074
|
if (value === null || value === undefined) return null;
|
|
24587
25075
|
if (value instanceof Date) return 'DATETIME';
|
|
@@ -24600,10 +25088,6 @@ ${svg}
|
|
|
24600
25088
|
}
|
|
24601
25089
|
|
|
24602
25090
|
function normalizeFeature(feature, fields) {
|
|
24603
|
-
fields.reduce(function(memo, field) {
|
|
24604
|
-
memo[field.name] = field.type;
|
|
24605
|
-
return memo;
|
|
24606
|
-
}, {});
|
|
24607
25091
|
var props = {};
|
|
24608
25092
|
fields.forEach(function(field) {
|
|
24609
25093
|
var value = feature.properties && Object.prototype.hasOwnProperty.call(feature.properties, field.name) ?
|
|
@@ -24622,11 +25106,8 @@ ${svg}
|
|
|
24622
25106
|
|
|
24623
25107
|
function getExportSrs(dataset) {
|
|
24624
25108
|
var info = dataset.info || {};
|
|
24625
|
-
var gpkgCrs = normalizeSrsForInsert(info.geopackage_crs);
|
|
25109
|
+
var gpkgCrs = resolveExportSrs(normalizeSrsForInsert(info.geopackage_crs), info.wkt1);
|
|
24626
25110
|
if (gpkgCrs) {
|
|
24627
|
-
if (!gpkgCrs.definition && info.wkt1) {
|
|
24628
|
-
gpkgCrs.definition = info.wkt1;
|
|
24629
|
-
}
|
|
24630
25111
|
return gpkgCrs;
|
|
24631
25112
|
}
|
|
24632
25113
|
var parsed = parseCrsString(info.crs_string);
|
|
@@ -24642,13 +25123,83 @@ ${svg}
|
|
|
24642
25123
|
if (info.wkt1) {
|
|
24643
25124
|
return buildCustomSrsFromWkt(info.wkt1);
|
|
24644
25125
|
}
|
|
25126
|
+
// If the dataset has an in-memory CRS object (e.g. set by -proj) but no
|
|
25127
|
+
// metadata source, derive a WKT1 definition from it so a projected CRS
|
|
25128
|
+
// isn't lost on round-trip.
|
|
25129
|
+
var derivedWkt = getWktFromDatasetCrs(dataset);
|
|
25130
|
+
if (derivedWkt) {
|
|
25131
|
+
return buildCustomSrsFromWkt(derivedWkt);
|
|
25132
|
+
}
|
|
25133
|
+
// Unknown CRS fallback:
|
|
25134
|
+
// - probable decimal-degree coordinates => allow WGS84 assumption
|
|
25135
|
+
// - otherwise use undefined cartesian CRS (null/unknown projection)
|
|
25136
|
+
if (probablyDecimalDegreeBounds(getDatasetBounds(dataset))) {
|
|
25137
|
+
return {
|
|
25138
|
+
srs_id: 4326,
|
|
25139
|
+
organization: 'EPSG',
|
|
25140
|
+
organization_coordsys_id: 4326
|
|
25141
|
+
};
|
|
25142
|
+
}
|
|
24645
25143
|
return {
|
|
24646
|
-
srs_id:
|
|
24647
|
-
|
|
24648
|
-
|
|
25144
|
+
srs_id: -1,
|
|
25145
|
+
srs_name: 'Undefined Cartesian SRS',
|
|
25146
|
+
organization: 'NONE',
|
|
25147
|
+
organization_coordsys_id: -1
|
|
24649
25148
|
};
|
|
24650
25149
|
}
|
|
24651
25150
|
|
|
25151
|
+
function getWktFromDatasetCrs(dataset) {
|
|
25152
|
+
// Only derive from an explicitly-set CRS object (e.g. from -proj).
|
|
25153
|
+
// This avoids building a custom SRS for lat/lon datasets that were
|
|
25154
|
+
// auto-defaulted to WGS84 by getDatasetCRS().
|
|
25155
|
+
var info = dataset && dataset.info;
|
|
25156
|
+
if (!info || !info.crs) return null;
|
|
25157
|
+
try {
|
|
25158
|
+
return crsToPrj(info.crs) || null;
|
|
25159
|
+
} catch (e) {
|
|
25160
|
+
return null;
|
|
25161
|
+
}
|
|
25162
|
+
}
|
|
25163
|
+
|
|
25164
|
+
function resolveExportSrs(srs, fallbackWkt) {
|
|
25165
|
+
if (!srs) return null;
|
|
25166
|
+
var out = Object.assign({}, srs);
|
|
25167
|
+
if (!out.definition && fallbackWkt) {
|
|
25168
|
+
out.definition = fallbackWkt;
|
|
25169
|
+
}
|
|
25170
|
+
if (out.srs_id == null && out.organization_coordsys_id != null) {
|
|
25171
|
+
out.srs_id = out.organization_coordsys_id;
|
|
25172
|
+
}
|
|
25173
|
+
if (out.srs_id == null) {
|
|
25174
|
+
var fromWkt = parseWktAuthority(out.definition);
|
|
25175
|
+
if (fromWkt) {
|
|
25176
|
+
out.srs_id = fromWkt.srs_id;
|
|
25177
|
+
if (!out.organization) out.organization = fromWkt.organization;
|
|
25178
|
+
if (out.organization_coordsys_id == null) {
|
|
25179
|
+
out.organization_coordsys_id = fromWkt.organization_coordsys_id;
|
|
25180
|
+
}
|
|
25181
|
+
}
|
|
25182
|
+
}
|
|
25183
|
+
if (out.srs_id == null && out.definition) {
|
|
25184
|
+
var custom = buildCustomSrsFromWkt(out.definition);
|
|
25185
|
+
out.srs_id = custom.srs_id;
|
|
25186
|
+
if (!out.srs_name) out.srs_name = custom.srs_name;
|
|
25187
|
+
if (!out.organization) out.organization = custom.organization;
|
|
25188
|
+
if (out.organization_coordsys_id == null) {
|
|
25189
|
+
out.organization_coordsys_id = custom.organization_coordsys_id;
|
|
25190
|
+
}
|
|
25191
|
+
if (!out.description) out.description = custom.description;
|
|
25192
|
+
}
|
|
25193
|
+
if (out.srs_id == null) return null;
|
|
25194
|
+
if (out.organization_coordsys_id == null) {
|
|
25195
|
+
out.organization_coordsys_id = out.srs_id;
|
|
25196
|
+
}
|
|
25197
|
+
if (!out.organization) {
|
|
25198
|
+
out.organization = 'NONE';
|
|
25199
|
+
}
|
|
25200
|
+
return out;
|
|
25201
|
+
}
|
|
25202
|
+
|
|
24652
25203
|
function parseCrsString(str) {
|
|
24653
25204
|
if (!str || typeof str != 'string') return null;
|
|
24654
25205
|
var match = str.match(/^([a-z]+):(\d+)$/i);
|
|
@@ -24740,9 +25291,10 @@ ${svg}
|
|
|
24740
25291
|
var geopackage = require$1('@ngageoint/geopackage');
|
|
24741
25292
|
var spatialReferenceSystem = new geopackage.SpatialReferenceSystem();
|
|
24742
25293
|
spatialReferenceSystem.srs_id = srs.srs_id;
|
|
24743
|
-
spatialReferenceSystem.srs_name = srs.srs_name || (srs.organization + ':' + srs.organization_coordsys_id);
|
|
24744
|
-
spatialReferenceSystem.organization = srs.organization;
|
|
24745
|
-
spatialReferenceSystem.organization_coordsys_id = srs.organization_coordsys_id
|
|
25294
|
+
spatialReferenceSystem.srs_name = srs.srs_name || (String(srs.organization || 'NONE') + ':' + srs.organization_coordsys_id);
|
|
25295
|
+
spatialReferenceSystem.organization = srs.organization || 'NONE';
|
|
25296
|
+
spatialReferenceSystem.organization_coordsys_id = srs.organization_coordsys_id != null ?
|
|
25297
|
+
srs.organization_coordsys_id : srs.srs_id;
|
|
24746
25298
|
spatialReferenceSystem.definition = srs.definition;
|
|
24747
25299
|
spatialReferenceSystem.description = srs.description || null;
|
|
24748
25300
|
gpkg.createSpatialReferenceSystem(spatialReferenceSystem);
|
|
@@ -24751,7 +25303,9 @@ ${svg}
|
|
|
24751
25303
|
function getSourceSrsWithoutReprojection(tableSrs, fallbackSrs) {
|
|
24752
25304
|
var target = normalizeSrsForInsert(tableSrs) || normalizeSrsForInsert(fallbackSrs);
|
|
24753
25305
|
return {
|
|
24754
|
-
srs_id
|
|
25306
|
+
// geopackage writer rejects negative srs_id for source metadata;
|
|
25307
|
+
// use 0 (undefined geographic) when target is undefined cartesian (-1).
|
|
25308
|
+
srs_id: target.srs_id < 0 ? 0 : target.srs_id,
|
|
24755
25309
|
organization: 'EPSG',
|
|
24756
25310
|
organization_coordsys_id: 4326
|
|
24757
25311
|
};
|
|
@@ -24828,18 +25382,21 @@ ${svg}
|
|
|
24828
25382
|
if (normalized.organization_coordsys_id == null && normalized.organizationCoordsysId != null) {
|
|
24829
25383
|
normalized.organization_coordsys_id = normalized.organizationCoordsysId;
|
|
24830
25384
|
}
|
|
24831
|
-
if (normalized.srs_id
|
|
24832
|
-
normalized.srs_id = normalized.
|
|
25385
|
+
if (normalized.srs_id != null) {
|
|
25386
|
+
normalized.srs_id = +normalized.srs_id;
|
|
25387
|
+
if (!Number.isFinite(normalized.srs_id)) {
|
|
25388
|
+
normalized.srs_id = null;
|
|
25389
|
+
}
|
|
24833
25390
|
}
|
|
24834
|
-
|
|
24835
|
-
|
|
24836
|
-
normalized.organization = 'EPSG';
|
|
25391
|
+
if (normalized.organization) {
|
|
25392
|
+
normalized.organization = String(normalized.organization).toUpperCase();
|
|
24837
25393
|
}
|
|
24838
|
-
normalized.
|
|
24839
|
-
|
|
24840
|
-
normalized.organization_coordsys_id
|
|
25394
|
+
if (normalized.organization_coordsys_id != null) {
|
|
25395
|
+
normalized.organization_coordsys_id = +normalized.organization_coordsys_id;
|
|
25396
|
+
if (!Number.isFinite(normalized.organization_coordsys_id)) {
|
|
25397
|
+
normalized.organization_coordsys_id = null;
|
|
25398
|
+
}
|
|
24841
25399
|
}
|
|
24842
|
-
normalized.organization_coordsys_id = +normalized.organization_coordsys_id || 4326;
|
|
24843
25400
|
return normalized;
|
|
24844
25401
|
}
|
|
24845
25402
|
|
|
@@ -26869,6 +27426,10 @@ ${svg}
|
|
|
26869
27426
|
.option('name', {
|
|
26870
27427
|
describe: 'rename the imported layer(s)'
|
|
26871
27428
|
})
|
|
27429
|
+
.option('layers', {
|
|
27430
|
+
type: 'strings',
|
|
27431
|
+
describe: '[GPKG] comma-sep. list of layers to import'
|
|
27432
|
+
})
|
|
26872
27433
|
.option('geometry-type', {
|
|
26873
27434
|
// undocumented; GeoJSON import rejects all but one kind of geometry
|
|
26874
27435
|
// describe: '[GeoJSON] Import one kind of geometry (point|polygon|polyline)'
|
|
@@ -31463,24 +32024,35 @@ ${svg}
|
|
|
31463
32024
|
var geopackage = require$1('@ngageoint/geopackage');
|
|
31464
32025
|
var gpkg;
|
|
31465
32026
|
var datasets;
|
|
31466
|
-
var source;
|
|
31467
32027
|
var tmpPath = null;
|
|
31468
32028
|
|
|
31469
32029
|
if (!geopackage || !geopackage.GeoPackageAPI) {
|
|
31470
32030
|
stop$1('GeoPackage library is not loaded');
|
|
31471
32031
|
}
|
|
31472
32032
|
|
|
31473
|
-
|
|
31474
|
-
|
|
31475
|
-
|
|
31476
|
-
tmpPath = writeGeoPackageTempFile(content);
|
|
31477
|
-
source = tmpPath;
|
|
31478
|
-
} else {
|
|
31479
|
-
source = new Uint8Array(content);
|
|
31480
|
-
}
|
|
31481
|
-
gpkg = await geopackage.GeoPackageAPI.open(source);
|
|
32033
|
+
({gpkg, tmpPath} = await openGeoPackage(content, geopackage));
|
|
32034
|
+
var availableLayers;
|
|
32035
|
+
var filterApplied;
|
|
31482
32036
|
try {
|
|
31483
|
-
|
|
32037
|
+
try {
|
|
32038
|
+
({datasets, availableLayers, filterApplied} = readFeatureTableDatasets(gpkg, opts));
|
|
32039
|
+
} catch (e) {
|
|
32040
|
+
if (!runningInBrowser() && isLocalCsProjError(e)) {
|
|
32041
|
+
gpkg.close();
|
|
32042
|
+
if (tmpPath) {
|
|
32043
|
+
sanitizeGeoPackageCrsMetadata(tmpPath);
|
|
32044
|
+
} else if (utils.isString(content)) {
|
|
32045
|
+
tmpPath = copyGeoPackageTempFile(content);
|
|
32046
|
+
sanitizeGeoPackageCrsMetadata(tmpPath);
|
|
32047
|
+
} else {
|
|
32048
|
+
throw e;
|
|
32049
|
+
}
|
|
32050
|
+
gpkg = await geopackage.GeoPackageAPI.open(tmpPath);
|
|
32051
|
+
({datasets, availableLayers, filterApplied} = readFeatureTableDatasets(gpkg, opts));
|
|
32052
|
+
} else {
|
|
32053
|
+
throw e;
|
|
32054
|
+
}
|
|
32055
|
+
}
|
|
31484
32056
|
} finally {
|
|
31485
32057
|
gpkg.close();
|
|
31486
32058
|
removeTempGeoPackageFile(tmpPath);
|
|
@@ -31489,13 +32061,72 @@ ${svg}
|
|
|
31489
32061
|
if (datasets.length === 0) {
|
|
31490
32062
|
return {
|
|
31491
32063
|
layers: [{name: '', data: null}],
|
|
31492
|
-
info: {
|
|
32064
|
+
info: {
|
|
32065
|
+
_gpkg_available_layers: availableLayers,
|
|
32066
|
+
_gpkg_placeholder: !!filterApplied
|
|
32067
|
+
}
|
|
31493
32068
|
};
|
|
31494
32069
|
}
|
|
31495
32070
|
|
|
31496
32071
|
await initProjLib(datasets);
|
|
32072
|
+
var merged = mergeGeoPackageDatasets(datasets);
|
|
32073
|
+
var mergedArr = Array.isArray(merged) ? merged : [merged];
|
|
32074
|
+
mergedArr.forEach(function(ds) {
|
|
32075
|
+
ds.info = ds.info || {};
|
|
32076
|
+
ds.info._gpkg_available_layers = availableLayers;
|
|
32077
|
+
});
|
|
32078
|
+
return merged;
|
|
32079
|
+
}
|
|
31497
32080
|
|
|
31498
|
-
|
|
32081
|
+
async function openGeoPackage(content, geopackage) {
|
|
32082
|
+
var source;
|
|
32083
|
+
var tmpPath = null;
|
|
32084
|
+
if (utils.isString(content)) {
|
|
32085
|
+
if (!runningInBrowser()) {
|
|
32086
|
+
try {
|
|
32087
|
+
return {
|
|
32088
|
+
gpkg: await geopackage.GeoPackageAPI.open(content),
|
|
32089
|
+
tmpPath: null
|
|
32090
|
+
};
|
|
32091
|
+
} catch (e) {
|
|
32092
|
+
if (!isLocalCsProjError(e)) throw e;
|
|
32093
|
+
tmpPath = copyGeoPackageTempFile(content);
|
|
32094
|
+
sanitizeGeoPackageCrsMetadata(tmpPath);
|
|
32095
|
+
return {
|
|
32096
|
+
gpkg: await geopackage.GeoPackageAPI.open(tmpPath),
|
|
32097
|
+
tmpPath: tmpPath
|
|
32098
|
+
};
|
|
32099
|
+
}
|
|
32100
|
+
}
|
|
32101
|
+
source = content;
|
|
32102
|
+
} else if (!runningInBrowser()) {
|
|
32103
|
+
tmpPath = writeGeoPackageTempFile(content);
|
|
32104
|
+
try {
|
|
32105
|
+
return {
|
|
32106
|
+
gpkg: await geopackage.GeoPackageAPI.open(tmpPath),
|
|
32107
|
+
tmpPath: tmpPath
|
|
32108
|
+
};
|
|
32109
|
+
} catch (e) {
|
|
32110
|
+
if (!isLocalCsProjError(e)) throw e;
|
|
32111
|
+
sanitizeGeoPackageCrsMetadata(tmpPath);
|
|
32112
|
+
return {
|
|
32113
|
+
gpkg: await geopackage.GeoPackageAPI.open(tmpPath),
|
|
32114
|
+
tmpPath: tmpPath
|
|
32115
|
+
};
|
|
32116
|
+
}
|
|
32117
|
+
} else {
|
|
32118
|
+
source = new Uint8Array(content);
|
|
32119
|
+
}
|
|
32120
|
+
return {
|
|
32121
|
+
gpkg: await geopackage.GeoPackageAPI.open(source),
|
|
32122
|
+
tmpPath: null
|
|
32123
|
+
};
|
|
32124
|
+
}
|
|
32125
|
+
|
|
32126
|
+
function isLocalCsProjError(err) {
|
|
32127
|
+
var msg = err && err.message ? String(err.message) : '';
|
|
32128
|
+
return msg.includes("havn't handled \"_\" in keyword yet") ||
|
|
32129
|
+
msg.includes('LOCAL_CS');
|
|
31499
32130
|
}
|
|
31500
32131
|
|
|
31501
32132
|
function writeGeoPackageTempFile(content) {
|
|
@@ -31504,10 +32135,40 @@ ${svg}
|
|
|
31504
32135
|
var path = require$1('path');
|
|
31505
32136
|
var unique = Date.now() + '-' + process.pid + '-' + Math.random().toString(36).slice(2);
|
|
31506
32137
|
var tmpPath = path.join(os.tmpdir(), 'mapshaper-gpkg-import-' + unique + '.gpkg');
|
|
31507
|
-
|
|
32138
|
+
if (Buffer.isBuffer(content)) {
|
|
32139
|
+
fs.writeFileSync(tmpPath, content);
|
|
32140
|
+
} else if (content instanceof Uint8Array) {
|
|
32141
|
+
fs.writeFileSync(tmpPath, Buffer.from(content.buffer, content.byteOffset, content.byteLength));
|
|
32142
|
+
} else if (content instanceof ArrayBuffer) {
|
|
32143
|
+
fs.writeFileSync(tmpPath, Buffer.from(content));
|
|
32144
|
+
} else {
|
|
32145
|
+
fs.writeFileSync(tmpPath, Buffer.from(new Uint8Array(content)));
|
|
32146
|
+
}
|
|
31508
32147
|
return tmpPath;
|
|
31509
32148
|
}
|
|
31510
32149
|
|
|
32150
|
+
function copyGeoPackageTempFile(filepath) {
|
|
32151
|
+
var fs = require$1('fs');
|
|
32152
|
+
var os = require$1('os');
|
|
32153
|
+
var path = require$1('path');
|
|
32154
|
+
var unique = Date.now() + '-' + process.pid + '-' + Math.random().toString(36).slice(2);
|
|
32155
|
+
var tmpPath = path.join(os.tmpdir(), 'mapshaper-gpkg-import-' + unique + '.gpkg');
|
|
32156
|
+
fs.copyFileSync(filepath, tmpPath);
|
|
32157
|
+
return tmpPath;
|
|
32158
|
+
}
|
|
32159
|
+
|
|
32160
|
+
function sanitizeGeoPackageCrsMetadata(filepath) {
|
|
32161
|
+
var Database = require$1('better-sqlite3');
|
|
32162
|
+
var db = new Database(filepath);
|
|
32163
|
+
try {
|
|
32164
|
+
// GDAL may write LOCAL_CS definitions that proj4 can't parse.
|
|
32165
|
+
// Normalize to 'undefined' so GeoPackageAPI.open() won't throw.
|
|
32166
|
+
db.prepare("UPDATE gpkg_spatial_ref_sys SET definition = 'undefined' WHERE definition LIKE 'LOCAL_CS[%'").run();
|
|
32167
|
+
} finally {
|
|
32168
|
+
db.close();
|
|
32169
|
+
}
|
|
32170
|
+
}
|
|
32171
|
+
|
|
31511
32172
|
function removeTempGeoPackageFile(filepath) {
|
|
31512
32173
|
if (!filepath) return;
|
|
31513
32174
|
var fs = require$1('fs');
|
|
@@ -31526,13 +32187,207 @@ ${svg}
|
|
|
31526
32187
|
}
|
|
31527
32188
|
|
|
31528
32189
|
function readFeatureTableDatasets(gpkg, opts) {
|
|
31529
|
-
var
|
|
31530
|
-
|
|
32190
|
+
var selected = getSelectedGeoPackageLayers(opts);
|
|
32191
|
+
var availableLayers = gpkg.getFeatureTables() || [];
|
|
32192
|
+
var tables = filterGeoPackageTables(availableLayers, selected);
|
|
32193
|
+
var filterApplied = Array.isArray(selected) && selected.length > 0;
|
|
32194
|
+
var datasets = tables.map(function(table) {
|
|
31531
32195
|
return readFeatureTable(gpkg, table, opts);
|
|
31532
32196
|
});
|
|
32197
|
+
return {datasets: datasets, availableLayers: availableLayers, filterApplied: filterApplied};
|
|
32198
|
+
}
|
|
32199
|
+
|
|
32200
|
+
function getSelectedGeoPackageLayers(opts) {
|
|
32201
|
+
if (!opts) return null;
|
|
32202
|
+
return opts.gpkg_layers || opts.layers || null;
|
|
32203
|
+
}
|
|
32204
|
+
|
|
32205
|
+
function filterGeoPackageTables(tables, selected) {
|
|
32206
|
+
if (!Array.isArray(selected) || selected.length === 0) return tables;
|
|
32207
|
+
var index = selected.reduce(function(memo, name) {
|
|
32208
|
+
memo[name] = true;
|
|
32209
|
+
return memo;
|
|
32210
|
+
}, {});
|
|
32211
|
+
return tables.filter(function(name) {
|
|
32212
|
+
return !!index[name];
|
|
32213
|
+
});
|
|
32214
|
+
}
|
|
32215
|
+
|
|
32216
|
+
function mergeGeoPackageDatasets(datasets) {
|
|
32217
|
+
var groups = groupGeoPackageDatasets(datasets);
|
|
32218
|
+
var merged = groups.reduce(function(memo, group) {
|
|
32219
|
+
return memo.concat(mergeGeoPackageDatasetGroup(group));
|
|
32220
|
+
}, []);
|
|
32221
|
+
return merged.length == 1 ? merged[0] : merged;
|
|
31533
32222
|
}
|
|
31534
32223
|
|
|
31535
|
-
function
|
|
32224
|
+
function mergeGeoPackageDatasetGroup(group) {
|
|
32225
|
+
var pathDatasets = [];
|
|
32226
|
+
var pointDatasets = [];
|
|
32227
|
+
var dataDatasets = [];
|
|
32228
|
+
|
|
32229
|
+
group.forEach(function(dataset) {
|
|
32230
|
+
var kind = getDatasetGeometryKind(dataset);
|
|
32231
|
+
if (kind == 'path') {
|
|
32232
|
+
pathDatasets.push(dataset);
|
|
32233
|
+
} else if (kind == 'point') {
|
|
32234
|
+
pointDatasets.push(dataset);
|
|
32235
|
+
} else {
|
|
32236
|
+
dataDatasets.push(dataset);
|
|
32237
|
+
}
|
|
32238
|
+
});
|
|
32239
|
+
|
|
32240
|
+
var output = [];
|
|
32241
|
+
if (dataDatasets.length > 0) {
|
|
32242
|
+
output.push(dataDatasets.length == 1 ? dataDatasets[0] : mergeDatasets(dataDatasets));
|
|
32243
|
+
}
|
|
32244
|
+
if (pointDatasets.length > 0) {
|
|
32245
|
+
output.push(pointDatasets.length == 1 ? pointDatasets[0] : mergeDatasets(pointDatasets));
|
|
32246
|
+
}
|
|
32247
|
+
if (pathDatasets.length > 0) {
|
|
32248
|
+
var groupedPaths = groupPathDatasetsBySharedArcs(pathDatasets);
|
|
32249
|
+
if (groupedPaths.mergedDataset) {
|
|
32250
|
+
output.push(groupedPaths.mergedDataset);
|
|
32251
|
+
} else {
|
|
32252
|
+
groupedPaths.components.forEach(function(component) {
|
|
32253
|
+
output.push(component.length == 1 ? component[0] : mergeDatasets(component));
|
|
32254
|
+
});
|
|
32255
|
+
}
|
|
32256
|
+
}
|
|
32257
|
+
return output;
|
|
32258
|
+
}
|
|
32259
|
+
|
|
32260
|
+
function groupGeoPackageDatasets(datasets) {
|
|
32261
|
+
var index = {};
|
|
32262
|
+
datasets.forEach(function(dataset) {
|
|
32263
|
+
var key = getGeoPackageDatasetGroupKey(dataset);
|
|
32264
|
+
if (!index[key]) index[key] = [];
|
|
32265
|
+
index[key].push(dataset);
|
|
32266
|
+
});
|
|
32267
|
+
return Object.keys(index).map(function(key) {
|
|
32268
|
+
return index[key];
|
|
32269
|
+
});
|
|
32270
|
+
}
|
|
32271
|
+
|
|
32272
|
+
function getGeoPackageDatasetGroupKey(dataset) {
|
|
32273
|
+
var info = dataset.info || {};
|
|
32274
|
+
var crs = info.geopackage_crs || null;
|
|
32275
|
+
if (isDataOnlyDataset(dataset)) {
|
|
32276
|
+
return 'data-only';
|
|
32277
|
+
}
|
|
32278
|
+
if (crs) {
|
|
32279
|
+
var org = normalizeCrsOrg(crs.organization);
|
|
32280
|
+
var code = normalizeNumericCode(crs.organization_coordsys_id);
|
|
32281
|
+
if (isTrustedCrsAuthority(org) && code !== null && !(org == 'NONE' && code <= 0)) {
|
|
32282
|
+
return 'crs:' + org + ':' + code;
|
|
32283
|
+
}
|
|
32284
|
+
if (isUsableWkt1Definition(crs.definition)) {
|
|
32285
|
+
return 'wkt:' + crs.definition;
|
|
32286
|
+
}
|
|
32287
|
+
}
|
|
32288
|
+
return probablyDecimalDegreeBounds(getDatasetBounds(dataset)) ?
|
|
32289
|
+
'unknown:unprojected' :
|
|
32290
|
+
'unknown:projected';
|
|
32291
|
+
}
|
|
32292
|
+
|
|
32293
|
+
function isDataOnlyDataset(dataset) {
|
|
32294
|
+
return (dataset.layers || []).every(function(lyr) {
|
|
32295
|
+
return !lyr.geometry_type;
|
|
32296
|
+
});
|
|
32297
|
+
}
|
|
32298
|
+
|
|
32299
|
+
function getDatasetGeometryKind(dataset) {
|
|
32300
|
+
if (isDataOnlyDataset(dataset)) return 'data';
|
|
32301
|
+
if (datasetHasPaths(dataset)) return 'path';
|
|
32302
|
+
var hasPoints = (dataset.layers || []).some(function(lyr) {
|
|
32303
|
+
return layerHasPoints(lyr);
|
|
32304
|
+
});
|
|
32305
|
+
return hasPoints ? 'point' : 'data';
|
|
32306
|
+
}
|
|
32307
|
+
|
|
32308
|
+
function groupPathDatasetsBySharedArcs(datasets) {
|
|
32309
|
+
if (datasets.length < 2) {
|
|
32310
|
+
return {
|
|
32311
|
+
mergedDataset: datasets[0] || null,
|
|
32312
|
+
components: []
|
|
32313
|
+
};
|
|
32314
|
+
}
|
|
32315
|
+
var copy = datasets.map(copyDatasetForExport);
|
|
32316
|
+
var merged = mergeDatasets(copy);
|
|
32317
|
+
if (!merged.arcs || merged.arcs.size() === 0) {
|
|
32318
|
+
return {
|
|
32319
|
+
mergedDataset: null,
|
|
32320
|
+
components: datasets.map(function(dataset) { return [dataset]; })
|
|
32321
|
+
};
|
|
32322
|
+
}
|
|
32323
|
+
buildTopology(merged);
|
|
32324
|
+
var parent = datasets.map(function(_, i) { return i; });
|
|
32325
|
+
var arcOwner = new Map();
|
|
32326
|
+
merged.layers.forEach(function(layer, layerIdx) {
|
|
32327
|
+
var seen = new Set();
|
|
32328
|
+
forEachArcId(layer.shapes || [], function(arcId) {
|
|
32329
|
+
seen.add(arcId < 0 ? ~arcId : arcId);
|
|
32330
|
+
});
|
|
32331
|
+
seen.forEach(function(absId) {
|
|
32332
|
+
if (!arcOwner.has(absId)) {
|
|
32333
|
+
arcOwner.set(absId, layerIdx);
|
|
32334
|
+
} else {
|
|
32335
|
+
union(parent, layerIdx, arcOwner.get(absId));
|
|
32336
|
+
}
|
|
32337
|
+
});
|
|
32338
|
+
});
|
|
32339
|
+
var components = {};
|
|
32340
|
+
datasets.forEach(function(dataset, i) {
|
|
32341
|
+
var root = find(parent, i);
|
|
32342
|
+
if (!components[root]) components[root] = [];
|
|
32343
|
+
components[root].push(dataset);
|
|
32344
|
+
});
|
|
32345
|
+
var grouped = Object.keys(components).map(function(key) {
|
|
32346
|
+
return components[key];
|
|
32347
|
+
});
|
|
32348
|
+
if (grouped.length == 1) {
|
|
32349
|
+
return {
|
|
32350
|
+
mergedDataset: merged,
|
|
32351
|
+
components: []
|
|
32352
|
+
};
|
|
32353
|
+
}
|
|
32354
|
+
return {
|
|
32355
|
+
mergedDataset: null,
|
|
32356
|
+
components: grouped
|
|
32357
|
+
};
|
|
32358
|
+
}
|
|
32359
|
+
|
|
32360
|
+
function find(parent, i) {
|
|
32361
|
+
var p = parent[i];
|
|
32362
|
+
if (p !== i) {
|
|
32363
|
+
parent[i] = find(parent, p);
|
|
32364
|
+
}
|
|
32365
|
+
return parent[i];
|
|
32366
|
+
}
|
|
32367
|
+
|
|
32368
|
+
function union(parent, a, b) {
|
|
32369
|
+
var ra = find(parent, a);
|
|
32370
|
+
var rb = find(parent, b);
|
|
32371
|
+
if (ra !== rb) {
|
|
32372
|
+
parent[rb] = ra;
|
|
32373
|
+
}
|
|
32374
|
+
}
|
|
32375
|
+
|
|
32376
|
+
function normalizeNumericCode(val) {
|
|
32377
|
+
var n = +val;
|
|
32378
|
+
return Number.isFinite(n) ? n : null;
|
|
32379
|
+
}
|
|
32380
|
+
|
|
32381
|
+
function normalizeCrsOrg(org) {
|
|
32382
|
+
if (!org || org == 'undefined') return null;
|
|
32383
|
+
return String(org).toUpperCase();
|
|
32384
|
+
}
|
|
32385
|
+
|
|
32386
|
+
function isTrustedCrsAuthority(org) {
|
|
32387
|
+
return org == 'EPSG' || org == 'ESRI' || org == 'NONE';
|
|
32388
|
+
}
|
|
32389
|
+
|
|
32390
|
+
function convertOrgToProjString(crs) {
|
|
31536
32391
|
var org = crs.organization.toLowerCase();
|
|
31537
32392
|
if (org == 'epsg' || org == 'esri') {
|
|
31538
32393
|
return org + ':' + crs.organization_coordsys_id;
|
|
@@ -31560,14 +32415,25 @@ ${svg}
|
|
|
31560
32415
|
});
|
|
31561
32416
|
dataset.info = dataset.info || {};
|
|
31562
32417
|
dataset.info.geopackage_crs = crs;
|
|
31563
|
-
if (crs?.definition
|
|
32418
|
+
if (isUsableWkt1Definition(crs?.definition)) {
|
|
31564
32419
|
dataset.info.wkt1 = crs.definition;
|
|
31565
32420
|
} else if (crs?.organization && crs.organization !== 'undefined') {
|
|
31566
|
-
|
|
32421
|
+
var crsString = convertOrgToProjString(crs);
|
|
32422
|
+
if (crsString) {
|
|
32423
|
+
dataset.info.crs_string = crsString;
|
|
32424
|
+
}
|
|
31567
32425
|
}
|
|
31568
32426
|
return dataset;
|
|
31569
32427
|
}
|
|
31570
32428
|
|
|
32429
|
+
function isUsableWkt1Definition(defn) {
|
|
32430
|
+
if (!defn || defn === 'undefined') return false;
|
|
32431
|
+
// GDAL may emit LOCAL_CS["Undefined SRS", ...] for null CRS;
|
|
32432
|
+
// this is not a usable projected/geographic CRS for mapshaper.
|
|
32433
|
+
if (/^\s*LOCAL_CS\[/i.test(defn)) return false;
|
|
32434
|
+
return true;
|
|
32435
|
+
}
|
|
32436
|
+
|
|
31571
32437
|
function convertFeatureRow(featureRow) {
|
|
31572
32438
|
var feature = {
|
|
31573
32439
|
type: 'Feature',
|
|
@@ -31583,8 +32449,7 @@ ${svg}
|
|
|
31583
32449
|
// skip feature if geometry can't be parsed
|
|
31584
32450
|
return null;
|
|
31585
32451
|
}
|
|
31586
|
-
|
|
31587
|
-
var geomColName = featureRow.geometryColumn.name;
|
|
32452
|
+
var geomColName = featureRow.geometryColumn && featureRow.geometryColumn.name;
|
|
31588
32453
|
for (var key in featureRow.values) {
|
|
31589
32454
|
if (Object.prototype.hasOwnProperty.call(featureRow.values, key) &&
|
|
31590
32455
|
key !== geomColName) {
|
|
@@ -32363,6 +33228,11 @@ ${svg}
|
|
|
32363
33228
|
} else {
|
|
32364
33229
|
return importContent(obj, opts);
|
|
32365
33230
|
}
|
|
33231
|
+
if (Array.isArray(dataset)) {
|
|
33232
|
+
return dataset.map(function(ds) {
|
|
33233
|
+
return finalizeImportedDataset(ds, dataFmt, data, opts);
|
|
33234
|
+
});
|
|
33235
|
+
}
|
|
32366
33236
|
return finalizeImportedDataset(dataset, dataFmt, data, opts);
|
|
32367
33237
|
}
|
|
32368
33238
|
|
|
@@ -32564,12 +33434,18 @@ ${svg}
|
|
|
32564
33434
|
|
|
32565
33435
|
cmd.importFiles = async function(catalog, opts) {
|
|
32566
33436
|
var files = opts.files || [];
|
|
32567
|
-
var dataset;
|
|
33437
|
+
var dataset, datasets, target;
|
|
32568
33438
|
|
|
32569
33439
|
if (opts.stdin) {
|
|
32570
33440
|
dataset = await importFileAsync('/dev/stdin', opts);
|
|
32571
|
-
|
|
32572
|
-
|
|
33441
|
+
datasets = normalizeImportedDatasets(dataset);
|
|
33442
|
+
catalog.addDatasets(datasets);
|
|
33443
|
+
if (datasets.length > 1) {
|
|
33444
|
+
catalog.setDefaultTargets(datasets.map(function(ds) {
|
|
33445
|
+
return {dataset: ds, layers: ds.layers};
|
|
33446
|
+
}));
|
|
33447
|
+
}
|
|
33448
|
+
return normalizeImportedTarget(datasets);
|
|
32573
33449
|
}
|
|
32574
33450
|
|
|
32575
33451
|
if (files.length > 0 === false) {
|
|
@@ -32604,14 +33480,22 @@ ${svg}
|
|
|
32604
33480
|
} else {
|
|
32605
33481
|
dataset = await importFilesTogetherAsync(files, opts);
|
|
32606
33482
|
}
|
|
33483
|
+
datasets = normalizeImportedDatasets(dataset);
|
|
33484
|
+
datasets = validateAndCleanGpkgSelection(datasets, opts);
|
|
32607
33485
|
|
|
32608
33486
|
if (opts.merge_files && files.length > 1) {
|
|
32609
33487
|
// TODO: deprecate and remove this option (use -merge-layers cmd instead)
|
|
32610
|
-
|
|
33488
|
+
datasets[0].layers = cmd.mergeLayers(datasets[0].layers);
|
|
32611
33489
|
}
|
|
32612
33490
|
|
|
32613
|
-
catalog.
|
|
32614
|
-
|
|
33491
|
+
catalog.addDatasets(datasets);
|
|
33492
|
+
if (datasets.length > 1) {
|
|
33493
|
+
catalog.setDefaultTargets(datasets.map(function(ds) {
|
|
33494
|
+
return {dataset: ds, layers: ds.layers};
|
|
33495
|
+
}));
|
|
33496
|
+
}
|
|
33497
|
+
target = normalizeImportedTarget(datasets);
|
|
33498
|
+
return target;
|
|
32615
33499
|
};
|
|
32616
33500
|
|
|
32617
33501
|
// replace any JSON data objects with filenames and cache the data
|
|
@@ -32712,7 +33596,8 @@ ${svg}
|
|
|
32712
33596
|
|
|
32713
33597
|
cli.checkFileExists(path, cache);
|
|
32714
33598
|
|
|
32715
|
-
if ((fileType == 'shp' || fileType == 'json' || fileType == 'text' || fileType == 'dbf'
|
|
33599
|
+
if ((fileType == 'shp' || fileType == 'json' || fileType == 'text' || fileType == 'dbf' ||
|
|
33600
|
+
fileType == 'gpkg') && !cached) {
|
|
32716
33601
|
// these file types are read incrementally
|
|
32717
33602
|
content = null;
|
|
32718
33603
|
|
|
@@ -32815,17 +33700,20 @@ ${svg}
|
|
|
32815
33700
|
// Import multiple files to a single dataset
|
|
32816
33701
|
function importFilesTogether(files, opts) {
|
|
32817
33702
|
var unbuiltTopology = false;
|
|
32818
|
-
var datasets = files.
|
|
33703
|
+
var datasets = files.reduce(function(memo, fname) {
|
|
32819
33704
|
// import without topology or snapping
|
|
32820
33705
|
var importOpts = utils.defaults({no_topology: true, snap: false, snap_interval: null, files: [fname]}, opts);
|
|
32821
|
-
var
|
|
33706
|
+
var imported = normalizeImportedDatasets(importFile(fname, importOpts));
|
|
32822
33707
|
// check if dataset contains non-topological paths
|
|
32823
33708
|
// TODO: may also need to rebuild topology if multiple topojson files are merged
|
|
32824
|
-
|
|
32825
|
-
|
|
32826
|
-
|
|
32827
|
-
|
|
32828
|
-
|
|
33709
|
+
imported.forEach(function(dataset) {
|
|
33710
|
+
if (dataset.arcs && dataset.arcs.size() > 0 && dataset.info.input_formats[0] != 'topojson') {
|
|
33711
|
+
unbuiltTopology = true;
|
|
33712
|
+
}
|
|
33713
|
+
memo.push(dataset);
|
|
33714
|
+
});
|
|
33715
|
+
return memo;
|
|
33716
|
+
}, []);
|
|
32829
33717
|
var combined = mergeDatasets(datasets);
|
|
32830
33718
|
// Build topology, if needed
|
|
32831
33719
|
// TODO: consider updating topology of TopoJSON files instead of concatenating arcs
|
|
@@ -32843,12 +33731,15 @@ ${svg}
|
|
|
32843
33731
|
for (var fname of files) {
|
|
32844
33732
|
// import without topology or snapping
|
|
32845
33733
|
var importOpts = utils.defaults({no_topology: true, snap: false, snap_interval: null, files: [fname]}, opts);
|
|
32846
|
-
var
|
|
32847
|
-
|
|
32848
|
-
|
|
32849
|
-
|
|
32850
|
-
|
|
33734
|
+
var imported = normalizeImportedDatasets(await importFileAsync(fname, importOpts));
|
|
33735
|
+
imported.forEach(function(dataset) {
|
|
33736
|
+
if (dataset.arcs && dataset.arcs.size() > 0 && dataset.info.input_formats[0] != 'topojson') {
|
|
33737
|
+
unbuiltTopology = true;
|
|
33738
|
+
}
|
|
33739
|
+
datasets.push(dataset);
|
|
33740
|
+
});
|
|
32851
33741
|
}
|
|
33742
|
+
datasets = validateAndCleanGpkgSelection(datasets, opts);
|
|
32852
33743
|
var combined = mergeDatasets(datasets);
|
|
32853
33744
|
if (unbuiltTopology && !opts.no_topology) {
|
|
32854
33745
|
cleanPathsAfterImport(combined, opts);
|
|
@@ -32857,6 +33748,60 @@ ${svg}
|
|
|
32857
33748
|
return combined;
|
|
32858
33749
|
}
|
|
32859
33750
|
|
|
33751
|
+
// Validate the GeoPackage layers= selection across all imported datasets,
|
|
33752
|
+
// remove any placeholder datasets produced by filter misses, and strip the
|
|
33753
|
+
// bookkeeping metadata attached by the GeoPackage importer.
|
|
33754
|
+
function validateAndCleanGpkgSelection(datasets, opts) {
|
|
33755
|
+
var availableSet = new Set();
|
|
33756
|
+
var importedSet = new Set();
|
|
33757
|
+
var sawGpkg = false;
|
|
33758
|
+
datasets.forEach(function(ds) {
|
|
33759
|
+
var info = ds && ds.info;
|
|
33760
|
+
if (!info || !Array.isArray(info._gpkg_available_layers)) return;
|
|
33761
|
+
sawGpkg = true;
|
|
33762
|
+
info._gpkg_available_layers.forEach(function(name) { availableSet.add(name); });
|
|
33763
|
+
if (!info._gpkg_placeholder) {
|
|
33764
|
+
(ds.layers || []).forEach(function(lyr) {
|
|
33765
|
+
if (lyr && lyr.name) importedSet.add(lyr.name);
|
|
33766
|
+
});
|
|
33767
|
+
}
|
|
33768
|
+
});
|
|
33769
|
+
if (sawGpkg && Array.isArray(opts.layers) && opts.layers.length > 0) {
|
|
33770
|
+
var missing = opts.layers.filter(function(name) {
|
|
33771
|
+
return !importedSet.has(name);
|
|
33772
|
+
});
|
|
33773
|
+
if (missing.length > 0) {
|
|
33774
|
+
stop$1(
|
|
33775
|
+
'Missing GeoPackage layer(s): ' + missing.join(', ') + '\n' +
|
|
33776
|
+
'Existing layers: ' + Array.from(availableSet).join(' ')
|
|
33777
|
+
);
|
|
33778
|
+
}
|
|
33779
|
+
}
|
|
33780
|
+
var cleaned = datasets.filter(function(ds) {
|
|
33781
|
+
return !(ds && ds.info && ds.info._gpkg_placeholder);
|
|
33782
|
+
});
|
|
33783
|
+
cleaned.forEach(function(ds) {
|
|
33784
|
+
if (ds && ds.info) {
|
|
33785
|
+
delete ds.info._gpkg_available_layers;
|
|
33786
|
+
delete ds.info._gpkg_placeholder;
|
|
33787
|
+
}
|
|
33788
|
+
});
|
|
33789
|
+
return cleaned;
|
|
33790
|
+
}
|
|
33791
|
+
|
|
33792
|
+
function normalizeImportedDatasets(datasetOrArray) {
|
|
33793
|
+
return Array.isArray(datasetOrArray) ? datasetOrArray : [datasetOrArray];
|
|
33794
|
+
}
|
|
33795
|
+
|
|
33796
|
+
function normalizeImportedTarget(datasets) {
|
|
33797
|
+
if (datasets.length == 1) return datasets[0];
|
|
33798
|
+
return {
|
|
33799
|
+
layers: datasets.reduce(function(memo, dataset) {
|
|
33800
|
+
return memo.concat(dataset.layers);
|
|
33801
|
+
}, [])
|
|
33802
|
+
};
|
|
33803
|
+
}
|
|
33804
|
+
|
|
32860
33805
|
function getUnsupportedFileMessage(path) {
|
|
32861
33806
|
var ext = getFileExtension(path);
|
|
32862
33807
|
var msg = 'Unable to import ' + path;
|
|
@@ -39139,6 +40084,7 @@ ${svg}
|
|
|
39139
40084
|
|
|
39140
40085
|
// assumes layers and arcs have been prepared for clipping
|
|
39141
40086
|
function clipPolygons(targetShapes, clipShapes, nodes, type, optsArg) {
|
|
40087
|
+
profileStart('clipPolygons');
|
|
39142
40088
|
var arcs = nodes.arcs;
|
|
39143
40089
|
var opts = optsArg || {};
|
|
39144
40090
|
var clipFlags = new Uint8Array(arcs.size());
|
|
@@ -39149,59 +40095,47 @@ ${svg}
|
|
|
39149
40095
|
var findPath = getPathFinder(nodes, useRoute, routeIsActive);
|
|
39150
40096
|
var dissolvePolygon = getPolygonDissolver(nodes);
|
|
39151
40097
|
|
|
39152
|
-
// The following cleanup step is a performance bottleneck (it often takes longer than
|
|
39153
|
-
// other clipping operations) and is usually not needed. Furthermore, it only
|
|
39154
|
-
// eliminates a few kinds of problems, like target polygons with abnormal winding
|
|
39155
|
-
// or overlapping rings. TODO: try to optimize or remove it for all cases
|
|
39156
|
-
|
|
39157
|
-
// skipping shape cleanup when using the experimental fast bbox clipping option
|
|
39158
|
-
// if (!opts.bbox2 && !opts.no_cleanup) {
|
|
39159
40098
|
if (!opts.bbox2) {
|
|
39160
|
-
|
|
40099
|
+
profileStart('cp.dissolveTargetRings');
|
|
39161
40100
|
targetShapes = targetShapes.map(dissolvePolygon);
|
|
40101
|
+
profileEnd('cp.dissolveTargetRings');
|
|
39162
40102
|
}
|
|
39163
40103
|
|
|
39164
|
-
|
|
39165
|
-
// an unreliable dissolve function.
|
|
39166
|
-
// Now, clip shapes are dissolved using a more reliable (but slower)
|
|
39167
|
-
// function in mapshaper-clip-erase.js
|
|
39168
|
-
// clipShapes = [dissolvePolygon(internal.concatShapes(clipShapes))];
|
|
39169
|
-
|
|
39170
|
-
// Open pathways in the clip/erase layer
|
|
39171
|
-
// Need to expose clip/erase routes in both directions by setting route
|
|
39172
|
-
// in both directions to visible -- this is how cut-out shapes are detected
|
|
39173
|
-
// Or-ing with 0x11 makes both directions visible (so reverse paths will block)
|
|
40104
|
+
profileStart('cp.openClipRoutes');
|
|
39174
40105
|
openArcRoutes(clipShapes, arcs, clipFlags, type == 'clip', type == 'erase', true, 0x11);
|
|
40106
|
+
profileEnd('cp.openClipRoutes');
|
|
40107
|
+
profileStart('cp.PathIndex#1');
|
|
39175
40108
|
var index = new PathIndex(clipShapes, arcs);
|
|
40109
|
+
profileEnd('cp.PathIndex#1');
|
|
40110
|
+
profileStart('cp.clipShapes');
|
|
39176
40111
|
var clippedShapes = targetShapes.map(function(shape, i) {
|
|
39177
40112
|
if (shape) {
|
|
39178
40113
|
return clipPolygon(shape, type, index);
|
|
39179
40114
|
}
|
|
39180
40115
|
return null;
|
|
39181
40116
|
});
|
|
40117
|
+
profileEnd('cp.clipShapes');
|
|
39182
40118
|
|
|
39183
|
-
markPathsAsUsed(clippedShapes, routeFlags);
|
|
39184
|
-
|
|
39185
|
-
|
|
39186
|
-
// add clip/erase polygons that are fully contained in a target polygon
|
|
39187
|
-
// need to index only non-intersecting clip shapes
|
|
39188
|
-
// (Intersecting shapes have one or more arcs that have been scanned)
|
|
39189
|
-
|
|
39190
|
-
// first, find shapes that do not intersect the target layer
|
|
39191
|
-
// (these could be inside or outside the target polygons)
|
|
40119
|
+
markPathsAsUsed(clippedShapes, routeFlags);
|
|
39192
40120
|
|
|
40121
|
+
profileStart('cp.findUndividedClip');
|
|
39193
40122
|
var undividedClipShapes = findUndividedClipShapes(clipShapes);
|
|
40123
|
+
profileEnd('cp.findUndividedClip');
|
|
39194
40124
|
|
|
39195
|
-
closeArcRoutes(clipShapes, arcs, routeFlags, true, true);
|
|
40125
|
+
closeArcRoutes(clipShapes, arcs, routeFlags, true, true);
|
|
40126
|
+
profileStart('cp.PathIndex#2');
|
|
39196
40127
|
index = new PathIndex(undividedClipShapes, arcs);
|
|
40128
|
+
profileEnd('cp.PathIndex#2');
|
|
40129
|
+
profileStart('cp.findInteriorPaths');
|
|
39197
40130
|
targetShapes.forEach(function(shape, shapeId) {
|
|
39198
|
-
// find clipping paths that are internal to this target polygon
|
|
39199
40131
|
var paths = shape ? findInteriorPaths(shape, type, index) : null;
|
|
39200
40132
|
if (paths) {
|
|
39201
40133
|
clippedShapes[shapeId] = (clippedShapes[shapeId] || []).concat(paths);
|
|
39202
40134
|
}
|
|
39203
40135
|
});
|
|
40136
|
+
profileEnd('cp.findInteriorPaths');
|
|
39204
40137
|
|
|
40138
|
+
profileEnd('clipPolygons');
|
|
39205
40139
|
return clippedShapes;
|
|
39206
40140
|
|
|
39207
40141
|
function clipPolygon(shape, type, index) {
|
|
@@ -39629,31 +40563,36 @@ ${svg}
|
|
|
39629
40563
|
// @clipSrc: layer in @dataset or filename
|
|
39630
40564
|
// @type: 'clip' or 'erase'
|
|
39631
40565
|
function clipLayers(targetLayers, clipSrc, targetDataset, type, opts) {
|
|
40566
|
+
profileStart('clipLayers');
|
|
39632
40567
|
var usingPathClip = utils.some(targetLayers, layerHasPaths);
|
|
39633
|
-
var mergedDataset, clipLyr, nodes;
|
|
40568
|
+
var mergedDataset, clipLyr, nodes, result;
|
|
39634
40569
|
opts = opts || {no_cleanup: true}; // TODO: update testing functions
|
|
39635
40570
|
if (opts.bbox2 && usingPathClip) { // assumes target dataset has arcs
|
|
39636
|
-
|
|
40571
|
+
result = clipLayersByBBox(targetLayers, targetDataset, opts);
|
|
40572
|
+
profileEnd('clipLayers');
|
|
40573
|
+
return result;
|
|
39637
40574
|
}
|
|
40575
|
+
profileStart('mergeLayersForOverlay');
|
|
39638
40576
|
mergedDataset = mergeLayersForOverlay(targetLayers, targetDataset, clipSrc, opts);
|
|
40577
|
+
profileEnd('mergeLayersForOverlay');
|
|
39639
40578
|
clipLyr = mergedDataset.layers[mergedDataset.layers.length-1];
|
|
39640
40579
|
if (usingPathClip) {
|
|
39641
|
-
// add vertices at all line intersections
|
|
39642
|
-
// (generally slower than actual clipping)
|
|
39643
40580
|
nodes = addIntersectionCuts(mergedDataset, opts);
|
|
39644
40581
|
targetDataset.arcs = mergedDataset.arcs;
|
|
39645
|
-
|
|
39646
|
-
// that might confuse the clipping function)
|
|
39647
|
-
// use a data-free copy of the clip lyr, so data records are not dissolved
|
|
39648
|
-
// (this avoids triggering an unnecessary and expensive DBF read operation in some cases).
|
|
40582
|
+
profileStart('clipDissolvePolygonLayer2');
|
|
39649
40583
|
clipLyr = utils.defaults({data: null}, clipLyr);
|
|
39650
40584
|
clipLyr = dissolvePolygonLayer2(clipLyr, mergedDataset, {quiet: true, silent: true});
|
|
40585
|
+
profileEnd('clipDissolvePolygonLayer2');
|
|
39651
40586
|
|
|
39652
40587
|
} else {
|
|
39653
40588
|
nodes = new NodeCollection(mergedDataset.arcs);
|
|
39654
40589
|
}
|
|
39655
40590
|
|
|
39656
|
-
|
|
40591
|
+
profileStart('clipLayersByLayer');
|
|
40592
|
+
result = clipLayersByLayer(targetLayers, clipLyr, nodes, type, opts);
|
|
40593
|
+
profileEnd('clipLayersByLayer');
|
|
40594
|
+
profileEnd('clipLayers');
|
|
40595
|
+
return result;
|
|
39657
40596
|
}
|
|
39658
40597
|
|
|
39659
40598
|
function clipLayersByBBox(layers, dataset, opts) {
|
|
@@ -50192,7 +51131,7 @@ ${svg}
|
|
|
50192
51131
|
});
|
|
50193
51132
|
}
|
|
50194
51133
|
|
|
50195
|
-
var version = "0.6.
|
|
51134
|
+
var version = "0.6.119";
|
|
50196
51135
|
|
|
50197
51136
|
// Parse command line args into commands and run them
|
|
50198
51137
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -50890,6 +51829,7 @@ ${svg}
|
|
|
50890
51829
|
LayerUtils,
|
|
50891
51830
|
Lines,
|
|
50892
51831
|
Logging,
|
|
51832
|
+
Profile,
|
|
50893
51833
|
Merging,
|
|
50894
51834
|
MosaicIndex$1,
|
|
50895
51835
|
OptionParsingUtils,
|