mapshaper 0.6.110 → 0.6.112

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
@@ -1211,8 +1211,13 @@
1211
1211
  if (len >= 2) {
1212
1212
  first = str.charAt(0);
1213
1213
  last = str.charAt(len-1);
1214
- if (first == '"' && last == '"' && !str.includes('","') ||
1215
- first == "'" && last == "'" && !str.includes("','")) {
1214
+ // if (first == '"' && last == '"' && !str.includes('","') ||
1215
+ // first == "'" && last == "'" && !str.includes("','")) {
1216
+ // don't strip if there are unescaped quotes
1217
+ // e.g. expressions that start and end with quotes
1218
+ // e.g. comma-separated list of quoted values
1219
+ if (first == '"' && last == '"' && !/[^\\]"./.test(str) ||
1220
+ first == "'" && last == "'" && !/[^\\]'./.test(str)) {
1216
1221
  str = str.substr(1, len-2);
1217
1222
  // remove string escapes
1218
1223
  str = str.replace(first == '"' ? /\\(?=")/g : /\\(?=')/g, '');
@@ -1347,6 +1352,18 @@
1347
1352
  }
1348
1353
  }
1349
1354
 
1355
+ function time(slug) {
1356
+ if (useDebug()) {
1357
+ console.time(slug);
1358
+ }
1359
+ }
1360
+
1361
+ function timeEnd(slug) {
1362
+ if (useDebug()) {
1363
+ console.timeEnd(slug);
1364
+ }
1365
+ }
1366
+
1350
1367
  function printError(err) {
1351
1368
  var msg;
1352
1369
  if (!LOGGING) return;
@@ -1465,6 +1482,8 @@
1465
1482
  setLoggingForCLI: setLoggingForCLI,
1466
1483
  setLoggingFunctions: setLoggingFunctions,
1467
1484
  stop: stop,
1485
+ time: time,
1486
+ timeEnd: timeEnd,
1468
1487
  truncateString: truncateString,
1469
1488
  useDebug: useDebug,
1470
1489
  useVerbose: useVerbose,
@@ -3113,1037 +3132,70 @@
3113
3132
  snapEndpointsByInterval: snapEndpointsByInterval
3114
3133
  });
3115
3134
 
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_();
3135
+ function toScaledStr(num, k) {
3136
+ var abs = Math.abs(num);
3137
+ var signed = num != abs;
3138
+ // String() matches behavior of big.js
3139
+ // (as opposed to toFixed() or toPrecision())
3140
+ var s = abs < 1e-6 ? abs.toFixed(k) : String(abs);
3141
+ var parts = s.split('.');
3142
+ var integer = parts[0] == '0' ? '' : parts[0];
3143
+ var decimal = parts.length == 2 ? parts[1] : '';
3144
+ if (decimal.length < k) {
3145
+ decimal = decimal.padEnd(k, '0');
3146
+ } else if (decimal.length > k) {
3147
+ decimal = decimal.slice(0, k);
3148
+ }
3149
+ var s2 = integer + decimal;
3150
+ // remove any leading 0s
3151
+ while (s2[0] == 0 && s2.length > 1) {
3152
+ s2 = s2.slice(1);
3153
+ }
3154
+ if (signed) {
3155
+ s2 = '-' + s2;
3156
+ }
3157
+ return s2;
3158
+ }
3159
+
3160
+ // returns Number
3161
+ function fromScaledStr(s, decimals) {
3162
+ var signed = s[0] == '-';
3163
+ var uns = signed ? s.slice(1) : s;
3164
+ var s2, len;
3165
+ len = uns.length;
3166
+ if (len > decimals) {
3167
+ s2 = uns.slice(0, len - decimals) + '.' + uns.slice(-decimals);
3168
+ } else if (len == decimals) {
3169
+ s2 = '0.' + uns;
3170
+ } else {
3171
+ s2 = '0.' + uns.padStart(decimals, '0');
3172
+ }
3173
+ if (signed) {
3174
+ s2 = '-' + s2;
3175
+ }
3176
+ return Number(s2);
3177
+ }
3178
+
3179
+ function findBigIntScaleFactor() {
3180
+ var minVal = Infinity;
3181
+ var intLen = 0, s;
3182
+ for (var i=0, n=arguments.length; i<n; i++) {
3183
+ minVal = Math.min(minVal, Math.abs(arguments[i]));
3184
+ }
3185
+ if (minVal >= 1) {
3186
+ s = minVal.toFixed(1);
3187
+ intLen = s.indexOf('.');
3188
+ } else if (minVal !== 0) {
3189
+ s = minVal.toFixed(10).slice(2); // decimal part, up to _ decimals
3190
+ while (s[0] === '0') {
3191
+ intLen--;
3192
+ s = s.slice(1);
3193
+ }
3194
+ }
3195
+ return 17 - intLen;
3196
+ }
3197
+
3198
+ //import { findCrossIntersection_big } from '../geom/mapshaper-segment-geom-big';
4147
3199
 
4148
3200
  // Find the intersection between two 2D segments
4149
3201
  // Returns 0, 1 or 2 [x, y] locations as null, [x, y], or [x1, y1, x2, y2]
@@ -4179,18 +3231,31 @@
4179
3231
  return touches || cross || null;
4180
3232
  }
4181
3233
 
4182
-
4183
3234
  function findCrossIntersection(ax, ay, bx, by, cx, cy, dx, dy, eps) {
4184
3235
  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;
3236
+ // The normal-precision hit function works for all inputs when eps > 0 because
3237
+ // the geometries that cause the ordinary function fails are detected as
3238
+ // 'touches' or endpoint hits (at least this was true in all the real-world
3239
+ // data samples that were tested).
3240
+ //
3241
+ if (eps > 0 && !segmentHit_fast(ax, ay, bx, by, cx, cy, dx, dy)) {
3242
+ return null;
3243
+ } else if (eps === 0 && !segmentHit_robust(ax, ay, bx, by, cx, cy, dx, dy)) {
3244
+ return null;
3245
+ }
4187
3246
 
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)
3247
+ // in a typical layer with many intersections along shared polygon boundaries,
3248
+ // robust is preferred in most (>90%) segment intersections in order to keep
3249
+ // the positional error within a small interval (e.g. 50% of eps)
4191
3250
  //
4192
3251
  if (useRobustCross(ax, ay, bx, by, cx, cy, dx, dy)) {
4193
3252
  p = findCrossIntersection_robust(ax, ay, bx, by, cx, cy, dx, dy);
3253
+ // var p2 = findCrossIntersection_big(ax, ay, bx, by, cx, cy, dx, dy);
3254
+ // var dx = p[0] - p2[0];
3255
+ // var dy = p[1] - p2[1];
3256
+ // if (dx != 0 || dy != 0) {
3257
+ // console.log(dx, dy)
3258
+ // }
4194
3259
  } else {
4195
3260
  p = findCrossIntersection_fast(ax, ay, bx, by, cx, cy, dx, dy);
4196
3261
  }
@@ -4210,41 +3275,6 @@
4210
3275
  return p;
4211
3276
  }
4212
3277
 
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
3278
  function findCrossIntersection_fast(ax, ay, bx, by, cx, cy, dx, dy) {
4249
3279
  var den = determinant2D(bx - ax, by - ay, dx - cx, dy - cy);
4250
3280
  var m = orient2D(cx, cy, dx, dy, ax, ay) / den;
@@ -4259,6 +3289,33 @@
4259
3289
  return p;
4260
3290
  }
4261
3291
 
3292
+ // this function, using BigInt, is 3-4x faster than the version using big.js
3293
+ function findCrossIntersection_robust(ax, ay, bx, by, cx, cy, dx, dy) {
3294
+ var d = findBigIntScaleFactor(ax, ay, bx, by, cx, cy, dx, dy);
3295
+ var d2 = 16; // scale numerator of integer division by this many decimal digits
3296
+ var k_bi = 10000000000000000n; // matches d2
3297
+ var ax_bi = BigInt(toScaledStr(ax, d));
3298
+ var ay_bi = BigInt(toScaledStr(ay, d));
3299
+ var bx_bi = BigInt(toScaledStr(bx, d));
3300
+ var by_bi = BigInt(toScaledStr(by, d));
3301
+ var cx_bi = BigInt(toScaledStr(cx, d));
3302
+ var cy_bi = BigInt(toScaledStr(cy, d));
3303
+ var dx_bi = BigInt(toScaledStr(dx, d));
3304
+ var dy_bi = BigInt(toScaledStr(dy, d));
3305
+ var den = determinant2D(bx_bi - ax_bi, by_bi - ay_bi, dx_bi - cx_bi, dy_bi - cy_bi);
3306
+ if (den === 0n) {
3307
+ debug('DIV0 error - should have been identified as collinear "touch" intersection.');
3308
+ return null;
3309
+ }
3310
+ var num = orient2D(cx_bi, cy_bi, dx_bi, dy_bi, ax_bi, ay_bi) * k_bi;
3311
+ var m_bi = num / den;
3312
+ var x_bi = ax_bi * k_bi + m_bi * (bx_bi - ax_bi);
3313
+ var y_bi = ay_bi * k_bi + m_bi * (by_bi - ay_bi);
3314
+ var x = fromScaledStr(x_bi.toString(), d + d2);
3315
+ var y = fromScaledStr(y_bi.toString(), d + d2);
3316
+ return [x, y];
3317
+ }
3318
+
4262
3319
  function useRobustCross(ax, ay, bx, by, cx, cy, dx, dy) {
4263
3320
  // angle and seg length ratio thresholds were found by comparing
4264
3321
  // fast and robust outputs on sample data
@@ -4270,7 +3327,7 @@
4270
3327
  return false;
4271
3328
  }
4272
3329
 
4273
- // Returns smaller unsigned angle between two segments
3330
+ // Returns smaller of two angles between two segments (unsigned)
4274
3331
  function innerAngle(ax, ay, bx, by, cx, cy, dx, dy) {
4275
3332
  var v1x = bx - ax;
4276
3333
  var v1y = by - ay;
@@ -4419,18 +3476,6 @@
4419
3476
  p[1] = y;
4420
3477
  }
4421
3478
 
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
3479
 
4435
3480
  // a: coordinate of point
4436
3481
  // b: endpoint coordinate of segment
@@ -4463,10 +3508,6 @@
4463
3508
  return a * d - b * c;
4464
3509
  }
4465
3510
 
4466
- function determinant2D_big(a, b, c, d) {
4467
- return a.times(d).minus(b.times(c));
4468
- }
4469
-
4470
3511
  // returns a positive value if the points a, b, and c are arranged in
