mapshaper 0.6.109 → 0.6.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mapshaper.js 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 (getStashedVar('VERBOSE')) {
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 (getStashedVar('DEBUG')) {
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,1044 @@
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
+ /*
3117
+ * big.js v7.0.1
3118
+ * A small, fast, easy-to-use library for arbitrary-precision decimal arithmetic.
3119
+ * Copyright (c) 2025 Michael Mclaughlin
3120
+ * https://github.com/MikeMcl/big.js/LICENCE.md
3121
+ */
3122
+
3123
+
3124
+ /************************************** EDITABLE DEFAULTS *****************************************/
3125
+
3126
+
3127
+ // The default values below must be integers within the stated ranges.
3128
+
3129
+ /*
3130
+ * The maximum number of decimal places (DP) of the results of operations involving division:
3131
+ * div and sqrt, and pow with negative exponents.
3132
+ */
3133
+ var DP = 20, // 0 to MAX_DP
3134
+
3135
+ /*
3136
+ * The rounding mode (RM) used when rounding to the above decimal places.
3137
+ *
3138
+ * 0 Towards zero (i.e. truncate, no rounding). (ROUND_DOWN)
3139
+ * 1 To nearest neighbour. If equidistant, round up. (ROUND_HALF_UP)
3140
+ * 2 To nearest neighbour. If equidistant, to even. (ROUND_HALF_EVEN)
3141
+ * 3 Away from zero. (ROUND_UP)
3142
+ */
3143
+ RM = 1, // 0, 1, 2 or 3
3144
+
3145
+ // The maximum value of DP and Big.DP.
3146
+ MAX_DP = 1E6, // 0 to 1000000
3147
+
3148
+ // The maximum magnitude of the exponent argument to the pow method.
3149
+ MAX_POWER = 1E6, // 1 to 1000000
3150
+
3151
+ /*
3152
+ * The negative exponent (NE) at and beneath which toString returns exponential notation.
3153
+ * (JavaScript numbers: -7)
3154
+ * -1000000 is the minimum recommended exponent value of a Big.
3155
+ */
3156
+ NE = -7, // 0 to -1000000
3157
+
3158
+ /*
3159
+ * The positive exponent (PE) at and above which toString returns exponential notation.
3160
+ * (JavaScript numbers: 21)
3161
+ * 1000000 is the maximum recommended exponent value of a Big, but this limit is not enforced.
3162
+ */
3163
+ PE = 21, // 0 to 1000000
3164
+
3165
+ /*
3166
+ * When true, an error will be thrown if a primitive number is passed to the Big constructor,
3167
+ * or if valueOf is called, or if toNumber is called on a Big which cannot be converted to a
3168
+ * primitive number without a loss of precision.
3169
+ */
3170
+ STRICT = false, // true or false
3171
+
3172
+
3173
+ /**************************************************************************************************/
3174
+
3175
+
3176
+ // Error messages.
3177
+ NAME = '[big.js] ',
3178
+ INVALID = NAME + 'Invalid ',
3179
+ INVALID_DP = INVALID + 'decimal places',
3180
+ INVALID_RM = INVALID + 'rounding mode',
3181
+ DIV_BY_ZERO = NAME + 'Division by zero',
3182
+
3183
+ // The shared prototype object.
3184
+ P = {},
3185
+ UNDEFINED = void 0,
3186
+ NUMERIC = /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i;
3187
+
3188
+
3189
+ /*
3190
+ * Create and return a Big constructor.
3191
+ */
3192
+ function _Big_() {
3193
+
3194
+ /*
3195
+ * The Big constructor and exported function.
3196
+ * Create and return a new instance of a Big number object.
3197
+ *
3198
+ * n {number|string|Big} A numeric value.
3199
+ */
3200
+ function Big(n) {
3201
+ var x = this;
3202
+
3203
+ // Enable constructor usage without new.
3204
+ if (!(x instanceof Big)) {
3205
+ return n === UNDEFINED && arguments.length === 0 ? _Big_() : new Big(n);
3206
+ }
3207
+
3208
+
3209
+ // Duplicate.
3210
+ if (n instanceof Big) {
3211
+ x.s = n.s;
3212
+ x.e = n.e;
3213
+ x.c = n.c.slice();
3214
+ } else {
3215
+ if (typeof n !== 'string') {
3216
+ if (Big.strict === true && typeof n !== 'bigint') {
3217
+ throw TypeError(INVALID + 'value');
3218
+ }
3219
+
3220
+ // Minus zero?
3221
+ n = n === 0 && 1 / n < 0 ? '-0' : String(n);
3222
+ }
3223
+
3224
+ parse(x, n);
3225
+ }
3226
+
3227
+ // Retain a reference to this Big constructor.
3228
+ // Shadow Big.prototype.constructor which points to Object.
3229
+ x.constructor = Big;
3230
+ }
3231
+
3232
+ Big.prototype = P;
3233
+ Big.DP = DP;
3234
+ Big.RM = RM;
3235
+ Big.NE = NE;
3236
+ Big.PE = PE;
3237
+ Big.strict = STRICT;
3238
+ Big.roundDown = 0;
3239
+ Big.roundHalfUp = 1;
3240
+ Big.roundHalfEven = 2;
3241
+ Big.roundUp = 3;
3242
+
3243
+ return Big;
3244
+ }
3245
+
3246
+
3247
+ /*
3248
+ * Parse the number or string value passed to a Big constructor.
3249
+ *
3250
+ * x {Big} A Big number instance.
3251
+ * n {number|string} A numeric value.
3252
+ */
3253
+ function parse(x, n) {
3254
+ var e, i, nl;
3255
+
3256
+ if (!NUMERIC.test(n)) {
3257
+ throw Error(INVALID + 'number');
3258
+ }
3259
+
3260
+ // Determine sign.
3261
+ x.s = n.charAt(0) == '-' ? (n = n.slice(1), -1) : 1;
3262
+
3263
+ // Decimal point?
3264
+ if ((e = n.indexOf('.')) > -1) n = n.replace('.', '');
3265
+
3266
+ // Exponential form?
3267
+ if ((i = n.search(/e/i)) > 0) {
3268
+
3269
+ // Determine exponent.
3270
+ if (e < 0) e = i;
3271
+ e += +n.slice(i + 1);
3272
+ n = n.substring(0, i);
3273
+ } else if (e < 0) {
3274
+
3275
+ // Integer.
3276
+ e = n.length;
3277
+ }
3278
+
3279
+ nl = n.length;
3280
+
3281
+ // Determine leading zeros.
3282
+ for (i = 0; i < nl && n.charAt(i) == '0';) ++i;
3283
+
3284
+ if (i == nl) {
3285
+
3286
+ // Zero.
3287
+ x.c = [x.e = 0];
3288
+ } else {
3289
+
3290
+ // Determine trailing zeros.
3291
+ for (; nl > 0 && n.charAt(--nl) == '0';);
3292
+ x.e = e - i - 1;
3293
+ x.c = [];
3294
+
3295
+ // Convert string to array of digits without leading/trailing zeros.
3296
+ for (e = 0; i <= nl;) x.c[e++] = +n.charAt(i++);
3297
+ }
3298
+
3299
+ return x;
3300
+ }
3301
+
3302
+
3303
+ /*
3304
+ * Round Big x to a maximum of sd significant digits using rounding mode rm.
3305
+ *
3306
+ * x {Big} The Big to round.
3307
+ * sd {number} Significant digits: integer, 0 to MAX_DP inclusive.
3308
+ * rm {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
3309
+ * [more] {boolean} Whether the result of division was truncated.
3310
+ */
3311
+ function round(x, sd, rm, more) {
3312
+ var xc = x.c;
3313
+
3314
+ if (rm === UNDEFINED) rm = x.constructor.RM;
3315
+ if (rm !== 0 && rm !== 1 && rm !== 2 && rm !== 3) {
3316
+ throw Error(INVALID_RM);
3317
+ }
3318
+
3319
+ if (sd < 1) {
3320
+ more =
3321
+ rm === 3 && (more || !!xc[0]) || sd === 0 && (
3322
+ rm === 1 && xc[0] >= 5 ||
3323
+ rm === 2 && (xc[0] > 5 || xc[0] === 5 && (more || xc[1] !== UNDEFINED))
3324
+ );
3325
+
3326
+ xc.length = 1;
3327
+
3328
+ if (more) {
3329
+
3330
+ // 1, 0.1, 0.01, 0.001, 0.0001 etc.
3331
+ x.e = x.e - sd + 1;
3332
+ xc[0] = 1;
3333
+ } else {
3334
+
3335
+ // Zero.
3336
+ xc[0] = x.e = 0;
3337
+ }
3338
+ } else if (sd < xc.length) {
3339
+
3340
+ // xc[sd] is the digit after the digit that may be rounded up.
3341
+ more =
3342
+ rm === 1 && xc[sd] >= 5 ||
3343
+ rm === 2 && (xc[sd] > 5 || xc[sd] === 5 &&
3344
+ (more || xc[sd + 1] !== UNDEFINED || xc[sd - 1] & 1)) ||
3345
+ rm === 3 && (more || !!xc[0]);
3346
+
3347
+ // Remove any digits after the required precision.
3348
+ xc.length = sd;
3349
+
3350
+ // Round up?
3351
+ if (more) {
3352
+
3353
+ // Rounding up may mean the previous digit has to be rounded up.
3354
+ for (; ++xc[--sd] > 9;) {
3355
+ xc[sd] = 0;
3356
+ if (sd === 0) {
3357
+ ++x.e;
3358
+ xc.unshift(1);
3359
+ break;
3360
+ }
3361
+ }
3362
+ }
3363
+
3364
+ // Remove trailing zeros.
3365
+ for (sd = xc.length; !xc[--sd];) xc.pop();
3366
+ }
3367
+
3368
+ return x;
3369
+ }
3370
+
3371
+
3372
+ /*
3373
+ * Return a string representing the value of Big x in normal or exponential notation.
3374
+ * Handles P.toExponential, P.toFixed, P.toJSON, P.toPrecision, P.toString and P.valueOf.
3375
+ */
3376
+ function stringify$1(x, doExponential, isNonzero) {
3377
+ var e = x.e,
3378
+ s = x.c.join(''),
3379
+ n = s.length;
3380
+
3381
+ // Exponential notation?
3382
+ if (doExponential) {
3383
+ s = s.charAt(0) + (n > 1 ? '.' + s.slice(1) : '') + (e < 0 ? 'e' : 'e+') + e;
3384
+
3385
+ // Normal notation.
3386
+ } else if (e < 0) {
3387
+ for (; ++e;) s = '0' + s;
3388
+ s = '0.' + s;
3389
+ } else if (e > 0) {
3390
+ if (++e > n) {
3391
+ for (e -= n; e--;) s += '0';
3392
+ } else if (e < n) {
3393
+ s = s.slice(0, e) + '.' + s.slice(e);
3394
+ }
3395
+ } else if (n > 1) {
3396
+ s = s.charAt(0) + '.' + s.slice(1);
3397
+ }
3398
+
3399
+ return x.s < 0 && isNonzero ? '-' + s : s;
3400
+ }
3401
+
3402
+
3403
+ // Prototype/instance methods
3404
+
3405
+
3406
+ /*
3407
+ * Return a new Big whose value is the absolute value of this Big.
3408
+ */
3409
+ P.abs = function () {
3410
+ var x = new this.constructor(this);
3411
+ x.s = 1;
3412
+ return x;
3413
+ };
3414
+
3415
+
3416
+ /*
3417
+ * Return 1 if the value of this Big is greater than the value of Big y,
3418
+ * -1 if the value of this Big is less than the value of Big y, or
3419
+ * 0 if they have the same value.
3420
+ */
3421
+ P.cmp = function (y) {
3422
+ var isneg,
3423
+ x = this,
3424
+ xc = x.c,
3425
+ yc = (y = new x.constructor(y)).c,
3426
+ i = x.s,
3427
+ j = y.s,
3428
+ k = x.e,
3429
+ l = y.e;
3430
+
3431
+ // Either zero?
3432
+ if (!xc[0] || !yc[0]) return !xc[0] ? !yc[0] ? 0 : -j : i;
3433
+
3434
+ // Signs differ?
3435
+ if (i != j) return i;
3436
+
3437
+ isneg = i < 0;
3438
+
3439
+ // Compare exponents.
3440
+ if (k != l) return k > l ^ isneg ? 1 : -1;
3441
+
3442
+ j = (k = xc.length) < (l = yc.length) ? k : l;
3443
+
3444
+ // Compare digit by digit.
3445
+ for (i = -1; ++i < j;) {
3446
+ if (xc[i] != yc[i]) return xc[i] > yc[i] ^ isneg ? 1 : -1;
3447
+ }
3448
+
3449
+ // Compare lengths.
3450
+ return k == l ? 0 : k > l ^ isneg ? 1 : -1;
3451
+ };
3452
+
3453
+
3454
+ /*
3455
+ * Return a new Big whose value is the value of this Big divided by the value of Big y, rounded,
3456
+ * if necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM.
3457
+ */
3458
+ P.div = function (y) {
3459
+ var x = this,
3460
+ Big = x.constructor,
3461
+ a = x.c, // dividend
3462
+ b = (y = new Big(y)).c, // divisor
3463
+ k = x.s == y.s ? 1 : -1,
3464
+ dp = Big.DP;
3465
+
3466
+ if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
3467
+ throw Error(INVALID_DP);
3468
+ }
3469
+
3470
+ // Divisor is zero?
3471
+ if (!b[0]) {
3472
+ throw Error(DIV_BY_ZERO);
3473
+ }
3474
+
3475
+ // Dividend is 0? Return +-0.
3476
+ if (!a[0]) {
3477
+ y.s = k;
3478
+ y.c = [y.e = 0];
3479
+ return y;
3480
+ }
3481
+
3482
+ var bl, bt, n, cmp, ri,
3483
+ bz = b.slice(),
3484
+ ai = bl = b.length,
3485
+ al = a.length,
3486
+ r = a.slice(0, bl), // remainder
3487
+ rl = r.length,
3488
+ q = y, // quotient
3489
+ qc = q.c = [],
3490
+ qi = 0,
3491
+ p = dp + (q.e = x.e - y.e) + 1; // precision of the result
3492
+
3493
+ q.s = k;
3494
+ k = p < 0 ? 0 : p;
3495
+
3496
+ // Create version of divisor with leading zero.
3497
+ bz.unshift(0);
3498
+
3499
+ // Add zeros to make remainder as long as divisor.
3500
+ for (; rl++ < bl;) r.push(0);
3501
+
3502
+ do {
3503
+
3504
+ // n is how many times the divisor goes into current remainder.
3505
+ for (n = 0; n < 10; n++) {
3506
+
3507
+ // Compare divisor and remainder.
3508
+ if (bl != (rl = r.length)) {
3509
+ cmp = bl > rl ? 1 : -1;
3510
+ } else {
3511
+ for (ri = -1, cmp = 0; ++ri < bl;) {
3512
+ if (b[ri] != r[ri]) {
3513
+ cmp = b[ri] > r[ri] ? 1 : -1;
3514
+ break;
3515
+ }
3516
+ }
3517
+ }
3518
+
3519
+ // If divisor < remainder, subtract divisor from remainder.
3520
+ if (cmp < 0) {
3521
+
3522
+ // Remainder can't be more than 1 digit longer than divisor.
3523
+ // Equalise lengths using divisor with extra leading zero?
3524
+ for (bt = rl == bl ? b : bz; rl;) {
3525
+ if (r[--rl] < bt[rl]) {
3526
+ ri = rl;
3527
+ for (; ri && !r[--ri];) r[ri] = 9;
3528
+ --r[ri];
3529
+ r[rl] += 10;
3530
+ }
3531
+ r[rl] -= bt[rl];
3532
+ }
3533
+
3534
+ for (; !r[0];) r.shift();
3535
+ } else {
3536
+ break;
3537
+ }
3538
+ }
3539
+
3540
+ // Add the digit n to the result array.
3541
+ qc[qi++] = cmp ? n : ++n;
3542
+
3543
+ // Update the remainder.
3544
+ if (r[0] && cmp) r[rl] = a[ai] || 0;
3545
+ else r = [a[ai]];
3546
+
3547
+ } while ((ai++ < al || r[0] !== UNDEFINED) && k--);
3548
+
3549
+ // Leading zero? Do not remove if result is simply zero (qi == 1).
3550
+ if (!qc[0] && qi != 1) {
3551
+
3552
+ // There can't be more than one zero.
3553
+ qc.shift();
3554
+ q.e--;
3555
+ p--;
3556
+ }
3557
+
3558
+ // Round?
3559
+ if (qi > p) round(q, p, Big.RM, r[0] !== UNDEFINED);
3560
+
3561
+ return q;
3562
+ };
3563
+
3564
+
3565
+ /*
3566
+ * Return true if the value of this Big is equal to the value of Big y, otherwise return false.
3567
+ */
3568
+ P.eq = function (y) {
3569
+ return this.cmp(y) === 0;
3570
+ };
3571
+
3572
+
3573
+ /*
3574
+ * Return true if the value of this Big is greater than the value of Big y, otherwise return
3575
+ * false.
3576
+ */
3577
+ P.gt = function (y) {
3578
+ return this.cmp(y) > 0;
3579
+ };
3580
+
3581
+
3582
+ /*
3583
+ * Return true if the value of this Big is greater than or equal to the value of Big y, otherwise
3584
+ * return false.
3585
+ */
3586
+ P.gte = function (y) {
3587
+ return this.cmp(y) > -1;
3588
+ };
3589
+
3590
+
3591
+ /*
3592
+ * Return true if the value of this Big is less than the value of Big y, otherwise return false.
3593
+ */
3594
+ P.lt = function (y) {
3595
+ return this.cmp(y) < 0;
3596
+ };
3597
+
3598
+
3599
+ /*
3600
+ * Return true if the value of this Big is less than or equal to the value of Big y, otherwise
3601
+ * return false.
3602
+ */
3603
+ P.lte = function (y) {
3604
+ return this.cmp(y) < 1;
3605
+ };
3606
+
3607
+
3608
+ /*
3609
+ * Return a new Big whose value is the value of this Big minus the value of Big y.
3610
+ */
3611
+ P.minus = P.sub = function (y) {
3612
+ var i, j, t, xlty,
3613
+ x = this,
3614
+ Big = x.constructor,
3615
+ a = x.s,
3616
+ b = (y = new Big(y)).s;
3617
+
3618
+ // Signs differ?
3619
+ if (a != b) {
3620
+ y.s = -b;
3621
+ return x.plus(y);
3622
+ }
3623
+
3624
+ var xc = x.c.slice(),
3625
+ xe = x.e,
3626
+ yc = y.c,
3627
+ ye = y.e;
3628
+
3629
+ // Either zero?
3630
+ if (!xc[0] || !yc[0]) {
3631
+ if (yc[0]) {
3632
+ y.s = -b;
3633
+ } else if (xc[0]) {
3634
+ y = new Big(x);
3635
+ } else {
3636
+ y.s = 1;
3637
+ }
3638
+ return y;
3639
+ }
3640
+
3641
+ // Determine which is the bigger number. Prepend zeros to equalise exponents.
3642
+ if (a = xe - ye) {
3643
+
3644
+ if (xlty = a < 0) {
3645
+ a = -a;
3646
+ t = xc;
3647
+ } else {
3648
+ ye = xe;
3649
+ t = yc;
3650
+ }
3651
+
3652
+ t.reverse();
3653
+ for (b = a; b--;) t.push(0);
3654
+ t.reverse();
3655
+ } else {
3656
+
3657
+ // Exponents equal. Check digit by digit.
3658
+ j = ((xlty = xc.length < yc.length) ? xc : yc).length;
3659
+
3660
+ for (a = b = 0; b < j; b++) {
3661
+ if (xc[b] != yc[b]) {
3662
+ xlty = xc[b] < yc[b];
3663
+ break;
3664
+ }
3665
+ }
3666
+ }
3667
+
3668
+ // x < y? Point xc to the array of the bigger number.
3669
+ if (xlty) {
3670
+ t = xc;
3671
+ xc = yc;
3672
+ yc = t;
3673
+ y.s = -y.s;
3674
+ }
3675
+
3676
+ /*
3677
+ * Append zeros to xc if shorter. No need to add zeros to yc if shorter as subtraction only
3678
+ * needs to start at yc.length.
3679
+ */
3680
+ if ((b = (j = yc.length) - (i = xc.length)) > 0) for (; b--;) xc[i++] = 0;
3681
+
3682
+ // Subtract yc from xc.
3683
+ for (b = i; j > a;) {
3684
+ if (xc[--j] < yc[j]) {
3685
+ for (i = j; i && !xc[--i];) xc[i] = 9;
3686
+ --xc[i];
3687
+ xc[j] += 10;
3688
+ }
3689
+
3690
+ xc[j] -= yc[j];
3691
+ }
3692
+
3693
+ // Remove trailing zeros.
3694
+ for (; xc[--b] === 0;) xc.pop();
3695
+
3696
+ // Remove leading zeros and adjust exponent accordingly.
3697
+ for (; xc[0] === 0;) {
3698
+ xc.shift();
3699
+ --ye;
3700
+ }
3701
+
3702
+ if (!xc[0]) {
3703
+
3704
+ // n - n = +0
3705
+ y.s = 1;
3706
+
3707
+ // Result must be zero.
3708
+ xc = [ye = 0];
3709
+ }
3710
+
3711
+ y.c = xc;
3712
+ y.e = ye;
3713
+
3714
+ return y;
3715
+ };
3716
+
3717
+
3718
+ /*
3719
+ * Return a new Big whose value is the value of this Big modulo the value of Big y.
3720
+ */
3721
+ P.mod = function (y) {
3722
+ var ygtx,
3723
+ x = this,
3724
+ Big = x.constructor,
3725
+ a = x.s,
3726
+ b = (y = new Big(y)).s;
3727
+
3728
+ if (!y.c[0]) {
3729
+ throw Error(DIV_BY_ZERO);
3730
+ }
3731
+
3732
+ x.s = y.s = 1;
3733
+ ygtx = y.cmp(x) == 1;
3734
+ x.s = a;
3735
+ y.s = b;
3736
+
3737
+ if (ygtx) return new Big(x);
3738
+
3739
+ a = Big.DP;
3740
+ b = Big.RM;
3741
+ Big.DP = Big.RM = 0;
3742
+ x = x.div(y);
3743
+ Big.DP = a;
3744
+ Big.RM = b;
3745
+
3746
+ return this.minus(x.times(y));
3747
+ };
3748
+
3749
+
3750
+ /*
3751
+ * Return a new Big whose value is the value of this Big negated.
3752
+ */
3753
+ P.neg = function () {
3754
+ var x = new this.constructor(this);
3755
+ x.s = -x.s;
3756
+ return x;
3757
+ };
3758
+
3759
+
3760
+ /*
3761
+ * Return a new Big whose value is the value of this Big plus the value of Big y.
3762
+ */
3763
+ P.plus = P.add = function (y) {
3764
+ var e, k, t,
3765
+ x = this,
3766
+ Big = x.constructor;
3767
+
3768
+ y = new Big(y);
3769
+
3770
+ // Signs differ?
3771
+ if (x.s != y.s) {
3772
+ y.s = -y.s;
3773
+ return x.minus(y);
3774
+ }
3775
+
3776
+ var xe = x.e,
3777
+ xc = x.c,
3778
+ ye = y.e,
3779
+ yc = y.c;
3780
+
3781
+ // Either zero?
3782
+ if (!xc[0] || !yc[0]) {
3783
+ if (!yc[0]) {
3784
+ if (xc[0]) {
3785
+ y = new Big(x);
3786
+ } else {
3787
+ y.s = x.s;
3788
+ }
3789
+ }
3790
+ return y;
3791
+ }
3792
+
3793
+ xc = xc.slice();
3794
+
3795
+ // Prepend zeros to equalise exponents.
3796
+ // Note: reverse faster than unshifts.
3797
+ if (e = xe - ye) {
3798
+ if (e > 0) {
3799
+ ye = xe;
3800
+ t = yc;
3801
+ } else {
3802
+ e = -e;
3803
+ t = xc;
3804
+ }
3805
+
3806
+ t.reverse();
3807
+ for (; e--;) t.push(0);
3808
+ t.reverse();
3809
+ }
3810
+
3811
+ // Point xc to the longer array.
3812
+ if (xc.length - yc.length < 0) {
3813
+ t = yc;
3814
+ yc = xc;
3815
+ xc = t;
3816
+ }
3817
+
3818
+ e = yc.length;
3819
+
3820
+ // Only start adding at yc.length - 1 as the further digits of xc can be left as they are.
3821
+ for (k = 0; e; xc[e] %= 10) k = (xc[--e] = xc[e] + yc[e] + k) / 10 | 0;
3822
+
3823
+ // No need to check for zero, as +x + +y != 0 && -x + -y != 0
3824
+
3825
+ if (k) {
3826
+ xc.unshift(k);
3827
+ ++ye;
3828
+ }
3829
+
3830
+ // Remove trailing zeros.
3831
+ for (e = xc.length; xc[--e] === 0;) xc.pop();
3832
+
3833
+ y.c = xc;
3834
+ y.e = ye;
3835
+
3836
+ return y;
3837
+ };
3838
+
3839
+
3840
+ /*
3841
+ * Return a Big whose value is the value of this Big raised to the power n.
3842
+ * If n is negative, round to a maximum of Big.DP decimal places using rounding
3843
+ * mode Big.RM.
3844
+ *
3845
+ * n {number} Integer, -MAX_POWER to MAX_POWER inclusive.
3846
+ */
3847
+ P.pow = function (n) {
3848
+ var x = this,
3849
+ one = new x.constructor('1'),
3850
+ y = one,
3851
+ isneg = n < 0;
3852
+
3853
+ if (n !== ~~n || n < -MAX_POWER || n > MAX_POWER) {
3854
+ throw Error(INVALID + 'exponent');
3855
+ }
3856
+
3857
+ if (isneg) n = -n;
3858
+
3859
+ for (;;) {
3860
+ if (n & 1) y = y.times(x);
3861
+ n >>= 1;
3862
+ if (!n) break;
3863
+ x = x.times(x);
3864
+ }
3865
+
3866
+ return isneg ? one.div(y) : y;
3867
+ };
3868
+
3869
+
3870
+ /*
3871
+ * Return a new Big whose value is the value of this Big rounded to a maximum precision of sd
3872
+ * significant digits using rounding mode rm, or Big.RM if rm is not specified.
3873
+ *
3874
+ * sd {number} Significant digits: integer, 1 to MAX_DP inclusive.
3875
+ * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
3876
+ */
3877
+ P.prec = function (sd, rm) {
3878
+ if (sd !== ~~sd || sd < 1 || sd > MAX_DP) {
3879
+ throw Error(INVALID + 'precision');
3880
+ }
3881
+ return round(new this.constructor(this), sd, rm);
3882
+ };
3883
+
3884
+
3885
+ /*
3886
+ * Return a new Big whose value is the value of this Big rounded to a maximum of dp decimal places
3887
+ * using rounding mode rm, or Big.RM if rm is not specified.
3888
+ * If dp is negative, round to an integer which is a multiple of 10**-dp.
3889
+ * If dp is not specified, round to 0 decimal places.
3890
+ *
3891
+ * dp? {number} Integer, -MAX_DP to MAX_DP inclusive.
3892
+ * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
3893
+ */
3894
+ P.round = function (dp, rm) {
3895
+ if (dp === UNDEFINED) dp = 0;
3896
+ else if (dp !== ~~dp || dp < -MAX_DP || dp > MAX_DP) {
3897
+ throw Error(INVALID_DP);
3898
+ }
3899
+ return round(new this.constructor(this), dp + this.e + 1, rm);
3900
+ };
3901
+
3902
+
3903
+ /*
3904
+ * Return a new Big whose value is the square root of the value of this Big, rounded, if
3905
+ * necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM.
3906
+ */
3907
+ P.sqrt = function () {
3908
+ var r, c, t,
3909
+ x = this,
3910
+ Big = x.constructor,
3911
+ s = x.s,
3912
+ e = x.e,
3913
+ half = new Big('0.5');
3914
+
3915
+ // Zero?
3916
+ if (!x.c[0]) return new Big(x);
3917
+
3918
+ // Negative?
3919
+ if (s < 0) {
3920
+ throw Error(NAME + 'No square root');
3921
+ }
3922
+
3923
+ // Estimate.
3924
+ s = Math.sqrt(+stringify$1(x, true, true));
3925
+
3926
+ // Math.sqrt underflow/overflow?
3927
+ // Re-estimate: pass x coefficient to Math.sqrt as integer, then adjust the result exponent.
3928
+ if (s === 0 || s === 1 / 0) {
3929
+ c = x.c.join('');
3930
+ if (!(c.length + e & 1)) c += '0';
3931
+ s = Math.sqrt(c);
3932
+ e = ((e + 1) / 2 | 0) - (e < 0 || e & 1);
3933
+ r = new Big((s == 1 / 0 ? '5e' : (s = s.toExponential()).slice(0, s.indexOf('e') + 1)) + e);
3934
+ } else {
3935
+ r = new Big(s + '');
3936
+ }
3937
+
3938
+ e = r.e + (Big.DP += 4);
3939
+
3940
+ // Newton-Raphson iteration.
3941
+ do {
3942
+ t = r;
3943
+ r = half.times(t.plus(x.div(t)));
3944
+ } while (t.c.slice(0, e).join('') !== r.c.slice(0, e).join(''));
3945
+
3946
+ return round(r, (Big.DP -= 4) + r.e + 1, Big.RM);
3947
+ };
3948
+
3949
+
3950
+ /*
3951
+ * Return a new Big whose value is the value of this Big times the value of Big y.
3952
+ */
3953
+ P.times = P.mul = function (y) {
3954
+ var c,
3955
+ x = this,
3956
+ Big = x.constructor,
3957
+ xc = x.c,
3958
+ yc = (y = new Big(y)).c,
3959
+ a = xc.length,
3960
+ b = yc.length,
3961
+ i = x.e,
3962
+ j = y.e;
3963
+
3964
+ // Determine sign of result.
3965
+ y.s = x.s == y.s ? 1 : -1;
3966
+
3967
+ // Return signed 0 if either 0.
3968
+ if (!xc[0] || !yc[0]) {
3969
+ y.c = [y.e = 0];
3970
+ return y;
3971
+ }
3972
+
3973
+ // Initialise exponent of result as x.e + y.e.
3974
+ y.e = i + j;
3975
+
3976
+ // If array xc has fewer digits than yc, swap xc and yc, and lengths.
3977
+ if (a < b) {
3978
+ c = xc;
3979
+ xc = yc;
3980
+ yc = c;
3981
+ j = a;
3982
+ a = b;
3983
+ b = j;
3984
+ }
3985
+
3986
+ // Initialise coefficient array of result with zeros.
3987
+ for (c = new Array(j = a + b); j--;) c[j] = 0;
3988
+
3989
+ // Multiply.
3990
+
3991
+ // i is initially xc.length.
3992
+ for (i = b; i--;) {
3993
+ b = 0;
3994
+
3995
+ // a is yc.length.
3996
+ for (j = a + i; j > i;) {
3997
+
3998
+ // Current sum of products at this digit position, plus carry.
3999
+ b = c[j] + yc[i] * xc[j - i - 1] + b;
4000
+ c[j--] = b % 10;
4001
+
4002
+ // carry
4003
+ b = b / 10 | 0;
4004
+ }
4005
+
4006
+ c[j] = b;
4007
+ }
4008
+
4009
+ // Increment result exponent if there is a final carry, otherwise remove leading zero.
4010
+ if (b) ++y.e;
4011
+ else c.shift();
4012
+
4013
+ // Remove trailing zeros.
4014
+ for (i = c.length; !c[--i];) c.pop();
4015
+ y.c = c;
4016
+
4017
+ return y;
4018
+ };
4019
+
4020
+
4021
+ /*
4022
+ * Return a string representing the value of this Big in exponential notation rounded to dp fixed
4023
+ * decimal places using rounding mode rm, or Big.RM if rm is not specified.
4024
+ *
4025
+ * dp? {number} Decimal places: integer, 0 to MAX_DP inclusive.
4026
+ * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
4027
+ */
4028
+ P.toExponential = function (dp, rm) {
4029
+ var x = this,
4030
+ n = x.c[0];
4031
+
4032
+ if (dp !== UNDEFINED) {
4033
+ if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
4034
+ throw Error(INVALID_DP);
4035
+ }
4036
+ x = round(new x.constructor(x), ++dp, rm);
4037
+ for (; x.c.length < dp;) x.c.push(0);
4038
+ }
4039
+
4040
+ return stringify$1(x, true, !!n);
4041
+ };
4042
+
4043
+
4044
+ /*
4045
+ * Return a string representing the value of this Big in normal notation rounded to dp fixed
4046
+ * decimal places using rounding mode rm, or Big.RM if rm is not specified.
4047
+ *
4048
+ * dp? {number} Decimal places: integer, 0 to MAX_DP inclusive.
4049
+ * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
4050
+ *
4051
+ * (-0).toFixed(0) is '0', but (-0.1).toFixed(0) is '-0'.
4052
+ * (-0).toFixed(1) is '0.0', but (-0.01).toFixed(1) is '-0.0'.
4053
+ */
4054
+ P.toFixed = function (dp, rm) {
4055
+ var x = this,
4056
+ n = x.c[0];
4057
+
4058
+ if (dp !== UNDEFINED) {
4059
+ if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
4060
+ throw Error(INVALID_DP);
4061
+ }
4062
+ x = round(new x.constructor(x), dp + x.e + 1, rm);
4063
+
4064
+ // x.e may have changed if the value is rounded up.
4065
+ for (dp = dp + x.e + 1; x.c.length < dp;) x.c.push(0);
4066
+ }
4067
+
4068
+ return stringify$1(x, false, !!n);
4069
+ };
4070
+
4071
+
4072
+ /*
4073
+ * Return a string representing the value of this Big.
4074
+ * Return exponential notation if this Big has a positive exponent equal to or greater than
4075
+ * Big.PE, or a negative exponent equal to or less than Big.NE.
4076
+ * Omit the sign for negative zero.
4077
+ */
4078
+ P.toJSON = P.toString = function () {
4079
+ var x = this,
4080
+ Big = x.constructor;
4081
+ return stringify$1(x, x.e <= Big.NE || x.e >= Big.PE, !!x.c[0]);
4082
+ };
4083
+
4084
+ if (typeof Symbol !== "undefined") {
4085
+ P[Symbol.for('nodejs.util.inspect.custom')] = P.toJSON;
4086
+ }
4087
+
4088
+
4089
+ /*
4090
+ * Return the value of this Big as a primitive number.
4091
+ */
4092
+ P.toNumber = function () {
4093
+ var n = +stringify$1(this, true, true);
4094
+ if (this.constructor.strict === true && !this.eq(n.toString())) {
4095
+ throw Error(NAME + 'Imprecise conversion');
4096
+ }
4097
+ return n;
4098
+ };
4099
+
4100
+
4101
+ /*
4102
+ * Return a string representing the value of this Big rounded to sd significant digits using
4103
+ * rounding mode rm, or Big.RM if rm is not specified.
4104
+ * Use exponential notation if sd is less than the number of digits necessary to represent
4105
+ * the integer part of the value in normal notation.
4106
+ *
4107
+ * sd {number} Significant digits: integer, 1 to MAX_DP inclusive.
4108
+ * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
4109
+ */
4110
+ P.toPrecision = function (sd, rm) {
4111
+ var x = this,
4112
+ Big = x.constructor,
4113
+ n = x.c[0];
4114
+
4115
+ if (sd !== UNDEFINED) {
4116
+ if (sd !== ~~sd || sd < 1 || sd > MAX_DP) {
4117
+ throw Error(INVALID + 'precision');
4118
+ }
4119
+ x = round(new Big(x), sd, rm);
4120
+ for (; x.c.length < sd;) x.c.push(0);
4121
+ }
4122
+
4123
+ return stringify$1(x, sd <= x.e || x.e <= Big.NE || x.e >= Big.PE, !!n);
4124
+ };
4125
+
4126
+
4127
+ /*
4128
+ * Return a string representing the value of this Big.
4129
+ * Return exponential notation if this Big has a positive exponent equal to or greater than
4130
+ * Big.PE, or a negative exponent equal to or less than Big.NE.
4131
+ * Include the sign for negative zero.
4132
+ */
4133
+ P.valueOf = function () {
4134
+ var x = this,
4135
+ Big = x.constructor;
4136
+ if (Big.strict === true) {
4137
+ throw Error(NAME + 'valueOf disallowed');
4138
+ }
4139
+ return stringify$1(x, x.e <= Big.NE || x.e >= Big.PE, true);
4140
+ };
4141
+
4142
+
4143
+ // Export
4144
+
4145
+
4146
+ var Big = _Big_();
4147
+
3156
4148
  // Find the intersection between two 2D segments
