mapshaper 0.6.109 → 0.6.111
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/bin/mapshaper-gui +3 -2
- package/mapshaper.js +817 -523
- package/package.json +2 -2
- package/www/assets/SourceSans3-VariableFont_wght.ttf +0 -0
- package/www/elements.css +1 -1
- package/www/index.html +45 -51
- package/www/mapshaper-gui.js +211 -34
- package/www/mapshaper.js +817 -523
- package/www/page.css +21 -23
- package/www/assets/SourceSansPro-Regular.woff +0 -0
- package/www/assets/SourceSansPro-Semibold.woff +0 -0
package/www/mapshaper.js
CHANGED
|
@@ -1328,13 +1328,21 @@
|
|
|
1328
1328
|
|
|
1329
1329
|
function verbose() {
|
|
1330
1330
|
// verbose can be set globally with the -verbose command or separately for each command
|
|
1331
|
-
if (
|
|
1331
|
+
if (useVerbose()) {
|
|
1332
1332
|
message.apply(null, arguments);
|
|
1333
1333
|
}
|
|
1334
1334
|
}
|
|
1335
1335
|
|
|
1336
|
+
function useVerbose() {
|
|
1337
|
+
return getStashedVar('VERBOSE');
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function useDebug() {
|
|
1341
|
+
return getStashedVar('DEBUG');
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1336
1344
|
function debug() {
|
|
1337
|
-
if (
|
|
1345
|
+
if (useDebug()) {
|
|
1338
1346
|
logArgs(arguments);
|
|
1339
1347
|
}
|
|
1340
1348
|
}
|
|
@@ -1458,6 +1466,8 @@
|
|
|
1458
1466
|
setLoggingFunctions: setLoggingFunctions,
|
|
1459
1467
|
stop: stop,
|
|
1460
1468
|
truncateString: truncateString,
|
|
1469
|
+
useDebug: useDebug,
|
|
1470
|
+
useVerbose: useVerbose,
|
|
1461
1471
|
verbose: verbose,
|
|
1462
1472
|
warn: warn
|
|
1463
1473
|
});
|
|
@@ -2265,44 +2275,6 @@
|
|
|
2265
2275
|
return dist * R$1;
|
|
2266
2276
|
}
|
|
2267
2277
|
|
|
2268
|
-
// TODO: make this safe for small angles
|
|
2269
|
-
function innerAngle(ax, ay, bx, by, cx, cy) {
|
|
2270
|
-
var ab = distance2D(ax, ay, bx, by),
|
|
2271
|
-
bc = distance2D(bx, by, cx, cy),
|
|
2272
|
-
theta, dotp;
|
|
2273
|
-
if (ab === 0 || bc === 0) {
|
|
2274
|
-
theta = 0;
|
|
2275
|
-
} else {
|
|
2276
|
-
dotp = ((ax - bx) * (cx - bx) + (ay - by) * (cy - by)) / (ab * bc);
|
|
2277
|
-
if (dotp >= 1 - 1e-14) {
|
|
2278
|
-
theta = 0;
|
|
2279
|
-
} else if (dotp <= -1 + 1e-14) {
|
|
2280
|
-
theta = Math.PI;
|
|
2281
|
-
} else {
|
|
2282
|
-
theta = Math.acos(dotp); // consider using other formula at small dp
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
return theta;
|
|
2286
|
-
}
|
|
2287
|
-
|
|
2288
|
-
function innerAngle3D(ax, ay, az, bx, by, bz, cx, cy, cz) {
|
|
2289
|
-
var ab = distance3D(ax, ay, az, bx, by, bz),
|
|
2290
|
-
bc = distance3D(bx, by, bz, cx, cy, cz),
|
|
2291
|
-
theta, dotp;
|
|
2292
|
-
if (ab === 0 || bc === 0) {
|
|
2293
|
-
theta = 0;
|
|
2294
|
-
} else {
|
|
2295
|
-
dotp = ((ax - bx) * (cx - bx) + (ay - by) * (cy - by) + (az - bz) * (cz - bz)) / (ab * bc);
|
|
2296
|
-
if (dotp >= 1) {
|
|
2297
|
-
theta = 0;
|
|
2298
|
-
} else if (dotp <= -1) {
|
|
2299
|
-
theta = Math.PI;
|
|
2300
|
-
} else {
|
|
2301
|
-
theta = Math.acos(dotp); // consider using other formula at small dp
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
return theta;
|
|
2305
|
-
}
|
|
2306
2278
|
|
|
2307
2279
|
function triangleArea(ax, ay, bx, by, cx, cy) {
|
|
2308
2280
|
var area = Math.abs(((ay - cy) * (bx - cx) + (by - cy) * (cx - ax)) / 2);
|
|
@@ -2391,36 +2363,10 @@
|
|
|
2391
2363
|
return distanceSq(px, py, ax + t * (bx - ax), ay + t * (by - ay));
|
|
2392
2364
|
}
|
|
2393
2365
|
|
|
2394
|
-
|
|
2395
|
-
// internal.reversePathCoords = function(arr, start, len) {
|
|
2396
|
-
// var i = start,
|
|
2397
|
-
// j = start + len - 1,
|
|
2398
|
-
// tmp;
|
|
2399
|
-
// while (i < j) {
|
|
2400
|
-
// tmp = arr[i];
|
|
2401
|
-
// arr[i] = arr[j];
|
|
2402
|
-
// arr[j] = tmp;
|
|
2403
|
-
// i++;
|
|
2404
|
-
// j--;
|
|
2405
|
-
// }
|
|
2406
|
-
// };
|
|
2407
|
-
|
|
2408
|
-
// merge B into A
|
|
2409
|
-
// function mergeBounds(a, b) {
|
|
2410
|
-
// if (b[0] < a[0]) a[0] = b[0];
|
|
2411
|
-
// if (b[1] < a[1]) a[1] = b[1];
|
|
2412
|
-
// if (b[2] > a[2]) a[2] = b[2];
|
|
2413
|
-
// if (b[3] > a[3]) a[3] = b[3];
|
|
2414
|
-
// }
|
|
2415
|
-
|
|
2416
2366
|
function containsBounds(a, b) {
|
|
2417
2367
|
return a[0] <= b[0] && a[2] >= b[2] && a[1] <= b[1] && a[3] >= b[3];
|
|
2418
2368
|
}
|
|
2419
2369
|
|
|
2420
|
-
// function boundsArea(b) {
|
|
2421
|
-
// return (b[2] - b[0]) * (b[3] - b[1]);
|
|
2422
|
-
// }
|
|
2423
|
-
|
|
2424
2370
|
var Geom = /*#__PURE__*/Object.freeze({
|
|
2425
2371
|
__proto__: null,
|
|
2426
2372
|
D2R: D2R,
|
|
@@ -2438,9 +2384,7 @@
|
|
|
2438
2384
|
distanceSq: distanceSq,
|
|
2439
2385
|
distanceSq3D: distanceSq3D,
|
|
2440
2386
|
greatCircleDistance: greatCircleDistance,
|
|
2441
|
-
innerAngle: innerAngle,
|
|
2442
2387
|
innerAngle2: innerAngle2,
|
|
2443
|
-
innerAngle3D: innerAngle3D,
|
|
2444
2388
|
lngLatToXYZ: lngLatToXYZ,
|
|
2445
2389
|
pointSegDistSq: pointSegDistSq,
|
|
2446
2390
|
pointSegDistSq2: pointSegDistSq2,
|
|
@@ -2962,7 +2906,22 @@
|
|
|
2962
2906
|
// @coords: Array of relevant coordinates (e.g. bbox coordinates of vertex coordinates
|
|
2963
2907
|
// of two intersecting segments).
|
|
2964
2908
|
//
|
|
2909
|
+
|
|
2910
|
+
// Updated the original function with a smaller interval... which works
|
|
2911
|
+
// better on a (limited) set of real-world sample data
|
|
2912
|
+
// (less likely to create erroneous output)
|
|
2965
2913
|
function getHighPrecisionSnapInterval(coords) {
|
|
2914
|
+
var n = Math.max.apply(null, coords.map(Math.abs));
|
|
2915
|
+
var ceil = n <= 1 ? 1 : 2 ** Math.ceil(Math.log2(n));
|
|
2916
|
+
// console.log(ceil < ceil + ceil / 2 ** 51) // true
|
|
2917
|
+
// console.log(ceil < ceil + ceil / 2 ** 52) // true
|
|
2918
|
+
// console.log(ceil < ceil + ceil / 2 ** 53) // false
|
|
2919
|
+
var interval = ceil / 2 ** 51;
|
|
2920
|
+
// console.log('interval:', interval)
|
|
2921
|
+
return interval;
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
function getHighPrecisionSnapInterval_old(coords) {
|
|
2966
2925
|
var maxCoord = Math.max.apply(null, coords.map(Math.abs));
|
|
2967
2926
|
return maxCoord * 1e-14;
|
|
2968
2927
|
}
|
|
@@ -3148,11 +3107,77 @@
|
|
|
3148
3107
|
getCoordinateIds: getCoordinateIds,
|
|
3149
3108
|
getEndpointIds: getEndpointIds,
|
|
3150
3109
|
getHighPrecisionSnapInterval: getHighPrecisionSnapInterval,
|
|
3110
|
+
getHighPrecisionSnapInterval_old: getHighPrecisionSnapInterval_old,
|
|
3151
3111
|
snapCoords: snapCoords,
|
|
3152
3112
|
snapCoordsByInterval: snapCoordsByInterval,
|
|
3153
3113
|
snapEndpointsByInterval: snapEndpointsByInterval
|
|
3154
3114
|
});
|
|
3155
3115
|
|
|
3116
|
+
function toScaledStr(num, k) {
|
|
3117
|
+
var abs = Math.abs(num);
|
|
3118
|
+
var signed = num != abs;
|
|
3119
|
+
// String() matches behavior of big.js
|
|
3120
|
+
// (as opposed to toFixed() or toPrecision())
|
|
3121
|
+
var s = abs < 1e-6 ? abs.toFixed(k) : String(abs);
|
|
3122
|
+
var parts = s.split('.');
|
|
3123
|
+
var integer = parts[0] == '0' ? '' : parts[0];
|
|
3124
|
+
var decimal = parts.length == 2 ? parts[1] : '';
|
|
3125
|
+
if (decimal.length < k) {
|
|
3126
|
+
decimal = decimal.padEnd(k, '0');
|
|
3127
|
+
} else if (decimal.length > k) {
|
|
3128
|
+
decimal = decimal.slice(0, k);
|
|
3129
|
+
}
|
|
3130
|
+
var s2 = integer + decimal;
|
|
3131
|
+
// remove any leading 0s
|
|
3132
|
+
while (s2[0] == 0 && s2.length > 1) {
|
|
3133
|
+
s2 = s2.slice(1);
|
|
3134
|
+
}
|
|
3135
|
+
if (signed) {
|
|
3136
|
+
s2 = '-' + s2;
|
|
3137
|
+
}
|
|
3138
|
+
return s2;
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
// returns Number
|
|
3142
|
+
function fromScaledStr(s, decimals) {
|
|
3143
|
+
var signed = s[0] == '-';
|
|
3144
|
+
var uns = signed ? s.slice(1) : s;
|
|
3145
|
+
var s2, len;
|
|
3146
|
+
len = uns.length;
|
|
3147
|
+
if (len > decimals) {
|
|
3148
|
+
s2 = uns.slice(0, len - decimals) + '.' + uns.slice(-decimals);
|
|
3149
|
+
} else if (len == decimals) {
|
|
3150
|
+
s2 = '0.' + uns;
|
|
3151
|
+
} else {
|
|
3152
|
+
s2 = '0.' + uns.padStart(decimals, '0');
|
|
3153
|
+
}
|
|
3154
|
+
if (signed) {
|
|
3155
|
+
s2 = '-' + s2;
|
|
3156
|
+
}
|
|
3157
|
+
return Number(s2);
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
function findBigIntScaleFactor() {
|
|
3161
|
+
var minVal = Infinity;
|
|
3162
|
+
var intLen = 0, s;
|
|
3163
|
+
for (var i=0, n=arguments.length; i<n; i++) {
|
|
3164
|
+
minVal = Math.min(minVal, Math.abs(arguments[i]));
|
|
3165
|
+
}
|
|
3166
|
+
if (minVal >= 1) {
|
|
3167
|
+
s = minVal.toFixed(1);
|
|
3168
|
+
intLen = s.indexOf('.');
|
|
3169
|
+
} else if (minVal !== 0) {
|
|
3170
|
+
s = minVal.toFixed(10).slice(2); // decimal part, up to _ decimals
|
|
3171
|
+
while (s[0] === '0') {
|
|
3172
|
+
intLen--;
|
|
3173
|
+
s = s.slice(1);
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
return 17 - intLen;
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
//import { findCrossIntersection_big } from '../geom/mapshaper-segment-geom-big';
|
|
3180
|
+
|
|
3156
3181
|
// Find the intersection between two 2D segments
|
|
3157
3182
|
// Returns 0, 1 or 2 [x, y] locations as null, [x, y], or [x1, y1, x2, y2]
|
|
3158
3183
|
// Special cases:
|
|
@@ -3164,43 +3189,59 @@
|
|
|
3164
3189
|
function segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy, epsArg) {
|
|
3165
3190
|
// Use a small tolerance interval, so collinear segments and T-intersections
|
|
3166
3191
|
// are detected (floating point rounding often causes exact functions to fail)
|
|
3167
|
-
var eps = epsArg
|
|
3192
|
+
var eps = epsArg > 0 ? epsArg :
|
|
3168
3193
|
getHighPrecisionSnapInterval([ax, ay, bx, by, cx, cy, dx, dy]);
|
|
3169
3194
|
var epsSq = eps * eps;
|
|
3170
3195
|
var touches, cross;
|
|
3196
|
+
|
|
3171
3197
|
// Detect 0, 1 or 2 'touch' intersections, where a vertex of one segment
|
|
3172
3198
|
// is very close to the other segment's linear portion.
|
|
3173
3199
|
// One touch indicates either a T-intersection or two overlapping collinear
|
|
3174
3200
|
// segments that share an endpoint. Two touches indicates overlapping
|
|
3175
3201
|
// collinear segments that do not share an endpoint.
|
|
3176
3202
|
touches = findPointSegTouches(epsSq, ax, ay, bx, by, cx, cy, dx, dy);
|
|
3177
|
-
// if (touches) return touches;
|
|
3178
3203
|
// Ignore endpoint-only intersections
|
|
3179
3204
|
if (!touches && testEndpointHit(epsSq, ax, ay, bx, by, cx, cy, dx, dy)) {
|
|
3180
3205
|
return null;
|
|
3181
3206
|
}
|
|
3182
3207
|
// Detect cross intersection
|
|
3183
|
-
|
|
3208
|
+
// (TODO: consider cross intersections that are also endpoint hits)
|
|
3209
|
+
if (!touches) {
|
|
3210
|
+
cross = findCrossIntersection(ax, ay, bx, by, cx, cy, dx, dy, eps);
|
|
3211
|
+
}
|
|
3184
3212
|
return touches || cross || null;
|
|
3185
3213
|
}
|
|
3186
3214
|
|
|
3187
|
-
|
|
3188
|
-
// Find the intersection point of two segments that cross each other,
|
|
3189
|
-
// or return null if the segments do not cross.
|
|
3190
|
-
// Assumes endpoint intersections have already been detected
|
|
3191
3215
|
function findCrossIntersection(ax, ay, bx, by, cx, cy, dx, dy, eps) {
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3216
|
+
var p;
|
|
3217
|
+
// The normal-precision hit function works for all inputs when eps > 0 because
|
|
3218
|
+
// the geometries that cause the ordinary function fails are detected as
|
|
3219
|
+
// 'touches' or endpoint hits (at least this was true in all the real-world
|
|
3220
|
+
// data samples that were tested).
|
|
3221
|
+
//
|
|
3222
|
+
if (eps > 0 && !segmentHit_fast(ax, ay, bx, by, cx, cy, dx, dy)) {
|
|
3223
|
+
return null;
|
|
3224
|
+
} else if (eps === 0 && !segmentHit_robust(ax, ay, bx, by, cx, cy, dx, dy)) {
|
|
3201
3225
|
return null;
|
|
3202
3226
|
}
|
|
3203
3227
|
|
|
3228
|
+
// in a typical layer with many intersections along shared polygon boundaries,
|
|
3229
|
+
// robust is preferred in most (>90%) segment intersections in order to keep
|
|
3230
|
+
// the positional error within a small interval (e.g. 50% of eps)
|
|
3231
|
+
//
|
|
3232
|
+
if (useRobustCross(ax, ay, bx, by, cx, cy, dx, dy)) {
|
|
3233
|
+
p = findCrossIntersection_robust(ax, ay, bx, by, cx, cy, dx, dy);
|
|
3234
|
+
// var p2 = findCrossIntersection_big(ax, ay, bx, by, cx, cy, dx, dy);
|
|
3235
|
+
// var dx = p[0] - p2[0];
|
|
3236
|
+
// var dy = p[1] - p2[1];
|
|
3237
|
+
// if (dx != 0 || dy != 0) {
|
|
3238
|
+
// console.log(dx, dy)
|
|
3239
|
+
// }
|
|
3240
|
+
} else {
|
|
3241
|
+
p = findCrossIntersection_fast(ax, ay, bx, by, cx, cy, dx, dy);
|
|
3242
|
+
}
|
|
3243
|
+
if (!p) return null;
|
|
3244
|
+
|
|
3204
3245
|
// Snap p to a vertex if very close to one
|
|
3205
3246
|
// This avoids tiny segments caused by T-intersection overshoots and prevents
|
|
3206
3247
|
// pathfinder errors related to f-p rounding.
|
|
@@ -3215,9 +3256,90 @@
|
|
|
3215
3256
|
return p;
|
|
3216
3257
|
}
|
|
3217
3258
|
|
|
3259
|
+
function findCrossIntersection_fast(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
3260
|
+
var den = determinant2D(bx - ax, by - ay, dx - cx, dy - cy);
|
|
3261
|
+
var m = orient2D(cx, cy, dx, dy, ax, ay) / den;
|
|
3262
|
+
var p = [ax + m * (bx - ax), ay + m * (by - ay)];
|
|
3263
|
+
if (Math.abs(den) < 1e-25) {
|
|
3264
|
+
// changed from 1e-18 to 1e-25 (see geom ex1)
|
|
3265
|
+
// assume that collinear and near-collinear segment intersections have been
|
|
3266
|
+
// accounted for already.
|
|
3267
|
+
// TODO: is this really true?
|
|
3268
|
+
return null;
|
|
3269
|
+
}
|
|
3270
|
+
return p;
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
// this function, using BigInt, is 3-4x faster than the version using big.js
|
|
3274
|
+
function findCrossIntersection_robust(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
3275
|
+
var d = findBigIntScaleFactor(ax, ay, bx, by, cx, cy, dx, dy);
|
|
3276
|
+
var d2 = 16; // scale numerator of integer division by this many decimal digits
|
|
3277
|
+
var k_bi = 10000000000000000n; // matches d2
|
|
3278
|
+
var ax_bi = BigInt(toScaledStr(ax, d));
|
|
3279
|
+
var ay_bi = BigInt(toScaledStr(ay, d));
|
|
3280
|
+
var bx_bi = BigInt(toScaledStr(bx, d));
|
|
3281
|
+
var by_bi = BigInt(toScaledStr(by, d));
|
|
3282
|
+
var cx_bi = BigInt(toScaledStr(cx, d));
|
|
3283
|
+
var cy_bi = BigInt(toScaledStr(cy, d));
|
|
3284
|
+
var dx_bi = BigInt(toScaledStr(dx, d));
|
|
3285
|
+
var dy_bi = BigInt(toScaledStr(dy, d));
|
|
3286
|
+
var den = determinant2D(bx_bi - ax_bi, by_bi - ay_bi, dx_bi - cx_bi, dy_bi - cy_bi);
|
|
3287
|
+
if (den === 0n) {
|
|
3288
|
+
debug('DIV0 error - should have been identified as collinear "touch" intersection.');
|
|
3289
|
+
return null;
|
|
3290
|
+
}
|
|
3291
|
+
var num = orient2D(cx_bi, cy_bi, dx_bi, dy_bi, ax_bi, ay_bi) * k_bi;
|
|
3292
|
+
var m_bi = num / den;
|
|
3293
|
+
var x_bi = ax_bi * k_bi + m_bi * (bx_bi - ax_bi);
|
|
3294
|
+
var y_bi = ay_bi * k_bi + m_bi * (by_bi - ay_bi);
|
|
3295
|
+
var x = fromScaledStr(x_bi.toString(), d + d2);
|
|
3296
|
+
var y = fromScaledStr(y_bi.toString(), d + d2);
|
|
3297
|
+
return [x, y];
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
function useRobustCross(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
3301
|
+
// angle and seg length ratio thresholds were found by comparing
|
|
3302
|
+
// fast and robust outputs on sample data
|
|
3303
|
+
if (innerAngle(ax, ay, bx, by, cx, cy, dx, dy) < 0.1) return true;
|
|
3304
|
+
var len1 = distance2D(ax, ay, bx, by);
|
|
3305
|
+
var len2 = distance2D(cx, cy, dx, dy);
|
|
3306
|
+
var ratio = len1 < len2 ? len1 / len2 : len2 / len1 || 0;
|
|
3307
|
+
if (ratio < 0.001) return true;
|
|
3308
|
+
return false;
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
// Returns smaller of two angles between two segments (unsigned)
|
|
3312
|
+
function innerAngle(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
3313
|
+
var v1x = bx - ax;
|
|
3314
|
+
var v1y = by - ay;
|
|
3315
|
+
var v2x = dx - cx;
|
|
3316
|
+
var v2y = dy - cy;
|
|
3317
|
+
var dot = v1x * v2x + v1y * v2y;
|
|
3318
|
+
var mag1Sq = v1x * v1x + v1y * v1y;
|
|
3319
|
+
var mag2Sq = v2x * v2x + v2y * v2y;
|
|
3320
|
+
if (mag1Sq === 0 || mag2Sq === 0) {
|
|
3321
|
+
return 0;
|
|
3322
|
+
}
|
|
3323
|
+
var cosTheta = dot / Math.sqrt(mag1Sq * mag2Sq);
|
|
3324
|
+
var theta;
|
|
3325
|
+
if (cosTheta > 1 - 1e-14) {
|
|
3326
|
+
theta = 0;
|
|
3327
|
+
} else if (cosTheta < -1 + 1e-14) {
|
|
3328
|
+
theta = Math.PI;
|
|
3329
|
+
} else {
|
|
3330
|
+
theta = Math.acos(cosTheta);
|
|
3331
|
+
}
|
|
3332
|
+
if (theta >= Math.PI / 2) {
|
|
3333
|
+
theta = Math.PI - theta;
|
|
3334
|
+
}
|
|
3335
|
+
return theta;
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3218
3338
|
function testEndpointHit(epsSq, ax, ay, bx, by, cx, cy, dx, dy) {
|
|
3219
|
-
return distanceSq(ax, ay, cx, cy) <= epsSq ||
|
|
3220
|
-
distanceSq(
|
|
3339
|
+
return distanceSq(ax, ay, cx, cy) <= epsSq ||
|
|
3340
|
+
distanceSq(ax, ay, dx, dy) <= epsSq ||
|
|
3341
|
+
distanceSq(bx, by, cx, cy) <= epsSq ||
|
|
3342
|
+
distanceSq(bx, by, dx, dy) <= epsSq;
|
|
3221
3343
|
}
|
|
3222
3344
|
|
|
3223
3345
|
function findPointSegTouches(epsSq, ax, ay, bx, by, cx, cy, dx, dy) {
|
|
@@ -3228,6 +3350,9 @@
|
|
|
3228
3350
|
collectPointSegTouch(touches, epsSq, dx, dy, ax, ay, bx, by);
|
|
3229
3351
|
if (touches.length === 0) return null;
|
|
3230
3352
|
if (touches.length > 4) {
|
|
3353
|
+
// console.log('XX', touches.length)
|
|
3354
|
+
// console.log('Seg 1', getSegFeature(ax, ay, bx, by, true))
|
|
3355
|
+
// console.log('Seg 2', getSegFeature(cx, cy, dx, dy, false))
|
|
3231
3356
|
// Geometrically, more than two touch intersections can not occur.
|
|
3232
3357
|
// Is it possible that fp rounding or a bug might result in >2 touches?
|
|
3233
3358
|
debug('Intersection detection error');
|
|
@@ -3244,10 +3369,10 @@
|
|
|
3244
3369
|
var pa = distanceSq(ax, ay, px, py);
|
|
3245
3370
|
var pb = distanceSq(bx, by, px, py);
|
|
3246
3371
|
if (pa <= epsSq || pb <= epsSq) return; // ignore endpoint hits
|
|
3372
|
+
// console.log("Dist:", Math.sqrt(pab), "eps:", Math.sqrt(epsSq), "p:", px, py)
|
|
3247
3373
|
arr.push(px, py); // T intersection at P and AB
|
|
3248
3374
|
}
|
|
3249
3375
|
|
|
3250
|
-
|
|
3251
3376
|
// Used by mapshaper-undershoots.js
|
|
3252
3377
|
// TODO: make more robust, make sure result is compatible with segmentIntersection()
|
|
3253
3378
|
// (rounding errors currently must be handled downstream)
|
|
@@ -3296,16 +3421,43 @@
|
|
|
3296
3421
|
// when a segment is vertical or horizontal. This has caused problems when
|
|
3297
3422
|
// repeatedly applying bbox clipping along the same segment
|
|
3298
3423
|
var x = p[0],
|
|
3299
|
-
y = p[1]
|
|
3424
|
+
y = p[1],
|
|
3425
|
+
s1out = false,
|
|
3426
|
+
s2out = false;
|
|
3427
|
+
|
|
3300
3428
|
// assumes that segment ranges intersect
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3429
|
+
if (outsideRange(x, ax, bx)) {
|
|
3430
|
+
x = clampToClosestEndpoint(x, ax, bx);
|
|
3431
|
+
s1out = true;
|
|
3432
|
+
}
|
|
3433
|
+
if (outsideRange(x, cx, dx)) {
|
|
3434
|
+
x = clampToClosestEndpoint(x, cx, dx);
|
|
3435
|
+
s2out = true;
|
|
3436
|
+
}
|
|
3437
|
+
if (outsideRange(y, ay, by)) {
|
|
3438
|
+
y = clampToClosestEndpoint(y, ay, by);
|
|
3439
|
+
s1out = true;
|
|
3440
|
+
}
|
|
3441
|
+
if (outsideRange(y, cy, dy)) {
|
|
3442
|
+
y = clampToClosestEndpoint(y, cy, dy);
|
|
3443
|
+
s2out = true;
|
|
3444
|
+
}
|
|
3445
|
+
if ((s1out || s2out)) {
|
|
3446
|
+
debug('Clamping a segment intersection point');
|
|
3447
|
+
// console.log("angle:", innerAngle(ax, ay, bx, by, cx, cy, dx, dy))
|
|
3448
|
+
// console.log('Feature 1', getSegFeature(ax, ay, bx, by, s1out));
|
|
3449
|
+
// console.log('Feature 2', getSegFeature(cx, cy, dx, dy, s2out));
|
|
3450
|
+
// console.log('Point:', JSON.stringify({
|
|
3451
|
+
// type: 'Feature',
|
|
3452
|
+
// properties: {fill: 'red'},
|
|
3453
|
+
// geometry: {type: 'Point', coordinates: [p[0], p[1]]}
|
|
3454
|
+
// }));
|
|
3455
|
+
}
|
|
3305
3456
|
p[0] = x;
|
|
3306
3457
|
p[1] = y;
|
|
3307
3458
|
}
|
|
3308
3459
|
|
|
3460
|
+
|
|
3309
3461
|
// a: coordinate of point
|
|
3310
3462
|
// b: endpoint coordinate of segment
|
|
3311
3463
|
// c: other endpoint of segment
|
|
@@ -3321,16 +3473,13 @@
|
|
|
3321
3473
|
return out;
|
|
3322
3474
|
}
|
|
3323
3475
|
|
|
3324
|
-
function
|
|
3325
|
-
var lim;
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
debug("[clampToCloseRange()] large clamping interval", a, b, c);
|
|
3330
|
-
}
|
|
3331
|
-
a = lim;
|
|
3476
|
+
function clampToClosestEndpoint(a, b, c) {
|
|
3477
|
+
var lim = Math.abs(a - b) < Math.abs(a - c) ? b : c;
|
|
3478
|
+
var interval = Math.abs(a - lim);
|
|
3479
|
+
if (interval > 1e-15) {
|
|
3480
|
+
debug("[clampToClosestEndpoint()] large clamping interval:", interval);
|
|
3332
3481
|
}
|
|
3333
|
-
return
|
|
3482
|
+
return lim;
|
|
3334
3483
|
}
|
|
3335
3484
|
|
|
3336
3485
|
// Determinant of matrix
|
|
@@ -3348,10 +3497,35 @@
|
|
|
3348
3497
|
return determinant2D(ax - cx, ay - cy, bx - cx, by - cy);
|
|
3349
3498
|
}
|
|
3350
3499
|
|
|
3500
|
+
function orient2D_robust(ax, ay, bx, by, cx, cy) {
|
|
3501
|
+
var d = findBigIntScaleFactor(ax, ay, bx, by, cx, cy);
|
|
3502
|
+
var ax_bi = BigInt(toScaledStr(ax, d));
|
|
3503
|
+
var ay_bi = BigInt(toScaledStr(ay, d));
|
|
3504
|
+
var bx_bi = BigInt(toScaledStr(bx, d));
|
|
3505
|
+
var by_bi = BigInt(toScaledStr(by, d));
|
|
3506
|
+
var cx_bi = BigInt(toScaledStr(cx, d));
|
|
3507
|
+
var cy_bi = BigInt(toScaledStr(cy, d));
|
|
3508
|
+
var o2d_bi = orient2D(ax_bi, ay_bi, bx_bi, by_bi, cx_bi, cy_bi);
|
|
3509
|
+
return fromScaledStr(o2d_bi.toString(), d);
|
|
3510
|
+
}
|
|
3511
|
+
|
|
3512
|
+
function segmentHit_robust(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
3513
|
+
var d = findBigIntScaleFactor(ax, ay, bx, by, cx, cy, dx, dy);
|
|
3514
|
+
var ax_bi = BigInt(toScaledStr(ax, d));
|
|
3515
|
+
var ay_bi = BigInt(toScaledStr(ay, d));
|
|
3516
|
+
var bx_bi = BigInt(toScaledStr(bx, d));
|
|
3517
|
+
var by_bi = BigInt(toScaledStr(by, d));
|
|
3518
|
+
var cx_bi = BigInt(toScaledStr(cx, d));
|
|
3519
|
+
var cy_bi = BigInt(toScaledStr(cy, d));
|
|
3520
|
+
var dx_bi = BigInt(toScaledStr(dx, d));
|
|
3521
|
+
var dy_bi = BigInt(toScaledStr(dy, d));
|
|
3522
|
+
return segmentHit_fast(ax_bi, ay_bi, bx_bi, by_bi, cx_bi, cy_bi, dx_bi, dy_bi);
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3351
3525
|
// Source: Sedgewick, _Algorithms in C_
|
|
3352
3526
|
// (Other functions were tried that were more sensitive to floating point errors
|
|
3353
3527
|
// than this function)
|
|
3354
|
-
function
|
|
3528
|
+
function segmentHit_fast(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
3355
3529
|
return orient2D(ax, ay, bx, by, cx, cy) *
|
|
3356
3530
|
orient2D(ax, ay, bx, by, dx, dy) <= 0 &&
|
|
3357
3531
|
orient2D(cx, cy, dx, dy, ax, ay) *
|
|
@@ -3381,8 +3555,10 @@
|
|
|
3381
3555
|
var SegmentGeom = /*#__PURE__*/Object.freeze({
|
|
3382
3556
|
__proto__: null,
|
|
3383
3557
|
findClosestPointOnSeg: findClosestPointOnSeg,
|
|
3558
|
+
findCrossIntersection_robust: findCrossIntersection_robust,
|
|
3384
3559
|
orient2D: orient2D,
|
|
3385
|
-
|
|
3560
|
+
orient2D_robust: orient2D_robust,
|
|
3561
|
+
segmentHit_fast: segmentHit_fast,
|
|
3386
3562
|
segmentIntersection: segmentIntersection,
|
|
3387
3563
|
segmentTurn: segmentTurn
|
|
3388
3564
|
});
|
|
@@ -3692,7 +3868,7 @@
|
|
|
3692
3868
|
// editPart: callback function
|
|
3693
3869
|
function editShapes(shapes, editPart) {
|
|
3694
3870
|
for (var i=0, n=shapes.length; i<n; i++) {
|
|
3695
|
-
shapes[i] = editShapeParts(shapes[i], editPart);
|
|
3871
|
+
shapes[i] = editShapeParts(shapes[i], editPart, i);
|
|
3696
3872
|
}
|
|
3697
3873
|
}
|
|
3698
3874
|
|
|
@@ -3700,8 +3876,9 @@
|
|
|
3700
3876
|
// @cb: function(part, i, parts)
|
|
3701
3877
|
// If @cb returns an array, it replaces the existing value
|
|
3702
3878
|
// If @cb returns null, the path is removed from the feature
|
|
3879
|
+
// @shpId: (optional) id of shape
|
|
3703
3880
|
//
|
|
3704
|
-
function editShapeParts(parts, cb) {
|
|
3881
|
+
function editShapeParts(parts, cb, shpId) {
|
|
3705
3882
|
if (!parts) return null; // null geometry not edited
|
|
3706
3883
|
if (!utils.isArray(parts)) error("Expected an array, received:", parts);
|
|
3707
3884
|
var nulls = 0,
|
|
@@ -3709,7 +3886,7 @@
|
|
|
3709
3886
|
retn;
|
|
3710
3887
|
|
|
3711
3888
|
for (var i=0; i<n; i++) {
|
|
3712
|
-
retn = cb(parts[i], i, parts);
|
|
3889
|
+
retn = cb(parts[i], i, parts, shpId);
|
|
3713
3890
|
if (retn === null) {
|
|
3714
3891
|
nulls++;
|
|
3715
3892
|
parts[i] = null;
|
|
@@ -5756,7 +5933,7 @@
|
|
|
5756
5933
|
//
|
|
5757
5934
|
this.forEach2 = function(cb) {
|
|
5758
5935
|
for (var arcId=0, n=this.size(); arcId<n; arcId++) {
|
|
5759
|
-
cb(_ii[arcId], _nn[arcId], _xx, _yy, _zz
|
|
5936
|
+
cb(arcId, _ii[arcId], _nn[arcId], _xx, _yy, _zz);
|
|
5760
5937
|
}
|
|
5761
5938
|
};
|
|
5762
5939
|
|
|
@@ -5891,6 +6068,44 @@
|
|
|
5891
6068
|
};
|
|
5892
6069
|
|
|
5893
6070
|
this.arcIsDegenerate = function(arcId) {
|
|
6071
|
+
return this.arcHasZeroLength(arcId) || this.arcIsSpike(arcId) || this.arcIsSpike_v1(arcId);
|
|
6072
|
+
};
|
|
6073
|
+
|
|
6074
|
+
// only finds two-segment spikes
|
|
6075
|
+
this.arcIsSpike_v1 = function(arcId) {
|
|
6076
|
+
var iter = this.getArcIter(arcId);
|
|
6077
|
+
var x0, y0;
|
|
6078
|
+
if (iter.hasNext()) {
|
|
6079
|
+
x0 = iter.x;
|
|
6080
|
+
y0 = iter.y;
|
|
6081
|
+
}
|
|
6082
|
+
iter.hasNext(); // ignore second point
|
|
6083
|
+
if (iter.hasNext()) {
|
|
6084
|
+
if (iter.x == x0 && iter.y == y0 && !iter.hasNext()) {
|
|
6085
|
+
// three-vertex arc, first two are the same
|
|
6086
|
+
return true;
|
|
6087
|
+
}
|
|
6088
|
+
}
|
|
6089
|
+
return false;
|
|
6090
|
+
};
|
|
6091
|
+
|
|
6092
|
+
// identifies n-segment spikes
|
|
6093
|
+
this.arcIsSpike = function(arcId) {
|
|
6094
|
+
arcId = absArcId(arcId);
|
|
6095
|
+
var i = _ii[arcId];
|
|
6096
|
+
var j = i + _nn[arcId] - 1;
|
|
6097
|
+
while (j > i) {
|
|
6098
|
+
if (_xx[i] != _xx[j] || _yy[i] != _yy[j]) {
|
|
6099
|
+
return false;
|
|
6100
|
+
}
|
|
6101
|
+
j--;
|
|
6102
|
+
i++;
|
|
6103
|
+
}
|
|
6104
|
+
return true;
|
|
6105
|
+
};
|
|
6106
|
+
|
|
6107
|
+
|
|
6108
|
+
this.arcHasZeroLength = function(arcId) {
|
|
5894
6109
|
var iter = this.getArcIter(arcId);
|
|
5895
6110
|
var i = 0,
|
|
5896
6111
|
x, y;
|
|
@@ -6397,10 +6612,10 @@
|
|
|
6397
6612
|
// Visit each point in the path, up to but not including the last point
|
|
6398
6613
|
for (var i = start; i < end; i++) {
|
|
6399
6614
|
if (pointIsArcEndpoint(i)) {
|
|
6400
|
-
if (firstNodeId
|
|
6401
|
-
arcIds.push(addEdge(arcStartId, i));
|
|
6402
|
-
} else {
|
|
6615
|
+
if (firstNodeId == -1) {
|
|
6403
6616
|
firstNodeId = i;
|
|
6617
|
+
} else {
|
|
6618
|
+
arcIds.push(addEdge(arcStartId, i));
|
|
6404
6619
|
}
|
|
6405
6620
|
arcStartId = i;
|
|
6406
6621
|
}
|
|
@@ -6991,7 +7206,7 @@
|
|
|
6991
7206
|
yy2 = new Float64Array(n),
|
|
6992
7207
|
ids2 = new Int32Array(n);
|
|
6993
7208
|
|
|
6994
|
-
arcs.forEach2(function(i, n, xx, yy, zz
|
|
7209
|
+
arcs.forEach2(function(arcId, i, n, xx, yy, zz) {
|
|
6995
7210
|
var start = i,
|
|
6996
7211
|
end = i + n - 1,
|
|
6997
7212
|
start2 = arcId * 2,
|
|
@@ -7042,7 +7257,7 @@
|
|
|
7042
7257
|
layers.forEach(function(lyr) {
|
|
7043
7258
|
// modify copies of the original shapes; original shapes should be unmodified
|
|
7044
7259
|
// (need to test this)
|
|
7045
|
-
lyr.shapes = lyr.shapes.map(function(shape) {
|
|
7260
|
+
lyr.shapes = lyr.shapes.map(function(shape, i) {
|
|
7046
7261
|
return editShapeParts(shape && shape.concat(), translatePath);
|
|
7047
7262
|
});
|
|
7048
7263
|
});
|
|
@@ -12584,11 +12799,178 @@
|
|
|
12584
12799
|
stringifyAsNDJSON: stringifyAsNDJSON
|
|
12585
12800
|
});
|
|
12586
12801
|
|
|
12802
|
+
function findNearestVertices(p, shp, arcs) {
|
|
12803
|
+
var p2 = findNearestVertex(p[0], p[1], shp, arcs);
|
|
12804
|
+
return findVertexIds(p2.x, p2.y, arcs);
|
|
12805
|
+
}
|
|
12806
|
+
|
|
12807
|
+
function snapVerticesToPoint(ids, p, arcs) {
|
|
12808
|
+
var data = arcs.getVertexData();
|
|
12809
|
+
ids.forEach(function(idx) {
|
|
12810
|
+
setVertexCoords(p[0], p[1], idx, arcs);
|
|
12811
|
+
arcs.updateArcBounds(findArcIdFromVertexId(idx, data.ii));
|
|
12812
|
+
});
|
|
12813
|
+
}
|
|
12814
|
+
|
|
12815
|
+
|
|
12816
|
+
// p: point to snap
|
|
12817
|
+
// ids: ids of nearby vertices, possibly including an arc endpoint
|
|
12818
|
+
function snapPointToArcEndpoint(p, ids, arcs) {
|
|
12819
|
+
var p2, dx, dy;
|
|
12820
|
+
ids.forEach(function(idx) {
|
|
12821
|
+
if (vertexIsArcStart(idx, arcs)) {
|
|
12822
|
+
p2 = getVertexCoords(idx + 1, arcs);
|
|
12823
|
+
} else if (vertexIsArcEnd(idx, arcs)) {
|
|
12824
|
+
p2 = getVertexCoords(idx - 1, arcs);
|
|
12825
|
+
}
|
|
12826
|
+
});
|
|
12827
|
+
if (!p2) return;
|
|
12828
|
+
dx = p2[0] - p[0];
|
|
12829
|
+
dy = p2[1] - p[1];
|
|
12830
|
+
if (Math.abs(dx) > Math.abs(dy)) {
|
|
12831
|
+
p[1] = p2[1]; // snap y coord
|
|
12832
|
+
} else {
|
|
12833
|
+
p[0] = p2[0];
|
|
12834
|
+
}
|
|
12835
|
+
}
|
|
12836
|
+
|
|
12837
|
+
// Find ids of vertices with identical coordinates to x,y in an ArcCollection
|
|
12838
|
+
// Caveat: does not exclude vertices that are not visible at the
|
|
12839
|
+
// current level of simplification.
|
|
12840
|
+
function findVertexIds(x, y, arcs) {
|
|
12841
|
+
var data = arcs.getVertexData(),
|
|
12842
|
+
xx = data.xx,
|
|
12843
|
+
yy = data.yy,
|
|
12844
|
+
ids = [];
|
|
12845
|
+
for (var i=0, n=xx.length; i<n; i++) {
|
|
12846
|
+
if (xx[i] == x && yy[i] == y) ids.push(i);
|
|
12847
|
+
}
|
|
12848
|
+
return ids;
|
|
12849
|
+
}
|
|
12850
|
+
|
|
12851
|
+
function getVertexCoords(i, arcs) {
|
|
12852
|
+
var data = arcs.getVertexData();
|
|
12853
|
+
return [data.xx[i], data.yy[i]];
|
|
12854
|
+
}
|
|
12855
|
+
|
|
12856
|
+
function vertexIsArcEnd(idx, arcs) {
|
|
12857
|
+
// Test whether the vertex at index @idx is the endpoint of an arc
|
|
12858
|
+
var data = arcs.getVertexData(),
|
|
12859
|
+
ii = data.ii,
|
|
12860
|
+
nn = data.nn;
|
|
12861
|
+
for (var j=0, n=ii.length; j<n; j++) {
|
|
12862
|
+
if (idx === ii[j] + nn[j] - 1) return true;
|
|
12863
|
+
}
|
|
12864
|
+
return false;
|
|
12865
|
+
}
|
|
12866
|
+
|
|
12867
|
+
function vertexIsArcEndpoint(idx, arcs) {
|
|
12868
|
+
return vertexIsArcStart(idx, arcs) || vertexIsArcEnd(idx, arcs);
|
|
12869
|
+
}
|
|
12870
|
+
|
|
12871
|
+
function vertexIsArcStart(idx, arcs) {
|
|
12872
|
+
var ii = arcs.getVertexData().ii;
|
|
12873
|
+
for (var j=0, n=ii.length; j<n; j++) {
|
|
12874
|
+
if (idx === ii[j]) return true;
|
|
12875
|
+
}
|
|
12876
|
+
return false;
|
|
12877
|
+
}
|
|
12878
|
+
|
|
12879
|
+
function getArcStartCoords(arcId, arcs) {
|
|
12880
|
+
var coords = getArcEndpointCoords(arcId, arcs);
|
|
12881
|
+
return coords[0];
|
|
12882
|
+
}
|
|
12883
|
+
|
|
12884
|
+
function getArcEndCoords(arcId, arcs) {
|
|
12885
|
+
var coords = getArcEndpointCoords(arcId, arcs);
|
|
12886
|
+
return coords[1];
|
|
12887
|
+
}
|
|
12888
|
+
|
|
12889
|
+
function getArcEndpointCoords(arcId, arcs) {
|
|
12890
|
+
if (arcId < 0) {
|
|
12891
|
+
return getArcEndpointCoords(~arcId, arcs).reverse();
|
|
12892
|
+
}
|
|
12893
|
+
var data = arcs.getVertexData();
|
|
12894
|
+
var i = data.ii[arcId];
|
|
12895
|
+
var n = data.nn[arcId];
|
|
12896
|
+
var a = [data.xx[i], data.yy[i]];
|
|
12897
|
+
var b = [data.xx[i + n - 1], data.yy[i + n - 1]];
|
|
12898
|
+
return [a, b];
|
|
12899
|
+
}
|
|
12900
|
+
|
|
12901
|
+
function setVertexCoords(x, y, i, arcs) {
|
|
12902
|
+
var data = arcs.getVertexData();
|
|
12903
|
+
data.xx[i] = x;
|
|
12904
|
+
data.yy[i] = y;
|
|
12905
|
+
}
|
|
12906
|
+
|
|
12907
|
+
function findNearestVertex(x, y, shp, arcs, spherical) {
|
|
12908
|
+
var calcLen = spherical ? geom.greatCircleDistance : geom.distance2D,
|
|
12909
|
+
minLen = Infinity,
|
|
12910
|
+
minX, minY, dist, iter;
|
|
12911
|
+
for (var i=0; i<shp.length; i++) {
|
|
12912
|
+
iter = arcs.getShapeIter(shp[i]);
|
|
12913
|
+
while (iter.hasNext()) {
|
|
12914
|
+
dist = calcLen(x, y, iter.x, iter.y);
|
|
12915
|
+
if (dist < minLen) {
|
|
12916
|
+
minLen = dist;
|
|
12917
|
+
minX = iter.x;
|
|
12918
|
+
minY = iter.y;
|
|
12919
|
+
}
|
|
12920
|
+
}
|
|
12921
|
+
}
|
|
12922
|
+
return minLen < Infinity ? {x: minX, y: minY} : null;
|
|
12923
|
+
}
|
|
12924
|
+
|
|
12925
|
+
var VertexUtils = /*#__PURE__*/Object.freeze({
|
|
12926
|
+
__proto__: null,
|
|
12927
|
+
findNearestVertex: findNearestVertex,
|
|
12928
|
+
findNearestVertices: findNearestVertices,
|
|
12929
|
+
findVertexIds: findVertexIds,
|
|
12930
|
+
getArcEndCoords: getArcEndCoords,
|
|
12931
|
+
getArcEndpointCoords: getArcEndpointCoords,
|
|
12932
|
+
getArcStartCoords: getArcStartCoords,
|
|
12933
|
+
getVertexCoords: getVertexCoords,
|
|
12934
|
+
setVertexCoords: setVertexCoords,
|
|
12935
|
+
snapPointToArcEndpoint: snapPointToArcEndpoint,
|
|
12936
|
+
snapVerticesToPoint: snapVerticesToPoint,
|
|
12937
|
+
vertexIsArcEnd: vertexIsArcEnd,
|
|
12938
|
+
vertexIsArcEndpoint: vertexIsArcEndpoint,
|
|
12939
|
+
vertexIsArcStart: vertexIsArcStart
|
|
12940
|
+
});
|
|
12941
|
+
|
|
12942
|
+
function debugConnectedArcs(ids, arcs) {
|
|
12943
|
+
var colors = ['orange', 'blue', 'green', 'red', 'magenta', 'grey'];
|
|
12944
|
+
var features = ids.map(function(arcId, i) {
|
|
12945
|
+
return getArcFeature(arcId, arcs, {arcId: arcId, stroke: colors[i] || 'black'});
|
|
12946
|
+
});
|
|
12947
|
+
var geojson = '{"type": "FeatureCollection", "features": [' + features.join(',') + ']}';
|
|
12948
|
+
debug(geojson);
|
|
12949
|
+
}
|
|
12950
|
+
|
|
12951
|
+
function getArcFeature(arcId, arcs, properties) {
|
|
12952
|
+
return JSON.stringify({
|
|
12953
|
+
type: "Feature",
|
|
12954
|
+
properties: properties,
|
|
12955
|
+
geometry: GeoJSON.exportLineGeom([[arcId]], arcs)
|
|
12956
|
+
});
|
|
12957
|
+
}
|
|
12958
|
+
|
|
12587
12959
|
function isValidArc(arcId, arcs) {
|
|
12588
12960
|
// check for arcs with no vertices
|
|
12589
12961
|
// TODO: also check for other kinds of degenerate arcs
|
|
12590
12962
|
// (e.g. collapsed arcs consisting of identical points)
|
|
12591
|
-
|
|
12963
|
+
var len = arcs.getArcLength(arcId);
|
|
12964
|
+
if (len >= 2 === false) {
|
|
12965
|
+
return false;
|
|
12966
|
+
}
|
|
12967
|
+
// if (len <=3) {
|
|
12968
|
+
// var endpoints = getArcEndpointCoords(arcId, arcs);
|
|
12969
|
+
// if (pointsAreEqual(endpoints[0], endpoints[1])) {
|
|
12970
|
+
// return false;
|
|
12971
|
+
// }
|
|
12972
|
+
// }
|
|
12973
|
+
return true;
|
|
12592
12974
|
}
|
|
12593
12975
|
|
|
12594
12976
|
// Return id of rightmost connected arc in relation to @fromArcId
|
|
@@ -12620,11 +13002,11 @@
|
|
|
12620
13002
|
error("Duplicate point error");
|
|
12621
13003
|
}*/
|
|
12622
13004
|
|
|
12623
|
-
|
|
12624
13005
|
for (j=0; j<ids.length; j++) {
|
|
12625
13006
|
candId = ids[j];
|
|
12626
13007
|
if (!isValidArc(candId, arcs)) {
|
|
12627
13008
|
// skip empty arcs
|
|
13009
|
+
debug('skipping one arc:', candId, 'out of:', ids.length, ids);
|
|
12628
13010
|
continue;
|
|
12629
13011
|
}
|
|
12630
13012
|
icand = arcs.indexOfVertex(candId, -2);
|
|
@@ -12635,6 +13017,14 @@
|
|
|
12635
13017
|
continue;
|
|
12636
13018
|
}
|
|
12637
13019
|
code = chooseRighthandPath(fromX, fromY, nodeX, nodeY, xx[ito], yy[ito], xx[icand], yy[icand]);
|
|
13020
|
+
|
|
13021
|
+
if (xx[ito] == xx[icand] && yy[ito] == yy[icand]) {
|
|
13022
|
+
debug("Pathfinder warning: duplicate segments: i:", ito, "j:", icand, "ids:", [fromArcId].concat(ids));
|
|
13023
|
+
if (useDebug()) {
|
|
13024
|
+
// debugDuplicatePathfinderSegments(nodeX, nodeY, ito, icand, nodes.arcs);
|
|
13025
|
+
debugConnectedArcs(ids, nodes.arcs);
|
|
13026
|
+
}
|
|
13027
|
+
}
|
|
12638
13028
|
if (code == 2) {
|
|
12639
13029
|
ito = icand;
|
|
12640
13030
|
toArcId = candId;
|
|
@@ -12644,26 +13034,22 @@
|
|
|
12644
13034
|
|
|
12645
13035
|
if (toArcId == fromArcId) {
|
|
12646
13036
|
// This shouldn't occur, assuming that other arcs are present
|
|
12647
|
-
error("Pathfinder error");
|
|
13037
|
+
error("Pathfinder error", toArcId, fromArcId);
|
|
12648
13038
|
}
|
|
12649
13039
|
return toArcId;
|
|
12650
13040
|
}
|
|
12651
13041
|
|
|
12652
|
-
// TODO: consider using simpler internal.chooseRighthandPath2()
|
|
12653
13042
|
// Returns 1 if node->a, return 2 if node->b, else return 0
|
|
12654
13043
|
// TODO: better handling of identical angles (better -- avoid creating them)
|
|
12655
13044
|
function chooseRighthandPath(fromX, fromY, nodeX, nodeY, ax, ay, bx, by) {
|
|
12656
13045
|
var angleA = geom.signedAngle(fromX, fromY, nodeX, nodeY, ax, ay);
|
|
12657
13046
|
var angleB = geom.signedAngle(fromX, fromY, nodeX, nodeY, bx, by);
|
|
13047
|
+
// should use arbitrary precision math to evaluate angles smaller than this
|
|
13048
|
+
// (all observed errors caused by fp rounding occured with smaller angles than this)
|
|
13049
|
+
var smallAngle = 0.001;
|
|
12658
13050
|
var code;
|
|
12659
13051
|
if (angleA <= 0 || angleB <= 0) {
|
|
12660
13052
|
debug("[chooseRighthandPath()] 0 angle(s):", angleA, angleB);
|
|
12661
|
-
if (angleA <= 0) {
|
|
12662
|
-
debug(' A orient2D:', geom.orient2D(fromX, fromY, nodeX, nodeY, ax, ay));
|
|
12663
|
-
}
|
|
12664
|
-
if (angleB <= 0) {
|
|
12665
|
-
debug(' B orient2D:', geom.orient2D(fromX, fromY, nodeX, nodeY, bx, by));
|
|
12666
|
-
}
|
|
12667
13053
|
// TODO: test against "from" segment
|
|
12668
13054
|
if (angleA > 0) {
|
|
12669
13055
|
code = 1;
|
|
@@ -12672,23 +13058,23 @@
|
|
|
12672
13058
|
} else {
|
|
12673
13059
|
code = 0;
|
|
12674
13060
|
}
|
|
12675
|
-
} else if (angleA < angleB) {
|
|
13061
|
+
} else if (angleA < angleB - smallAngle) {
|
|
12676
13062
|
code = 1;
|
|
12677
|
-
} else if (angleB < angleA) {
|
|
13063
|
+
} else if (angleB < angleA - smallAngle) {
|
|
12678
13064
|
code = 2;
|
|
12679
13065
|
} else if (isNaN(angleA) || isNaN(angleB)) {
|
|
12680
13066
|
// probably a duplicate point, which should not occur
|
|
12681
13067
|
error('Invalid node geometry');
|
|
12682
13068
|
} else {
|
|
12683
|
-
//
|
|
12684
|
-
code =
|
|
12685
|
-
debug('[chooseRighthandPath()] equal angles:', angleA, '
|
|
13069
|
+
// Close-to-equal or equal angles: use more exact test.
|
|
13070
|
+
code = chooseBetweenClosePaths(nodeX, nodeY, ax, ay, bx, by);
|
|
13071
|
+
// debug('[chooseRighthandPath()] close-to-equal angles:', Math.abs(angleA) - Math.abs(angleB), 'hi-res code:', code);
|
|
12686
13072
|
}
|
|
12687
13073
|
return code;
|
|
12688
13074
|
}
|
|
12689
13075
|
|
|
12690
|
-
function
|
|
12691
|
-
var orient =
|
|
13076
|
+
function chooseBetweenClosePaths(nodeX, nodeY, ax, ay, bx, by) {
|
|
13077
|
+
var orient = orient2D_robust(ax, ay, nodeX, nodeY, bx, by);
|
|
12692
13078
|
var code;
|
|
12693
13079
|
if (orient > 0) {
|
|
12694
13080
|
code = 2;
|
|
@@ -12702,7 +13088,7 @@
|
|
|
12702
13088
|
|
|
12703
13089
|
var PathfinderUtils = /*#__PURE__*/Object.freeze({
|
|
12704
13090
|
__proto__: null,
|
|
12705
|
-
|
|
13091
|
+
chooseBetweenClosePaths: chooseBetweenClosePaths,
|
|
12706
13092
|
getRightmostArc: getRightmostArc
|
|
12707
13093
|
});
|
|
12708
13094
|
|
|
@@ -13227,7 +13613,7 @@
|
|
|
13227
13613
|
}
|
|
13228
13614
|
|
|
13229
13615
|
// test two candidate segments for intersection
|
|
13230
|
-
hit =
|
|
13616
|
+
hit = segmentIntersection(s1p1x, s1p1y, s1p2x, s1p2y,
|
|
13231
13617
|
s2p1x, s2p1y, s2p2x, s2p2y, tolerance);
|
|
13232
13618
|
if (hit) {
|
|
13233
13619
|
seg1 = [s1p1, s1p2];
|
|
@@ -13285,146 +13671,6 @@
|
|
|
13285
13671
|
sortIntersections: sortIntersections
|
|
13286
13672
|
});
|
|
13287
13673
|
|
|
13288
|
-
function findNearestVertices(p, shp, arcs) {
|
|
13289
|
-
var p2 = findNearestVertex(p[0], p[1], shp, arcs);
|
|
13290
|
-
return findVertexIds(p2.x, p2.y, arcs);
|
|
13291
|
-
}
|
|
13292
|
-
|
|
13293
|
-
function snapVerticesToPoint(ids, p, arcs) {
|
|
13294
|
-
var data = arcs.getVertexData();
|
|
13295
|
-
ids.forEach(function(idx) {
|
|
13296
|
-
setVertexCoords(p[0], p[1], idx, arcs);
|
|
13297
|
-
arcs.updateArcBounds(findArcIdFromVertexId(idx, data.ii));
|
|
13298
|
-
});
|
|
13299
|
-
}
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
// p: point to snap
|
|
13303
|
-
// ids: ids of nearby vertices, possibly including an arc endpoint
|
|
13304
|
-
function snapPointToArcEndpoint(p, ids, arcs) {
|
|
13305
|
-
var p2, dx, dy;
|
|
13306
|
-
ids.forEach(function(idx) {
|
|
13307
|
-
if (vertexIsArcStart(idx, arcs)) {
|
|
13308
|
-
p2 = getVertexCoords(idx + 1, arcs);
|
|
13309
|
-
} else if (vertexIsArcEnd(idx, arcs)) {
|
|
13310
|
-
p2 = getVertexCoords(idx - 1, arcs);
|
|
13311
|
-
}
|
|
13312
|
-
});
|
|
13313
|
-
if (!p2) return;
|
|
13314
|
-
dx = p2[0] - p[0];
|
|
13315
|
-
dy = p2[1] - p[1];
|
|
13316
|
-
if (Math.abs(dx) > Math.abs(dy)) {
|
|
13317
|
-
p[1] = p2[1]; // snap y coord
|
|
13318
|
-
} else {
|
|
13319
|
-
p[0] = p2[0];
|
|
13320
|
-
}
|
|
13321
|
-
}
|
|
13322
|
-
|
|
13323
|
-
// Find ids of vertices with identical coordinates to x,y in an ArcCollection
|
|
13324
|
-
// Caveat: does not exclude vertices that are not visible at the
|
|
13325
|
-
// current level of simplification.
|
|
13326
|
-
function findVertexIds(x, y, arcs) {
|
|
13327
|
-
var data = arcs.getVertexData(),
|
|
13328
|
-
xx = data.xx,
|
|
13329
|
-
yy = data.yy,
|
|
13330
|
-
ids = [];
|
|
13331
|
-
for (var i=0, n=xx.length; i<n; i++) {
|
|
13332
|
-
if (xx[i] == x && yy[i] == y) ids.push(i);
|
|
13333
|
-
}
|
|
13334
|
-
return ids;
|
|
13335
|
-
}
|
|
13336
|
-
|
|
13337
|
-
function getVertexCoords(i, arcs) {
|
|
13338
|
-
var data = arcs.getVertexData();
|
|
13339
|
-
return [data.xx[i], data.yy[i]];
|
|
13340
|
-
}
|
|
13341
|
-
|
|
13342
|
-
function vertexIsArcEnd(idx, arcs) {
|
|
13343
|
-
// Test whether the vertex at index @idx is the endpoint of an arc
|
|
13344
|
-
var data = arcs.getVertexData(),
|
|
13345
|
-
ii = data.ii,
|
|
13346
|
-
nn = data.nn;
|
|
13347
|
-
for (var j=0, n=ii.length; j<n; j++) {
|
|
13348
|
-
if (idx === ii[j] + nn[j] - 1) return true;
|
|
13349
|
-
}
|
|
13350
|
-
return false;
|
|
13351
|
-
}
|
|
13352
|
-
|
|
13353
|
-
function vertexIsArcEndpoint(idx, arcs) {
|
|
13354
|
-
return vertexIsArcStart(idx, arcs) || vertexIsArcEnd(idx, arcs);
|
|
13355
|
-
}
|
|
13356
|
-
|
|
13357
|
-
function vertexIsArcStart(idx, arcs) {
|
|
13358
|
-
var ii = arcs.getVertexData().ii;
|
|
13359
|
-
for (var j=0, n=ii.length; j<n; j++) {
|
|
13360
|
-
if (idx === ii[j]) return true;
|
|
13361
|
-
}
|
|
13362
|
-
return false;
|
|
13363
|
-
}
|
|
13364
|
-
|
|
13365
|
-
function getArcStartCoords(arcId, arcs) {
|
|
13366
|
-
var coords = getArcEndpointCoords(arcId, arcs);
|
|
13367
|
-
return coords[0];
|
|
13368
|
-
}
|
|
13369
|
-
|
|
13370
|
-
function getArcEndCoords(arcId, arcs) {
|
|
13371
|
-
var coords = getArcEndpointCoords(arcId, arcs);
|
|
13372
|
-
return coords[1];
|
|
13373
|
-
}
|
|
13374
|
-
|
|
13375
|
-
function getArcEndpointCoords(arcId, arcs) {
|
|
13376
|
-
if (arcId < 0) {
|
|
13377
|
-
return getArcEndpointCoords(~arcId, arcs).reverse();
|
|
13378
|
-
}
|
|
13379
|
-
var data = arcs.getVertexData();
|
|
13380
|
-
var i = data.ii[arcId];
|
|
13381
|
-
var n = data.nn[arcId];
|
|
13382
|
-
var a = [data.xx[i], data.yy[i]];
|
|
13383
|
-
var b = [data.xx[i + n - 1], data.yy[i + n - 1]];
|
|
13384
|
-
return [a, b];
|
|
13385
|
-
}
|
|
13386
|
-
|
|
13387
|
-
function setVertexCoords(x, y, i, arcs) {
|
|
13388
|
-
var data = arcs.getVertexData();
|
|
13389
|
-
data.xx[i] = x;
|
|
13390
|
-
data.yy[i] = y;
|
|
13391
|
-
}
|
|
13392
|
-
|
|
13393
|
-
function findNearestVertex(x, y, shp, arcs, spherical) {
|
|
13394
|
-
var calcLen = spherical ? geom.greatCircleDistance : geom.distance2D,
|
|
13395
|
-
minLen = Infinity,
|
|
13396
|
-
minX, minY, dist, iter;
|
|
13397
|
-
for (var i=0; i<shp.length; i++) {
|
|
13398
|
-
iter = arcs.getShapeIter(shp[i]);
|
|
13399
|
-
while (iter.hasNext()) {
|
|
13400
|
-
dist = calcLen(x, y, iter.x, iter.y);
|
|
13401
|
-
if (dist < minLen) {
|
|
13402
|
-
minLen = dist;
|
|
13403
|
-
minX = iter.x;
|
|
13404
|
-
minY = iter.y;
|
|
13405
|
-
}
|
|
13406
|
-
}
|
|
13407
|
-
}
|
|
13408
|
-
return minLen < Infinity ? {x: minX, y: minY} : null;
|
|
13409
|
-
}
|
|
13410
|
-
|
|
13411
|
-
var VertexUtils = /*#__PURE__*/Object.freeze({
|
|
13412
|
-
__proto__: null,
|
|
13413
|
-
findNearestVertex: findNearestVertex,
|
|
13414
|
-
findNearestVertices: findNearestVertices,
|
|
13415
|
-
findVertexIds: findVertexIds,
|
|
13416
|
-
getArcEndCoords: getArcEndCoords,
|
|
13417
|
-
getArcEndpointCoords: getArcEndpointCoords,
|
|
13418
|
-
getArcStartCoords: getArcStartCoords,
|
|
13419
|
-
getVertexCoords: getVertexCoords,
|
|
13420
|
-
setVertexCoords: setVertexCoords,
|
|
13421
|
-
snapPointToArcEndpoint: snapPointToArcEndpoint,
|
|
13422
|
-
snapVerticesToPoint: snapVerticesToPoint,
|
|
13423
|
-
vertexIsArcEnd: vertexIsArcEnd,
|
|
13424
|
-
vertexIsArcEndpoint: vertexIsArcEndpoint,
|
|
13425
|
-
vertexIsArcStart: vertexIsArcStart
|
|
13426
|
-
});
|
|
13427
|
-
|
|
13428
13674
|
// arcs: ArcCollection containing original coordinates
|
|
13429
13675
|
function getRepairFunction(arcs) {
|
|
13430
13676
|
var arcsOrig = arcs.getCopy();
|
|
@@ -13856,6 +14102,9 @@
|
|
|
13856
14102
|
getSliverTest(dataset.arcs, threshold, sliverControl) :
|
|
13857
14103
|
getMinAreaTest(threshold, dataset);
|
|
13858
14104
|
var label = getSliverLabel(getAreaLabel(threshold, crs), sliverControl > 0);
|
|
14105
|
+
if (opts.keep_shapes) {
|
|
14106
|
+
filter = keepShapes(filter);
|
|
14107
|
+
}
|
|
13859
14108
|
return {
|
|
13860
14109
|
threshold: threshold,
|
|
13861
14110
|
filter: filter,
|
|
@@ -13863,6 +14112,23 @@
|
|
|
13863
14112
|
};
|
|
13864
14113
|
}
|
|
13865
14114
|
|
|
14115
|
+
// wrap path filter in a function that ensures at least one part of every shape
|
|
14116
|
+
// is retained.
|
|
14117
|
+
function keepShapes(filter) {
|
|
14118
|
+
var flags;
|
|
14119
|
+
return function(path, pathId, paths) {
|
|
14120
|
+
// console.log("keepShapes()", path, pathId, paths)
|
|
14121
|
+
if (pathId === 0) {
|
|
14122
|
+
flags = paths.map(path => filter(path));
|
|
14123
|
+
}
|
|
14124
|
+
if (flags.length > 0 && flags.every(Boolean)) {
|
|
14125
|
+
// kludge ... assumes that the first path is a valid ring (e.g. not a)
|
|
14126
|
+
flags[0] = false;
|
|
14127
|
+
}
|
|
14128
|
+
return flags[pathId];
|
|
14129
|
+
};
|
|
14130
|
+
}
|
|
14131
|
+
|
|
13866
14132
|
function getSliverLabel(areaStr, variable) {
|
|
13867
14133
|
if (variable) {
|
|
13868
14134
|
areaStr = areaStr.replace(' ', '+ ') + ' variable';
|
|
@@ -16508,6 +16774,7 @@
|
|
|
16508
16774
|
|
|
16509
16775
|
function cleanPath(path, arcs) {
|
|
16510
16776
|
var nulls = 0;
|
|
16777
|
+
// console.log("[cleanPath()]", path, path?.[0])
|
|
16511
16778
|
for (var i=0, n=path.length; i<n; i++) {
|
|
16512
16779
|
if (arcs.arcIsDegenerate(path[i])) {
|
|
16513
16780
|
nulls++;
|
|
@@ -16796,54 +17063,45 @@
|
|
|
16796
17063
|
if (changed || opts.rebuild_topology) {
|
|
16797
17064
|
buildTopology(dataset);
|
|
16798
17065
|
}
|
|
16799
|
-
|
|
16800
|
-
//
|
|
16801
|
-
// TODO: consider alternative -- avoid creating degenerate arcs
|
|
16802
|
-
// in insertCutPoints()
|
|
17066
|
+
// Remove degenerate shapes
|
|
17067
|
+
// Without this step, pathfinder function would encounter dead ends.
|
|
16803
17068
|
dataset.layers.forEach(function(lyr) {
|
|
16804
17069
|
if (layerHasPaths(lyr)) {
|
|
16805
17070
|
cleanShapes(lyr.shapes, arcs, lyr.geometry_type);
|
|
16806
17071
|
}
|
|
16807
17072
|
});
|
|
16808
|
-
|
|
16809
|
-
// Further clean-up -- remove duplicate and missing arcs
|
|
17073
|
+
// Further clean-up -- remove duplicate and unused arcs, etc.
|
|
16810
17074
|
nodes = cleanArcReferences(dataset);
|
|
16811
17075
|
return nodes;
|
|
16812
17076
|
}
|
|
16813
17077
|
|
|
16814
17078
|
function snapAndCut(dataset, snapDist) {
|
|
16815
17079
|
var arcs = dataset.arcs;
|
|
16816
|
-
var cutOpts = snapDist > 0 ? {} : {tolerance: 0};
|
|
17080
|
+
var cutOpts = snapDist > 0 ? {tolerance: snapDist} : {tolerance: 0};
|
|
16817
17081
|
var coordsHaveChanged = false;
|
|
16818
17082
|
var snapCount, dupeCount, cutCount;
|
|
16819
|
-
|
|
16820
|
-
dupeCount = arcs.dedupCoords();
|
|
16821
|
-
|
|
16822
|
-
// why was topology built here previously????
|
|
16823
|
-
// if (snapCount > 0 || dupeCount > 0) {
|
|
16824
|
-
// // Detect topology again if coordinates have changed
|
|
16825
|
-
// internal.buildTopology(dataset);
|
|
16826
|
-
// }
|
|
17083
|
+
var maxLoops = 4, loopCount = 0;
|
|
16827
17084
|
|
|
16828
|
-
// cut
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
coordsHaveChanged = true;
|
|
16832
|
-
}
|
|
16833
|
-
|
|
16834
|
-
// perform a second snap + cut pass if needed
|
|
16835
|
-
if (cutCount > 0) {
|
|
17085
|
+
// snap + cut until no more cuts are needed
|
|
17086
|
+
do {
|
|
17087
|
+
loopCount++;
|
|
16836
17088
|
cutCount = 0;
|
|
16837
17089
|
snapCount = snapCoordsByInterval(arcs, snapDist);
|
|
16838
|
-
arcs.dedupCoords();
|
|
16839
|
-
if (snapCount > 0) {
|
|
17090
|
+
dupeCount = arcs.dedupCoords();
|
|
17091
|
+
if (snapCount > 0 || cutCount > 0 || loopCount == 1) {
|
|
17092
|
+
// cut arcs at points where segments intersect
|
|
16840
17093
|
cutCount = cutPathsAtIntersections(dataset, cutOpts);
|
|
16841
17094
|
}
|
|
16842
|
-
|
|
16843
|
-
|
|
16844
|
-
|
|
16845
|
-
|
|
16846
|
-
|
|
17095
|
+
coordsHaveChanged |= (snapCount + dupeCount + cutCount) > 0;
|
|
17096
|
+
debug("[snapAndCut] pass:", loopCount, "snaps:", snapCount, "dupes:", dupeCount, "cuts:", cutCount);
|
|
17097
|
+
} while (loopCount < maxLoops && cutCount > 0);
|
|
17098
|
+
|
|
17099
|
+
// should this be added?
|
|
17100
|
+
// if (cutCount > 0 && loopCount == maxLoops) {
|
|
17101
|
+
// snapCount = snapCoordsByInterval(arcs, snapDist);
|
|
17102
|
+
// arcs.dedupCoords(); // need to do this here?
|
|
17103
|
+
// }
|
|
17104
|
+
|
|
16847
17105
|
return coordsHaveChanged;
|
|
16848
17106
|
}
|
|
16849
17107
|
|
|
@@ -17028,21 +17286,21 @@
|
|
|
17028
17286
|
function filterSortedCutPoints(points, arcs) {
|
|
17029
17287
|
var filtered = [],
|
|
17030
17288
|
pointId = 0;
|
|
17031
|
-
arcs.forEach2(function(i, n, xx, yy) {
|
|
17032
|
-
var j = i + n - 1,
|
|
17033
|
-
|
|
17034
|
-
|
|
17035
|
-
|
|
17036
|
-
|
|
17289
|
+
arcs.forEach2(function(arcId, i, n, xx, yy) {
|
|
17290
|
+
var j = i + n - 1, // idx of second endpoint
|
|
17291
|
+
x1 = xx[i],
|
|
17292
|
+
y1 = yy[i],
|
|
17293
|
+
x2 = xx[j],
|
|
17294
|
+
y2 = yy[j],
|
|
17037
17295
|
p, pp;
|
|
17038
17296
|
|
|
17039
17297
|
while (pointId < points.length && points[pointId].i <= j) {
|
|
17040
17298
|
p = points[pointId];
|
|
17299
|
+
pointId++;
|
|
17041
17300
|
pp = filtered[filtered.length - 1]; // previous point
|
|
17042
|
-
if (p.x ==
|
|
17301
|
+
if (p.x == x1 && p.y == y1 && p.i == i || p.x == x2 && p.y == y2 && p.i >= j - 1) ; else if (pp && pp.x == p.x && pp.y == p.y && pp.i == p.i) ; else {
|
|
17043
17302
|
filtered.push(p);
|
|
17044
17303
|
}
|
|
17045
|
-
pointId++;
|
|
17046
17304
|
}
|
|
17047
17305
|
});
|
|
17048
17306
|
return filtered;
|
|
@@ -18370,6 +18628,7 @@
|
|
|
18370
18628
|
return geom;
|
|
18371
18629
|
};
|
|
18372
18630
|
|
|
18631
|
+
// ids: shape data (array of array of arc ids)
|
|
18373
18632
|
GeoJSON.exportLineGeom = function(ids, arcs) {
|
|
18374
18633
|
var obj = exportPathData(ids, arcs, "polyline");
|
|
18375
18634
|
if (obj.pointCount === 0) return null;
|
|
@@ -18385,6 +18644,7 @@
|
|
|
18385
18644
|
};
|
|
18386
18645
|
};
|
|
18387
18646
|
|
|
18647
|
+
// ids: shape data (array of array of arc ids)
|
|
18388
18648
|
GeoJSON.exportPolygonGeom = function(ids, arcs, opts) {
|
|
18389
18649
|
var obj = exportPathData(ids, arcs, "polygon");
|
|
18390
18650
|
if (obj.pointCount === 0) return null;
|
|
@@ -18777,203 +19037,6 @@
|
|
|
18777
19037
|
catalog.getDatasets().push(o);
|
|
18778
19038
|
}
|
|
18779
19039
|
|
|
18780
|
-
// Apply rotation, scale and/or shift to some or all of the features in a dataset
|
|
18781
|
-
//
|
|
18782
|
-
cmd.affine = function(targetLayers, dataset, opts) {
|
|
18783
|
-
// Need to separate the targeted shapes from any other shapes that share
|
|
18784
|
-
// the same topology. So we duplicate any arcs that are shared by the targeted
|
|
18785
|
-
// shapes and their topological neighbors and remap arc references in the
|
|
18786
|
-
// neighbors to point to the copies.
|
|
18787
|
-
// TODO: explore alternative: if some arcs are shared between transformed and
|
|
18788
|
-
// non-transformed shapes, first remove topology, then tranform, then rebuild topology
|
|
18789
|
-
//
|
|
18790
|
-
var rotateArg = opts.rotate || 0;
|
|
18791
|
-
var scaleArg = opts.scale || 1;
|
|
18792
|
-
var shiftArg = opts.shift ? convertIntervalPair(opts.shift, getDatasetCRS(dataset)) : [0, 0];
|
|
18793
|
-
var arcs = dataset.arcs;
|
|
18794
|
-
var targetShapes = [];
|
|
18795
|
-
var otherShapes = [];
|
|
18796
|
-
var targetPoints = [];
|
|
18797
|
-
var targetFlags, otherFlags, transform;
|
|
18798
|
-
dataset.layers.filter(layerHasGeometry).forEach(function(lyr) {
|
|
18799
|
-
var hits = [],
|
|
18800
|
-
misses = [],
|
|
18801
|
-
test;
|
|
18802
|
-
if (targetLayers.indexOf(lyr) == -1) {
|
|
18803
|
-
misses = lyr.shapes;
|
|
18804
|
-
} else if (opts.where) {
|
|
18805
|
-
test = compileFeatureExpression(opts.where, lyr, dataset.arcs);
|
|
18806
|
-
lyr.shapes.forEach(function(shp, i) {
|
|
18807
|
-
(test(i) ? hits : misses).push(shp);
|
|
18808
|
-
});
|
|
18809
|
-
} else {
|
|
18810
|
-
hits = lyr.shapes;
|
|
18811
|
-
}
|
|
18812
|
-
if (lyr.geometry_type == 'point') {
|
|
18813
|
-
targetPoints = targetPoints.concat(hits);
|
|
18814
|
-
} else {
|
|
18815
|
-
targetShapes = targetShapes.concat(hits);
|
|
18816
|
-
otherShapes = otherShapes.concat(misses);
|
|
18817
|
-
}
|
|
18818
|
-
});
|
|
18819
|
-
var anchorArg = getAffineAnchor({arcs: dataset.arcs, layers: [{
|
|
18820
|
-
geometry_type: 'point', shapes: targetPoints}, {geometry_type: 'polyline',
|
|
18821
|
-
shapes: targetShapes}]}, opts);
|
|
18822
|
-
transform = getAffineTransform(rotateArg, scaleArg, shiftArg, anchorArg);
|
|
18823
|
-
if (opts.fit_bbox) {
|
|
18824
|
-
transform = getFitBoxTransform(opts.fit_bbox, targetPoints, targetShapes, arcs);
|
|
18825
|
-
}
|
|
18826
|
-
if (targetShapes.length > 0) {
|
|
18827
|
-
targetFlags = new Uint8Array(arcs.size());
|
|
18828
|
-
otherFlags = new Uint8Array(arcs.size());
|
|
18829
|
-
countArcsInShapes(targetShapes, targetFlags);
|
|
18830
|
-
if (otherShapes.length > 0) {
|
|
18831
|
-
countArcsInShapes(otherShapes, otherFlags);
|
|
18832
|
-
applyArrayMask(otherFlags, targetFlags);
|
|
18833
|
-
dataset.arcs = duplicateSelectedArcs(otherShapes, arcs, otherFlags);
|
|
18834
|
-
}
|
|
18835
|
-
dataset.arcs.transformPoints(function(x, y, arcId) {
|
|
18836
|
-
if (arcId < targetFlags.length && targetFlags[arcId] > 0) {
|
|
18837
|
-
return transform(x, y);
|
|
18838
|
-
}
|
|
18839
|
-
});
|
|
18840
|
-
}
|
|
18841
|
-
forEachPoint(targetPoints, function(p) {
|
|
18842
|
-
var p2 = transform(p[0], p[1]);
|
|
18843
|
-
p[0] = p2[0];
|
|
18844
|
-
p[1] = p2[1];
|
|
18845
|
-
});
|
|
18846
|
-
};
|
|
18847
|
-
|
|
18848
|
-
function getAffineAnchor(dataset, opts) {
|
|
18849
|
-
var anchor, bounds;
|
|
18850
|
-
if (opts.anchor) {
|
|
18851
|
-
anchor = opts.anchor;
|
|
18852
|
-
} else {
|
|
18853
|
-
// get bounds of selected shapes to calculate center of rotation/scale
|
|
18854
|
-
bounds = getDatasetBounds(dataset);
|
|
18855
|
-
anchor = [bounds.centerX(), bounds.centerY()];
|
|
18856
|
-
}
|
|
18857
|
-
return anchor;
|
|
18858
|
-
}
|
|
18859
|
-
|
|
18860
|
-
// TODO: handle problems with unprojected datasets
|
|
18861
|
-
// option 1: don't allow affine transformation of unprojected data
|
|
18862
|
-
// option 2: error if transformed data exceeds valid coordinate range
|
|
18863
|
-
// source: http://mathworld.wolfram.com/AffineTransformation.html
|
|
18864
|
-
function getAffineTransform(rotation, scale, shift, anchor) {
|
|
18865
|
-
var angle = rotation * Math.PI / 180;
|
|
18866
|
-
var a = scale * Math.cos(angle);
|
|
18867
|
-
var b = -scale * Math.sin(angle);
|
|
18868
|
-
return function(x, y) {
|
|
18869
|
-
var x2 = a * (x - anchor[0]) - b * (y - anchor[1]) + shift[0] + anchor[0];
|
|
18870
|
-
var y2 = b * (x - anchor[0]) + a * (y - anchor[1]) + shift[1] + anchor[1];
|
|
18871
|
-
return [x2, y2];
|
|
18872
|
-
};
|
|
18873
|
-
}
|
|
18874
|
-
|
|
18875
|
-
function getFitBoxTransform(bbox, points, shapes, arcs) {
|
|
18876
|
-
var dataset = {
|
|
18877
|
-
arcs: arcs,
|
|
18878
|
-
layers: []
|
|
18879
|
-
};
|
|
18880
|
-
if (points && points.length) {
|
|
18881
|
-
dataset.layers.push({
|
|
18882
|
-
geometry_type: 'point',
|
|
18883
|
-
shapes: points
|
|
18884
|
-
});
|
|
18885
|
-
}
|
|
18886
|
-
if (shapes && shapes.length) {
|
|
18887
|
-
dataset.layers.push({
|
|
18888
|
-
geometry_type: 'polyline',
|
|
18889
|
-
shapes: shapes
|
|
18890
|
-
});
|
|
18891
|
-
}
|
|
18892
|
-
var frame = calcFrameData(dataset, {fit_bbox: bbox});
|
|
18893
|
-
var fromBounds = new Bounds(frame.bbox);
|
|
18894
|
-
var toBounds = new Bounds(frame.bbox2);
|
|
18895
|
-
var fwd = fromBounds.getTransform(toBounds, false);
|
|
18896
|
-
return function(x, y) {
|
|
18897
|
-
return fwd.transform(x, y);
|
|
18898
|
-
};
|
|
18899
|
-
}
|
|
18900
|
-
|
|
18901
|
-
function applyArrayMask(destArr, maskArr) {
|
|
18902
|
-
for (var i=0, n=destArr.length; i<n; i++) {
|
|
18903
|
-
if (maskArr[i] === 0) destArr[i] = 0;
|
|
18904
|
-
}
|
|
18905
|
-
}
|
|
18906
|
-
|
|
18907
|
-
function duplicateSelectedArcs(shapes, arcs, flags) {
|
|
18908
|
-
var arcCount = 0;
|
|
18909
|
-
var vertexCount = 0;
|
|
18910
|
-
var data = arcs.getVertexData();
|
|
18911
|
-
var xx = [], yy = [], nn = [], map = [], n;
|
|
18912
|
-
for (var i=0, len=flags.length; i<len; i++) {
|
|
18913
|
-
if (flags[i] > 0) {
|
|
18914
|
-
map[i] = arcs.size() + arcCount;
|
|
18915
|
-
n = data.nn[i];
|
|
18916
|
-
utils.copyElements(data.xx, data.ii[i], xx, vertexCount, n);
|
|
18917
|
-
utils.copyElements(data.yy, data.ii[i], yy, vertexCount, n);
|
|
18918
|
-
nn.push(n);
|
|
18919
|
-
vertexCount += n;
|
|
18920
|
-
arcCount++;
|
|
18921
|
-
}
|
|
18922
|
-
}
|
|
18923
|
-
forEachArcId(shapes, function(id) {
|
|
18924
|
-
var absId = absArcId(id);
|
|
18925
|
-
if (flags[absId] > 0) {
|
|
18926
|
-
return id < 0 ? ~map[absId] : map[absId];
|
|
18927
|
-
}
|
|
18928
|
-
});
|
|
18929
|
-
return mergeArcs([arcs, new ArcCollection(nn, xx, yy)]);
|
|
18930
|
-
}
|
|
18931
|
-
|
|
18932
|
-
var roundCoord$2 = getRoundingFunction(0.01);
|
|
18933
|
-
|
|
18934
|
-
function stringifyVertex(p) {
|
|
18935
|
-
return ' ' + roundCoord$2(p[0]) + ' ' + roundCoord$2(p[1]);
|
|
18936
|
-
}
|
|
18937
|
-
|
|
18938
|
-
function isCubicCtrl(p) {
|
|
18939
|
-
return p.length > 2 && p[2] == 'C';
|
|
18940
|
-
}
|
|
18941
|
-
|
|
18942
|
-
function stringifyPolygonCoords(coords) {
|
|
18943
|
-
var parts = [];
|
|
18944
|
-
for (var i=0; i<coords.length; i++) {
|
|
18945
|
-
parts.push(stringifyLineStringCoords(coords[i]) + ' Z');
|
|
18946
|
-
}
|
|
18947
|
-
return parts.length > 0 ? parts.join(' ') : '';
|
|
18948
|
-
}
|
|
18949
|
-
|
|
18950
|
-
function stringifyLineStringCoords(coords) {
|
|
18951
|
-
if (coords.length === 0) return '';
|
|
18952
|
-
var d = 'M';
|
|
18953
|
-
var fromCurve = false;
|
|
18954
|
-
var p, i, n;
|
|
18955
|
-
for (i=0, n=coords.length; i<n; i++) {
|
|
18956
|
-
p = coords[i];
|
|
18957
|
-
if (isCubicCtrl(p)) {
|
|
18958
|
-
// TODO: add defensive check
|
|
18959
|
-
d += ' C' + stringifyVertex(p) + stringifyVertex(coords[++i]) + stringifyVertex(coords[++i]);
|
|
18960
|
-
fromCurve = true;
|
|
18961
|
-
} else if (fromCurve) {
|
|
18962
|
-
d += ' L' + stringifyVertex(p);
|
|
18963
|
-
fromCurve = false;
|
|
18964
|
-
} else {
|
|
18965
|
-
d += stringifyVertex(p);
|
|
18966
|
-
}
|
|
18967
|
-
}
|
|
18968
|
-
return d;
|
|
18969
|
-
}
|
|
18970
|
-
|
|
18971
|
-
var SvgPathUtils = /*#__PURE__*/Object.freeze({
|
|
18972
|
-
__proto__: null,
|
|
18973
|
-
stringifyLineStringCoords: stringifyLineStringCoords,
|
|
18974
|
-
stringifyPolygonCoords: stringifyPolygonCoords
|
|
18975
|
-
});
|
|
18976
|
-
|
|
18977
19040
|
/* example patterns
|
|
18978
19041
|
hatches 1px black 1px red 1px white
|
|
18979
19042
|
1px black 1px red 1px white // same as above (hatches is default)
|
|
@@ -19562,7 +19625,7 @@
|
|
|
19562
19625
|
}
|
|
19563
19626
|
|
|
19564
19627
|
function importLineString(coords) {
|
|
19565
|
-
var d = stringifyLineStringCoords(coords);
|
|
19628
|
+
var d = stringifyLineStringCoords$1(coords);
|
|
19566
19629
|
return {
|
|
19567
19630
|
tag: 'path',
|
|
19568
19631
|
properties: {d: d}
|
|
@@ -19570,7 +19633,7 @@
|
|
|
19570
19633
|
}
|
|
19571
19634
|
|
|
19572
19635
|
function importMultiLineString(coords) {
|
|
19573
|
-
var d = coords.map(stringifyLineStringCoords).join(' ');
|
|
19636
|
+
var d = coords.map(stringifyLineStringCoords$1).join(' ');
|
|
19574
19637
|
return {
|
|
19575
19638
|
tag: 'path',
|
|
19576
19639
|
properties: {d: d}
|
|
@@ -19592,7 +19655,7 @@
|
|
|
19592
19655
|
var o = {
|
|
19593
19656
|
tag: 'path',
|
|
19594
19657
|
properties: {
|
|
19595
|
-
d: stringifyPolygonCoords(coords)
|
|
19658
|
+
d: stringifyPolygonCoords$1(coords)
|
|
19596
19659
|
}
|
|
19597
19660
|
};
|
|
19598
19661
|
if (coords.length > 1) {
|
|
@@ -19601,6 +19664,23 @@
|
|
|
19601
19664
|
return o;
|
|
19602
19665
|
}
|
|
19603
19666
|
|
|
19667
|
+
function stringifyPolygonCoords$1(coords) {
|
|
19668
|
+
var parts = [];
|
|
19669
|
+
for (var i=0; i<coords.length; i++) {
|
|
19670
|
+
parts.push(stringifyLineStringCoords$1(coords[i]) + ' Z');
|
|
19671
|
+
}
|
|
19672
|
+
return parts.length > 0 ? parts.join(' ') : '';
|
|
19673
|
+
}
|
|
19674
|
+
|
|
19675
|
+
function stringifyLineStringCoords$1(coords) {
|
|
19676
|
+
if (coords.length === 0) return '';
|
|
19677
|
+
var d = 'M';
|
|
19678
|
+
for (var i=0, n=coords.length; i<n; i++) {
|
|
19679
|
+
d += ' ' + coords[i][0] + ' ' + coords[i][1];
|
|
19680
|
+
}
|
|
19681
|
+
return d;
|
|
19682
|
+
}
|
|
19683
|
+
|
|
19604
19684
|
var GeojsonToSvg = /*#__PURE__*/Object.freeze({
|
|
19605
19685
|
__proto__: null,
|
|
19606
19686
|
flattenMultiPolygonCoords: flattenMultiPolygonCoords,
|
|
@@ -20490,7 +20570,7 @@
|
|
|
20490
20570
|
opts = Object.assign({invert_y: true, margin: "1"}, opts);
|
|
20491
20571
|
frame = getFrameData(dataset, opts);
|
|
20492
20572
|
fitDatasetToFrame(dataset, frame);
|
|
20493
|
-
setCoordinatePrecision(dataset, opts.precision || 0.
|
|
20573
|
+
setCoordinatePrecision(dataset, opts.precision || 0.01);
|
|
20494
20574
|
|
|
20495
20575
|
// error if one or more svg_data fields are not present in any layers
|
|
20496
20576
|
if (opts.svg_data) validateSvgDataFields(dataset.layers, opts.svg_data);
|
|
@@ -22246,7 +22326,7 @@ ${svg}
|
|
|
22246
22326
|
if (opts.presimplify) {
|
|
22247
22327
|
fromZ = getPresimplifyFunction(bounds.width());
|
|
22248
22328
|
}
|
|
22249
|
-
arcs.forEach2(function(i, n, xx, yy, zz) {
|
|
22329
|
+
arcs.forEach2(function(arcId, i, n, xx, yy, zz) {
|
|
22250
22330
|
var arc = [], p;
|
|
22251
22331
|
for (var j=i + n; i<j; i++) {
|
|
22252
22332
|
p = [xx[i], yy[i]];
|
|
@@ -24842,6 +24922,11 @@ ${svg}
|
|
|
24842
24922
|
.option('sliver-control', sliverControlOpt)
|
|
24843
24923
|
.option('snap-interval', snapIntervalOpt)
|
|
24844
24924
|
.option('no-snap', noSnapOpt)
|
|
24925
|
+
// // not useful in -clean -- clean removes gaps, not slivers
|
|
24926
|
+
// .option('keep-shapes', {
|
|
24927
|
+
// type: 'flag',
|
|
24928
|
+
// describe: 'protect sliver polygons from complete removal'
|
|
24929
|
+
// })
|
|
24845
24930
|
.option('allow-overlaps', {
|
|
24846
24931
|
describe: 'allow polygons to overlap (disables gap fill)',
|
|
24847
24932
|
type: 'flag'
|
|
@@ -24893,6 +24978,10 @@ ${svg}
|
|
|
24893
24978
|
})
|
|
24894
24979
|
.option('name', nameOpt)
|
|
24895
24980
|
.option('no-snap', noSnapOpt)
|
|
24981
|
+
.option('no-cleanup', {
|
|
24982
|
+
// for debugging - no documentation
|
|
24983
|
+
type: 'flag'
|
|
24984
|
+
})
|
|
24896
24985
|
.option('target', targetOpt)
|
|
24897
24986
|
.option('no-replace', noReplaceOpt);
|
|
24898
24987
|
|
|
@@ -25128,6 +25217,10 @@ ${svg}
|
|
|
25128
25217
|
.option('bbox', bboxOpt)
|
|
25129
25218
|
.option('name', nameOpt)
|
|
25130
25219
|
.option('no-snap', noSnapOpt)
|
|
25220
|
+
.option('no-cleanup', {
|
|
25221
|
+
// for debugging - no documentation
|
|
25222
|
+
type: 'flag'
|
|
25223
|
+
})
|
|
25131
25224
|
.option('target', targetOpt)
|
|
25132
25225
|
.option('no-replace', noReplaceOpt);
|
|
25133
25226
|
|
|
@@ -25238,6 +25331,10 @@ ${svg}
|
|
|
25238
25331
|
type: 'flag',
|
|
25239
25332
|
describe: 'delete features with null geometry'
|
|
25240
25333
|
})
|
|
25334
|
+
.option('keep-shapes', {
|
|
25335
|
+
type: 'flag',
|
|
25336
|
+
describe: 'protect sliver polygons from complete removal'
|
|
25337
|
+
})
|
|
25241
25338
|
.option('target', targetOpt);
|
|
25242
25339
|
|
|
25243
25340
|
parser.command('graticule')
|
|
@@ -25445,9 +25542,8 @@ ${svg}
|
|
|
25445
25542
|
.option('calc', calcOpt)
|
|
25446
25543
|
.option('name', nameOpt)
|
|
25447
25544
|
.option('target', targetOpt)
|
|
25448
|
-
.option('
|
|
25449
|
-
|
|
25450
|
-
})
|
|
25545
|
+
.option('snap-interval', snapIntervalOpt)
|
|
25546
|
+
.option('no-snap', noSnapOpt)
|
|
25451
25547
|
.option('no-replace', noReplaceOpt);
|
|
25452
25548
|
|
|
25453
25549
|
parser.command('point-grid')
|
|
@@ -29843,6 +29939,158 @@ ${svg}
|
|
|
29843
29939
|
stop('Unable to import coordinates');
|
|
29844
29940
|
}
|
|
29845
29941
|
|
|
29942
|
+
// Apply rotation, scale and/or shift to some or all of the features in a dataset
|
|
29943
|
+
//
|
|
29944
|
+
cmd.affine = function(targetLayers, dataset, opts) {
|
|
29945
|
+
// Need to separate the targeted shapes from any other shapes that share
|
|
29946
|
+
// the same topology. So we duplicate any arcs that are shared by the targeted
|
|
29947
|
+
// shapes and their topological neighbors and remap arc references in the
|
|
29948
|
+
// neighbors to point to the copies.
|
|
29949
|
+
// TODO: explore alternative: if some arcs are shared between transformed and
|
|
29950
|
+
// non-transformed shapes, first remove topology, then tranform, then rebuild topology
|
|
29951
|
+
//
|
|
29952
|
+
var rotateArg = opts.rotate || 0;
|
|
29953
|
+
var scaleArg = opts.scale || 1;
|
|
29954
|
+
var shiftArg = opts.shift ? convertIntervalPair(opts.shift, getDatasetCRS(dataset)) : [0, 0];
|
|
29955
|
+
var arcs = dataset.arcs;
|
|
29956
|
+
var targetShapes = [];
|
|
29957
|
+
var otherShapes = [];
|
|
29958
|
+
var targetPoints = [];
|
|
29959
|
+
var targetFlags, otherFlags, transform;
|
|
29960
|
+
dataset.layers.filter(layerHasGeometry).forEach(function(lyr) {
|
|
29961
|
+
var hits = [],
|
|
29962
|
+
misses = [],
|
|
29963
|
+
test;
|
|
29964
|
+
if (targetLayers.indexOf(lyr) == -1) {
|
|
29965
|
+
misses = lyr.shapes;
|
|
29966
|
+
} else if (opts.where) {
|
|
29967
|
+
test = compileFeatureExpression(opts.where, lyr, dataset.arcs);
|
|
29968
|
+
lyr.shapes.forEach(function(shp, i) {
|
|
29969
|
+
(test(i) ? hits : misses).push(shp);
|
|
29970
|
+
});
|
|
29971
|
+
} else {
|
|
29972
|
+
hits = lyr.shapes;
|
|
29973
|
+
}
|
|
29974
|
+
if (lyr.geometry_type == 'point') {
|
|
29975
|
+
targetPoints = targetPoints.concat(hits);
|
|
29976
|
+
} else {
|
|
29977
|
+
targetShapes = targetShapes.concat(hits);
|
|
29978
|
+
otherShapes = otherShapes.concat(misses);
|
|
29979
|
+
}
|
|
29980
|
+
});
|
|
29981
|
+
var anchorArg = getAffineAnchor({arcs: dataset.arcs, layers: [{
|
|
29982
|
+
geometry_type: 'point', shapes: targetPoints}, {geometry_type: 'polyline',
|
|
29983
|
+
shapes: targetShapes}]}, opts);
|
|
29984
|
+
transform = getAffineTransform(rotateArg, scaleArg, shiftArg, anchorArg);
|
|
29985
|
+
if (opts.fit_bbox) {
|
|
29986
|
+
transform = getFitBoxTransform(opts.fit_bbox, targetPoints, targetShapes, arcs);
|
|
29987
|
+
}
|
|
29988
|
+
if (targetShapes.length > 0) {
|
|
29989
|
+
targetFlags = new Uint8Array(arcs.size());
|
|
29990
|
+
otherFlags = new Uint8Array(arcs.size());
|
|
29991
|
+
countArcsInShapes(targetShapes, targetFlags);
|
|
29992
|
+
if (otherShapes.length > 0) {
|
|
29993
|
+
countArcsInShapes(otherShapes, otherFlags);
|
|
29994
|
+
applyArrayMask(otherFlags, targetFlags);
|
|
29995
|
+
dataset.arcs = duplicateSelectedArcs(otherShapes, arcs, otherFlags);
|
|
29996
|
+
}
|
|
29997
|
+
dataset.arcs.transformPoints(function(x, y, arcId) {
|
|
29998
|
+
if (arcId < targetFlags.length && targetFlags[arcId] > 0) {
|
|
29999
|
+
return transform(x, y);
|
|
30000
|
+
}
|
|
30001
|
+
});
|
|
30002
|
+
}
|
|
30003
|
+
forEachPoint(targetPoints, function(p) {
|
|
30004
|
+
var p2 = transform(p[0], p[1]);
|
|
30005
|
+
p[0] = p2[0];
|
|
30006
|
+
p[1] = p2[1];
|
|
30007
|
+
});
|
|
30008
|
+
};
|
|
30009
|
+
|
|
30010
|
+
function getAffineAnchor(dataset, opts) {
|
|
30011
|
+
var anchor, bounds;
|
|
30012
|
+
if (opts.anchor) {
|
|
30013
|
+
anchor = opts.anchor;
|
|
30014
|
+
} else {
|
|
30015
|
+
// get bounds of selected shapes to calculate center of rotation/scale
|
|
30016
|
+
bounds = getDatasetBounds(dataset);
|
|
30017
|
+
anchor = [bounds.centerX(), bounds.centerY()];
|
|
30018
|
+
}
|
|
30019
|
+
return anchor;
|
|
30020
|
+
}
|
|
30021
|
+
|
|
30022
|
+
// TODO: handle problems with unprojected datasets
|
|
30023
|
+
// option 1: don't allow affine transformation of unprojected data
|
|
30024
|
+
// option 2: error if transformed data exceeds valid coordinate range
|
|
30025
|
+
// source: http://mathworld.wolfram.com/AffineTransformation.html
|
|
30026
|
+
function getAffineTransform(rotation, scale, shift, anchor) {
|
|
30027
|
+
var angle = rotation * Math.PI / 180;
|
|
30028
|
+
var a = scale * Math.cos(angle);
|
|
30029
|
+
var b = -scale * Math.sin(angle);
|
|
30030
|
+
return function(x, y) {
|
|
30031
|
+
var x2 = a * (x - anchor[0]) - b * (y - anchor[1]) + shift[0] + anchor[0];
|
|
30032
|
+
var y2 = b * (x - anchor[0]) + a * (y - anchor[1]) + shift[1] + anchor[1];
|
|
30033
|
+
return [x2, y2];
|
|
30034
|
+
};
|
|
30035
|
+
}
|
|
30036
|
+
|
|
30037
|
+
function getFitBoxTransform(bbox, points, shapes, arcs) {
|
|
30038
|
+
var dataset = {
|
|
30039
|
+
arcs: arcs,
|
|
30040
|
+
layers: []
|
|
30041
|
+
};
|
|
30042
|
+
if (points && points.length) {
|
|
30043
|
+
dataset.layers.push({
|
|
30044
|
+
geometry_type: 'point',
|
|
30045
|
+
shapes: points
|
|
30046
|
+
});
|
|
30047
|
+
}
|
|
30048
|
+
if (shapes && shapes.length) {
|
|
30049
|
+
dataset.layers.push({
|
|
30050
|
+
geometry_type: 'polyline',
|
|
30051
|
+
shapes: shapes
|
|
30052
|
+
});
|
|
30053
|
+
}
|
|
30054
|
+
var frame = calcFrameData(dataset, {fit_bbox: bbox});
|
|
30055
|
+
var fromBounds = new Bounds(frame.bbox);
|
|
30056
|
+
var toBounds = new Bounds(frame.bbox2);
|
|
30057
|
+
var fwd = fromBounds.getTransform(toBounds, false);
|
|
30058
|
+
return function(x, y) {
|
|
30059
|
+
return fwd.transform(x, y);
|
|
30060
|
+
};
|
|
30061
|
+
}
|
|
30062
|
+
|
|
30063
|
+
function applyArrayMask(destArr, maskArr) {
|
|
30064
|
+
for (var i=0, n=destArr.length; i<n; i++) {
|
|
30065
|
+
if (maskArr[i] === 0) destArr[i] = 0;
|
|
30066
|
+
}
|
|
30067
|
+
}
|
|
30068
|
+
|
|
30069
|
+
function duplicateSelectedArcs(shapes, arcs, flags) {
|
|
30070
|
+
var arcCount = 0;
|
|
30071
|
+
var vertexCount = 0;
|
|
30072
|
+
var data = arcs.getVertexData();
|
|
30073
|
+
var xx = [], yy = [], nn = [], map = [], n;
|
|
30074
|
+
for (var i=0, len=flags.length; i<len; i++) {
|
|
30075
|
+
if (flags[i] > 0) {
|
|
30076
|
+
map[i] = arcs.size() + arcCount;
|
|
30077
|
+
n = data.nn[i];
|
|
30078
|
+
utils.copyElements(data.xx, data.ii[i], xx, vertexCount, n);
|
|
30079
|
+
utils.copyElements(data.yy, data.ii[i], yy, vertexCount, n);
|
|
30080
|
+
nn.push(n);
|
|
30081
|
+
vertexCount += n;
|
|
30082
|
+
arcCount++;
|
|
30083
|
+
}
|
|
30084
|
+
}
|
|
30085
|
+
forEachArcId(shapes, function(id) {
|
|
30086
|
+
var absId = absArcId(id);
|
|
30087
|
+
if (flags[absId] > 0) {
|
|
30088
|
+
return id < 0 ? ~map[absId] : map[absId];
|
|
30089
|
+
}
|
|
30090
|
+
});
|
|
30091
|
+
return mergeArcs([arcs, new ArcCollection(nn, xx, yy)]);
|
|
30092
|
+
}
|
|
30093
|
+
|
|
29846
30094
|
const epsilon = 1.1102230246251565e-16;
|
|
29847
30095
|
const splitter = 134217729;
|
|
29848
30096
|
const resulterrbound = (3 + 8 * epsilon) * epsilon;
|
|
@@ -34876,7 +35124,7 @@ ${svg}
|
|
|
34876
35124
|
var ringTest = filterData.filter;
|
|
34877
35125
|
var removed = 0;
|
|
34878
35126
|
var pathFilter = function(path, i, paths) {
|
|
34879
|
-
if (ringTest(path)) {
|
|
35127
|
+
if (ringTest(path, i, paths)) {
|
|
34880
35128
|
removed++;
|
|
34881
35129
|
return null;
|
|
34882
35130
|
}
|
|
@@ -35016,6 +35264,7 @@ ${svg}
|
|
|
35016
35264
|
// or overlapping rings. TODO: try to optimize or remove it for all cases
|
|
35017
35265
|
|
|
35018
35266
|
// skipping shape cleanup when using the experimental fast bbox clipping option
|
|
35267
|
+
// if (!opts.bbox2 && !opts.no_cleanup) {
|
|
35019
35268
|
if (!opts.bbox2) {
|
|
35020
35269
|
// clean each target polygon by dissolving its rings
|
|
35021
35270
|
targetShapes = targetShapes.map(dissolvePolygon);
|
|
@@ -35512,7 +35761,7 @@ ${svg}
|
|
|
35512
35761
|
} else {
|
|
35513
35762
|
nodes = new NodeCollection(mergedDataset.arcs);
|
|
35514
35763
|
}
|
|
35515
|
-
|
|
35764
|
+
|
|
35516
35765
|
return clipLayersByLayer(targetLayers, clipLyr, nodes, type, opts);
|
|
35517
35766
|
}
|
|
35518
35767
|
|
|
@@ -40504,7 +40753,7 @@ ${svg}
|
|
|
40504
40753
|
|
|
40505
40754
|
function createMeridianPart(x, ymin, ymax) {
|
|
40506
40755
|
var coords = densifyPathByInterval([[x, ymin], [x, ymax]], precision);
|
|
40507
|
-
meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord$
|
|
40756
|
+
meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord$2(x)}));
|
|
40508
40757
|
}
|
|
40509
40758
|
|
|
40510
40759
|
function createParallel(y) {
|
|
@@ -40514,7 +40763,7 @@ ${svg}
|
|
|
40514
40763
|
}
|
|
40515
40764
|
|
|
40516
40765
|
// remove tiny offsets
|
|
40517
|
-
function roundCoord$
|
|
40766
|
+
function roundCoord$2(x) {
|
|
40518
40767
|
return +x.toFixed(3) || 0;
|
|
40519
40768
|
}
|
|
40520
40769
|
|
|
@@ -44613,7 +44862,7 @@ ${svg}
|
|
|
44613
44862
|
});
|
|
44614
44863
|
};
|
|
44615
44864
|
|
|
44616
|
-
var roundCoord = getRoundingFunction(0.01);
|
|
44865
|
+
var roundCoord$1 = getRoundingFunction(0.01);
|
|
44617
44866
|
|
|
44618
44867
|
function getSymbolFillColor(d) {
|
|
44619
44868
|
return d.fill || 'magenta';
|
|
@@ -44664,8 +44913,8 @@ ${svg}
|
|
|
44664
44913
|
|
|
44665
44914
|
function roundCoordsForSVG(coords) {
|
|
44666
44915
|
forEachSymbolCoord(coords, function(p) {
|
|
44667
|
-
p[0] = roundCoord(p[0]);
|
|
44668
|
-
p[1] = roundCoord(p[1]);
|
|
44916
|
+
p[0] = roundCoord$1(p[0]);
|
|
44917
|
+
p[1] = roundCoord$1(p[1]);
|
|
44669
44918
|
});
|
|
44670
44919
|
}
|
|
44671
44920
|
|
|
@@ -45390,7 +45639,7 @@ ${svg}
|
|
|
45390
45639
|
// Filter arcs based on an array of thresholds
|
|
45391
45640
|
function applyArcThresholds(arcs, thresholds) {
|
|
45392
45641
|
arcs.getVertexData().zz;
|
|
45393
|
-
arcs.forEach2(function(start, n, xx, yy, zz
|
|
45642
|
+
arcs.forEach2(function(arcId, start, n, xx, yy, zz) {
|
|
45394
45643
|
var arcZ = thresholds[arcId];
|
|
45395
45644
|
var z;
|
|
45396
45645
|
for (var i=1; i<n-1; i++) {
|
|
@@ -46044,7 +46293,7 @@ ${svg}
|
|
|
46044
46293
|
});
|
|
46045
46294
|
}
|
|
46046
46295
|
|
|
46047
|
-
var version = "0.6.
|
|
46296
|
+
var version = "0.6.111";
|
|
46048
46297
|
|
|
46049
46298
|
// Parse command line args into commands and run them
|
|
46050
46299
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -46376,6 +46625,51 @@ ${svg}
|
|
|
46376
46625
|
testCommands: testCommands
|
|
46377
46626
|
});
|
|
46378
46627
|
|
|
46628
|
+
var roundCoord = getRoundingFunction(0.01);
|
|
46629
|
+
|
|
46630
|
+
function stringifyVertex(p) {
|
|
46631
|
+
return ' ' + roundCoord(p[0]) + ' ' + roundCoord(p[1]);
|
|
46632
|
+
}
|
|
46633
|
+
|
|
46634
|
+
function isCubicCtrl(p) {
|
|
46635
|
+
return p.length > 2 && p[2] == 'C';
|
|
46636
|
+
}
|
|
46637
|
+
|
|
46638
|
+
function stringifyPolygonCoords(coords) {
|
|
46639
|
+
var parts = [];
|
|
46640
|
+
for (var i=0; i<coords.length; i++) {
|
|
46641
|
+
parts.push(stringifyLineStringCoords(coords[i]) + ' Z');
|
|
46642
|
+
}
|
|
46643
|
+
return parts.length > 0 ? parts.join(' ') : '';
|
|
46644
|
+
}
|
|
46645
|
+
|
|
46646
|
+
function stringifyLineStringCoords(coords) {
|
|
46647
|
+
if (coords.length === 0) return '';
|
|
46648
|
+
var d = 'M';
|
|
46649
|
+
var fromCurve = false;
|
|
46650
|
+
var p, i, n;
|
|
46651
|
+
for (i=0, n=coords.length; i<n; i++) {
|
|
46652
|
+
p = coords[i];
|
|
46653
|
+
if (isCubicCtrl(p)) {
|
|
46654
|
+
// TODO: add defensive check
|
|
46655
|
+
d += ' C' + stringifyVertex(p) + stringifyVertex(coords[++i]) + stringifyVertex(coords[++i]);
|
|
46656
|
+
fromCurve = true;
|
|
46657
|
+
} else if (fromCurve) {
|
|
46658
|
+
d += ' L' + stringifyVertex(p);
|
|
46659
|
+
fromCurve = false;
|
|
46660
|
+
} else {
|
|
46661
|
+
d += stringifyVertex(p);
|
|
46662
|
+
}
|
|
46663
|
+
}
|
|
46664
|
+
return d;
|
|
46665
|
+
}
|
|
46666
|
+
|
|
46667
|
+
var SvgPathUtils = /*#__PURE__*/Object.freeze({
|
|
46668
|
+
__proto__: null,
|
|
46669
|
+
stringifyLineStringCoords: stringifyLineStringCoords,
|
|
46670
|
+
stringifyPolygonCoords: stringifyPolygonCoords
|
|
46671
|
+
});
|
|
46672
|
+
|
|
46379
46673
|
// Return an array containing points from a path iterator, clipped to a bounding box
|
|
46380
46674
|
// Currently using this function for clipping styled polygons in the GUI to speed up layer rendering.
|
|
46381
46675
|
// Artifacts along the edges make this unsuitable for clipping datasets
|