4471
3512
  // counterclockwise order, a negative value if the points are in clockwise
4472
3513
  // order, and zero if the points are collinear.
@@ -4475,22 +3516,31 @@
4475
3516
  return determinant2D(ax - cx, ay - cy, bx - cx, by - cy);
4476
3517
  }
4477
3518
 
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);
3519
+ function orient2D_robust(ax, ay, bx, by, cx, cy) {
3520
+ var d = findBigIntScaleFactor(ax, ay, bx, by, cx, cy);
3521
+ var ax_bi = BigInt(toScaledStr(ax, d));
3522
+ var ay_bi = BigInt(toScaledStr(ay, d));
3523
+ var bx_bi = BigInt(toScaledStr(bx, d));
3524
+ var by_bi = BigInt(toScaledStr(by, d));
3525
+ var cx_bi = BigInt(toScaledStr(cx, d));
3526
+ var cy_bi = BigInt(toScaledStr(cy, d));
3527
+ var o2d_bi = orient2D(ax_bi, ay_bi, bx_bi, by_bi, cx_bi, cy_bi);
3528
+ return fromScaledStr(o2d_bi.toString(), d);
4482
3529
  }
4483
3530
 
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));
3531
+ function segmentHit_robust(ax, ay, bx, by, cx, cy, dx, dy) {
3532
+ var d = findBigIntScaleFactor(ax, ay, bx, by, cx, cy, dx, dy);
3533
+ var ax_bi = BigInt(toScaledStr(ax, d));
3534
+ var ay_bi = BigInt(toScaledStr(ay, d));
3535
+ var bx_bi = BigInt(toScaledStr(bx, d));
3536
+ var by_bi = BigInt(toScaledStr(by, d));
3537
+ var cx_bi = BigInt(toScaledStr(cx, d));
3538
+ var cy_bi = BigInt(toScaledStr(cy, d));
3539
+ var dx_bi = BigInt(toScaledStr(dx, d));
3540
+ var dy_bi = BigInt(toScaledStr(dy, d));
3541
+ return segmentHit_fast(ax_bi, ay_bi, bx_bi, by_bi, cx_bi, cy_bi, dx_bi, dy_bi);
4486
3542
  }
4487
3543
 
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
-
4494
3544
  // Source: Sedgewick, _Algorithms in C_
4495
3545
  // (Other functions were tried that were more sensitive to floating point errors
4496
3546
  // than this function)
@@ -4501,25 +3551,6 @@
4501
3551
  orient2D(cx, cy, dx, dy, bx, by) <= 0;
4502
3552
  }
4503
3553
 
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
-
4523
3554
  // Useful for determining if a segment that intersects another segment is
4524
3555
  // entering or leaving an enclosed buffer area
4525
3556
  // returns -1 if angle of p1p2 -> p3p4 is counter-clockwise (left turn)
@@ -4543,11 +3574,9 @@
4543
3574
  var SegmentGeom = /*#__PURE__*/Object.freeze({
4544
3575
  __proto__: null,
4545
3576
  findClosestPointOnSeg: findClosestPointOnSeg,
3577
+ findCrossIntersection_robust: findCrossIntersection_robust,
4546
3578
  orient2D: orient2D,
4547
- orient2D_big: orient2D_big,
4548
- orient2D_big2: orient2D_big2,
4549
- segmentHit_big: segmentHit_big,
4550
- segmentHit_big2: segmentHit_big2,
3579
+ orient2D_robust: orient2D_robust,
4551
3580
  segmentHit_fast: segmentHit_fast,
4552
3581
  segmentIntersection: segmentIntersection,
4553
3582
  segmentTurn: segmentTurn
@@ -4878,10 +3907,12 @@
4878
3907
  for (var i=0; i<n; i++) {
4879
3908
  retn = cb(parts[i], i, parts, shpId);
4880
3909
  if (retn === null) {
4881
- nulls++;
4882
3910
  parts[i] = null;
4883
3911
  } else if (utils.isArray(retn)) {
4884
- parts[i] = retn;
3912
+ parts[i] = retn.length > 0 ? retn : null;
3913
+ }
3914
+ if (parts[i] === null) {
3915
+ nulls++;
4885
3916
  }
4886
3917
  }
4887
3918
  if (nulls == n) {
@@ -7058,10 +6089,11 @@
7058
6089
  };
7059
6090
 
7060
6091
  this.arcIsDegenerate = function(arcId) {
7061
- return this.arcHasZeroLength(arcId) || this.arcIsSpike(arcId);
6092
+ return this.arcHasZeroLength(arcId) || this.arcIsSpike(arcId) || this.arcIsSpike_v1(arcId);
7062
6093
  };
7063
6094
 
7064
- this.arcIsSpike = function(arcId) {
6095
+ // only finds two-segment spikes
6096
+ this.arcIsSpike_v1 = function(arcId) {
7065
6097
  var iter = this.getArcIter(arcId);
7066
6098
  var x0, y0;
7067
6099
  if (iter.hasNext()) {
@@ -7078,6 +6110,22 @@
7078
6110
  return false;
7079
6111
  };
7080
6112
 
6113
+ // identifies n-segment spikes
6114
+ this.arcIsSpike = function(arcId) {
6115
+ arcId = absArcId(arcId);
6116
+ var i = _ii[arcId];
6117
+ var j = i + _nn[arcId] - 1;
6118
+ while (j > i) {
6119
+ if (_xx[i] != _xx[j] || _yy[i] != _yy[j]) {
6120
+ return false;
6121
+ }
6122
+ j--;
6123
+ i++;
6124
+ }
6125
+ return true;
6126
+ };
6127
+
6128
+
7081
6129
  this.arcHasZeroLength = function(arcId) {
7082
6130
  var iter = this.getArcIter(arcId);
7083
6131
  var i = 0,
@@ -8512,7 +7560,15 @@
8512
7560
  }
8513
7561
 
8514
7562
  function mergeOutputLayerIntoDataset(lyr, dataset, dataset2, opts) {
8515
- if (!dataset2 || dataset2.layers.length != 1) {
7563
+ var output = mergeOutputLayersIntoDataset(lyr, dataset, dataset2, opts);
7564
+ if (output.length != 1) {
7565
+ error('Expected 1 output layer, received:', output.length);
7566
+ }
7567
+ return output[0];
7568
+ }
7569
+
7570
+ function mergeOutputLayersIntoDataset(lyr, dataset, dataset2, opts) {
7571
+ if (!dataset2 || dataset2.layers.length === 0) {
8516
7572
  error('Invalid source dataset');
8517
7573
  }
8518
7574
  if (dataset.layers.includes(lyr) === false) {
@@ -8521,24 +7577,21 @@
8521
7577
  // this command returns merged layers instead of adding them to target dataset
8522
7578
  var outputLayers = mergeDatasetsIntoDataset(dataset, [dataset2]);
8523
7579
  var lyr2 = outputLayers[0];
8524
-
8525
7580
  // TODO: find a more reliable way of knowing when to copy data
8526
7581
  var copyData = !lyr2.data && lyr.data && getFeatureCount(lyr2) == lyr.data.size();
8527
-
8528
7582
  if (copyData) {
8529
7583
  lyr2.data = opts.no_replace ? lyr.data.clone() : lyr.data;
8530
7584
  }
8531
- if (opts.no_replace) ; else {
8532
- lyr2 = Object.assign(lyr, {data: null, shapes: null}, lyr2);
7585
+ lyr2.name = opts.name || lyr.name;
7586
+ if (!opts.no_replace) {
7587
+ outputLayers[0] = Object.assign(lyr, {data: null, shapes: null}, lyr2);
8533
7588
  if (layerHasPaths(lyr)) {
8534
7589
  // Remove unused arcs from replaced layer
8535
7590
  // TODO: consider using clean insead of this
8536
7591
  dissolveArcs(dataset);
8537
7592
  }
8538
7593
  }
8539
-
8540
- lyr2.name = opts.name || lyr2.name;
8541
- return lyr2;
7594
+ return outputLayers;
8542
7595
  }
8543
7596
 
8544
7597
  // Transform the points in a dataset in-place; don't clean up corrupted shapes
@@ -8566,6 +7619,7 @@
8566
7619
  getDatasetBounds: getDatasetBounds,
8567
7620
  mergeDatasetInfo: mergeDatasetInfo,
8568
7621
  mergeOutputLayerIntoDataset: mergeOutputLayerIntoDataset,
7622
+ mergeOutputLayersIntoDataset: mergeOutputLayersIntoDataset,
8569
7623
  pruneArcs: pruneArcs,
8570
7624
  replaceLayerContents: replaceLayerContents,
8571
7625
  replaceLayers: replaceLayers,
@@ -12521,7 +11575,7 @@
12521
11575
  shapes: lyr.shapes || null,
12522
11576
  data: data,
12523
11577
  menu_order: lyr.menu_order || null,
12524
- pinned: lyr.pinned || false,
11578
+ pinned: lyr.pinned || opts.show_all || false,
12525
11579
  active: !!(lyr.active || lyr == opts.active_layer) // lyr.active: deprecated
12526
11580
  };
12527
11581
  }
@@ -13912,6 +12966,23 @@
13912
12966
  vertexIsArcStart: vertexIsArcStart
13913
12967
  });
13914
12968
 
12969
+ function debugConnectedArcs(ids, arcs) {
12970
+ var colors = ['orange', 'blue', 'green', 'red', 'magenta', 'grey'];
12971
+ var features = ids.map(function(arcId, i) {
12972
+ return getArcFeature(arcId, arcs, {arcId: arcId, stroke: colors[i] || 'black'});
12973
+ });
12974
+ var geojson = '{"type": "FeatureCollection", "features": [' + features.join(',') + ']}';
12975
+ debug(geojson);
12976
+ }
12977
+
12978
+ function getArcFeature(arcId, arcs, properties) {
12979
+ return JSON.stringify({
12980
+ type: "Feature",
12981
+ properties: properties,
12982
+ geometry: GeoJSON.exportLineGeom([[arcId]], arcs)
12983
+ });
12984
+ }
12985
+
13915
12986
  function isValidArc(arcId, arcs) {
13916
12987
  // check for arcs with no vertices
13917
12988
  // TODO: also check for other kinds of degenerate arcs
@@ -13958,7 +13029,6 @@
13958
13029
  error("Duplicate point error");
13959
13030
  }*/
13960
13031
 
13961
-
13962
13032
  for (j=0; j<ids.length; j++) {
13963
13033
  candId = ids[j];
13964
13034
  if (!isValidArc(candId, arcs)) {
@@ -13977,6 +13047,10 @@
13977
13047
 
13978
13048
  if (xx[ito] == xx[icand] && yy[ito] == yy[icand]) {
13979
13049
  debug("Pathfinder warning: duplicate segments: i:", ito, "j:", icand, "ids:", [fromArcId].concat(ids));
13050
+ if (useDebug()) {
13051
+ // debugDuplicatePathfinderSegments(nodeX, nodeY, ito, icand, nodes.arcs);
13052
+ debugConnectedArcs(ids, nodes.arcs);
13053
+ }
13980
13054
  }
13981
13055
  if (code == 2) {
13982
13056
  ito = icand;
@@ -14027,8 +13101,7 @@
14027
13101
  }
14028
13102
 
14029
13103
  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);
13104
+ var orient = orient2D_robust(ax, ay, nodeX, nodeY, bx, by);
14032
13105
  var code;
14033
13106
  if (orient > 0) {
14034
13107
  code = 2;
@@ -18016,17 +17089,14 @@
18016
17089
  if (changed || opts.rebuild_topology) {
18017
17090
  buildTopology(dataset);
18018
17091
  }
18019
-
18020
- // Clean shapes by removing collapsed arc references, etc.
18021
- // TODO: consider alternative -- avoid creating degenerate arcs
18022
- // in insertCutPoints()
17092
+ // Remove degenerate shapes
17093
+ // Without this step, pathfinder function would encounter dead ends.
18023
17094
  dataset.layers.forEach(function(lyr) {
18024
17095
  if (layerHasPaths(lyr)) {
18025
17096
  cleanShapes(lyr.shapes, arcs, lyr.geometry_type);
18026
17097
  }
18027
17098
  });
18028
-
18029
- // Further clean-up -- remove duplicate and missing arcs
17099
+ // Further clean-up -- remove duplicate and unused arcs, etc.
18030
17100
  nodes = cleanArcReferences(dataset);
18031
17101
  return nodes;
18032
17102
  }
@@ -18036,38 +17106,26 @@
18036
17106
  var cutOpts = snapDist > 0 ? {tolerance: snapDist} : {tolerance: 0};
18037
17107
  var coordsHaveChanged = false;
18038
17108
  var snapCount, dupeCount, cutCount;
18039
- var maxPasses = 4, passCount = 0;
18040
- snapCount = snapCoordsByInterval(arcs, snapDist);
18041
- dupeCount = arcs.dedupCoords();
18042
-
18043
- // why was topology built here previously????
18044
- // if (snapCount > 0 || dupeCount > 0) {
18045
- // // Detect topology again if coordinates have changed
18046
- // internal.buildTopology(dataset);
18047
- // }
18048
-
18049
- // cut arcs at points where segments intersect
18050
- cutCount = cutPathsAtIntersections(dataset, cutOpts);
18051
- passCount++;
18052
- if (cutCount > 0 || snapCount > 0 || dupeCount > 0) {
18053
- coordsHaveChanged = true;
18054
- }
17109
+ var maxLoops = 4, loopCount = 0;
18055
17110
 
18056
- // perform a second snap + cut pass if needed
18057
- while (cutCount > 0 && passCount < maxPasses) {
18058
- passCount++;
17111
+ // snap + cut until no more cuts are needed
17112
+ do {
17113
+ loopCount++;
17114
+ cutCount = 0;
18059
17115
  snapCount = snapCoordsByInterval(arcs, snapDist);
18060
17116
  dupeCount = arcs.dedupCoords();
18061
- // cutCount = 0;
18062
- if (snapCount > 0 || cutCount > 0) {
17117
+ if (snapCount > 0 || cutCount > 0 || loopCount == 1) {
17118
+ // cut arcs at points where segments intersect
18063
17119
  cutCount = cutPathsAtIntersections(dataset, cutOpts);
18064
- debug("[snapAndCut] pass:", passCount, "snaps:", snapCount, "dupes:", dupeCount, "cuts:", cutCount);
18065
17120
  }
18066
- }
17121
+ coordsHaveChanged |= (snapCount + dupeCount + cutCount) > 0;
17122
+ debug("[snapAndCut] pass:", loopCount, "snaps:", snapCount, "dupes:", dupeCount, "cuts:", cutCount);
17123
+ } while (loopCount < maxLoops && cutCount > 0);
18067
17124
 
18068
- // if (cutCount > 0) {
17125
+ // should this be added?
17126
+ // if (cutCount > 0 && loopCount == maxLoops) {
17127
+ // snapCount = snapCoordsByInterval(arcs, snapDist);
18069
17128
  // arcs.dedupCoords(); // need to do this here?
18070
- // debug('Second-pass vertices added:', cutCount, 'consider third pass?');
18071
17129
  // }
18072
17130
 
18073
17131
  return coordsHaveChanged;
@@ -19596,6 +18654,7 @@
19596
18654
  return geom;
19597
18655
  };
19598
18656
 
18657
+ // ids: shape data (array of array of arc ids)
19599
18658
  GeoJSON.exportLineGeom = function(ids, arcs) {
19600
18659
  var obj = exportPathData(ids, arcs, "polyline");
19601
18660
  if (obj.pointCount === 0) return null;
@@ -19611,6 +18670,7 @@
19611
18670
  };
19612
18671
  };
19613
18672
 
18673
+ // ids: shape data (array of array of arc ids)
19614
18674
  GeoJSON.exportPolygonGeom = function(ids, arcs, opts) {
19615
18675
  var obj = exportPathData(ids, arcs, "polygon");
19616
18676
  if (obj.pointCount === 0) return null;
@@ -25689,6 +24749,10 @@ ${svg}
25689
24749
  type: 'flag',
25690
24750
  describe: '[CSV] export numbers with decimal commas not points'
25691
24751
  })
24752
+ .option('show-all', {
24753
+ type: 'flag',
24754
+ describe: '[Snapshot] show all layers in Web UI'
24755
+ })
25692
24756
  .option('final', {
25693
24757
  type: 'flag' // for testing
25694
24758
  })
@@ -25737,24 +24801,54 @@ ${svg}
25737
24801
  // describe: 'number of vertices to use when buffering points',
25738
24802
  type: 'integer'
25739
24803
  })