3157
4149
  // Returns 0, 1 or 2 [x, y] locations as null, [x, y], or [x1, y1, x2, y2]
3158
4150
  // Special cases:
@@ -3164,42 +4156,45 @@
3164
4156
  function segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy, epsArg) {
3165
4157
  // Use a small tolerance interval, so collinear segments and T-intersections
3166
4158
  // are detected (floating point rounding often causes exact functions to fail)
3167
- var eps = epsArg >= 0 ? epsArg :
4159
+ var eps = epsArg > 0 ? epsArg :
3168
4160
  getHighPrecisionSnapInterval([ax, ay, bx, by, cx, cy, dx, dy]);
3169
4161
  var epsSq = eps * eps;
3170
4162
  var touches, cross;
4163
+
3171
4164
  // Detect 0, 1 or 2 'touch' intersections, where a vertex of one segment
3172
4165
  // is very close to the other segment's linear portion.
3173
4166
  // One touch indicates either a T-intersection or two overlapping collinear
3174
4167
  // segments that share an endpoint. Two touches indicates overlapping
3175
4168
  // collinear segments that do not share an endpoint.
3176
4169
  touches = findPointSegTouches(epsSq, ax, ay, bx, by, cx, cy, dx, dy);
3177
- // if (touches) return touches;
3178
4170
  // Ignore endpoint-only intersections
3179
4171
  if (!touches && testEndpointHit(epsSq, ax, ay, bx, by, cx, cy, dx, dy)) {
3180
4172
  return null;
3181
4173
  }
3182
4174
  // Detect cross intersection
3183
- cross = findCrossIntersection(ax, ay, bx, by, cx, cy, dx, dy, eps);
4175
+ // (TODO: consider cross intersections that are also endpoint hits)
4176
+ if (!touches) {
4177
+ cross = findCrossIntersection(ax, ay, bx, by, cx, cy, dx, dy, eps);
4178
+ }
3184
4179
  return touches || cross || null;
3185
4180
  }
3186
4181
 
3187
4182
 
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
4183
  function findCrossIntersection(ax, ay, bx, by, cx, cy, dx, dy, eps) {
3192
- if (!segmentHit(ax, ay, bx, by, cx, cy, dx, dy)) return null;
3193
- var den = determinant2D(bx - ax, by - ay, dx - cx, dy - cy);
3194
- var m = orient2D(cx, cy, dx, dy, ax, ay) / den;
3195
- var p = [ax + m * (bx - ax), ay + m * (by - ay)];
3196
- if (Math.abs(den) < 1e-25) {
3197
- // changed from 1e-18 to 1e-25 (see geom ex1)
3198
- // assume that collinear and near-collinear segment intersections have been
3199
- // accounted for already.
3200
- // TODO: is this a valid assumption?
3201
- return null;
4184
+ var p;
4185
+ if (eps > 0 && !segmentHit_fast(ax, ay, bx, by, cx, cy, dx, dy)) return null;
4186
+ else if (eps === 0 && !segmentHit_big(ax, ay, bx, by, cx, cy, dx, dy)) return null;
4187
+
4188
+ // in a typical layer with many intersections, robust is preferred in
4189
+ // most (>90%) segment intersections in order to keep the positional
4190
+ // error within a small interval (e.g. 50% of eps)
4191
+ //
4192
+ if (useRobustCross(ax, ay, bx, by, cx, cy, dx, dy)) {
4193
+ p = findCrossIntersection_robust(ax, ay, bx, by, cx, cy, dx, dy);
4194
+ } else {
4195
+ p = findCrossIntersection_fast(ax, ay, bx, by, cx, cy, dx, dy);
3202
4196
  }
4197
+ if (!p) return null;
3203
4198
 
3204
4199
  // Snap p to a vertex if very close to one
3205
4200
  // This avoids tiny segments caused by T-intersection overshoots and prevents
@@ -3215,9 +4210,98 @@
3215
4210
  return p;
3216
4211
  }
3217
4212
 
4213
+ // Find the intersection point of two segments that cross each other,
4214
+ // or return null if the segments do not cross.
4215
+ // Assumes endpoint intersections have already been detected
4216
+ function findCrossIntersection_robust(ax, ay, bx, by, cx, cy, dx, dy) {
4217
+ var ax_big = Big(ax);
4218
+ var ay_big = Big(ay);
4219
+ var bx_big = Big(bx);
4220
+ var by_big = Big(by);
4221
+ var cx_big = Big(cx);
4222
+ var cy_big = Big(cy);
4223
+ var dx_big = Big(dx);
4224
+ var dy_big = Big(dy);
4225
+ var v1x = bx_big.minus(ax_big);
4226
+ var v1y = by_big.minus(ay_big);
4227
+ var den_big = determinant2D_big(v1x, v1y, dx_big.minus(cx), dy_big.minus(cy));
4228
+ if (den_big.eq(0)) {
4229
+ debug("DIV0 error (should have been caught upstream)");
4230
+ // console.log("hit?", segmentHit_big(ax, ay, bx, by, cx, cy, dx, dy))
4231
+ // console.log('Seg 1', getSegFeature(ax, ay, bx, by, true))
4232
+ // console.log('Seg 2', getSegFeature(cx, cy, dx, dy, false))
4233
+ return null;
4234
+ }
4235
+ // perform division using regular math, which does not reduce overall
4236
+ // precision in test data (big.js division is very slow)
4237
+ // tests show identical result to:
4238
+ // orient2D_big(cx_big, cy_big, dx_big, dy_big, ax_big, ay_big).div(den_big)
4239
+ var m = orient2D_big(cx_big, cy_big, dx_big, dy_big, ax_big,
4240
+ ay_big).toNumber() / den_big.toNumber();
4241
+ var m_big = Big(m);
4242
+ var x_big = ax_big.plus(m_big.times(v1x).round(16));
4243
+ var y_big = ay_big.plus(m_big.times(v1y).round(16));
4244
+ var p = [x_big.toNumber(), y_big.toNumber()];
4245
+ return p;
4246
+ }
4247
+
4248
+ function findCrossIntersection_fast(ax, ay, bx, by, cx, cy, dx, dy) {
4249
+ var den = determinant2D(bx - ax, by - ay, dx - cx, dy - cy);
4250
+ var m = orient2D(cx, cy, dx, dy, ax, ay) / den;
4251
+ var p = [ax + m * (bx - ax), ay + m * (by - ay)];
4252
+ if (Math.abs(den) < 1e-25) {
4253
+ // changed from 1e-18 to 1e-25 (see geom ex1)
4254
+ // assume that collinear and near-collinear segment intersections have been
4255
+ // accounted for already.
4256
+ // TODO: is this really true?
4257
+ return null;
4258
+ }
4259
+ return p;
4260
+ }
4261
+
4262
+ function useRobustCross(ax, ay, bx, by, cx, cy, dx, dy) {
4263
+ // angle and seg length ratio thresholds were found by comparing
4264
+ // fast and robust outputs on sample data
4265
+ if (innerAngle(ax, ay, bx, by, cx, cy, dx, dy) < 0.1) return true;
4266
+ var len1 = distance2D(ax, ay, bx, by);
4267
+ var len2 = distance2D(cx, cy, dx, dy);
4268
+ var ratio = len1 < len2 ? len1 / len2 : len2 / len1 || 0;
4269
+ if (ratio < 0.001) return true;
4270
+ return false;
4271
+ }
4272
+
4273
+ // Returns smaller unsigned angle between two segments
4274
+ function innerAngle(ax, ay, bx, by, cx, cy, dx, dy) {
4275
+ var v1x = bx - ax;
4276
+ var v1y = by - ay;
4277
+ var v2x = dx - cx;
4278
+ var v2y = dy - cy;
4279
+ var dot = v1x * v2x + v1y * v2y;
4280
+ var mag1Sq = v1x * v1x + v1y * v1y;
4281
+ var mag2Sq = v2x * v2x + v2y * v2y;
4282
+ if (mag1Sq === 0 || mag2Sq === 0) {
4283
+ return 0;
4284
+ }
4285
+ var cosTheta = dot / Math.sqrt(mag1Sq * mag2Sq);
4286
+ var theta;
4287
+ if (cosTheta > 1 - 1e-14) {
4288
+ theta = 0;
4289
+ } else if (cosTheta < -1 + 1e-14) {
4290
+ theta = Math.PI;
4291
+ } else {
4292
+ theta = Math.acos(cosTheta);
4293
+ }
4294
+ if (theta >= Math.PI / 2) {
4295
+ theta = Math.PI - theta;
4296
+ }
4297
+ return theta;
4298
+ }
4299
+
3218
4300
  function testEndpointHit(epsSq, ax, ay, bx, by, cx, cy, dx, dy) {
3219
- return distanceSq(ax, ay, cx, cy) <= epsSq || distanceSq(ax, ay, dx, dy) <= epsSq ||
3220
- distanceSq(bx, by, cx, cy) <= epsSq || distanceSq(bx, by, dx, dy) <= epsSq;
4301
+ return distanceSq(ax, ay, cx, cy) <= epsSq ||
4302
+ distanceSq(ax, ay, dx, dy) <= epsSq ||
4303
+ distanceSq(bx, by, cx, cy) <= epsSq ||
4304
+ distanceSq(bx, by, dx, dy) <= epsSq;
3221
4305
  }
3222
4306
 
3223
4307
  function findPointSegTouches(epsSq, ax, ay, bx, by, cx, cy, dx, dy) {
@@ -3228,6 +4312,9 @@
3228
4312
  collectPointSegTouch(touches, epsSq, dx, dy, ax, ay, bx, by);
3229
4313
  if (touches.length === 0) return null;
3230
4314
  if (touches.length > 4) {
4315
+ // console.log('XX', touches.length)
4316
+ // console.log('Seg 1', getSegFeature(ax, ay, bx, by, true))
4317
+ // console.log('Seg 2', getSegFeature(cx, cy, dx, dy, false))
3231
4318
  // Geometrically, more than two touch intersections can not occur.
3232
4319
  // Is it possible that fp rounding or a bug might result in >2 touches?
3233
4320
  debug('Intersection detection error');
@@ -3244,10 +4331,10 @@
3244
4331
  var pa = distanceSq(ax, ay, px, py);
3245
4332
  var pb = distanceSq(bx, by, px, py);
3246
4333
  if (pa <= epsSq || pb <= epsSq) return; // ignore endpoint hits
4334
+ // console.log("Dist:", Math.sqrt(pab), "eps:", Math.sqrt(epsSq), "p:", px, py)
3247
4335
  arr.push(px, py); // T intersection at P and AB
3248
4336
  }
3249
4337
 
3250
-
3251
4338
  // Used by mapshaper-undershoots.js
3252
4339
  // TODO: make more robust, make sure result is compatible with segmentIntersection()
3253
4340
  // (rounding errors currently must be handled downstream)
@@ -3296,16 +4383,55 @@
3296
4383
  // when a segment is vertical or horizontal. This has caused problems when
3297
4384
  // repeatedly applying bbox clipping along the same segment
3298
4385
  var x = p[0],
3299
- y = p[1];
4386
+ y = p[1],
4387
+ s1out = false,
4388
+ s2out = false;
4389
+
3300
4390
  // assumes that segment ranges intersect
3301
- x = clampToCloseRange(x, ax, bx);
3302
- x = clampToCloseRange(x, cx, dx);
3303
- y = clampToCloseRange(y, ay, by);
3304
- y = clampToCloseRange(y, cy, dy);
4391
+ if (outsideRange(x, ax, bx)) {
4392
+ x = clampToClosestEndpoint(x, ax, bx);
4393
+ s1out = true;
4394
+ }
4395
+ if (outsideRange(x, cx, dx)) {
4396
+ x = clampToClosestEndpoint(x, cx, dx);
4397
+ s2out = true;
4398
+ }
4399
+ if (outsideRange(y, ay, by)) {
4400
+ y = clampToClosestEndpoint(y, ay, by);
4401
+ s1out = true;
4402
+ }
4403
+ if (outsideRange(y, cy, dy)) {
4404
+ y = clampToClosestEndpoint(y, cy, dy);
4405
+ s2out = true;
4406
+ }
4407
+ if ((s1out || s2out)) {
4408
+ debug('Clamping a segment intersection point');
4409
+ // console.log("angle:", innerAngle(ax, ay, bx, by, cx, cy, dx, dy))
4410
+ // console.log('Feature 1', getSegFeature(ax, ay, bx, by, s1out));
4411
+ // console.log('Feature 2', getSegFeature(cx, cy, dx, dy, s2out));
4412
+ // console.log('Point:', JSON.stringify({
4413
+ // type: 'Feature',
4414
+ // properties: {fill: 'red'},
4415
+ // geometry: {type: 'Point', coordinates: [p[0], p[1]]}
4416
+ // }));
4417
+ }
3305
4418
  p[0] = x;
3306
4419
  p[1] = y;
3307
4420
  }
3308
4421
 
4422
+ // function getSegFeature(x1, y1, x2, y2, hot) {
4423
+ // return JSON.stringify({
4424
+ // type: "Feature",
4425
+ // properties: {
4426
+ // stroke: hot ? "orange" : "blue"
4427
+ // },
4428
+ // geometry: {
4429
+ // type: "LineString",
4430
+ // coordinates: [[x1, y1], [x2, y2]]
4431
+ // }
4432
+ // });
4433
+ // }
4434
+
3309
4435
  // a: coordinate of point
3310
4436
  // b: endpoint coordinate of segment
3311
4437
  // c: other endpoint of segment
@@ -3321,16 +4447,13 @@
3321
4447
  return out;
3322
4448
  }