24804
+ .option('arc-quality', {
24805
+ // segments per quarter-circle in joins and caps
24806
+ type: 'integer'
24807
+ })
24808
+ .option('slice-length', {
24809
+ // max path segments per buffer section
24810
+ type: 'integer'
24811
+ })
25740
24812
  .option('backtrack', {
24813
+ // ...
25741
24814
  type: 'integer'
25742
24815
  })
24816
+ .option('cap-style', {
24817
+ describe: 'flat or round (default is round)'
24818
+ })
25743
24819
  .option('type', {
25744
24820
  // left, right, outer, inner (default is full buffer)
25745
24821
  })
25746
24822
  .option('planar', {
25747
24823
  type: 'flag'
25748
24824
  })
25749
- .option('v2', { // use v2 method
24825
+ .option('v2', {
24826
+ type: 'flag'
24827
+ })
24828
+ .option('v3', {
24829
+ type: 'flag'
24830
+ })
24831
+ .option('debug-offset', {
25750
24832
  type: 'flag'
25751
24833
  })
25752
- .option('debug-division', {
24834
+ .option('debug-winding', {
25753
24835
  type: 'flag'
25754
24836
  })
24837
+ .option('debug-points', {
24838
+ type: 'flag'
24839
+ })
24840
+ // .option('debug-division', {
24841
+ // type: 'flag'
24842
+ // })
25755
24843
  .option('debug-mosaic', {
25756
24844
  type: 'flag'
25757
24845
  })
24846
+ .option('left', {
24847
+ type: 'flag'
24848
+ })
24849
+ .option('right', {
24850
+ type: 'flag'
24851
+ })
25758
24852
  .option('no-cleanup', {
25759
24853
  type: 'flag'
25760
24854
  })
@@ -31891,14 +30985,112 @@ ${svg}
31891
30985
  return dataset;
31892
30986
  }
31893
30987
 
30988
+ function getPolygonRewinder(nodes) {
30989
+ var splitter = getSelfIntersectionSplitter(nodes);
30990
+ return rewindPolygon;
30991
+
30992
+ function rewindPolygon(shp) {
30993
+ var shp2 = [];
30994
+ forEachShapePart(shp, function(ids) {
30995
+ var rings = splitter(ids);
30996
+ var path;
30997
+ for (var i=0; i<rings.length; i++) {
30998
+ path = rings[i];
30999
+ if (geom.getPlanarPathArea(path, nodes.arcs) < 0) {
31000
+ path = reversePath(path);
31001
+ }
31002
+ shp2.push(path);
31003
+ }
31004
+ });
31005
+ return shp2.length > 0 ? shp2 : null;
31006
+ }
31007
+ }
31008
+
31009
+ function rewindPolygonParts(lyr, nodes) {
31010
+ var rewind = getPolygonRewinder(nodes);
31011
+ lyr.shapes = lyr.shapes.map(function(shp) {
31012
+ return rewind(shp);
31013
+ });
31014
+ }
31015
+
31016
+ // TODO: Need to rethink polygon repair: these function can cause problems
31017
+ // when part of a self-intersecting polygon is removed
31018
+ //
31019
+ function repairPolygonGeometry(layers, dataset, opts) {
31020
+ var nodes = addIntersectionCuts(dataset);
31021
+ layers.forEach(function(lyr) {
31022
+ repairSelfIntersections(lyr, nodes);
31023
+ });
31024
+ return layers;
31025
+ }
31026
+
31027
+ // Remove any small shapes formed by twists in each ring
31028
+ // // OOPS, NO // Retain only the part with largest area
31029
+ // // this causes problems when a cut-off hole has a matching ring in another polygon
31030
+ // TODO: consider cases where cut-off parts should be retained
31031
+ //
31032
+ function repairSelfIntersections(lyr, nodes) {
31033
+ var splitter = getSelfIntersectionSplitter(nodes);
31034
+
31035
+ lyr.shapes = lyr.shapes.map(function(shp, i) {
31036
+ return cleanPolygon(shp);
31037
+ });
31038
+
31039
+ function cleanPolygon(shp) {
31040
+ var cleanedPolygon = [];
31041
+ forEachShapePart(shp, function(ids) {
31042
+ // TODO: consider returning null if path can't be split
31043
+ var splitIds = splitter(ids);
31044
+ if (splitIds.length === 0) {
31045
+ error("[cleanPolygon()] Defective path:", ids);
31046
+ } else if (splitIds.length == 1) {
31047
+ cleanedPolygon.push(splitIds[0]);
31048
+ } else {
31049
+ var shapeArea = geom.getPlanarPathArea(ids, nodes.arcs),
31050
+ sign = shapeArea > 0 ? 1 : -1,
31051
+ mainRing;
31052
+
31053
+ splitIds.reduce(function(max, ringIds, i) {
31054
+ var pathArea = geom.getPlanarPathArea(ringIds, nodes.arcs) * sign;
31055
+ if (pathArea > max) {
31056
+ mainRing = ringIds;
31057
+ max = pathArea;
31058
+ }
31059
+ return max;
31060
+ }, 0);
31061
+
31062
+ if (mainRing) {
31063
+ cleanedPolygon.push(mainRing);
31064
+ }
31065
+ }
31066
+ });
31067
+ return cleanedPolygon.length > 0 ? cleanedPolygon : null;
31068
+ }
31069
+ }
31070
+
31071
+ var PolygonRepair = /*#__PURE__*/Object.freeze({
31072
+ __proto__: null,
31073
+ getPolygonRewinder: getPolygonRewinder,
31074
+ repairPolygonGeometry: repairPolygonGeometry,
31075
+ repairSelfIntersections: repairSelfIntersections,
31076
+ rewindPolygonParts: rewindPolygonParts
31077
+ });
31078
+
31894
31079
  function dissolveBufferDataset(dataset, optsArg) {
31895
31080
  var opts = optsArg || {};
31896
31081
  var lyr = dataset.layers[0];
31897
31082
  var tmp;
31898
- var nodes = addIntersectionCuts(dataset, {});
31899
- if (opts.debug_division) {
31900
- return debugBufferDivision(lyr, nodes);
31083
+ if (opts.debug_offset) {
31084
+ return; // raw offset path
31901
31085
  }
31086
+ var nodes = addIntersectionCuts(dataset, {rebuild_topology: true});
31087
+ if (opts.debug_winding) {
31088
+ rewindPolygonParts(lyr, nodes);
31089
+ // debugRingsAndHoles(lyr, nodes);
31090
+ return;
31091
+ }
31092
+ rewindPolygonParts(lyr, nodes);
31093
+
31902
31094
  var mosaicIndex = new MosaicIndex(lyr, nodes, {flat: false, no_holes: false});
31903
31095
  if (opts.debug_mosaic) {
31904
31096
  tmp = composeMosaicLayer(lyr, mosaicIndex.mosaic);
@@ -31921,49 +31113,6 @@ ${svg}
31921
31113
  }
31922
31114
  }
31923
31115
 
31924
- function debugBufferDivision(lyr, nodes) {
31925
- var divide = getHoleDivider(nodes);
31926
- var shapes2 = [];
31927
- var records = [];
31928
- lyr.shapes.forEach(divideShape);
31929
- lyr.shapes = shapes2;
31930
- lyr.data = new DataTable(records);
31931
- return lyr;
31932
-
31933
- function divideShape(shp) {
31934
- var cw = [], ccw = [];
31935
- divide(shp, cw, ccw);
31936
- cw.forEach(function(ring) {
31937
- shapes2.push([ring]);
31938
- records.push({type: 'ring'});
31939
- });
31940
- ccw.forEach(function(hole) {
31941
- shapes2.push([reversePath(hole)]);
31942
- records.push({type: 'hole'});
31943
- });
31944
- }
31945
- }
31946
-
31947
- // n = number of segments used to approximate a circle
31948
- // Returns tolerance as a percent of circle radius
31949
- function getBufferToleranceFromCircleSegments(n) {
31950
- return 1 - Math.cos(Math.PI / n);
31951
- }
31952
-
31953
- function getArcDegreesFromTolerancePct(pct) {
31954
- return 360 * Math.acos(1 - pct) / Math.PI;
31955
- }
31956
-
31957
- // n = number of segments used to approximate a circle
31958
- // Returns tolerance as a percent of circle radius
31959
- function getBufferToleranceFromCircleSegments2(n) {
31960
- return 1 / Math.cos(Math.PI / n) - 1;
31961
- }
31962
-
31963
- function getArcDegreesFromTolerancePct2(pct) {
31964
- return 360 * Math.acos(1 / (pct + 1)) / Math.PI;
31965
- }
31966
-
31967
31116
  // return constant distance in meters, or return null if unparsable
31968
31117
  function parseConstantBufferDistance(str, crs) {
31969
31118
  var parsed = parseMeasure2(str);
@@ -31971,16 +31120,6 @@ ${svg}
31971
31120
  return convertDistanceParam(str, crs) || null;
31972
31121
  }
31973
31122
 
31974
- function getBufferToleranceFunction(dataset, opts) {
31975
- var crs = getDatasetCRS(dataset);
31976
- var constTol = opts.tolerance ? parseConstantBufferDistance(opts.tolerance, crs) : 0;
31977
- var pctOfRadius = 1/100;
31978
- return function(meterDist) {
31979
- if (constTol) return constTol;
31980
- return constTol ? constTol : meterDist * pctOfRadius;
31981
- };
31982
- }
31983
-
31984
31123
  function getBufferDistanceFunction(lyr, dataset, opts) {
31985
31124
  if (!opts.radius) {
31986
31125
  stop('Missing expected radius parameter');
@@ -31999,17 +31138,579 @@ ${svg}
31999
31138
  };
32000
31139
  }
32001
31140
 
32002
- var BufferCommon = /*#__PURE__*/Object.freeze({
32003
- __proto__: null,
32004
- dissolveBufferDataset: dissolveBufferDataset,
32005
- getArcDegreesFromTolerancePct: getArcDegreesFromTolerancePct,
32006
- getArcDegreesFromTolerancePct2: getArcDegreesFromTolerancePct2,
32007
- getBufferDistanceFunction: getBufferDistanceFunction,
32008
- getBufferToleranceFromCircleSegments: getBufferToleranceFromCircleSegments,
32009
- getBufferToleranceFromCircleSegments2: getBufferToleranceFromCircleSegments2,
32010
- getBufferToleranceFunction: getBufferToleranceFunction,
32011
- parseConstantBufferDistance: parseConstantBufferDistance
32012
- });
31141
+ function BufferBuilder(opts) {
31142
+ var self = {};
31143
+ var buffer, path, insideFlags;
31144
+ var points = [];
31145
+ var backtrackSteps = opts.backtrack >= 0 ? opts.backtrack : 100;
31146
+ var backtrackStopIdx = 0; // lowest vertex id in backtrack pass
31147
+
31148
+ init();
31149
+
31150
+ function init() {
31151
+ buffer = [];
31152
+ path = [];
31153
+ insideFlags = [];
31154
+ backtrackStopIdx = 0;
31155
+ }
31156
+
31157
+ self.size = function() {
31158
+ return path.length + buffer.length;
31159
+ };
31160
+
31161
+ self.getDebugPoints = function() {
31162
+ var tmp = points;
31163
+ points = [];
31164
+ return tmp;
31165
+ };
31166
+
31167
+ function makeDebugPoints() {
31168
+ points = points.concat(buffer.map((p, i) => {
31169
+ return {
31170
+ type: 'Feature',
31171
+ geometry: {
31172
+ type: 'Point',
31173
+ coordinates: p
31174
+ },
31175
+ properties: {
31176
+ id: i,
31177
+ inside: insideFlags[i],
31178
+ fill: insideFlags[i] ? 'magenta' : 'dodgerblue'
31179
+ }
31180
+ };
31181
+ }));
31182
+ }
31183
+
31184
+ self.addPathVertex = function(p) {
31185
+ path.push(p);
31186
+ };
31187
+
31188
+ self.addBufferVertices = function(arr) {
31189
+ for (var i=0; i<arr.length; i++) {
31190
+ self.addBufferVertex(arr[i]);
31191
+ }
31192
+ };
31193
+
31194
+ self.addBufferVertex = function(p, isLeftTurn) {
31195
+ var bufferLen = insideFlags.length;
31196
+ var prevIdx = bufferLen - 1;
31197
+ if (isLeftTurn) {
31198
+ // first extruded point after left turn
31199
+ // assume that the point is inside the buffer
31200
+ insideFlags.push(true);
31201
+ buffer.push(p);
31202
+ return;
31203
+ }
31204
+ if (prevIdx == -1) {
31205
+ // first point in buffer
31206
+ buffer.push(p);
31207
+ insideFlags.push(false);
31208
+ return;
31209
+ }
31210
+ var prevP = buffer[prevIdx];
31211
+ var prevFlag = insideFlags[prevIdx];
31212
+ if (pointsAreSame(prevP, p)) {
31213
+ // console.log('SAME POINT')
31214
+ return;
31215
+ }
31216
+ // if no segment cross is detected below, inside/outside stays the same
31217
+ var newFlag = prevFlag;
31218
+ var hit, a, b, flagA, flagB;
31219
+ for (var i=0, idx = bufferLen - 3; i < backtrackSteps && idx >= backtrackStopIdx; i++, idx--) {
31220
+ a = buffer[idx];
31221
+ b = buffer[idx + 1];
31222
+ // TODO: consider using a geodetic intersection function for lat-long datasets
31223
+ // TODO: consider case of an endpoint hit
31224
+ // TODO: consider case of collinear hit
31225
+ hit = bufferIntersection$2(a[0], a[1], b[0], b[1], prevP[0], prevP[1], p[0], p[1]);
31226
+ if (!hit) {
31227
+ // continue scanning backward for an intersection
31228
+ continue;
31229
+ }
31230
+ // console.log("hit")
31231
+ flagA = insideFlags[idx];
31232
+ flagB = insideFlags[idx + 1];
31233
+ if (flagA && flagB) {
31234
+ // newest segment collides with an interior segment - do not rewind
31235
+ continue;
31236
+ }
31237
+ if (prevFlag === false) {
31238
+ // if segment intersects an outside segment from outside the buffer,
31239
+ // we are likely closing a loop,
31240
+ // so we stop backtracking and reset the backtrack limit to avoid
31241
+ // removing the loop
31242
+ backtrackStopIdx = i + 1;
31243
+ break;
31244
+ }
31245
+
31246
+ // newest segment collides with an exterior segment
31247
+ // * assume we're going from inside to outside
31248
+ // * and can therefore remove an interior loop
31249
+ // TODO: improve (this assumption does not hold when closing an oxbow type loop)
31250
+ while (buffer.length > idx + 1) {
31251
+ buffer.pop();
31252
+ insideFlags.pop();
31253
+ }
31254
+ buffer.push(hit);
31255
+ insideFlags.push(false);
31256
+ newFlag = false;
31257
+ // console.log("going out")
31258
+ break;
31259
+ }
31260
+
31261
+ buffer.push(p);
31262
+ insideFlags.push(newFlag);
31263
+ };
31264
+
31265
+ self.done = function() {
31266
+ if (opts.debug_points) {
31267
+ makeDebugPoints();
31268
+ }
31269
+ var ring = path.reverse().concat(buffer);
31270
+ if (ring.length < 3) {
31271
+ error('Defective buffer ring:', ring);
31272
+ }
31273
+ ring.push(ring[0]);
31274
+ init();
31275
+ return ring;
31276
+ };
31277
+
31278
+ return self;
31279
+ }
31280
+
31281
+ function pointsAreSame(a, b) {
31282
+ return a[0] === b[0] && a[1] === b[1];
31283
+ }
31284
+
31285
+ // Exclude segments with non-intersecting bounding boxes before
31286
+ // calling intersection function
31287
+ // Possibly slightly faster than direct call... not worth it?
31288
+ function bufferIntersection$2(ax, ay, bx, by, cx, cy, dx, dy) {
31289
+ if (ax < cx && ax < dx && bx < cx && bx < dx ||
31290
+ ax > cx && ax > dx && bx > cx && bx > dx ||
31291
+ ay < cy && ay < dy && by < cy && by < dy ||
31292
+ ay > cy && ay > dy && by > cy && by > dy) return null;
31293
+ return segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy);
31294
+ }
31295
+
31296
+ // Returns a function for generating GeoJSON MultiPolygon geometries
31297
+ function getPolylineBufferMaker$2(arcs, geod, getBearing, opts) {
31298
+ // var sliceLen = opts.slice_length || Infinity;
31299
+ var segsPerQuadrant = opts.arc_quality >= 2 ? opts.arc_quality : 12;
31300
+ var capStyle = opts.cap_style || 'round'; // expect 'round' or 'flat'
31301
+ var pathIter = new ShapeIter(arcs);
31302
+ var builder = new BufferBuilder(opts);
31303
+
31304
+ return function makeBufferGeoJSON(shape, distance) {
31305
+ var rings = [];
31306
+ (shape || []).forEach(function(path, i) {
31307
+ var pathRings = makeSinglePathRings(path, distance);
31308
+ rings = rings.concat(pathRings);
31309
+ });
31310
+ if (rings.length === 0) return null;
31311
+ var feat = {
31312
+ type: 'Feature',
31313
+ properties: null,
31314
+ geometry: {
31315
+ type: 'MultiPolygon',
31316
+ coordinates: rings.map(ring => [ring]) // to Polygon format
31317
+ }
31318
+ };
31319
+ if (opts.debug_points) {
31320
+ return [feat].concat(builder.getDebugPoints());
31321
+ }
31322
+ return feat;
31323
+ };
31324
+
31325
+ // each path may be converted into multiple buffer rings, which later
31326
+ // need to be dissolved
31327
+ function makeSinglePathRings(pathArcs, dist) {
31328
+ var revPathArcs;
31329
+ var rings = [];
31330
+ if (!opts.right || opts.left) {
31331
+ rings = rings.concat(makeLeftBufferRings(pathArcs, dist));
31332
+ }
31333
+ if (!opts.left || opts.right) {
31334
+ revPathArcs = reversePath(pathArcs.concat());
31335
+ rings = rings.concat(makeLeftBufferRings(revPathArcs, dist));
31336
+ }
31337
+ return rings;
31338
+ }
31339
+
31340
+ function makeLeftBufferRings(path, dist) {
31341
+ var rings = [];
31342
+ var x0, y0, x1, y1, x2, y2; // path traversal coords
31343
+ var p1, p2; // extruded points
31344
+ var bearing1, bearing2, prevBearing, joinAngle;
31345
+ var firstBearing;
31346
+ var i = 0;
31347
+ pathIter.init(path);
31348
+
31349
+ if (pathIter.hasNext()) {
31350
+ x0 = x2 = pathIter.x;
31351
+ y0 = y2 = pathIter.y;
31352
+ i++;
31353
+ }
31354
+
31355
+ while (pathIter.hasNext()) {
31356
+ // TODO: use a tolerance
31357
+ if (pathIter.x === x2 && pathIter.y === y2) {
31358
+ debug("skipping a duplicate point");
31359
+ continue;
31360
+ }
31361
+ x1 = x2;
31362
+ y1 = y2;
31363
+ x2 = pathIter.x;
31364
+ y2 = pathIter.y;
31365
+
31366
+ // calculate bearing at both segment points
31367
+ // TODO: no need to calculate twice with planar coordinates
31368
+ prevBearing = bearing2;
31369
+ bearing1 = getBearing(x1, y1, x2, y2);
31370
+ bearing2 = getBearing(x2, y2, x1, y1) - 180;
31371
+ p1 = geod(x1, y1, bearing1 - 90, dist);
31372
+ p2 = geod(x2, y2, bearing2 - 90, dist);
31373
+
31374
+ if (i == 1) {
31375
+ firstBearing = bearing1;
31376
+ builder.addPathVertex([x1, y1]);
31377
+ } else {
31378
+ joinAngle = getJoinAngle(prevBearing, bearing1);
31379
+ }
31380
+
31381
+ builder.addPathVertex([x2, y2]);
31382
+
31383
+ if (i > 1 && joinAngle > 0) {
31384
+ builder.addBufferVertices(makeRoundJoin(x1, y1, prevBearing - 90, joinAngle, dist));
31385
+ }
31386
+
31387
+ builder.addBufferVertex(p1, joinAngle < 0);
31388
+ builder.addBufferVertex(p2);
31389
+
31390
+ // TODO: restore slicing?
31391
+ // if (center.length - 1 >= sliceLen) {
31392
+ // builder.done(rings);
31393
+ // }
31394
+
31395
+ i++;
31396
+ }
31397
+
31398
+ if (x2 == x0 && y2 == y0) { // closed path
31399
+ // add join to finish closed path
31400
+ // TODO - figure out which bearing to use
31401
+ joinAngle = getJoinAngle(bearing2, firstBearing);
31402
+ if (joinAngle > 0) {
31403
+ builder.addBufferVertices(makeFinalJoin(x2, y2, bearing1 - 90, joinAngle, dist));
31404
+ }
31405
+ } else if (capStyle == 'round') { // open path
31406
+ // add a cap to finish open path
31407
+ builder.addBufferVertices(makeRoundCap(x2, y2, bearing2 - 90, dist));
31408
+ }
31409
+
31410
+ rings.push(builder.done());
31411
+ return rings;
31412
+ }
31413
+
31414
+ // function extendArray(arr, arr2) {
31415
+ // arr2.reverse();
31416
+ // while(arr2.length > 0) arr.push(arr2.pop());
31417
+ // }
31418
+
31419
+ function makeFinalJoin(x, y, direction, angle, dist) {
31420
+ var points = makeRoundJoin(x, y, direction, angle, dist);
31421
+ points.push(geod(x, y, direction + angle, dist));
31422
+ return points;
31423
+ }
31424
+
31425
+ function makeRoundCap(x, y, startDir, dist) {
31426
+ var points = makeRoundJoin(x, y, startDir, 180, dist);
31427
+ points.push(geod(x, y, startDir + 180, dist)); // add final vertex
31428
+ return points;
31429
+ }
31430
+
31431
+ // get interior vertices of an interpolated CW arc
31432
+ function makeRoundJoin(cx, cy, startBearing, arcAngle, dist) {
31433
+ var points = [];
31434
+ var increment = 90 / segsPerQuadrant;
31435
+ var angle = increment;
31436
+ while (angle < arcAngle) {
31437
+ points.push(geod(cx, cy, startBearing + angle, dist));
31438
+ angle += increment;
31439
+ }
31440
+ return points;
31441
+ }
31442
+
31443
+ // get angle between two extruded segments in degrees
31444
+ // positive angle means join in convex (range: 0-180 degrees)
31445
+ // negative angle means join is concave (range: -180-0 degrees)
31446
+ function getJoinAngle(direction1, direction2) {
31447
+ var delta = direction2 - direction1;
31448
+ if (delta > 180) {
31449
+ delta -= 360;
31450
+ }
31451
+ if (delta < -180) {
31452
+ delta += 360;
31453
+ }
31454
+ return delta;
31455
+ }
31456
+
31457
+ }
31458
+
31459
+ // Returns a function for generating GeoJSON MultiPolygon geometries
31460
+ function getPolylineBufferMaker$1(arcs, geod, getBearing, opts) {
31461
+ var sliceLen = opts.slice_length || Infinity;
31462
+ var backtrackSteps = opts.backtrack >= 0 ? opts.backtrack : 100;
31463
+ var segsPerQuadrant = opts.arc_quality >= 2 ? opts.arc_quality : 12;
31464
+ var capStyle = opts.cap_style || 'round'; // expect 'round' or 'flat'
31465
+ var pathIter = new ShapeIter(arcs);
31466
+ var left, center, rings;
31467
+
31468
+ return function makeBufferGeoJSON(shape, distance) {
31469
+ var rings = [];
31470
+ (shape || []).forEach(function(path, i) {
31471
+ var pathRings = makeSinglePathRings(path, distance);
31472
+ rings = rings.concat(pathRings);
31473
+ });
31474
+ if (rings.length === 0) return null;
31475
+ return {
31476
+ type: 'MultiPolygon',
31477
+ coordinates: rings.map(ring => [ring]) // to Polygon format
31478
+ };
31479
+ };
31480
+
31481
+ // each path may be converted into multiple buffer rings, which later
31482
+ // need to be dissolved
31483
+ function makeSinglePathRings(pathArcs, dist) {
31484
+ var revPathArcs;
31485
+ var rings = [];
31486
+ if (!opts.right || opts.left) {
31487
+ rings = rings.concat(makeLeftBufferRings(pathArcs, dist));
31488
+ }
31489
+ if (!opts.left || opts.right) {
31490
+ revPathArcs = reversePath(pathArcs.concat());
31491
+ rings = rings.concat(makeLeftBufferRings(revPathArcs, dist));
31492
+ }
31493
+ return rings;
31494
+ }
31495
+
31496
+ function makeLeftBufferRings(path, dist) {
31497
+ left = [];
31498
+ center = [];
31499
+ rings = [];
31500
+ var x0, y0, x1, y1, x2, y2; // path traversal coords
31501
+ var p1, p2; // extruded points
31502
+ var prevP;
31503
+ var bearing1, bearing2, prevBearing, joinAngle;
31504
+ var firstBearing;
31505
+ var i = 0;
31506
+ pathIter.init(path);
31507
+
31508
+ if (pathIter.hasNext()) {
31509
+ x0 = x2 = pathIter.x;
31510
+ y0 = y2 = pathIter.y;
31511
+ i++;
31512
+ }
31513
+
31514
+ while (pathIter.hasNext()) {
31515
+ // TODO: use a tolerance
31516
+ if (pathIter.x === x2 && pathIter.y === y2) {
31517
+ debug("skipping a duplicate point");
31518
+ continue;
31519
+ }
31520
+ x1 = x2;
31521
+ y1 = y2;
31522
+ x2 = pathIter.x;
31523
+ y2 = pathIter.y;
31524
+
31525
+ // calculate bearing at both segment points
31526
+ // TODO: no need to calculate twice with planar coordinates
31527
+ prevBearing = bearing2;
31528
+ bearing1 = getBearing(x1, y1, x2, y2);
31529
+ bearing2 = getBearing(x2, y2, x1, y1) - 180;
31530
+ // extrude current segment to the left
31531
+ prevP = p2;
31532
+ p1 = geod(x1, y1, bearing1 - 90, dist);
31533
+ p2 = geod(x2, y2, bearing2 - 90, dist);
31534
+
31535
+ if (i == 1) {
31536
+ firstBearing = bearing1;
31537
+ } else {
31538
+ joinAngle = getJoinAngle(prevBearing, bearing1);
31539
+ }
31540
+
31541
+ // extend center coords (i.e. original path)
31542
+ if (center.length == 0) {
31543
+ center.push([x1, y1]);
31544
+ }
31545
+ center.push([x2, y2]);
31546
+
31547
+ // if (veryCloseToPrevPoint(left, p1[0], p1[1])) {
31548
+ // console.log("VERY CLOSE")
31549
+ // // skip first point
31550
+ // addBufferVertex(p2);
31551
+ // }
31552
+
31553
+ if (i > 1 && joinAngle > 0) {
31554
+ if (prevP) {
31555
+ // start join from last extruded vertex of previous buffer slice
31556
+ addBufferVertex(prevP);
31557
+ }
31558
+ makeRoundJoin(x1, y1, prevBearing - 90, joinAngle, dist).forEach(addBufferVertex);
31559
+ addBufferVertex(p1);
31560
+ addBufferVertex(p2);
31561
+ // left.push(p1, p2)
31562
+ } else {
31563
+ // left.push(p1, p2)
31564
+ addBufferVertex(p1);
31565
+ addBufferVertex(p2);
31566
+ }
31567
+
31568
+ // TODO: finish
31569
+ if (center.length - 1 >= sliceLen) {
31570
+ finishRing();
31571
+ }
31572
+ i++;
31573
+ }
31574
+
31575
+ if (center.length > 1) {
31576
+ finishRing();
31577
+ }
31578
+
31579
+ if (x2 == x0 && y2 == y0) { // closed path
31580
+ // add join to finish closed path
31581
+ // TODO - figure out which bearing to use
31582
+ joinAngle = getJoinAngle(bearing2, firstBearing);
31583
+ if (joinAngle > 0) {
31584
+ appendJoinToRing(rings[rings.length-1], x2, y2, bearing1 - 90, joinAngle, dist);
31585
+ }
31586
+ } else { // open path
31587
+ // add a cap to finish open path
31588
+ // left.push.apply(left, makeCap(x2, y2, bearing, dist));
31589
+ appendCapToRing(rings[rings.length-1], x2, y2, bearing2, dist);
31590
+ }
31591
+
31592
+ return rings;
31593
+ }
31594
+
31595
+ function finishRing() {
31596
+ if (center.length < 2) {
31597
+ debug('defective path, skipping');
31598
+ return;
31599
+ }
31600
+ var ring = center.reverse().concat(left);
31601
+ ring.push(ring[0]);
31602
+
31603
+ // Start next partial (if there is one)
31604
+ left = [];
31605
+ center = [];
31606
+ rings.push(ring);
31607
+ }
31608
+
31609
+ // function extendArray(arr, arr2) {
31610
+ // arr2.reverse();
31611
+ // while(arr2.length > 0) arr.push(arr2.pop());
31612
+ // }
31613
+
31614
+ function appendJoinToRing(ring, x, y, direction, angle, dist) {
31615
+ var p1 = ring.pop();
31616
+ var coords = makeRoundJoin(x, y, direction, angle, dist);
31617
+ ring.push.apply(ring, coords);
31618
+ ring.push(geod(x, y, direction + angle, dist));
31619
+ ring.push(p1);
31620
+ }
31621
+
31622
+ function appendCapToRing(ring, x, y, direction, dist) {
31623
+ if (capStyle == 'flat' || ring.length < 4) {
31624
+ return;
31625
+ }
31626
+ var p1 = ring.pop();
31627
+ var coords = makeRoundCap(x, y, direction - 90, dist);
31628
+ ring.push.apply(ring, coords);
31629
+ ring.push(p1);
31630
+ }
31631
+
31632
+ function makeRoundCap(x, y, startDir, dist) {
31633
+ var points = makeRoundJoin(x, y, startDir, 180, dist);
31634
+ points.push(geod(x, y, startDir + 180, dist)); // add final vertex
31635
+ return points;
31636
+ }
31637
+
31638
+ // get interior vertices of an interpolated CW arc
31639
+ function makeRoundJoin(cx, cy, startBearing, arcAngle, dist) {
31640
+ var points = [];
31641
+ var increment = 90 / segsPerQuadrant;
31642
+ var angle = increment;
31643
+ while (angle < arcAngle) {
31644
+ points.push(geod(cx, cy, startBearing + angle, dist));
31645
+ angle += increment;
31646
+ }
31647
+ return points;
31648
+ }
31649
+
31650
+ // get angle between two extruded segments in degrees
31651
+ // positive angle means join in convex (range: 0-180 degrees)
31652
+ // negative angle means join is concave (range: -180-0 degrees)
31653
+ function getJoinAngle(direction1, direction2) {
31654
+ var delta = direction2 - direction1;
31655
+ if (delta > 180) {
31656
+ delta -= 360;
31657
+ }
31658
+ if (delta < -180) {
31659
+ delta += 360;
31660
+ }
31661
+ return delta;
31662
+ }
31663
+
31664
+
31665
+ function addBufferVertex(d) {
31666
+ var arr = left;
31667
+ var a, b, c, hit;
31668
+ // c is the start point of the segment formed by appending point d to the polyline.
31669
+ c = arr[arr.length - 1];
31670
+ for (var i=0, idx = arr.length - 3; i < backtrackSteps && idx >= 0; i++, idx--) {
31671
+ a = arr[idx];
31672
+ b = arr[idx + 1];
31673
+ // TODO: consider using a geodetic intersection function for lat-long datasets
31674
+ hit = bufferIntersection$1(a[0], a[1], b[0], b[1], c[0], c[1], d[0], d[1]);
31675
+ // TODO: disregard hits caused by oxbows
31676
+ if (hit) {
31677
+ // TODO: handle collinear segments
31678
+ // if (hit.length != 2) console.log('COLLINEAR', hit)
31679
+ // segments intersect -- replace two internal segment endpoints with xx point
31680
+ while (arr.length > idx + 1) arr.pop();
31681
+ appendPoint(arr, hit);
31682
+ c = hit; // update starting point of the newly added segment
31683
+ }
31684
+ }
31685
+ appendPoint(arr, d);
31686
+ }
31687
+
31688
+ }
31689
+
31690
+ // Test if two points are within a snapping tolerance
31691
+ // TODO: calculate the tolerance more sensibly
31692
+ function veryClose(x1, y1, x2, y2, tol) {
31693
+ var dist = geom.distance2D(x1, y1, x2, y2);
31694
+ return dist < tol;
31695
+ }
31696
+
31697
+ function appendPoint(arr, p) {
31698
+ var prev = arr[arr.length - 1];
31699
+ if (!prev || !veryClose(prev[0], prev[1], p[0], p[1], 1e-10)) {
31700
+ arr.push(p);
31701
+ }
31702
+ }
31703
+
31704
+ // Exclude segments with non-intersecting bounding boxes before
31705
+ // calling intersection function
31706
+ // Possibly slightly faster than direct call... not worth it?
31707
+ function bufferIntersection$1(ax, ay, bx, by, cx, cy, dx, dy) {
31708
+ if (ax < cx && ax < dx && bx < cx && bx < dx ||
31709
+ ax > cx && ax > dx && bx > cx && bx > dx ||
31710
+ ay < cy && ay < dy && by < cy && by < dy ||
31711
+ ay > cy && ay > dy && by > cy && by > dy) return null;
31712
+ return geom.segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy);
31713
+ }
32013
31714
 
32014
31715
  // Returns a function for generating GeoJSON geometries (MultiLineString or MultiPolygon)
32015
31716
  function getPolylineBufferMaker(arcs, geod, getBearing, opts) {
@@ -32203,28 +31904,6 @@ ${svg}
32203
31904
  };
32204
31905
  }
32205
31906
 
32206
- function addBufferVertex(arr, d, maxBacktrack) {
32207
- var a, b, c, hit;
32208
- for (var i=0, idx = arr.length - 3; i<maxBacktrack && idx >= 0; i++, idx--) {
32209
- c = arr[arr.length - 1];
32210
- a = arr[idx];
32211
- b = arr[idx + 1];
32212
- // TODO: consider using a geodetic intersection function for lat-long datasets
32213
- hit = bufferIntersection(a[0], a[1], b[0], b[1], c[0], c[1], d[0], d[1]);
32214
- if (hit) {
32215
- // TODO: handle collinear segments
32216
- if (hit.length != 2) ;
32217
- // segments intersect -- replace two internal segment endpoints with xx point
32218
- while (arr.length > idx + 1) arr.pop();
32219
- // TODO: check proximity of hit to several points
32220
- arr.push(hit);
32221
- }
32222
- }
32223
-
32224
- // TODO: check proximity to previous point
32225
- arr.push(d); // add new point
32226
- }
32227
-
32228
31907
  // Exclude segments with non-intersecting bounding boxes before
32229
31908
  // calling intersection function
32230
31909
  // Possibly slightly faster than direct call... not worth it?
@@ -32236,302 +31915,6 @@ ${svg}
32236
31915
  return geom.segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy);
32237
31916
  }
32238
31917
 
32239
- var PathBuffer = /*#__PURE__*/Object.freeze({
32240
- __proto__: null,
32241
- addBufferVertex: addBufferVertex,
32242
- bufferIntersection: bufferIntersection,
32243
- getPolylineBufferMaker: getPolylineBufferMaker
32244
- });
32245
-
32246
- function getPolylineBufferMaker2(arcs, geod, getBearing, opts) {
32247
- var makeLeftBuffer = getPathBufferMaker2(arcs, geod, getBearing, opts);
32248
- var geomType = opts.geometry_type;
32249
-
32250
- function polygonCoords(ring) {
32251
- return [ring];
32252
- }
32253
-
32254
- function needLeftBuffer(path, arcs) {
32255
- if (geomType == 'polyline') {
32256
- return opts.type != 'right';
32257
- }
32258
- // assume polygon type
32259
- if (opts.type == 'outer') {
32260
- return geom.getPathWinding(path, arcs) == 1;
32261
- }
32262
- if (opts.type == 'inner') {
32263
- return geom.getPathWinding(path, arcs) == -1;
32264
- }
32265
- return true;
32266
- }
32267
-
32268
- function needRightBuffer() {
32269
- return geomType == 'polyline' && opts.type != 'left';
32270
- }
32271
-
32272
- function makeBufferParts(pathArcs, dist) {
32273
- var leftPartials, rightPartials, parts, revPathArcs;
32274
-
32275
- if (needLeftBuffer(pathArcs, arcs)) {
32276
- leftPartials = makeLeftBuffer(pathArcs, dist);
32277
- }
32278
- if (needRightBuffer()) {
32279
- revPathArcs = reversePath(pathArcs.concat());
32280
- rightPartials = makeLeftBuffer(revPathArcs, dist);
32281
- }
32282
- parts = (leftPartials || []).concat(rightPartials || []);
32283
- return parts.map(polygonCoords);
32284
- }
32285
-
32286
- // Returns a GeoJSON Geometry (MultiLineString or MultiPolygon) or null
32287
- return function(shape, dist) {
32288
- var geom = {
32289
- type: 'MultiPolygon',
32290
- coordinates: []
32291
- };
32292
- for (var i=0; i<shape.length; i++) {
32293
- geom.coordinates = geom.coordinates.concat(makeBufferParts(shape[i], dist));
32294
- }
32295
- return geom.coordinates.length == 0 ? null : geom;
32296
- };
32297
- }
32298
-
32299
- function getPathBufferMaker2(arcs, geod, getBearing, opts) {
32300
- var backtrackSteps = opts.backtrack >= 0 ? opts.backtrack : 50;
32301
- var pathIter = new ShapeIter(arcs);
32302
- // var capStyle = opts.cap_style || 'round'; // expect 'round' or 'flat'
32303
- var partials, left, center;
32304
- var bounds;
32305
- // TODO: implement other join styles than round
32306
-
32307
- // function updateTolerance(dist) {
32308
-
32309
- // }
32310
-
32311
- function addRoundJoin(x, y, startDir, angle, dist) {
32312
- var increment = 10;
32313
- var endDir = startDir + angle;
32314
- var dir = startDir + increment;
32315
- while (dir < endDir) {
32316
- addBufferVertex(geod(x, y, dir, dist));
32317
- dir += increment;
32318
- }
32319
- }
32320
-
32321
- // function addRoundJoin2(arr, x, y, startDir, angle, dist) {
32322
- // var increment = 10;
32323
- // var endDir = startDir + angle;
32324
- // var dir = startDir + increment;
32325
- // while (dir < endDir) {
32326
- // addBufferVertex(arr, geod(x, y, dir, dist));
32327
- // dir += increment;
32328
- // }
32329
- // }
32330
-
32331
- // Test if two points are within a snapping tolerance
32332
- // TODO: calculate the tolerance more sensibly
32333
- function veryClose(x1, y1, x2, y2, tol) {
32334
- var dist = geom.distance2D(x1, y1, x2, y2);
32335
- return dist < tol;
32336
- }
32337
-
32338
- function veryCloseToPrevPoint(arr, x, y) {
32339
- var prev = arr[arr.length - 1];
32340
- return veryClose(prev[0], prev[1], x, y, 0.000001);
32341
- }
32342
-
32343
- function appendPoint(arr, p) {
32344
- var prev = arr[arr.length - 1];
32345
- if (!veryClose(prev[0], prev[1], p[0], p[1], 1e-10)) {
32346
- arr.push(p);
32347
- }
32348
- }
32349
-
32350
- // function makeCap(x, y, direction, dist) {
32351
- // if (capStyle == 'flat') {
32352
- // return [[x, y]];
32353
- // }
32354
- // return makeRoundCap(x, y, direction, dist);
32355
- // }
32356
-
32357
- // function makeRoundCap(x, y, segmentDir, dist) {
32358
- // var points = [];
32359
- // var increment = 10;
32360
- // var startDir = segmentDir - 90;
32361
- // var angle = increment;
32362
- // while (angle < 180) {
32363
- // points.push(geod(x, y, startDir + angle, dist));
32364
- // angle += increment;
32365
- // }
32366
- // return points;
32367
- // }
32368
-
32369
- // get angle between two extruded segments in degrees
32370
- // positive angle means join in convex (range: 0-180 degrees)
32371
- // negative angle means join is concave (range: -180-0 degrees)
32372
- function getJoinAngle(direction1, direction2) {
32373
- var delta = direction2 - direction1;
32374
- if (delta > 180) {
32375
- delta -= 360;
32376
- }
32377
- if (delta < -180) {
32378
- delta += 360;
32379
- }
32380
- return delta;
32381
- }
32382
-
32383
-
32384
- // TODO: handle polygon holes
32385
- function addBufferVertex(d) {
32386
- var arr = left;
32387
- var a, b, c, c0, hit;
32388
- // c is the start point of the segment formed by appending point d to the polyline.
32389
- c0 = c = arr[arr.length - 1];
32390
- for (var i=0, idx = arr.length - 3; idx >= 0; i++, idx--) {
32391
- a = arr[idx];
32392
- b = arr[idx + 1];
32393
- // TODO: consider using a geodetic intersection function for lat-long datasets
32394
- hit = bufferIntersection(a[0], a[1], b[0], b[1], c[0], c[1], d[0], d[1]);
32395
- if (hit) {
32396
- if (segmentTurn(a, b, c, d) == 1) {
32397
- // interpretation: segment cd crosses segment ab from outside to inside
32398
- // the buffer -- we need to start a new partial; otherwise,
32399
- // the following code would likely remove a loop representing
32400
- // an oxbow-type hole in the buffer.
32401
- //
32402
- finishPartial();
32403
- break;
32404
- }
32405
- // TODO: handle collinear segments (consider creating new partial)
32406
- // if (hit.length != 2) console.log('COLLINEAR', hit)
32407
-
32408
- // segments intersect, indicating a spurious loop: remove the loop and
32409
- // replace the endpoints of the intersecting segments with the intersection point.
32410
- while (arr.length > idx + 1) arr.pop();
32411
- appendPoint(arr, hit);
32412
- c = hit; // update starting point of the newly added segment
32413
- }
32414
-
32415
- // Maintain a bounding box around vertices before the backtrack limit.
32416
- // If the latest segment intersects this bounding box, there could be a self-
32417
- // intersection -- start a new partial to prevent self-intersection.
32418
- //
32419
- if (i >= backtrackSteps) {
32420
- if (!bounds) {
32421
- bounds = new Bounds();
32422
- bounds.mergePoint(a[0], a[1]);
32423
- }
32424
- bounds.mergePoint(b[0], b[1]);
32425
- if (testSegmentBoundsIntersection(c0, d, bounds)) {
32426
- finishPartial();
32427
- }
32428
- break;
32429
- }
32430
- }
32431
-
32432
- appendPoint(arr, d);
32433
- }
32434
-
32435
- function finishPartial() {
32436
- // Get endpoints of the two polylines, for starting the next partial
32437
- var leftEP = left[left.length - 1];
32438
- var centerEP = center[center.length - 1];
32439
-
32440
- // Make a polygon ring
32441
- var ring = [];
32442
- extendArray(ring, left);
32443
- center.reverse();
32444
- extendArray(ring, center);
32445
- ring.push(ring[0]); // close ring
32446
- partials.push(ring);
32447
-
32448
- // Start next partial
32449
- left.push(leftEP);
32450
- center.push(centerEP);
32451
-
32452
- // clear bbox
32453
- // bbox = null;
32454
- }
32455
-
32456
- function extendArray(arr, arr2) {
32457
- arr2.reverse();
32458
- while(arr2.length > 0) arr.push(arr2.pop());
32459
- }
32460
-
32461
- return function(path, dist) {
32462
- // var x0, y0;
32463
- var x1, y1, x2, y2;
32464
- var p1, p2;
32465
- // var firstBearing;
32466
- var bearing, prevBearing, joinAngle;
32467
- partials = [];
32468
- left = [];
32469
- center = [];
32470
- pathIter.init(path);
32471
-
32472
- // if (pathIter.hasNext()) {
32473
- // x0 = x2 = pathIter.x;
32474
- // y0 = y2 = pathIter.y;
32475
- // }
32476
- while (pathIter.hasNext()) {
32477
- // TODO: use a tolerance
32478
- if (pathIter.x === x2 && pathIter.y === y2) continue; // skip duplicate points
32479
- x1 = x2;
32480
- y1 = y2;
32481
- x2 = pathIter.x;
32482
- y2 = pathIter.y;
32483
-
32484
- prevBearing = bearing;
32485
- bearing = getBearing(x1, y1, x2, y2);
32486
- // shift original polyline segment to the left by buffer distance
32487
- p1 = geod(x1, y1, bearing - 90, dist);
32488
- p2 = geod(x2, y2, bearing - 90, dist);
32489
-
32490
- if (center.length === 0) {
32491
- // first loop, second point in this partial
32492
- // if (partials.length === 0) {
32493
- // firstBearing = bearing;
32494
- // }
32495
- left.push(p1, p2);
32496
- center.push([x1, y1], [x2, y2]);
32497
- } else {
32498
- //
32499
- joinAngle = getJoinAngle(prevBearing, bearing);
32500
- if (veryCloseToPrevPoint(left, p1[0], p1[1])) {
32501
- // skip first point
32502
- addBufferVertex(p2);
32503
- } else if (joinAngle > 0) {
32504
- addRoundJoin(x1, y1, prevBearing - 90, joinAngle, dist);
32505
- addBufferVertex(p1);
32506
- addBufferVertex(p2);
32507
- } else {
32508
- addBufferVertex(p1);
32509
- addBufferVertex(p2);
32510
- }
32511
- center.push([x2, y2]);
32512
- }
32513
- }
32514
-
32515
- if (center.length > 1) {
32516
- finishPartial();
32517
- }
32518
- // TODO: handle defective polylines
32519
-
32520
- // if (x2 == x0 && y2 == y0) {
32521
- // // add join to finish closed path
32522
- // joinAngle = getJoinAngle(bearing, firstBearing);
32523
- // if (joinAngle > 0) {
32524
- // addRoundJoin(leftpart, x2, y2, bearing - 90, joinAngle, dist);
32525
- // }
32526
- // } else {
32527
- // // add a cap to finish open path
32528
- // leftpart.push.apply(leftpart, makeCap(x2, y2, bearing, dist));
32529
- // }
32530
-
32531
- return partials;
32532
- };
32533
- }
32534
-
32535
31918
  // GeographicLib docs: https://geographiclib.sourceforge.io/html/js/