3323
4449
 
3324
- function clampToCloseRange(a, b, c) {
3325
- var lim;
3326
- if (outsideRange(a, b, c)) {
3327
- lim = Math.abs(a - b) < Math.abs(a - c) ? b : c;
3328
- if (Math.abs(a - lim) > 1e-15) {
3329
- debug("[clampToCloseRange()] large clamping interval", a, b, c);
3330
- }
3331
- a = lim;
4450
+ function clampToClosestEndpoint(a, b, c) {
4451
+ var lim = Math.abs(a - b) < Math.abs(a - c) ? b : c;
4452
+ var interval = Math.abs(a - lim);
4453
+ if (interval > 1e-15) {
4454
+ debug("[clampToClosestEndpoint()] large clamping interval:", interval);
3332
4455
  }
3333
- return a;
4456
+ return lim;
3334
4457
  }
3335
4458
 
3336
4459
  // Determinant of matrix
@@ -3340,6 +4463,10 @@
3340
4463
  return a * d - b * c;
3341
4464
  }
3342
4465
 
4466
+ function determinant2D_big(a, b, c, d) {
4467
+ return a.times(d).minus(b.times(c));
4468
+ }
4469
+
3343
4470
  // returns a positive value if the points a, b, and c are arranged in
3344
4471
  // counterclockwise order, a negative value if the points are in clockwise
3345
4472
  // order, and zero if the points are collinear.
@@ -3348,16 +4475,51 @@
3348
4475
  return determinant2D(ax - cx, ay - cy, bx - cx, by - cy);
3349
4476
  }