32536
31919
  // https://geographiclib.sourceforge.io/html/js/module-GeographicLib_Geodesic.Geodesic.html
32537
31920
  // https://geographiclib.sourceforge.io/html/js/tutorial-2-interface.html
@@ -32628,30 +32011,43 @@ ${svg}
32628
32011
  });
32629
32012
 
32630
32013
  function makePolylineBuffer(lyr, dataset, opts) {
32014
+ time('buffer');
32631
32015
  var geojson = makeShapeBufferGeoJSON(lyr, dataset, opts);
32632
32016
  var dataset2 = importGeoJSON(geojson, {});
32633
- dissolveBufferDataset(dataset2, opts);
32017
+ if (!opts.debug_points) {
32018
+ dissolveBufferDataset(dataset2, opts);
32019
+ }
32020
+ timeEnd('buffer');
32634
32021
  return dataset2;
32635
32022
  }
32636
32023
 
32637
32024
  function makeShapeBufferGeoJSON(lyr, dataset, opts) {
32638
32025
  var distanceFn = getBufferDistanceFunction(lyr, dataset, opts);
32639
- getBufferToleranceFunction(dataset, opts);
32026
+ // var toleranceFn = getBufferToleranceFunction(dataset, opts);
32640
32027
  var geod = getFastGeodeticSegmentFunction(getDatasetCRS(dataset));
32641
32028
  var getBearing = getBearingFunction(dataset);
32642
32029
  var makerOpts = Object.assign({geometry_type: lyr.geometry_type}, opts);
32643
- var factory = opts.v2 ? getPolylineBufferMaker2 : getPolylineBufferMaker;
32644
- var makeShapeBuffer = factory(dataset.arcs, geod, getBearing, makerOpts);
32645
- lyr.data ? lyr.data.getRecords() : null;
32646
- var geometries = lyr.shapes.map(function(shape, i) {
32647
- var dist = distanceFn(i);
32648
- if (!dist || !shape) return null;
32649
- return makeShapeBuffer(shape, dist, lyr.geometry_type);
32650
- });
32030
+ var makeShapeBuffer =
32031
+ opts.v2 && getPolylineBufferMaker$1(dataset.arcs, geod, getBearing, makerOpts) ||
32032
+ opts.v3 && getPolylineBufferMaker$2(dataset.arcs, geod, getBearing, makerOpts) ||
32033
+ getPolylineBufferMaker(dataset.arcs, geod, getBearing, makerOpts);
32034
+ // var records = lyr.data ? lyr.data.getRecords() : null;
32035
+ var arr = lyr.shapes.reduce(function(memo, shape, i) {
32036
+ var distance = distanceFn(i);
32037
+ if (!distance || !shape) return memo;
32038
+ // retn might be an array of features or a single feature
32039
+ var retn = makeShapeBuffer(shape, distance);
32040
+ return memo.concat(retn);
32041
+ }, []);
32042
+ var geojsonType = arr?.[0].type;
32651
32043
  // TODO: make sure that importer supports null geometries (not standard GeoJSON);
32652
- return {
32044
+ // console.log(arr)
32045
+ return geojsonType == 'Feature' ? {
32046
+ type: 'FeatureCollection',
32047
+ features: arr
32048
+ } : {
32653
32049
  type: 'GeometryCollection',
32654
- geometries: geometries
32050
+ geometries: arr
32655
32051
  };
32656
32052
  }
32657
32053
 
@@ -33103,8 +32499,7 @@ ${svg}
33103
32499
  stop("Unsupported geometry type");
33104
32500
  }
33105
32501
 
33106
- var lyr2 = mergeOutputLayerIntoDataset(lyr, dataset, dataset2, opts);
33107
- return [lyr2];
32502
+ return mergeOutputLayersIntoDataset(lyr, dataset, dataset2, opts);
33108
32503
  }
33109
32504
 
33110
32505
  // currently undocumented, used in tests
@@ -37755,9 +37150,6 @@ ${svg}
37755
37150
  getLayerInfo: getLayerInfo
37756
37151
  });
37757
37152
 
37758
- // import { importGeoJSON } from '../geojson/geojson-import';
37759
-
37760
-
37761
37153
  function addTargetProxies(targets, ctx) {
37762
37154
  if (targets && targets.length > 0) {
37763
37155
  var proxies = expandCommandTargets(targets).reduce(function(memo, target) {
@@ -37780,11 +37172,19 @@ ${svg}
37780
37172
  var proxy = getLayerInfo(target.layer, target.dataset); // layer_name, feature_count etc
37781
37173
  proxy.layer = target.layer;
37782
37174
  proxy.dataset = target.dataset;
37783
- addGetters(proxy, {
37784
- // export as an object, not a string or buffer
37785
- geojson: getGeoJSON
37175
+
37176
+ Object.defineProperty(proxy, 'geojson', {
37177
+ set: setGeoJSON,
37178
+ get: getGeoJSON
37786
37179
  });
37787
37180
 
37181
+ function setGeoJSON(o) {
37182
+ var dataset2 = importGeoJSON(o);
37183
+ var lyr2 = dataset2.layers[0];
37184
+ lyr2.name = target.layer.name;
37185
+ replaceLayerContents(target.layer, target.dataset, dataset2);
37186
+ }
37187
+
37788
37188
  function getGeoJSON() {
37789
37189
  var features = exportLayerAsGeoJSON(target.layer, target.dataset, {rfc7946: true}, true);
37790
37190
  return {
@@ -42303,7 +41703,7 @@ ${svg}
42303
41703
  function findPathMidpoint(path, arcs, useNearestVertex) {
42304
41704
  var halfLen = calcPathLen(path, arcs, false) / 2;
42305
41705
  var partialLen = 0;
42306
- var p;
41706
+ var p = null;
42307
41707
  forEachSegmentInPath(path, arcs, function(i, j, xx, yy) {
42308
41708
  var a = xx[i],
42309
41709
  b = yy[i],
@@ -42323,7 +41723,7 @@ ${svg}
42323
41723
  partialLen += segLen;
42324
41724
  });
42325
41725
  if (!p) {
42326
- error('Geometry error');
41726
+ debug('[findPathMidpoint()] defective path:', path);
42327
41727
  }
42328
41728
  return p;
42329
41729
  }
@@ -47259,7 +46659,7 @@ ${svg}
47259
46659
  });
47260
46660
  }
47261
46661
 
47262
- var version = "0.6.110";
46662
+ var version = "0.6.112";
47263
46663
 
47264
46664
  // Parse command line args into commands and run them
47265
46665
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -47862,67 +47262,6 @@ ${svg}
47862
47262
  return false;
47863
47263
  }
47864
47264
 
47865
- // TODO: Need to rethink polygon repair: these function can cause problems
47866
- // when part of a self-intersecting polygon is removed
47867
- //
47868
- function repairPolygonGeometry(layers, dataset, opts) {
47869
- var nodes = addIntersectionCuts(dataset);
47870
- layers.forEach(function(lyr) {
47871
- repairSelfIntersections(lyr, nodes);
47872
- });
47873
- return layers;
47874
- }
47875
-
47876
- // Remove any small shapes formed by twists in each ring
47877
- // // OOPS, NO // Retain only the part with largest area
47878
- // // this causes problems when a cut-off hole has a matching ring in another polygon
47879
- // TODO: consider cases where cut-off parts should be retained
47880
- //
47881
- function repairSelfIntersections(lyr, nodes) {
47882
- var splitter = getSelfIntersectionSplitter(nodes);
47883
-
47884
- lyr.shapes = lyr.shapes.map(function(shp, i) {
47885
- return cleanPolygon(shp);
47886
- });
47887
-
47888
- function cleanPolygon(shp) {
47889
- var cleanedPolygon = [];
47890
- forEachShapePart(shp, function(ids) {
47891
- // TODO: consider returning null if path can't be split
47892
- var splitIds = splitter(ids);
47893
- if (splitIds.length === 0) {
47894
- error("[cleanPolygon()] Defective path:", ids);
47895
- } else if (splitIds.length == 1) {
47896
- cleanedPolygon.push(splitIds[0]);
47897
- } else {
47898
- var shapeArea = geom.getPlanarPathArea(ids, nodes.arcs),
47899
- sign = shapeArea > 0 ? 1 : -1,
47900
- mainRing;
47901
-
47902
- splitIds.reduce(function(max, ringIds, i) {
47903
- var pathArea = geom.getPlanarPathArea(ringIds, nodes.arcs) * sign;
47904
- if (pathArea > max) {
47905
- mainRing = ringIds;
47906
- max = pathArea;
47907
- }
47908
- return max;
47909
- }, 0);
47910
-
47911
- if (mainRing) {
47912
- cleanedPolygon.push(mainRing);
47913
- }
47914
- }
47915
- });
47916
- return cleanedPolygon.length > 0 ? cleanedPolygon : null;
47917
- }
47918
- }
47919
-
47920
- var PolygonRepair = /*#__PURE__*/Object.freeze({
47921
- __proto__: null,
47922
- repairPolygonGeometry: repairPolygonGeometry,
47923
- repairSelfIntersections: repairSelfIntersections
47924
- });
47925
-
47926
47265
  // Attach functions exported by modules to the "internal" object,
47927
47266
  // so they can be run by tests and by the GUI.
47928
47267
  // TODO: rewrite tests to import functions directly from modules,
@@ -47969,7 +47308,7 @@ ${svg}
47969
47308
  ArcUtils,
47970
47309
  Bbox2Clipping,
47971
47310
  BinArray$1,
47972
- BufferCommon,
47311
+ // BufferCommon,
47973
47312
  Calc,
47974
47313
  CalcUtils,
47975
47314
  Catalog$1,
@@ -48024,7 +47363,7 @@ ${svg}
48024
47363
  OverlayUtils,
48025
47364
  Pack, Unpack,
48026
47365
  ParseCommands,
48027
- PathBuffer,
47366
+ // PathBuffer,
48028
47367
  PathEndpoints,
48029
47368
  PathExport,
48030
47369
  Pathfinder,