3350
4477
 
4478
+ function orient2D_big(ax, ay, bx, by, cx, cy) {
4479
+ var a = (ax.minus(cx)).times(by.minus(cy));
4480
+ var b = (ay.minus(cy)).times(bx.minus(cx));
4481
+ return a.minus(b);
4482
+ }
4483
+
4484
+ function orient2D_big2(ax, ay, bx, by, cx, cy) {
4485
+ return orient2D_big(Big(ax), Big(ay), Big(bx), Big(by), Big(cx), Big(cy));
4486
+ }
4487
+
4488
+
4489
+ // export function orient2D_v2(ax, ay, bx, by, cx, cy) {
4490
+ // return -orient2D_robust(ax, ay, bx, by, cx, cy);
4491
+ // }
4492
+
4493
+
3351
4494
  // Source: Sedgewick, _Algorithms in C_
3352
4495
  // (Other functions were tried that were more sensitive to floating point errors
3353
4496
  // than this function)
3354
- function segmentHit(ax, ay, bx, by, cx, cy, dx, dy) {
4497
+ function segmentHit_fast(ax, ay, bx, by, cx, cy, dx, dy) {
3355
4498
  return orient2D(ax, ay, bx, by, cx, cy) *
3356
4499
  orient2D(ax, ay, bx, by, dx, dy) <= 0 &&
3357
4500
  orient2D(cx, cy, dx, dy, ax, ay) *
3358
4501
  orient2D(cx, cy, dx, dy, bx, by) <= 0;
3359
4502
  }
3360
4503
 
4504
+ function segmentHit_big(ax, ay, bx, by, cx, cy, dx, dy) {
4505
+ return orient2D_big(ax, ay, bx, by, cx, cy).times(
4506
+ orient2D_big(ax, ay, bx, by, dx, dy)).lte(0) &&
4507
+ orient2D_big(cx, cy, dx, dy, ax, ay).times(
4508
+ orient2D_big(cx, cy, dx, dy, bx, by)).lte(0);
4509
+ }
4510
+
4511
+ function segmentHit_big2(ax, ay, bx, by, cx, cy, dx, dy) {
4512
+ return segmentHit_big(Big(ax), Big(ay), Big(bx), Big(by),
4513
+ Big(cx), Big(cy), Big(dx), Big(dy));
4514
+ }
4515
+
4516
+ // export function segmentHit_robust(ax, ay, bx, by, cx, cy, dx, dy) {
4517
+ // return -orient2D_robust(ax, ay, bx, by, cx, cy) *
4518
+ // -orient2D_robust(ax, ay, bx, by, dx, dy) <= 0 &&
4519
+ // -orient2D_robust(cx, cy, dx, dy, ax, ay) *
4520
+ // -orient2D_robust(cx, cy, dx, dy, bx, by) <= 0;
4521
+ // }
4522
+
3361
4523
  // Useful for determining if a segment that intersects another segment is
3362
4524
  // entering or leaving an enclosed buffer area
3363
4525
  // returns -1 if angle of p1p2 -> p3p4 is counter-clockwise (left turn)
@@ -3382,7 +4544,11 @@
3382
4544
  __proto__: null,
3383
4545
  findClosestPointOnSeg: findClosestPointOnSeg,
3384
4546
  orient2D: orient2D,
3385
- segmentHit: segmentHit,
4547
+ orient2D_big: orient2D_big,
4548
+ orient2D_big2: orient2D_big2,
4549
+ segmentHit_big: segmentHit_big,
4550
+ segmentHit_big2: segmentHit_big2,
4551
+ segmentHit_fast: segmentHit_fast,
3386
4552
  segmentIntersection: segmentIntersection,
3387
4553
  segmentTurn: segmentTurn
3388
4554
  });
@@ -3692,7 +4858,7 @@
3692
4858
  // editPart: callback function
3693
4859
  function editShapes(shapes, editPart) {
3694
4860
  for (var i=0, n=shapes.length; i<n; i++) {
3695
- shapes[i] = editShapeParts(shapes[i], editPart);
4861
+ shapes[i] = editShapeParts(shapes[i], editPart, i);
3696
4862
  }
3697
4863
  }
3698
4864
 
@@ -3700,8 +4866,9 @@
3700
4866
  // @cb: function(part, i, parts)
3701
4867
  // If @cb returns an array, it replaces the existing value
3702
4868
  // If @cb returns null, the path is removed from the feature
4869
+ // @shpId: (optional) id of shape
3703
4870
  //
3704
- function editShapeParts(parts, cb) {
4871
+ function editShapeParts(parts, cb, shpId) {
3705
4872
  if (!parts) return null; // null geometry not edited
3706
4873
  if (!utils.isArray(parts)) error("Expected an array, received:", parts);
3707
4874
  var nulls = 0,
@@ -3709,7 +4876,7 @@
3709
4876
  retn;
3710
4877
 
3711
4878
  for (var i=0; i<n; i++) {
3712
- retn = cb(parts[i], i, parts);
4879
+ retn = cb(parts[i], i, parts, shpId);
3713
4880
  if (retn === null) {
3714
4881
  nulls++;
3715
4882
  parts[i] = null;
@@ -5756,7 +6923,7 @@
5756
6923
  //
5757
6924
  this.forEach2 = function(cb) {
5758
6925
  for (var arcId=0, n=this.size(); arcId<n; arcId++) {
5759
- cb(_ii[arcId], _nn[arcId], _xx, _yy, _zz, arcId);
6926
+ cb(arcId, _ii[arcId], _nn[arcId], _xx, _yy, _zz);
5760
6927
  }
5761
6928
  };
5762
6929
 
@@ -5891,6 +7058,27 @@
5891
7058
  };
5892
7059
 
5893
7060
  this.arcIsDegenerate = function(arcId) {
7061
+ return this.arcHasZeroLength(arcId) || this.arcIsSpike(arcId);
7062
+ };
7063
+
7064
+ this.arcIsSpike = function(arcId) {
7065
+ var iter = this.getArcIter(arcId);
7066
+ var x0, y0;
7067
+ if (iter.hasNext()) {
7068
+ x0 = iter.x;
7069
+ y0 = iter.y;
7070
+ }
7071
+ iter.hasNext(); // ignore second point
7072
+ if (iter.hasNext()) {
7073
+ if (iter.x == x0 && iter.y == y0 && !iter.hasNext()) {
7074
+ // three-vertex arc, first two are the same
7075
+ return true;
7076
+ }
7077
+ }
7078
+ return false;
7079
+ };
7080
+
7081
+ this.arcHasZeroLength = function(arcId) {
5894
7082
  var iter = this.getArcIter(arcId);
5895
7083
  var i = 0,
5896
7084
  x, y;
@@ -6397,10 +7585,10 @@
6397
7585
  // Visit each point in the path, up to but not including the last point
6398
7586
  for (var i = start; i < end; i++) {
6399
7587
  if (pointIsArcEndpoint(i)) {
6400
- if (firstNodeId > -1) {
6401
- arcIds.push(addEdge(arcStartId, i));
6402
- } else {
7588
+ if (firstNodeId == -1) {
6403
7589
  firstNodeId = i;
7590
+ } else {
7591
+ arcIds.push(addEdge(arcStartId, i));
6404
7592
  }
6405
7593
  arcStartId = i;
6406
7594
  }
@@ -6991,7 +8179,7 @@
6991
8179
  yy2 = new Float64Array(n),
6992
8180
  ids2 = new Int32Array(n);
6993
8181
 
6994
- arcs.forEach2(function(i, n, xx, yy, zz, arcId) {
8182
+ arcs.forEach2(function(arcId, i, n, xx, yy, zz) {
6995
8183
  var start = i,
6996
8184
  end = i + n - 1,
6997
8185
  start2 = arcId * 2,
@@ -7042,7 +8230,7 @@
7042
8230
  layers.forEach(function(lyr) {
7043
8231
  // modify copies of the original shapes; original shapes should be unmodified
7044
8232
  // (need to test this)
7045
- lyr.shapes = lyr.shapes.map(function(shape) {
8233
+ lyr.shapes = lyr.shapes.map(function(shape, i) {
7046
8234
  return editShapeParts(shape && shape.concat(), translatePath);
7047
8235
  });
7048
8236
  });
@@ -12584,11 +13772,161 @@
12584
13772
  stringifyAsNDJSON: stringifyAsNDJSON
12585
13773
  });
12586
13774
 
13775
+ function findNearestVertices(p, shp, arcs) {
13776
+ var p2 = findNearestVertex(p[0], p[1], shp, arcs);
13777
+ return findVertexIds(p2.x, p2.y, arcs);
13778
+ }
13779
+
13780
+ function snapVerticesToPoint(ids, p, arcs) {
13781
+ var data = arcs.getVertexData();
13782
+ ids.forEach(function(idx) {
13783
+ setVertexCoords(p[0], p[1], idx, arcs);
13784
+ arcs.updateArcBounds(findArcIdFromVertexId(idx, data.ii));
13785
+ });
13786
+ }
13787
+
13788
+
13789
+ // p: point to snap
13790
+ // ids: ids of nearby vertices, possibly including an arc endpoint
13791
+ function snapPointToArcEndpoint(p, ids, arcs) {
13792
+ var p2, dx, dy;
13793
+ ids.forEach(function(idx) {
13794
+ if (vertexIsArcStart(idx, arcs)) {
13795
+ p2 = getVertexCoords(idx + 1, arcs);
13796
+ } else if (vertexIsArcEnd(idx, arcs)) {
13797
+ p2 = getVertexCoords(idx - 1, arcs);
13798
+ }
13799
+ });
13800
+ if (!p2) return;
13801
+ dx = p2[0] - p[0];
13802
+ dy = p2[1] - p[1];
13803
+ if (Math.abs(dx) > Math.abs(dy)) {
13804
+ p[1] = p2[1]; // snap y coord
13805
+ } else {
13806
+ p[0] = p2[0];
13807
+ }
13808
+ }
13809
+
13810
+ // Find ids of vertices with identical coordinates to x,y in an ArcCollection
13811
+ // Caveat: does not exclude vertices that are not visible at the
13812
+ // current level of simplification.
13813
+ function findVertexIds(x, y, arcs) {
13814
+ var data = arcs.getVertexData(),
13815
+ xx = data.xx,
13816
+ yy = data.yy,
13817
+ ids = [];
13818
+ for (var i=0, n=xx.length; i<n; i++) {
13819
+ if (xx[i] == x && yy[i] == y) ids.push(i);
13820
+ }
13821
+ return ids;
13822
+ }
13823
+
13824
+ function getVertexCoords(i, arcs) {
13825
+ var data = arcs.getVertexData();
13826
+ return [data.xx[i], data.yy[i]];
13827
+ }
13828
+
13829
+ function vertexIsArcEnd(idx, arcs) {
13830
+ // Test whether the vertex at index @idx is the endpoint of an arc
13831
+ var data = arcs.getVertexData(),
13832
+ ii = data.ii,
13833
+ nn = data.nn;
13834
+ for (var j=0, n=ii.length; j<n; j++) {
13835
+ if (idx === ii[j] + nn[j] - 1) return true;
13836
+ }
13837
+ return false;
13838
+ }
13839
+
13840
+ function vertexIsArcEndpoint(idx, arcs) {
13841
+ return vertexIsArcStart(idx, arcs) || vertexIsArcEnd(idx, arcs);
13842
+ }
13843
+
13844
+ function vertexIsArcStart(idx, arcs) {
13845
+ var ii = arcs.getVertexData().ii;
13846
+ for (var j=0, n=ii.length; j<n; j++) {
13847
+ if (idx === ii[j]) return true;
13848
+ }
13849
+ return false;
13850
+ }
13851
+
13852
+ function getArcStartCoords(arcId, arcs) {
13853
+ var coords = getArcEndpointCoords(arcId, arcs);
13854
+ return coords[0];
13855
+ }
13856
+
13857
+ function getArcEndCoords(arcId, arcs) {
13858
+ var coords = getArcEndpointCoords(arcId, arcs);
13859
+ return coords[1];
13860
+ }
13861
+
13862
+ function getArcEndpointCoords(arcId, arcs) {
13863
+ if (arcId < 0) {
13864
+ return getArcEndpointCoords(~arcId, arcs).reverse();
13865
+ }
13866
+ var data = arcs.getVertexData();
13867
+ var i = data.ii[arcId];
13868
+ var n = data.nn[arcId];
13869
+ var a = [data.xx[i], data.yy[i]];
13870
+ var b = [data.xx[i + n - 1], data.yy[i + n - 1]];
13871
+ return [a, b];
13872
+ }
13873
+
13874
+ function setVertexCoords(x, y, i, arcs) {
13875
+ var data = arcs.getVertexData();
13876
+ data.xx[i] = x;
13877
+ data.yy[i] = y;
13878
+ }
13879
+
13880
+ function findNearestVertex(x, y, shp, arcs, spherical) {
13881
+ var calcLen = spherical ? geom.greatCircleDistance : geom.distance2D,
13882
+ minLen = Infinity,
13883
+ minX, minY, dist, iter;
13884
+ for (var i=0; i<shp.length; i++) {
13885
+ iter = arcs.getShapeIter(shp[i]);
13886
+ while (iter.hasNext()) {
13887
+ dist = calcLen(x, y, iter.x, iter.y);
13888
+ if (dist < minLen) {
13889
+ minLen = dist;
13890
+ minX = iter.x;
13891
+ minY = iter.y;
13892
+ }
13893
+ }
13894
+ }
13895
+ return minLen < Infinity ? {x: minX, y: minY} : null;
13896
+ }
13897
+
13898
+ var VertexUtils = /*#__PURE__*/Object.freeze({
13899
+ __proto__: null,
13900
+ findNearestVertex: findNearestVertex,
13901
+ findNearestVertices: findNearestVertices,
13902
+ findVertexIds: findVertexIds,
13903
+ getArcEndCoords: getArcEndCoords,
13904
+ getArcEndpointCoords: getArcEndpointCoords,
13905
+ getArcStartCoords: getArcStartCoords,
13906
+ getVertexCoords: getVertexCoords,
13907
+ setVertexCoords: setVertexCoords,
13908
+ snapPointToArcEndpoint: snapPointToArcEndpoint,
13909
+ snapVerticesToPoint: snapVerticesToPoint,
13910
+ vertexIsArcEnd: vertexIsArcEnd,
13911
+ vertexIsArcEndpoint: vertexIsArcEndpoint,
13912
+ vertexIsArcStart: vertexIsArcStart
13913
+ });
13914
+
12587
13915
  function isValidArc(arcId, arcs) {
12588
13916
  // check for arcs with no vertices
12589
13917
  // TODO: also check for other kinds of degenerate arcs
12590
13918
  // (e.g. collapsed arcs consisting of identical points)
12591
- return arcs.getArcLength(arcId) > 1;
13919
+ var len = arcs.getArcLength(arcId);
13920
+ if (len >= 2 === false) {
13921
+ return false;
13922
+ }
13923
+ // if (len <=3) {
13924
+ // var endpoints = getArcEndpointCoords(arcId, arcs);
13925
+ // if (pointsAreEqual(endpoints[0], endpoints[1])) {
13926
+ // return false;
13927
+ // }
13928
+ // }
13929
+ return true;
12592
13930
  }
12593
13931
 
12594
13932
  // Return id of rightmost connected arc in relation to @fromArcId
@@ -12625,6 +13963,7 @@
12625
13963
  candId = ids[j];
12626
13964
  if (!isValidArc(candId, arcs)) {
12627
13965
  // skip empty arcs
13966
+ debug('skipping one arc:', candId, 'out of:', ids.length, ids);
12628
13967
  continue;
12629
13968
  }
12630
13969
  icand = arcs.indexOfVertex(candId, -2);
@@ -12635,6 +13974,10 @@
12635
13974
  continue;
12636
13975
  }
12637
13976
  code = chooseRighthandPath(fromX, fromY, nodeX, nodeY, xx[ito], yy[ito], xx[icand], yy[icand]);
13977
+
13978
+ if (xx[ito] == xx[icand] && yy[ito] == yy[icand]) {
13979
+ debug("Pathfinder warning: duplicate segments: i:", ito, "j:", icand, "ids:", [fromArcId].concat(ids));
13980
+ }
12638
13981
  if (code == 2) {
12639
13982
  ito = icand;
12640
13983
  toArcId = candId;
@@ -12644,26 +13987,22 @@
12644
13987
 
12645
13988
  if (toArcId == fromArcId) {
12646
13989
  // This shouldn't occur, assuming that other arcs are present
12647
- error("Pathfinder error");
13990
+ error("Pathfinder error", toArcId, fromArcId);
12648
13991
  }
12649
13992
  return toArcId;
12650
13993
  }
12651
13994
 
12652
- // TODO: consider using simpler internal.chooseRighthandPath2()
12653
13995
  // Returns 1 if node->a, return 2 if node->b, else return 0
12654
13996
  // TODO: better handling of identical angles (better -- avoid creating them)
12655
13997
  function chooseRighthandPath(fromX, fromY, nodeX, nodeY, ax, ay, bx, by) {
12656
13998
  var angleA = geom.signedAngle(fromX, fromY, nodeX, nodeY, ax, ay);
12657
13999
  var angleB = geom.signedAngle(fromX, fromY, nodeX, nodeY, bx, by);
14000
+ // should use arbitrary precision math to evaluate angles smaller than this
14001
+ // (all observed errors caused by fp rounding occured with smaller angles than this)
14002
+ var smallAngle = 0.001;
12658
14003
  var code;
12659
14004
  if (angleA <= 0 || angleB <= 0) {
12660
14005
  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
14006
  // TODO: test against "from" segment
12668
14007
  if (angleA > 0) {
12669
14008
  code = 1;
@@ -12672,23 +14011,24 @@
12672
14011
  } else {
12673
14012
  code = 0;
12674
14013
  }
12675
- } else if (angleA < angleB) {
14014
+ } else if (angleA < angleB - smallAngle) {
12676
14015
  code = 1;
12677
- } else if (angleB < angleA) {
14016
+ } else if (angleB < angleA - smallAngle) {
12678
14017
  code = 2;
12679
14018
  } else if (isNaN(angleA) || isNaN(angleB)) {
12680
14019
  // probably a duplicate point, which should not occur
12681
14020
  error('Invalid node geometry');
12682
14021
  } else {
12683
- // Equal angles: use fallback test that is less sensitive to rounding error
12684
- code = chooseRighthandVector(ax - nodeX, ay - nodeY, bx - nodeX, by - nodeY);
12685
- debug('[chooseRighthandPath()] equal angles:', angleA, 'fallback test:', code);
14022
+ // Close-to-equal or equal angles: use more exact test.
14023
+ code = chooseBetweenClosePaths(nodeX, nodeY, ax, ay, bx, by);
14024
+ // debug('[chooseRighthandPath()] close-to-equal angles:', Math.abs(angleA) - Math.abs(angleB), 'hi-res code:', code);
12686
14025
  }
12687
14026
  return code;
12688
14027
  }
12689
14028
 
12690
- function chooseRighthandVector(ax, ay, bx, by) {
12691
- var orient = geom.orient2D(ax, ay, 0, 0, bx, by);
14029
+ function chooseBetweenClosePaths(nodeX, nodeY, ax, ay, bx, by) {
14030
+ // var orient = orient2D_big2(ax, ay, 0, 0, bx, by);
14031
+ var orient = orient2D_big2(ax, ay, nodeX, nodeY, bx, by);
12692
14032
  var code;
12693
14033
  if (orient > 0) {
12694
14034
  code = 2;
@@ -12702,7 +14042,7 @@
12702
14042
 
12703
14043
  var PathfinderUtils = /*#__PURE__*/Object.freeze({
12704
14044
  __proto__: null,
12705
- chooseRighthandVector: chooseRighthandVector,
14045
+ chooseBetweenClosePaths: chooseBetweenClosePaths,
12706
14046
  getRightmostArc: getRightmostArc
12707
14047
  });
12708
14048
 
@@ -13227,7 +14567,7 @@
13227
14567
  }
13228
14568
 
13229
14569
  // test two candidate segments for intersection
13230
- hit = geom.segmentIntersection(s1p1x, s1p1y, s1p2x, s1p2y,
14570
+ hit = segmentIntersection(s1p1x, s1p1y, s1p2x, s1p2y,
13231
14571
  s2p1x, s2p1y, s2p2x, s2p2y, tolerance);
13232
14572
  if (hit) {
13233
14573
  seg1 = [s1p1, s1p2];
@@ -13285,146 +14625,6 @@
13285
14625
  sortIntersections: sortIntersections
13286
14626
  });
13287
14627
 
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
14628
  // arcs: ArcCollection containing original coordinates
13429
14629
  function getRepairFunction(arcs) {
13430
14630
  var arcsOrig = arcs.getCopy();
@@ -13856,6 +15056,9 @@
13856
15056
  getSliverTest(dataset.arcs, threshold, sliverControl) :
13857
15057
  getMinAreaTest(threshold, dataset);
13858
15058
  var label = getSliverLabel(getAreaLabel(threshold, crs), sliverControl > 0);
15059
+ if (opts.keep_shapes) {
15060
+ filter = keepShapes(filter);
15061
+ }
13859
15062
  return {
13860
15063
  threshold: threshold,
13861
15064
  filter: filter,
@@ -13863,6 +15066,23 @@
13863
15066
  };
13864
15067
  }
13865
15068
 
15069
+ // wrap path filter in a function that ensures at least one part of every shape
15070
+ // is retained.
15071
+ function keepShapes(filter) {
15072
+ var flags;
15073
+ return function(path, pathId, paths) {
15074
+ // console.log("keepShapes()", path, pathId, paths)
15075
+ if (pathId === 0) {
15076
+ flags = paths.map(path => filter(path));
15077
+ }
15078
+ if (flags.length > 0 && flags.every(Boolean)) {
15079
+ // kludge ... assumes that the first path is a valid ring (e.g. not a)
15080
+ flags[0] = false;
15081
+ }
15082
+ return flags[pathId];
15083
+ };
15084
+ }
15085
+
13866
15086
  function getSliverLabel(areaStr, variable) {
13867
15087
  if (variable) {
13868
15088
  areaStr = areaStr.replace(' ', '+ ') + ' variable';
@@ -16813,9 +18033,10 @@
16813
18033
 
16814
18034
  function snapAndCut(dataset, snapDist) {
16815
18035
  var arcs = dataset.arcs;
16816
- var cutOpts = snapDist > 0 ? {} : {tolerance: 0};
18036
+ var cutOpts = snapDist > 0 ? {tolerance: snapDist} : {tolerance: 0};
16817
18037
  var coordsHaveChanged = false;
16818
18038
  var snapCount, dupeCount, cutCount;
18039
+ var maxPasses = 4, passCount = 0;
16819
18040
  snapCount = snapCoordsByInterval(arcs, snapDist);
16820
18041
  dupeCount = arcs.dedupCoords();
16821
18042
 
@@ -16827,23 +18048,28 @@
16827
18048
 
16828
18049
  // cut arcs at points where segments intersect
16829
18050
  cutCount = cutPathsAtIntersections(dataset, cutOpts);
18051
+ passCount++;
16830
18052
  if (cutCount > 0 || snapCount > 0 || dupeCount > 0) {
16831
18053
  coordsHaveChanged = true;
16832
18054
  }
16833
18055
 
16834
18056
  // perform a second snap + cut pass if needed
16835
- if (cutCount > 0) {
16836
- cutCount = 0;
18057
+ while (cutCount > 0 && passCount < maxPasses) {
18058
+ passCount++;
16837
18059
  snapCount = snapCoordsByInterval(arcs, snapDist);
16838
- arcs.dedupCoords(); // need to do this here?
16839
- if (snapCount > 0) {
18060
+ dupeCount = arcs.dedupCoords();
18061
+ // cutCount = 0;
18062
+ if (snapCount > 0 || cutCount > 0) {
16840
18063
  cutCount = cutPathsAtIntersections(dataset, cutOpts);
16841
- }
16842
- if (cutCount > 0) {
16843
- arcs.dedupCoords(); // need to do this here?
16844
- debug('Second-pass vertices added:', cutCount, 'consider third pass?');
18064
+ debug("[snapAndCut] pass:", passCount, "snaps:", snapCount, "dupes:", dupeCount, "cuts:", cutCount);
16845
18065
  }
16846
18066
  }
18067
+
18068
+ // if (cutCount > 0) {
18069
+ // arcs.dedupCoords(); // need to do this here?
18070
+ // debug('Second-pass vertices added:', cutCount, 'consider third pass?');
18071
+ // }
18072
+
16847
18073
  return coordsHaveChanged;
16848
18074
  }
16849
18075
 
@@ -17028,21 +18254,21 @@
17028
18254
  function filterSortedCutPoints(points, arcs) {
17029
18255
  var filtered = [],
17030
18256
  pointId = 0;
17031
- arcs.forEach2(function(i, n, xx, yy) {
17032
- var j = i + n - 1,
17033
- x0 = xx[i],
17034
- y0 = yy[i],
17035
- xn = xx[j],
17036
- yn = yy[j],
18257
+ arcs.forEach2(function(arcId, i, n, xx, yy) {
18258
+ var j = i + n - 1, // idx of second endpoint
18259
+ x1 = xx[i],
18260
+ y1 = yy[i],
18261
+ x2 = xx[j],
18262
+ y2 = yy[j],
17037
18263
  p, pp;
17038
18264
 
17039
18265
  while (pointId < points.length && points[pointId].i <= j) {
17040
18266
  p = points[pointId];
18267
+ pointId++;
17041
18268
  pp = filtered[filtered.length - 1]; // previous point
17042
- if (p.x == x0 && p.y == y0 || p.x == xn && p.y == yn) ; else if (pp && pp.x == p.x && pp.y == p.y && pp.i == p.i) ; else {
18269
+ 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
18270
  filtered.push(p);
17044
18271
  }
17045
- pointId++;
17046
18272
  }
17047
18273
  });
17048
18274
  return filtered;
@@ -18777,203 +20003,6 @@
18777
20003
  catalog.getDatasets().push(o);
18778
20004
  }
18779
20005
 
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
20006
  /* example patterns
18978
20007
  hatches 1px black 1px red 1px white
18979
20008
  1px black 1px red 1px white // same as above (hatches is default)
@@ -19562,7 +20591,7 @@
19562
20591
  }
19563
20592
 
19564
20593
  function importLineString(coords) {
19565
- var d = stringifyLineStringCoords(coords);
20594
+ var d = stringifyLineStringCoords$1(coords);
19566
20595
  return {
19567
20596
  tag: 'path',
19568
20597
  properties: {d: d}
@@ -19570,7 +20599,7 @@
19570
20599
  }
19571
20600
 
19572
20601
  function importMultiLineString(coords) {
19573
- var d = coords.map(stringifyLineStringCoords).join(' ');
20602
+ var d = coords.map(stringifyLineStringCoords$1).join(' ');
19574
20603
  return {
19575
20604
  tag: 'path',
19576
20605
  properties: {d: d}
@@ -19592,7 +20621,7 @@
19592
20621
  var o = {
19593
20622
  tag: 'path',
19594
20623
  properties: {
19595
- d: stringifyPolygonCoords(coords)
20624
+ d: stringifyPolygonCoords$1(coords)
19596
20625
  }
19597
20626
  };
19598
20627
  if (coords.length > 1) {
@@ -19601,6 +20630,23 @@
19601
20630
  return o;
19602
20631
  }
19603
20632
 
20633
+ function stringifyPolygonCoords$1(coords) {
20634
+ var parts = [];
20635
+ for (var i=0; i<coords.length; i++) {
20636
+ parts.push(stringifyLineStringCoords$1(coords[i]) + ' Z');
20637
+ }
20638
+ return parts.length > 0 ? parts.join(' ') : '';
20639
+ }
20640
+
20641
+ function stringifyLineStringCoords$1(coords) {
20642
+ if (coords.length === 0) return '';
20643
+ var d = 'M';
20644
+ for (var i=0, n=coords.length; i<n; i++) {
20645
+ d += ' ' + coords[i][0] + ' ' + coords[i][1];
20646
+ }
20647
+ return d;
20648
+ }
20649
+
19604
20650
  var GeojsonToSvg = /*#__PURE__*/Object.freeze({
19605
20651
  __proto__: null,
19606
20652
  flattenMultiPolygonCoords: flattenMultiPolygonCoords,
@@ -20490,7 +21536,7 @@
20490
21536
  opts = Object.assign({invert_y: true, margin: "1"}, opts);
20491
21537
  frame = getFrameData(dataset, opts);
20492
21538
  fitDatasetToFrame(dataset, frame);
20493
- setCoordinatePrecision(dataset, opts.precision || 0.0001);
21539
+ setCoordinatePrecision(dataset, opts.precision || 0.01);
20494
21540
 
20495
21541
  // error if one or more svg_data fields are not present in any layers
20496
21542
  if (opts.svg_data) validateSvgDataFields(dataset.layers, opts.svg_data);
@@ -22246,7 +23292,7 @@ ${svg}
22246
23292
  if (opts.presimplify) {
22247
23293
  fromZ = getPresimplifyFunction(bounds.width());
22248
23294
  }
22249
- arcs.forEach2(function(i, n, xx, yy, zz) {
23295
+ arcs.forEach2(function(arcId, i, n, xx, yy, zz) {
22250
23296
  var arc = [], p;
22251
23297
  for (var j=i + n; i<j; i++) {
22252
23298
  p = [xx[i], yy[i]];
@@ -24842,6 +25888,11 @@ ${svg}
24842
25888
  .option('sliver-control', sliverControlOpt)
24843
25889
  .option('snap-interval', snapIntervalOpt)
24844
25890
  .option('no-snap', noSnapOpt)
25891
+ // // not useful in -clean -- clean removes gaps, not slivers
25892
+ // .option('keep-shapes', {
25893
+ // type: 'flag',
25894
+ // describe: 'protect sliver polygons from complete removal'
25895
+ // })
24845
25896
  .option('allow-overlaps', {
24846
25897
  describe: 'allow polygons to overlap (disables gap fill)',
24847
25898
  type: 'flag'
@@ -24893,6 +25944,10 @@ ${svg}
24893
25944
  })
24894
25945
  .option('name', nameOpt)
24895
25946
  .option('no-snap', noSnapOpt)
25947
+ .option('no-cleanup', {
25948
+ // for debugging - no documentation
25949
+ type: 'flag'
25950
+ })
24896
25951
  .option('target', targetOpt)
24897
25952
  .option('no-replace', noReplaceOpt);
24898
25953
 
@@ -25128,6 +26183,10 @@ ${svg}
25128
26183
  .option('bbox', bboxOpt)
25129
26184
  .option('name', nameOpt)
25130
26185
  .option('no-snap', noSnapOpt)
26186
+ .option('no-cleanup', {
26187
+ // for debugging - no documentation
26188
+ type: 'flag'
26189
+ })
25131
26190
  .option('target', targetOpt)
25132
26191
  .option('no-replace', noReplaceOpt);
25133
26192
 
@@ -25238,6 +26297,10 @@ ${svg}
25238
26297
  type: 'flag',
25239
26298
  describe: 'delete features with null geometry'
25240
26299
  })
26300
+ .option('keep-shapes', {
26301
+ type: 'flag',
26302
+ describe: 'protect sliver polygons from complete removal'
26303
+ })
25241
26304
  .option('target', targetOpt);
25242
26305
 
25243
26306
  parser.command('graticule')
@@ -25445,9 +26508,8 @@ ${svg}
25445
26508
  .option('calc', calcOpt)
25446
26509
  .option('name', nameOpt)
25447
26510
  .option('target', targetOpt)
25448
- .option('no-snap', { // undocumented, for debugging
25449
- type: 'flag'
25450
- })
26511
+ .option('snap-interval', snapIntervalOpt)
26512
+ .option('no-snap', noSnapOpt)
25451
26513
  .option('no-replace', noReplaceOpt);
25452
26514
 
25453
26515
  parser.command('point-grid')
@@ -29843,6 +30905,158 @@ ${svg}
29843
30905
  stop('Unable to import coordinates');
29844
30906
  }
29845
30907
 
30908
+ // Apply rotation, scale and/or shift to some or all of the features in a dataset
30909
+ //
30910
+ cmd.affine = function(targetLayers, dataset, opts) {
30911
+ // Need to separate the targeted shapes from any other shapes that share
30912
+ // the same topology. So we duplicate any arcs that are shared by the targeted
30913
+ // shapes and their topological neighbors and remap arc references in the
30914
+ // neighbors to point to the copies.
30915
+ // TODO: explore alternative: if some arcs are shared between transformed and
30916
+ // non-transformed shapes, first remove topology, then tranform, then rebuild topology
30917
+ //
30918
+ var rotateArg = opts.rotate || 0;
30919
+ var scaleArg = opts.scale || 1;
30920
+ var shiftArg = opts.shift ? convertIntervalPair(opts.shift, getDatasetCRS(dataset)) : [0, 0];
30921
+ var arcs = dataset.arcs;
30922
+ var targetShapes = [];
30923
+ var otherShapes = [];
30924
+ var targetPoints = [];
30925
+ var targetFlags, otherFlags, transform;
30926
+ dataset.layers.filter(layerHasGeometry).forEach(function(lyr) {
30927
+ var hits = [],
30928
+ misses = [],
30929
+ test;
30930
+ if (targetLayers.indexOf(lyr) == -1) {
30931
+ misses = lyr.shapes;
30932
+ } else if (opts.where) {
30933
+ test = compileFeatureExpression(opts.where, lyr, dataset.arcs);
30934
+ lyr.shapes.forEach(function(shp, i) {
30935
+ (test(i) ? hits : misses).push(shp);
30936
+ });
30937
+ } else {
30938
+ hits = lyr.shapes;
30939
+ }
30940
+ if (lyr.geometry_type == 'point') {
30941
+ targetPoints = targetPoints.concat(hits);
30942
+ } else {
30943
+ targetShapes = targetShapes.concat(hits);
30944
+ otherShapes = otherShapes.concat(misses);
30945
+ }
30946
+ });
30947
+ var anchorArg = getAffineAnchor({arcs: dataset.arcs, layers: [{
30948
+ geometry_type: 'point', shapes: targetPoints}, {geometry_type: 'polyline',
30949
+ shapes: targetShapes}]}, opts);
30950
+ transform = getAffineTransform(rotateArg, scaleArg, shiftArg, anchorArg);
30951
+ if (opts.fit_bbox) {
30952
+ transform = getFitBoxTransform(opts.fit_bbox, targetPoints, targetShapes, arcs);
30953
+ }
30954
+ if (targetShapes.length > 0) {
30955
+ targetFlags = new Uint8Array(arcs.size());
30956
+ otherFlags = new Uint8Array(arcs.size());
30957
+ countArcsInShapes(targetShapes, targetFlags);
30958
+ if (otherShapes.length > 0) {
30959
+ countArcsInShapes(otherShapes, otherFlags);
30960
+ applyArrayMask(otherFlags, targetFlags);
30961
+ dataset.arcs = duplicateSelectedArcs(otherShapes, arcs, otherFlags);
30962
+ }
30963
+ dataset.arcs.transformPoints(function(x, y, arcId) {
30964
+ if (arcId < targetFlags.length && targetFlags[arcId] > 0) {
30965
+ return transform(x, y);
30966
+ }
30967
+ });
30968
+ }
30969
+ forEachPoint(targetPoints, function(p) {
30970
+ var p2 = transform(p[0], p[1]);
30971
+ p[0] = p2[0];
30972
+ p[1] = p2[1];
30973
+ });
30974
+ };
30975
+
30976
+ function getAffineAnchor(dataset, opts) {
30977
+ var anchor, bounds;
30978
+ if (opts.anchor) {
30979
+ anchor = opts.anchor;
30980
+ } else {
30981
+ // get bounds of selected shapes to calculate center of rotation/scale
30982
+ bounds = getDatasetBounds(dataset);
30983
+ anchor = [bounds.centerX(), bounds.centerY()];
30984
+ }
30985
+ return anchor;
30986
+ }
30987
+
30988
+ // TODO: handle problems with unprojected datasets
30989
+ // option 1: don't allow affine transformation of unprojected data
30990
+ // option 2: error if transformed data exceeds valid coordinate range
30991
+ // source: http://mathworld.wolfram.com/AffineTransformation.html
30992
+ function getAffineTransform(rotation, scale, shift, anchor) {
30993
+ var angle = rotation * Math.PI / 180;
30994
+ var a = scale * Math.cos(angle);
30995
+ var b = -scale * Math.sin(angle);
30996
+ return function(x, y) {
30997
+ var x2 = a * (x - anchor[0]) - b * (y - anchor[1]) + shift[0] + anchor[0];
30998
+ var y2 = b * (x - anchor[0]) + a * (y - anchor[1]) + shift[1] + anchor[1];
30999
+ return [x2, y2];
31000
+ };
31001
+ }
31002
+
31003
+ function getFitBoxTransform(bbox, points, shapes, arcs) {
31004
+ var dataset = {
31005
+ arcs: arcs,
31006
+ layers: []
31007
+ };
31008
+ if (points && points.length) {
31009
+ dataset.layers.push({
31010
+ geometry_type: 'point',
31011
+ shapes: points
31012
+ });
31013
+ }
31014
+ if (shapes && shapes.length) {
31015
+ dataset.layers.push({
31016
+ geometry_type: 'polyline',
31017
+ shapes: shapes
31018
+ });
31019
+ }
31020
+ var frame = calcFrameData(dataset, {fit_bbox: bbox});
31021
+ var fromBounds = new Bounds(frame.bbox);
31022
+ var toBounds = new Bounds(frame.bbox2);
31023
+ var fwd = fromBounds.getTransform(toBounds, false);
31024
+ return function(x, y) {
31025
+ return fwd.transform(x, y);
31026
+ };
31027
+ }
31028
+
31029
+ function applyArrayMask(destArr, maskArr) {
31030
+ for (var i=0, n=destArr.length; i<n; i++) {
31031
+ if (maskArr[i] === 0) destArr[i] = 0;
31032
+ }
31033
+ }
31034
+
31035
+ function duplicateSelectedArcs(shapes, arcs, flags) {
31036
+ var arcCount = 0;
31037
+ var vertexCount = 0;
31038
+ var data = arcs.getVertexData();
31039
+ var xx = [], yy = [], nn = [], map = [], n;
31040
+ for (var i=0, len=flags.length; i<len; i++) {
31041
+ if (flags[i] > 0) {
31042
+ map[i] = arcs.size() + arcCount;
31043
+ n = data.nn[i];
31044
+ utils.copyElements(data.xx, data.ii[i], xx, vertexCount, n);
31045
+ utils.copyElements(data.yy, data.ii[i], yy, vertexCount, n);
31046
+ nn.push(n);
31047
+ vertexCount += n;
31048
+ arcCount++;
31049
+ }
31050
+ }
31051
+ forEachArcId(shapes, function(id) {
31052
+ var absId = absArcId(id);
31053
+ if (flags[absId] > 0) {
31054
+ return id < 0 ? ~map[absId] : map[absId];
31055
+ }
31056
+ });
31057
+ return mergeArcs([arcs, new ArcCollection(nn, xx, yy)]);
31058
+ }
31059
+
29846
31060
  const epsilon = 1.1102230246251565e-16;
29847
31061
  const splitter = 134217729;
29848
31062
  const resulterrbound = (3 + 8 * epsilon) * epsilon;
@@ -34876,7 +36090,7 @@ ${svg}
34876
36090
  var ringTest = filterData.filter;
34877
36091
  var removed = 0;
34878
36092
  var pathFilter = function(path, i, paths) {
34879
- if (ringTest(path)) {
36093
+ if (ringTest(path, i, paths)) {
34880
36094
  removed++;
34881
36095
  return null;
34882
36096
  }
@@ -35016,6 +36230,7 @@ ${svg}
35016
36230
  // or overlapping rings. TODO: try to optimize or remove it for all cases
35017
36231
 
35018
36232
  // skipping shape cleanup when using the experimental fast bbox clipping option
36233
+ // if (!opts.bbox2 && !opts.no_cleanup) {
35019
36234
  if (!opts.bbox2) {
35020
36235
  // clean each target polygon by dissolving its rings
35021
36236
  targetShapes = targetShapes.map(dissolvePolygon);
@@ -35512,7 +36727,7 @@ ${svg}
35512
36727
  } else {
35513
36728
  nodes = new NodeCollection(mergedDataset.arcs);
35514
36729
  }
35515
- // clipLyr = mergedDataset.layers.pop();
36730
+
35516
36731
  return clipLayersByLayer(targetLayers, clipLyr, nodes, type, opts);
35517
36732
  }
35518
36733
 
@@ -40504,7 +41719,7 @@ ${svg}
40504
41719
 
40505
41720
  function createMeridianPart(x, ymin, ymax) {
40506
41721
  var coords = densifyPathByInterval([[x, ymin], [x, ymax]], precision);
40507
- meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord$1(x)}));
41722
+ meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord$2(x)}));
40508
41723
  }
40509
41724
 
40510
41725
  function createParallel(y) {
@@ -40514,7 +41729,7 @@ ${svg}
40514
41729
  }
40515
41730
 
40516
41731
  // remove tiny offsets
40517
- function roundCoord$1(x) {
41732
+ function roundCoord$2(x) {
40518
41733
  return +x.toFixed(3) || 0;
40519
41734
  }
40520
41735
 
@@ -44613,7 +45828,7 @@ ${svg}
44613
45828
  });
44614
45829
  };
44615
45830
 
44616
- var roundCoord = getRoundingFunction(0.01);
45831
+ var roundCoord$1 = getRoundingFunction(0.01);
44617
45832
 
44618
45833
  function getSymbolFillColor(d) {
44619
45834
  return d.fill || 'magenta';
@@ -44664,8 +45879,8 @@ ${svg}
44664
45879
 
44665
45880
  function roundCoordsForSVG(coords) {
44666
45881
  forEachSymbolCoord(coords, function(p) {
44667
- p[0] = roundCoord(p[0]);
44668
- p[1] = roundCoord(p[1]);
45882
+ p[0] = roundCoord$1(p[0]);
45883
+ p[1] = roundCoord$1(p[1]);
44669
45884
  });
44670
45885
  }
44671
45886
 
@@ -45390,7 +46605,7 @@ ${svg}
45390
46605
  // Filter arcs based on an array of thresholds
45391
46606
  function applyArcThresholds(arcs, thresholds) {
45392
46607
  arcs.getVertexData().zz;
45393
- arcs.forEach2(function(start, n, xx, yy, zz, arcId) {
46608
+ arcs.forEach2(function(arcId, start, n, xx, yy, zz) {
45394
46609
  var arcZ = thresholds[arcId];
45395
46610
  var z;
45396
46611
  for (var i=1; i<n-1; i++) {
@@ -46044,7 +47259,7 @@ ${svg}
46044
47259
  });
46045
47260
  }
46046
47261
 
46047
- var version = "0.6.109";
47262
+ var version = "0.6.110";
46048
47263
 
46049
47264
  // Parse command line args into commands and run them
46050
47265
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -46376,6 +47591,51 @@ ${svg}
46376
47591
  testCommands: testCommands
46377
47592
  });
46378
47593
 
47594
+ var roundCoord = getRoundingFunction(0.01);
47595
+
47596
+ function stringifyVertex(p) {
47597
+ return ' ' + roundCoord(p[0]) + ' ' + roundCoord(p[1]);
47598
+ }
47599
+
47600
+ function isCubicCtrl(p) {
47601
+ return p.length > 2 && p[2] == 'C';
47602
+ }
47603
+
47604
+ function stringifyPolygonCoords(coords) {
47605
+ var parts = [];
47606
+ for (var i=0; i<coords.length; i++) {
47607
+ parts.push(stringifyLineStringCoords(coords[i]) + ' Z');
47608
+ }
47609
+ return parts.length > 0 ? parts.join(' ') : '';
47610
+ }
47611
+
47612
+ function stringifyLineStringCoords(coords) {
47613
+ if (coords.length === 0) return '';
47614
+ var d = 'M';
47615
+ var fromCurve = false;
47616
+ var p, i, n;
47617
+ for (i=0, n=coords.length; i<n; i++) {
47618
+ p = coords[i];
47619
+ if (isCubicCtrl(p)) {
47620
+ // TODO: add defensive check
47621
+ d += ' C' + stringifyVertex(p) + stringifyVertex(coords[++i]) + stringifyVertex(coords[++i]);
47622
+ fromCurve = true;
47623
+ } else if (fromCurve) {
47624
+ d += ' L' + stringifyVertex(p);
47625
+ fromCurve = false;
47626
+ } else {
47627
+ d += stringifyVertex(p);
47628
+ }
47629
+ }
47630
+ return d;
47631
+ }
47632
+
47633
+ var SvgPathUtils = /*#__PURE__*/Object.freeze({
47634
+ __proto__: null,
47635
+ stringifyLineStringCoords: stringifyLineStringCoords,
47636
+ stringifyPolygonCoords: stringifyPolygonCoords
47637
+ });
47638
+
46379
47639
  // Return an array containing points from a path iterator, clipped to a bounding box
46380
47640
  // Currently using this function for clipping styled polygons in the GUI to speed up layer rendering.
46381
47641
  // Artifacts along the edges make this unsuitable for clipping datasets