mapshaper 0.6.108 → 0.6.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mapshaper.js CHANGED
@@ -1328,13 +1328,21 @@
1328
1328
 
1329
1329
  function verbose() {
1330
1330
  // verbose can be set globally with the -verbose command or separately for each command
1331
- if (getStashedVar('VERBOSE')) {
1331
+ if (useVerbose()) {
1332
1332
  message.apply(null, arguments);
1333
1333
  }
1334
1334
  }
1335
1335
 
1336
+ function useVerbose() {
1337
+ return getStashedVar('VERBOSE');
1338
+ }
1339
+
1340
+ function useDebug() {
1341
+ return getStashedVar('DEBUG');
1342
+ }
1343
+
1336
1344
  function debug() {
1337
- if (getStashedVar('DEBUG')) {
1345
+ if (useDebug()) {
1338
1346
  logArgs(arguments);
1339
1347
  }
1340
1348
  }
@@ -1458,6 +1466,8 @@
1458
1466
  setLoggingFunctions: setLoggingFunctions,
1459
1467
  stop: stop,
1460
1468
  truncateString: truncateString,
1469
+ useDebug: useDebug,
1470
+ useVerbose: useVerbose,
1461
1471
  verbose: verbose,
1462
1472
  warn: warn
1463
1473
  });
@@ -2265,44 +2275,6 @@
2265
2275
  return dist * R$1;
2266
2276
  }
2267
2277
 
2268
- // TODO: make this safe for small angles
2269
- function innerAngle(ax, ay, bx, by, cx, cy) {
2270
- var ab = distance2D(ax, ay, bx, by),
2271
- bc = distance2D(bx, by, cx, cy),
2272
- theta, dotp;
2273
- if (ab === 0 || bc === 0) {
2274
- theta = 0;
2275
- } else {
2276
- dotp = ((ax - bx) * (cx - bx) + (ay - by) * (cy - by)) / (ab * bc);
2277
- if (dotp >= 1 - 1e-14) {
2278
- theta = 0;
2279
- } else if (dotp <= -1 + 1e-14) {
2280
- theta = Math.PI;
2281
- } else {
2282
- theta = Math.acos(dotp); // consider using other formula at small dp
2283
- }
2284
- }
2285
- return theta;
2286
- }
2287
-
2288
- function innerAngle3D(ax, ay, az, bx, by, bz, cx, cy, cz) {
2289
- var ab = distance3D(ax, ay, az, bx, by, bz),
2290
- bc = distance3D(bx, by, bz, cx, cy, cz),
2291
- theta, dotp;
2292
- if (ab === 0 || bc === 0) {
2293
- theta = 0;
2294
- } else {
2295
- dotp = ((ax - bx) * (cx - bx) + (ay - by) * (cy - by) + (az - bz) * (cz - bz)) / (ab * bc);
2296
- if (dotp >= 1) {
2297
- theta = 0;
2298
- } else if (dotp <= -1) {
2299
- theta = Math.PI;
2300
- } else {
2301
- theta = Math.acos(dotp); // consider using other formula at small dp
2302
- }
2303
- }
2304
- return theta;
2305
- }
2306
2278
 
2307
2279
  function triangleArea(ax, ay, bx, by, cx, cy) {
2308
2280
  var area = Math.abs(((ay - cy) * (bx - cx) + (by - cy) * (cx - ax)) / 2);
@@ -2391,36 +2363,10 @@
2391
2363
  return distanceSq(px, py, ax + t * (bx - ax), ay + t * (by - ay));
2392
2364
  }
2393
2365
 
2394
-
2395
- // internal.reversePathCoords = function(arr, start, len) {
2396
- // var i = start,
2397
- // j = start + len - 1,
2398
- // tmp;
2399
- // while (i < j) {
2400
- // tmp = arr[i];
2401
- // arr[i] = arr[j];
2402
- // arr[j] = tmp;
2403
- // i++;
2404
- // j--;
2405
- // }
2406
- // };
2407
-
2408
- // merge B into A
2409
- // function mergeBounds(a, b) {
2410
- // if (b[0] < a[0]) a[0] = b[0];
2411
- // if (b[1] < a[1]) a[1] = b[1];
2412
- // if (b[2] > a[2]) a[2] = b[2];
2413
- // if (b[3] > a[3]) a[3] = b[3];
2414
- // }
2415
-
2416
2366
  function containsBounds(a, b) {
2417
2367
  return a[0] <= b[0] && a[2] >= b[2] && a[1] <= b[1] && a[3] >= b[3];
2418
2368
  }
2419
2369
 
2420
- // function boundsArea(b) {
2421
- // return (b[2] - b[0]) * (b[3] - b[1]);
2422
- // }
2423
-
2424
2370
  var Geom = /*#__PURE__*/Object.freeze({
2425
2371
  __proto__: null,
2426
2372
  D2R: D2R,
@@ -2438,9 +2384,7 @@
2438
2384
  distanceSq: distanceSq,
2439
2385
  distanceSq3D: distanceSq3D,
2440
2386
  greatCircleDistance: greatCircleDistance,
2441
- innerAngle: innerAngle,
2442
2387
  innerAngle2: innerAngle2,
2443
- innerAngle3D: innerAngle3D,
2444
2388
  lngLatToXYZ: lngLatToXYZ,
2445
2389
  pointSegDistSq: pointSegDistSq,
2446
2390
  pointSegDistSq2: pointSegDistSq2,
@@ -2962,7 +2906,22 @@
2962
2906
  // @coords: Array of relevant coordinates (e.g. bbox coordinates of vertex coordinates
2963
2907
  // of two intersecting segments).
2964
2908
  //
2909
+
2910
+ // Updated the original function with a smaller interval... which works
2911
+ // better on a (limited) set of real-world sample data
2912
+ // (less likely to create erroneous output)
2965
2913
  function getHighPrecisionSnapInterval(coords) {
2914
+ var n = Math.max.apply(null, coords.map(Math.abs));
2915
+ var ceil = n <= 1 ? 1 : 2 ** Math.ceil(Math.log2(n));
2916
+ // console.log(ceil < ceil + ceil / 2 ** 51) // true
2917
+ // console.log(ceil < ceil + ceil / 2 ** 52) // true
2918
+ // console.log(ceil < ceil + ceil / 2 ** 53) // false
2919
+ var interval = ceil / 2 ** 51;
2920
+ // console.log('interval:', interval)
2921
+ return interval;
2922
+ }
2923
+
2924
+ function getHighPrecisionSnapInterval_old(coords) {
2966
2925
  var maxCoord = Math.max.apply(null, coords.map(Math.abs));
2967
2926
  return maxCoord * 1e-14;
2968
2927
  }
@@ -3148,11 +3107,1044 @@
3148
3107
  getCoordinateIds: getCoordinateIds,
3149
3108
  getEndpointIds: getEndpointIds,
3150
3109
  getHighPrecisionSnapInterval: getHighPrecisionSnapInterval,
3110
+ getHighPrecisionSnapInterval_old: getHighPrecisionSnapInterval_old,
3151
3111
  snapCoords: snapCoords,
3152
3112
  snapCoordsByInterval: snapCoordsByInterval,
3153
3113
  snapEndpointsByInterval: snapEndpointsByInterval
3154
3114
  });
3155
3115
 
3116
+ /*
3117
+ * big.js v7.0.1
3118
+ * A small, fast, easy-to-use library for arbitrary-precision decimal arithmetic.
3119
+ * Copyright (c) 2025 Michael Mclaughlin
3120
+ * https://github.com/MikeMcl/big.js/LICENCE.md
3121
+ */
3122
+
3123
+
3124
+ /************************************** EDITABLE DEFAULTS *****************************************/
3125
+
3126
+
3127
+ // The default values below must be integers within the stated ranges.
3128
+
3129
+ /*
3130
+ * The maximum number of decimal places (DP) of the results of operations involving division:
3131
+ * div and sqrt, and pow with negative exponents.
3132
+ */
3133
+ var DP = 20, // 0 to MAX_DP
3134
+
3135
+ /*
3136
+ * The rounding mode (RM) used when rounding to the above decimal places.
3137
+ *
3138
+ * 0 Towards zero (i.e. truncate, no rounding). (ROUND_DOWN)
3139
+ * 1 To nearest neighbour. If equidistant, round up. (ROUND_HALF_UP)
3140
+ * 2 To nearest neighbour. If equidistant, to even. (ROUND_HALF_EVEN)
3141
+ * 3 Away from zero. (ROUND_UP)
3142
+ */
3143
+ RM = 1, // 0, 1, 2 or 3
3144
+
3145
+ // The maximum value of DP and Big.DP.
3146
+ MAX_DP = 1E6, // 0 to 1000000
3147
+
3148
+ // The maximum magnitude of the exponent argument to the pow method.
3149
+ MAX_POWER = 1E6, // 1 to 1000000
3150
+
3151
+ /*
3152
+ * The negative exponent (NE) at and beneath which toString returns exponential notation.
3153
+ * (JavaScript numbers: -7)
3154
+ * -1000000 is the minimum recommended exponent value of a Big.
3155
+ */
3156
+ NE = -7, // 0 to -1000000
3157
+
3158
+ /*
3159
+ * The positive exponent (PE) at and above which toString returns exponential notation.
3160
+ * (JavaScript numbers: 21)
3161
+ * 1000000 is the maximum recommended exponent value of a Big, but this limit is not enforced.
3162
+ */
3163
+ PE = 21, // 0 to 1000000
3164
+
3165
+ /*
3166
+ * When true, an error will be thrown if a primitive number is passed to the Big constructor,
3167
+ * or if valueOf is called, or if toNumber is called on a Big which cannot be converted to a
3168
+ * primitive number without a loss of precision.
3169
+ */
3170
+ STRICT = false, // true or false
3171
+
3172
+
3173
+ /**************************************************************************************************/
3174
+
3175
+
3176
+ // Error messages.
3177
+ NAME = '[big.js] ',
3178
+ INVALID = NAME + 'Invalid ',
3179
+ INVALID_DP = INVALID + 'decimal places',
3180
+ INVALID_RM = INVALID + 'rounding mode',
3181
+ DIV_BY_ZERO = NAME + 'Division by zero',
3182
+
3183
+ // The shared prototype object.
3184
+ P = {},
3185
+ UNDEFINED = void 0,
3186
+ NUMERIC = /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i;
3187
+
3188
+
3189
+ /*
3190
+ * Create and return a Big constructor.
3191
+ */
3192
+ function _Big_() {
3193
+
3194
+ /*
3195
+ * The Big constructor and exported function.
3196
+ * Create and return a new instance of a Big number object.
3197
+ *
3198
+ * n {number|string|Big} A numeric value.
3199
+ */
3200
+ function Big(n) {
3201
+ var x = this;
3202
+
3203
+ // Enable constructor usage without new.
3204
+ if (!(x instanceof Big)) {
3205
+ return n === UNDEFINED && arguments.length === 0 ? _Big_() : new Big(n);
3206
+ }
3207
+
3208
+
3209
+ // Duplicate.
3210
+ if (n instanceof Big) {
3211
+ x.s = n.s;
3212
+ x.e = n.e;
3213
+ x.c = n.c.slice();
3214
+ } else {
3215
+ if (typeof n !== 'string') {
3216
+ if (Big.strict === true && typeof n !== 'bigint') {
3217
+ throw TypeError(INVALID + 'value');
3218
+ }
3219
+
3220
+ // Minus zero?
3221
+ n = n === 0 && 1 / n < 0 ? '-0' : String(n);
3222
+ }
3223
+
3224
+ parse(x, n);
3225
+ }
3226
+
3227
+ // Retain a reference to this Big constructor.
3228
+ // Shadow Big.prototype.constructor which points to Object.
3229
+ x.constructor = Big;
3230
+ }
3231
+
3232
+ Big.prototype = P;
3233
+ Big.DP = DP;
3234
+ Big.RM = RM;
3235
+ Big.NE = NE;
3236
+ Big.PE = PE;
3237
+ Big.strict = STRICT;
3238
+ Big.roundDown = 0;
3239
+ Big.roundHalfUp = 1;
3240
+ Big.roundHalfEven = 2;
3241
+ Big.roundUp = 3;
3242
+
3243
+ return Big;
3244
+ }
3245
+
3246
+
3247
+ /*
3248
+ * Parse the number or string value passed to a Big constructor.
3249
+ *
3250
+ * x {Big} A Big number instance.
3251
+ * n {number|string} A numeric value.
3252
+ */
3253
+ function parse(x, n) {
3254
+ var e, i, nl;
3255
+
3256
+ if (!NUMERIC.test(n)) {
3257
+ throw Error(INVALID + 'number');
3258
+ }
3259
+
3260
+ // Determine sign.
3261
+ x.s = n.charAt(0) == '-' ? (n = n.slice(1), -1) : 1;
3262
+
3263
+ // Decimal point?
3264
+ if ((e = n.indexOf('.')) > -1) n = n.replace('.', '');
3265
+
3266
+ // Exponential form?
3267
+ if ((i = n.search(/e/i)) > 0) {
3268
+
3269
+ // Determine exponent.
3270
+ if (e < 0) e = i;
3271
+ e += +n.slice(i + 1);
3272
+ n = n.substring(0, i);
3273
+ } else if (e < 0) {
3274
+
3275
+ // Integer.
3276
+ e = n.length;
3277
+ }
3278
+
3279
+ nl = n.length;
3280
+
3281
+ // Determine leading zeros.
3282
+ for (i = 0; i < nl && n.charAt(i) == '0';) ++i;
3283
+
3284
+ if (i == nl) {
3285
+
3286
+ // Zero.
3287
+ x.c = [x.e = 0];
3288
+ } else {
3289
+
3290
+ // Determine trailing zeros.
3291
+ for (; nl > 0 && n.charAt(--nl) == '0';);
3292
+ x.e = e - i - 1;
3293
+ x.c = [];
3294
+
3295
+ // Convert string to array of digits without leading/trailing zeros.
3296
+ for (e = 0; i <= nl;) x.c[e++] = +n.charAt(i++);
3297
+ }
3298
+
3299
+ return x;
3300
+ }
3301
+
3302
+
3303
+ /*
3304
+ * Round Big x to a maximum of sd significant digits using rounding mode rm.
3305
+ *
3306
+ * x {Big} The Big to round.
3307
+ * sd {number} Significant digits: integer, 0 to MAX_DP inclusive.
3308
+ * rm {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
3309
+ * [more] {boolean} Whether the result of division was truncated.
3310
+ */
3311
+ function round(x, sd, rm, more) {
3312
+ var xc = x.c;
3313
+
3314
+ if (rm === UNDEFINED) rm = x.constructor.RM;
3315
+ if (rm !== 0 && rm !== 1 && rm !== 2 && rm !== 3) {
3316
+ throw Error(INVALID_RM);
3317
+ }
3318
+
3319
+ if (sd < 1) {
3320
+ more =
3321
+ rm === 3 && (more || !!xc[0]) || sd === 0 && (
3322
+ rm === 1 && xc[0] >= 5 ||
3323
+ rm === 2 && (xc[0] > 5 || xc[0] === 5 && (more || xc[1] !== UNDEFINED))
3324
+ );
3325
+
3326
+ xc.length = 1;
3327
+
3328
+ if (more) {
3329
+
3330
+ // 1, 0.1, 0.01, 0.001, 0.0001 etc.
3331
+ x.e = x.e - sd + 1;
3332
+ xc[0] = 1;
3333
+ } else {
3334
+
3335
+ // Zero.
3336
+ xc[0] = x.e = 0;
3337
+ }
3338
+ } else if (sd < xc.length) {
3339
+
3340
+ // xc[sd] is the digit after the digit that may be rounded up.
3341
+ more =
3342
+ rm === 1 && xc[sd] >= 5 ||
3343
+ rm === 2 && (xc[sd] > 5 || xc[sd] === 5 &&
3344
+ (more || xc[sd + 1] !== UNDEFINED || xc[sd - 1] & 1)) ||
3345
+ rm === 3 && (more || !!xc[0]);
3346
+
3347
+ // Remove any digits after the required precision.
3348
+ xc.length = sd;
3349
+
3350
+ // Round up?
3351
+ if (more) {
3352
+
3353
+ // Rounding up may mean the previous digit has to be rounded up.
3354
+ for (; ++xc[--sd] > 9;) {
3355
+ xc[sd] = 0;
3356
+ if (sd === 0) {
3357
+ ++x.e;
3358
+ xc.unshift(1);
3359
+ break;
3360
+ }
3361
+ }
3362
+ }
3363
+
3364
+ // Remove trailing zeros.
3365
+ for (sd = xc.length; !xc[--sd];) xc.pop();
3366
+ }
3367
+
3368
+ return x;
3369
+ }
3370
+
3371
+
3372
+ /*
3373
+ * Return a string representing the value of Big x in normal or exponential notation.
3374
+ * Handles P.toExponential, P.toFixed, P.toJSON, P.toPrecision, P.toString and P.valueOf.
3375
+ */
3376
+ function stringify$1(x, doExponential, isNonzero) {
3377
+ var e = x.e,
3378
+ s = x.c.join(''),
3379
+ n = s.length;
3380
+
3381
+ // Exponential notation?
3382
+ if (doExponential) {
3383
+ s = s.charAt(0) + (n > 1 ? '.' + s.slice(1) : '') + (e < 0 ? 'e' : 'e+') + e;
3384
+
3385
+ // Normal notation.
3386
+ } else if (e < 0) {
3387
+ for (; ++e;) s = '0' + s;
3388
+ s = '0.' + s;
3389
+ } else if (e > 0) {
3390
+ if (++e > n) {
3391
+ for (e -= n; e--;) s += '0';
3392
+ } else if (e < n) {
3393
+ s = s.slice(0, e) + '.' + s.slice(e);
3394
+ }
3395
+ } else if (n > 1) {
3396
+ s = s.charAt(0) + '.' + s.slice(1);
3397
+ }
3398
+
3399
+ return x.s < 0 && isNonzero ? '-' + s : s;
3400
+ }
3401
+
3402
+
3403
+ // Prototype/instance methods
3404
+
3405
+
3406
+ /*
3407
+ * Return a new Big whose value is the absolute value of this Big.
3408
+ */
3409
+ P.abs = function () {
3410
+ var x = new this.constructor(this);
3411
+ x.s = 1;
3412
+ return x;
3413
+ };
3414
+
3415
+
3416
+ /*
3417
+ * Return 1 if the value of this Big is greater than the value of Big y,
3418
+ * -1 if the value of this Big is less than the value of Big y, or
3419
+ * 0 if they have the same value.
3420
+ */
3421
+ P.cmp = function (y) {
3422
+ var isneg,
3423
+ x = this,
3424
+ xc = x.c,
3425
+ yc = (y = new x.constructor(y)).c,
3426
+ i = x.s,
3427
+ j = y.s,
3428
+ k = x.e,
3429
+ l = y.e;
3430
+
3431
+ // Either zero?
3432
+ if (!xc[0] || !yc[0]) return !xc[0] ? !yc[0] ? 0 : -j : i;
3433
+
3434
+ // Signs differ?
3435
+ if (i != j) return i;
3436
+
3437
+ isneg = i < 0;
3438
+
3439
+ // Compare exponents.
3440
+ if (k != l) return k > l ^ isneg ? 1 : -1;
3441
+
3442
+ j = (k = xc.length) < (l = yc.length) ? k : l;
3443
+
3444
+ // Compare digit by digit.
3445
+ for (i = -1; ++i < j;) {
3446
+ if (xc[i] != yc[i]) return xc[i] > yc[i] ^ isneg ? 1 : -1;
3447
+ }
3448
+
3449
+ // Compare lengths.
3450
+ return k == l ? 0 : k > l ^ isneg ? 1 : -1;
3451
+ };
3452
+
3453
+
3454
+ /*
3455
+ * Return a new Big whose value is the value of this Big divided by the value of Big y, rounded,
3456
+ * if necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM.
3457
+ */
3458
+ P.div = function (y) {
3459
+ var x = this,
3460
+ Big = x.constructor,
3461
+ a = x.c, // dividend
3462
+ b = (y = new Big(y)).c, // divisor
3463
+ k = x.s == y.s ? 1 : -1,
3464
+ dp = Big.DP;
3465
+
3466
+ if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
3467
+ throw Error(INVALID_DP);
3468
+ }
3469
+
3470
+ // Divisor is zero?
3471
+ if (!b[0]) {
3472
+ throw Error(DIV_BY_ZERO);
3473
+ }
3474
+
3475
+ // Dividend is 0? Return +-0.
3476
+ if (!a[0]) {
3477
+ y.s = k;
3478
+ y.c = [y.e = 0];
3479
+ return y;
3480
+ }
3481
+
3482
+ var bl, bt, n, cmp, ri,
3483
+ bz = b.slice(),
3484
+ ai = bl = b.length,
3485
+ al = a.length,
3486
+ r = a.slice(0, bl), // remainder
3487
+ rl = r.length,
3488
+ q = y, // quotient
3489
+ qc = q.c = [],
3490
+ qi = 0,
3491
+ p = dp + (q.e = x.e - y.e) + 1; // precision of the result
3492
+
3493
+ q.s = k;
3494
+ k = p < 0 ? 0 : p;
3495
+
3496
+ // Create version of divisor with leading zero.
3497
+ bz.unshift(0);
3498
+
3499
+ // Add zeros to make remainder as long as divisor.
3500
+ for (; rl++ < bl;) r.push(0);
3501
+
3502
+ do {
3503
+
3504
+ // n is how many times the divisor goes into current remainder.
3505
+ for (n = 0; n < 10; n++) {
3506
+
3507
+ // Compare divisor and remainder.
3508
+ if (bl != (rl = r.length)) {
3509
+ cmp = bl > rl ? 1 : -1;
3510
+ } else {
3511
+ for (ri = -1, cmp = 0; ++ri < bl;) {
3512
+ if (b[ri] != r[ri]) {
3513
+ cmp = b[ri] > r[ri] ? 1 : -1;
3514
+ break;
3515
+ }
3516
+ }
3517
+ }
3518
+
3519
+ // If divisor < remainder, subtract divisor from remainder.
3520
+ if (cmp < 0) {
3521
+
3522
+ // Remainder can't be more than 1 digit longer than divisor.
3523
+ // Equalise lengths using divisor with extra leading zero?
3524
+ for (bt = rl == bl ? b : bz; rl;) {
3525
+ if (r[--rl] < bt[rl]) {
3526
+ ri = rl;
3527
+ for (; ri && !r[--ri];) r[ri] = 9;
3528
+ --r[ri];
3529
+ r[rl] += 10;
3530
+ }
3531
+ r[rl] -= bt[rl];
3532
+ }
3533
+
3534
+ for (; !r[0];) r.shift();
3535
+ } else {
3536
+ break;
3537
+ }
3538
+ }
3539
+
3540
+ // Add the digit n to the result array.
3541
+ qc[qi++] = cmp ? n : ++n;
3542
+
3543
+ // Update the remainder.
3544
+ if (r[0] && cmp) r[rl] = a[ai] || 0;
3545
+ else r = [a[ai]];
3546
+
3547
+ } while ((ai++ < al || r[0] !== UNDEFINED) && k--);
3548
+
3549
+ // Leading zero? Do not remove if result is simply zero (qi == 1).
3550
+ if (!qc[0] && qi != 1) {
3551
+
3552
+ // There can't be more than one zero.
3553
+ qc.shift();
3554
+ q.e--;
3555
+ p--;
3556
+ }
3557
+
3558
+ // Round?
3559
+ if (qi > p) round(q, p, Big.RM, r[0] !== UNDEFINED);
3560
+
3561
+ return q;
3562
+ };
3563
+
3564
+
3565
+ /*
3566
+ * Return true if the value of this Big is equal to the value of Big y, otherwise return false.
3567
+ */
3568
+ P.eq = function (y) {
3569
+ return this.cmp(y) === 0;
3570
+ };
3571
+
3572
+
3573
+ /*
3574
+ * Return true if the value of this Big is greater than the value of Big y, otherwise return
3575
+ * false.
3576
+ */
3577
+ P.gt = function (y) {
3578
+ return this.cmp(y) > 0;
3579
+ };
3580
+
3581
+
3582
+ /*
3583
+ * Return true if the value of this Big is greater than or equal to the value of Big y, otherwise
3584
+ * return false.
3585
+ */
3586
+ P.gte = function (y) {
3587
+ return this.cmp(y) > -1;
3588
+ };
3589
+
3590
+
3591
+ /*
3592
+ * Return true if the value of this Big is less than the value of Big y, otherwise return false.
3593
+ */
3594
+ P.lt = function (y) {
3595
+ return this.cmp(y) < 0;
3596
+ };
3597
+
3598
+
3599
+ /*
3600
+ * Return true if the value of this Big is less than or equal to the value of Big y, otherwise
3601
+ * return false.
3602
+ */
3603
+ P.lte = function (y) {
3604
+ return this.cmp(y) < 1;
3605
+ };
3606
+
3607
+
3608
+ /*
3609
+ * Return a new Big whose value is the value of this Big minus the value of Big y.
3610
+ */
3611
+ P.minus = P.sub = function (y) {
3612
+ var i, j, t, xlty,
3613
+ x = this,
3614
+ Big = x.constructor,
3615
+ a = x.s,
3616
+ b = (y = new Big(y)).s;
3617
+
3618
+ // Signs differ?
3619
+ if (a != b) {
3620
+ y.s = -b;
3621
+ return x.plus(y);
3622
+ }
3623
+
3624
+ var xc = x.c.slice(),
3625
+ xe = x.e,
3626
+ yc = y.c,
3627
+ ye = y.e;
3628
+
3629
+ // Either zero?
3630
+ if (!xc[0] || !yc[0]) {
3631
+ if (yc[0]) {
3632
+ y.s = -b;
3633
+ } else if (xc[0]) {
3634
+ y = new Big(x);
3635
+ } else {
3636
+ y.s = 1;
3637
+ }
3638
+ return y;
3639
+ }
3640
+
3641
+ // Determine which is the bigger number. Prepend zeros to equalise exponents.
3642
+ if (a = xe - ye) {
3643
+
3644
+ if (xlty = a < 0) {
3645
+ a = -a;
3646
+ t = xc;
3647
+ } else {
3648
+ ye = xe;
3649
+ t = yc;
3650
+ }
3651
+
3652
+ t.reverse();
3653
+ for (b = a; b--;) t.push(0);
3654
+ t.reverse();
3655
+ } else {
3656
+
3657
+ // Exponents equal. Check digit by digit.
3658
+ j = ((xlty = xc.length < yc.length) ? xc : yc).length;
3659
+
3660
+ for (a = b = 0; b < j; b++) {
3661
+ if (xc[b] != yc[b]) {
3662
+ xlty = xc[b] < yc[b];
3663
+ break;
3664
+ }
3665
+ }
3666
+ }
3667
+
3668
+ // x < y? Point xc to the array of the bigger number.
3669
+ if (xlty) {
3670
+ t = xc;
3671
+ xc = yc;
3672
+ yc = t;
3673
+ y.s = -y.s;
3674
+ }
3675
+
3676
+ /*
3677
+ * Append zeros to xc if shorter. No need to add zeros to yc if shorter as subtraction only
3678
+ * needs to start at yc.length.
3679
+ */
3680
+ if ((b = (j = yc.length) - (i = xc.length)) > 0) for (; b--;) xc[i++] = 0;
3681
+
3682
+ // Subtract yc from xc.
3683
+ for (b = i; j > a;) {
3684
+ if (xc[--j] < yc[j]) {
3685
+ for (i = j; i && !xc[--i];) xc[i] = 9;
3686
+ --xc[i];
3687
+ xc[j] += 10;
3688
+ }
3689
+
3690
+ xc[j] -= yc[j];
3691
+ }
3692
+
3693
+ // Remove trailing zeros.
3694
+ for (; xc[--b] === 0;) xc.pop();
3695
+
3696
+ // Remove leading zeros and adjust exponent accordingly.
3697
+ for (; xc[0] === 0;) {
3698
+ xc.shift();
3699
+ --ye;
3700
+ }
3701
+
3702
+ if (!xc[0]) {
3703
+
3704
+ // n - n = +0
3705
+ y.s = 1;
3706
+
3707
+ // Result must be zero.
3708
+ xc = [ye = 0];
3709
+ }
3710
+
3711
+ y.c = xc;
3712
+ y.e = ye;
3713
+
3714
+ return y;
3715
+ };
3716
+
3717
+
3718
+ /*
3719
+ * Return a new Big whose value is the value of this Big modulo the value of Big y.
3720
+ */
3721
+ P.mod = function (y) {
3722
+ var ygtx,
3723
+ x = this,
3724
+ Big = x.constructor,
3725
+ a = x.s,
3726
+ b = (y = new Big(y)).s;
3727
+
3728
+ if (!y.c[0]) {
3729
+ throw Error(DIV_BY_ZERO);
3730
+ }
3731
+
3732
+ x.s = y.s = 1;
3733
+ ygtx = y.cmp(x) == 1;
3734
+ x.s = a;
3735
+ y.s = b;
3736
+
3737
+ if (ygtx) return new Big(x);
3738
+
3739
+ a = Big.DP;
3740
+ b = Big.RM;
3741
+ Big.DP = Big.RM = 0;
3742
+ x = x.div(y);
3743
+ Big.DP = a;
3744
+ Big.RM = b;
3745
+
3746
+ return this.minus(x.times(y));
3747
+ };
3748
+
3749
+
3750
+ /*
3751
+ * Return a new Big whose value is the value of this Big negated.
3752
+ */
3753
+ P.neg = function () {
3754
+ var x = new this.constructor(this);
3755
+ x.s = -x.s;
3756
+ return x;
3757
+ };
3758
+
3759
+
3760
+ /*
3761
+ * Return a new Big whose value is the value of this Big plus the value of Big y.
3762
+ */
3763
+ P.plus = P.add = function (y) {
3764
+ var e, k, t,
3765
+ x = this,
3766
+ Big = x.constructor;
3767
+
3768
+ y = new Big(y);
3769
+
3770
+ // Signs differ?
3771
+ if (x.s != y.s) {
3772
+ y.s = -y.s;
3773
+ return x.minus(y);
3774
+ }
3775
+
3776
+ var xe = x.e,
3777
+ xc = x.c,
3778
+ ye = y.e,
3779
+ yc = y.c;
3780
+
3781
+ // Either zero?
3782
+ if (!xc[0] || !yc[0]) {
3783
+ if (!yc[0]) {
3784
+ if (xc[0]) {
3785
+ y = new Big(x);
3786
+ } else {
3787
+ y.s = x.s;
3788
+ }
3789
+ }
3790
+ return y;
3791
+ }
3792
+
3793
+ xc = xc.slice();
3794
+
3795
+ // Prepend zeros to equalise exponents.
3796
+ // Note: reverse faster than unshifts.
3797
+ if (e = xe - ye) {
3798
+ if (e > 0) {
3799
+ ye = xe;
3800
+ t = yc;
3801
+ } else {
3802
+ e = -e;
3803
+ t = xc;
3804
+ }
3805
+
3806
+ t.reverse();
3807
+ for (; e--;) t.push(0);
3808
+ t.reverse();
3809
+ }
3810
+
3811
+ // Point xc to the longer array.
3812
+ if (xc.length - yc.length < 0) {
3813
+ t = yc;
3814
+ yc = xc;
3815
+ xc = t;
3816
+ }
3817
+
3818
+ e = yc.length;
3819
+
3820
+ // Only start adding at yc.length - 1 as the further digits of xc can be left as they are.
3821
+ for (k = 0; e; xc[e] %= 10) k = (xc[--e] = xc[e] + yc[e] + k) / 10 | 0;
3822
+
3823
+ // No need to check for zero, as +x + +y != 0 && -x + -y != 0
3824
+
3825
+ if (k) {
3826
+ xc.unshift(k);
3827
+ ++ye;
3828
+ }
3829
+
3830
+ // Remove trailing zeros.
3831
+ for (e = xc.length; xc[--e] === 0;) xc.pop();
3832
+
3833
+ y.c = xc;
3834
+ y.e = ye;
3835
+
3836
+ return y;
3837
+ };
3838
+
3839
+
3840
+ /*
3841
+ * Return a Big whose value is the value of this Big raised to the power n.
3842
+ * If n is negative, round to a maximum of Big.DP decimal places using rounding
3843
+ * mode Big.RM.
3844
+ *
3845
+ * n {number} Integer, -MAX_POWER to MAX_POWER inclusive.
3846
+ */
3847
+ P.pow = function (n) {
3848
+ var x = this,
3849
+ one = new x.constructor('1'),
3850
+ y = one,
3851
+ isneg = n < 0;
3852
+
3853
+ if (n !== ~~n || n < -MAX_POWER || n > MAX_POWER) {
3854
+ throw Error(INVALID + 'exponent');
3855
+ }
3856
+
3857
+ if (isneg) n = -n;
3858
+
3859
+ for (;;) {
3860
+ if (n & 1) y = y.times(x);
3861
+ n >>= 1;
3862
+ if (!n) break;
3863
+ x = x.times(x);
3864
+ }
3865
+
3866
+ return isneg ? one.div(y) : y;
3867
+ };
3868
+
3869
+
3870
+ /*
3871
+ * Return a new Big whose value is the value of this Big rounded to a maximum precision of sd
3872
+ * significant digits using rounding mode rm, or Big.RM if rm is not specified.
3873
+ *
3874
+ * sd {number} Significant digits: integer, 1 to MAX_DP inclusive.
3875
+ * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
3876
+ */
3877
+ P.prec = function (sd, rm) {
3878
+ if (sd !== ~~sd || sd < 1 || sd > MAX_DP) {
3879
+ throw Error(INVALID + 'precision');
3880
+ }
3881
+ return round(new this.constructor(this), sd, rm);
3882
+ };
3883
+
3884
+
3885
+ /*
3886
+ * Return a new Big whose value is the value of this Big rounded to a maximum of dp decimal places
3887
+ * using rounding mode rm, or Big.RM if rm is not specified.
3888
+ * If dp is negative, round to an integer which is a multiple of 10**-dp.
3889
+ * If dp is not specified, round to 0 decimal places.
3890
+ *
3891
+ * dp? {number} Integer, -MAX_DP to MAX_DP inclusive.
3892
+ * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
3893
+ */
3894
+ P.round = function (dp, rm) {
3895
+ if (dp === UNDEFINED) dp = 0;
3896
+ else if (dp !== ~~dp || dp < -MAX_DP || dp > MAX_DP) {
3897
+ throw Error(INVALID_DP);
3898
+ }
3899
+ return round(new this.constructor(this), dp + this.e + 1, rm);
3900
+ };
3901
+
3902
+
3903
+ /*
3904
+ * Return a new Big whose value is the square root of the value of this Big, rounded, if
3905
+ * necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM.
3906
+ */
3907
+ P.sqrt = function () {
3908
+ var r, c, t,
3909
+ x = this,
3910
+ Big = x.constructor,
3911
+ s = x.s,
3912
+ e = x.e,
3913
+ half = new Big('0.5');
3914
+
3915
+ // Zero?
3916
+ if (!x.c[0]) return new Big(x);
3917
+
3918
+ // Negative?
3919
+ if (s < 0) {
3920
+ throw Error(NAME + 'No square root');
3921
+ }
3922
+
3923
+ // Estimate.
3924
+ s = Math.sqrt(+stringify$1(x, true, true));
3925
+
3926
+ // Math.sqrt underflow/overflow?
3927
+ // Re-estimate: pass x coefficient to Math.sqrt as integer, then adjust the result exponent.
3928
+ if (s === 0 || s === 1 / 0) {
3929
+ c = x.c.join('');
3930
+ if (!(c.length + e & 1)) c += '0';
3931
+ s = Math.sqrt(c);
3932
+ e = ((e + 1) / 2 | 0) - (e < 0 || e & 1);
3933
+ r = new Big((s == 1 / 0 ? '5e' : (s = s.toExponential()).slice(0, s.indexOf('e') + 1)) + e);
3934
+ } else {
3935
+ r = new Big(s + '');
3936
+ }
3937
+
3938
+ e = r.e + (Big.DP += 4);
3939
+
3940
+ // Newton-Raphson iteration.
3941
+ do {
3942
+ t = r;
3943
+ r = half.times(t.plus(x.div(t)));
3944
+ } while (t.c.slice(0, e).join('') !== r.c.slice(0, e).join(''));
3945
+
3946
+ return round(r, (Big.DP -= 4) + r.e + 1, Big.RM);
3947
+ };
3948
+
3949
+
3950
+ /*
3951
+ * Return a new Big whose value is the value of this Big times the value of Big y.
3952
+ */
3953
+ P.times = P.mul = function (y) {
3954
+ var c,
3955
+ x = this,
3956
+ Big = x.constructor,
3957
+ xc = x.c,
3958
+ yc = (y = new Big(y)).c,
3959
+ a = xc.length,
3960
+ b = yc.length,
3961
+ i = x.e,
3962
+ j = y.e;
3963
+
3964
+ // Determine sign of result.
3965
+ y.s = x.s == y.s ? 1 : -1;
3966
+
3967
+ // Return signed 0 if either 0.
3968
+ if (!xc[0] || !yc[0]) {
3969
+ y.c = [y.e = 0];
3970
+ return y;
3971
+ }
3972
+
3973
+ // Initialise exponent of result as x.e + y.e.
3974
+ y.e = i + j;
3975
+
3976
+ // If array xc has fewer digits than yc, swap xc and yc, and lengths.
3977
+ if (a < b) {
3978
+ c = xc;
3979
+ xc = yc;
3980
+ yc = c;
3981
+ j = a;
3982
+ a = b;
3983
+ b = j;
3984
+ }
3985
+
3986
+ // Initialise coefficient array of result with zeros.
3987
+ for (c = new Array(j = a + b); j--;) c[j] = 0;
3988
+
3989
+ // Multiply.
3990
+
3991
+ // i is initially xc.length.
3992
+ for (i = b; i--;) {
3993
+ b = 0;
3994
+
3995
+ // a is yc.length.
3996
+ for (j = a + i; j > i;) {
3997
+
3998
+ // Current sum of products at this digit position, plus carry.
3999
+ b = c[j] + yc[i] * xc[j - i - 1] + b;
4000
+ c[j--] = b % 10;
4001
+
4002
+ // carry
4003
+ b = b / 10 | 0;
4004
+ }
4005
+
4006
+ c[j] = b;
4007
+ }
4008
+
4009
+ // Increment result exponent if there is a final carry, otherwise remove leading zero.
4010
+ if (b) ++y.e;
4011
+ else c.shift();
4012
+
4013
+ // Remove trailing zeros.
4014
+ for (i = c.length; !c[--i];) c.pop();
4015
+ y.c = c;
4016
+
4017
+ return y;
4018
+ };
4019
+
4020
+
4021
+ /*
4022
+ * Return a string representing the value of this Big in exponential notation rounded to dp fixed
4023
+ * decimal places using rounding mode rm, or Big.RM if rm is not specified.
4024
+ *
4025
+ * dp? {number} Decimal places: integer, 0 to MAX_DP inclusive.
4026
+ * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
4027
+ */
4028
+ P.toExponential = function (dp, rm) {
4029
+ var x = this,
4030
+ n = x.c[0];
4031
+
4032
+ if (dp !== UNDEFINED) {
4033
+ if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
4034
+ throw Error(INVALID_DP);
4035
+ }
4036
+ x = round(new x.constructor(x), ++dp, rm);
4037
+ for (; x.c.length < dp;) x.c.push(0);
4038
+ }
4039
+
4040
+ return stringify$1(x, true, !!n);
4041
+ };
4042
+
4043
+
4044
+ /*
4045
+ * Return a string representing the value of this Big in normal notation rounded to dp fixed
4046
+ * decimal places using rounding mode rm, or Big.RM if rm is not specified.
4047
+ *
4048
+ * dp? {number} Decimal places: integer, 0 to MAX_DP inclusive.
4049
+ * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
4050
+ *
4051
+ * (-0).toFixed(0) is '0', but (-0.1).toFixed(0) is '-0'.
4052
+ * (-0).toFixed(1) is '0.0', but (-0.01).toFixed(1) is '-0.0'.
4053
+ */
4054
+ P.toFixed = function (dp, rm) {
4055
+ var x = this,
4056
+ n = x.c[0];
4057
+
4058
+ if (dp !== UNDEFINED) {
4059
+ if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
4060
+ throw Error(INVALID_DP);
4061
+ }
4062
+ x = round(new x.constructor(x), dp + x.e + 1, rm);
4063
+
4064
+ // x.e may have changed if the value is rounded up.
4065
+ for (dp = dp + x.e + 1; x.c.length < dp;) x.c.push(0);
4066
+ }
4067
+
4068
+ return stringify$1(x, false, !!n);
4069
+ };
4070
+
4071
+
4072
+ /*
4073
+ * Return a string representing the value of this Big.
4074
+ * Return exponential notation if this Big has a positive exponent equal to or greater than
4075
+ * Big.PE, or a negative exponent equal to or less than Big.NE.
4076
+ * Omit the sign for negative zero.
4077
+ */
4078
+ P.toJSON = P.toString = function () {
4079
+ var x = this,
4080
+ Big = x.constructor;
4081
+ return stringify$1(x, x.e <= Big.NE || x.e >= Big.PE, !!x.c[0]);
4082
+ };
4083
+
4084
+ if (typeof Symbol !== "undefined") {
4085
+ P[Symbol.for('nodejs.util.inspect.custom')] = P.toJSON;
4086
+ }
4087
+
4088
+
4089
+ /*
4090
+ * Return the value of this Big as a primitive number.
4091
+ */
4092
+ P.toNumber = function () {
4093
+ var n = +stringify$1(this, true, true);
4094
+ if (this.constructor.strict === true && !this.eq(n.toString())) {
4095
+ throw Error(NAME + 'Imprecise conversion');
4096
+ }
4097
+ return n;
4098
+ };
4099
+
4100
+
4101
+ /*
4102
+ * Return a string representing the value of this Big rounded to sd significant digits using
4103
+ * rounding mode rm, or Big.RM if rm is not specified.
4104
+ * Use exponential notation if sd is less than the number of digits necessary to represent
4105
+ * the integer part of the value in normal notation.
4106
+ *
4107
+ * sd {number} Significant digits: integer, 1 to MAX_DP inclusive.
4108
+ * rm? {number} Rounding mode: 0 (down), 1 (half-up), 2 (half-even) or 3 (up).
4109
+ */
4110
+ P.toPrecision = function (sd, rm) {
4111
+ var x = this,
4112
+ Big = x.constructor,
4113
+ n = x.c[0];
4114
+
4115
+ if (sd !== UNDEFINED) {
4116
+ if (sd !== ~~sd || sd < 1 || sd > MAX_DP) {
4117
+ throw Error(INVALID + 'precision');
4118
+ }
4119
+ x = round(new Big(x), sd, rm);
4120
+ for (; x.c.length < sd;) x.c.push(0);
4121
+ }
4122
+
4123
+ return stringify$1(x, sd <= x.e || x.e <= Big.NE || x.e >= Big.PE, !!n);
4124
+ };
4125
+
4126
+
4127
+ /*
4128
+ * Return a string representing the value of this Big.
4129
+ * Return exponential notation if this Big has a positive exponent equal to or greater than
4130
+ * Big.PE, or a negative exponent equal to or less than Big.NE.
4131
+ * Include the sign for negative zero.
4132
+ */
4133
+ P.valueOf = function () {
4134
+ var x = this,
4135
+ Big = x.constructor;
4136
+ if (Big.strict === true) {
4137
+ throw Error(NAME + 'valueOf disallowed');
4138
+ }
4139
+ return stringify$1(x, x.e <= Big.NE || x.e >= Big.PE, true);
4140
+ };
4141
+
4142
+
4143
+ // Export
4144
+
4145
+
4146
+ var Big = _Big_();
4147
+
3156
4148
  // Find the intersection between two 2D segments
3157
4149
  // Returns 0, 1 or 2 [x, y] locations as null, [x, y], or [x1, y1, x2, y2]
3158
4150
  // Special cases:
@@ -3164,41 +4156,45 @@
3164
4156
  function segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy, epsArg) {
3165
4157
  // Use a small tolerance interval, so collinear segments and T-intersections
3166
4158
  // are detected (floating point rounding often causes exact functions to fail)
3167
- var eps = epsArg >= 0 ? epsArg :
4159
+ var eps = epsArg > 0 ? epsArg :
3168
4160
  getHighPrecisionSnapInterval([ax, ay, bx, by, cx, cy, dx, dy]);
3169
4161
  var epsSq = eps * eps;
3170
4162
  var touches, cross;
4163
+
3171
4164
  // Detect 0, 1 or 2 'touch' intersections, where a vertex of one segment
3172
4165
  // is very close to the other segment's linear portion.
3173
4166
  // One touch indicates either a T-intersection or two overlapping collinear
3174
4167
  // segments that share an endpoint. Two touches indicates overlapping
3175
4168
  // collinear segments that do not share an endpoint.
3176
4169
  touches = findPointSegTouches(epsSq, ax, ay, bx, by, cx, cy, dx, dy);
3177
- // if (touches) return touches;
3178
4170
  // Ignore endpoint-only intersections
3179
4171
  if (!touches && testEndpointHit(epsSq, ax, ay, bx, by, cx, cy, dx, dy)) {
3180
4172
  return null;
3181
4173
  }
3182
4174
  // Detect cross intersection
3183
- cross = findCrossIntersection(ax, ay, bx, by, cx, cy, dx, dy, eps);
4175
+ // (TODO: consider cross intersections that are also endpoint hits)
4176
+ if (!touches) {
4177
+ cross = findCrossIntersection(ax, ay, bx, by, cx, cy, dx, dy, eps);
4178
+ }
3184
4179
  return touches || cross || null;
3185
4180
  }
3186
4181
 
3187
4182
 
3188
- // Find the intersection point of two segments that cross each other,
3189
- // or return null if the segments do not cross.
3190
- // Assumes endpoint intersections have already been detected
3191
4183
  function findCrossIntersection(ax, ay, bx, by, cx, cy, dx, dy, eps) {
3192
- if (!segmentHit(ax, ay, bx, by, cx, cy, dx, dy)) return null;
3193
- var den = determinant2D(bx - ax, by - ay, dx - cx, dy - cy);
3194
- var m = orient2D(cx, cy, dx, dy, ax, ay) / den;
3195
- var p = [ax + m * (bx - ax), ay + m * (by - ay)];
3196
- if (Math.abs(den) < 1e-18) {
3197
- // assume that collinear and near-collinear segment intersections have been
3198
- // accounted for already.
3199
- // TODO: is this a valid assumption?
3200
- return null;
4184
+ var p;
4185
+ if (eps > 0 && !segmentHit_fast(ax, ay, bx, by, cx, cy, dx, dy)) return null;
4186
+ else if (eps === 0 && !segmentHit_big(ax, ay, bx, by, cx, cy, dx, dy)) return null;
4187
+
4188
+ // in a typical layer with many intersections, robust is preferred in
4189
+ // most (>90%) segment intersections in order to keep the positional
4190
+ // error within a small interval (e.g. 50% of eps)
4191
+ //
4192
+ if (useRobustCross(ax, ay, bx, by, cx, cy, dx, dy)) {
4193
+ p = findCrossIntersection_robust(ax, ay, bx, by, cx, cy, dx, dy);
4194
+ } else {
4195
+ p = findCrossIntersection_fast(ax, ay, bx, by, cx, cy, dx, dy);
3201
4196
  }
4197
+ if (!p) return null;
3202
4198
 
3203
4199
  // Snap p to a vertex if very close to one
3204
4200
  // This avoids tiny segments caused by T-intersection overshoots and prevents
@@ -3214,9 +4210,98 @@
3214
4210
  return p;
3215
4211
  }
3216
4212
 
4213
+ // Find the intersection point of two segments that cross each other,
4214
+ // or return null if the segments do not cross.
4215
+ // Assumes endpoint intersections have already been detected
4216
+ function findCrossIntersection_robust(ax, ay, bx, by, cx, cy, dx, dy) {
4217
+ var ax_big = Big(ax);
4218
+ var ay_big = Big(ay);
4219
+ var bx_big = Big(bx);
4220
+ var by_big = Big(by);
4221
+ var cx_big = Big(cx);
4222
+ var cy_big = Big(cy);
4223
+ var dx_big = Big(dx);
4224
+ var dy_big = Big(dy);
4225
+ var v1x = bx_big.minus(ax_big);
4226
+ var v1y = by_big.minus(ay_big);
4227
+ var den_big = determinant2D_big(v1x, v1y, dx_big.minus(cx), dy_big.minus(cy));
4228
+ if (den_big.eq(0)) {
4229
+ debug("DIV0 error (should have been caught upstream)");
4230
+ // console.log("hit?", segmentHit_big(ax, ay, bx, by, cx, cy, dx, dy))
4231
+ // console.log('Seg 1', getSegFeature(ax, ay, bx, by, true))
4232
+ // console.log('Seg 2', getSegFeature(cx, cy, dx, dy, false))
4233
+ return null;
4234
+ }
4235
+ // perform division using regular math, which does not reduce overall
4236
+ // precision in test data (big.js division is very slow)
4237
+ // tests show identical result to:
4238
+ // orient2D_big(cx_big, cy_big, dx_big, dy_big, ax_big, ay_big).div(den_big)
4239
+ var m = orient2D_big(cx_big, cy_big, dx_big, dy_big, ax_big,
4240
+ ay_big).toNumber() / den_big.toNumber();
4241
+ var m_big = Big(m);
4242
+ var x_big = ax_big.plus(m_big.times(v1x).round(16));
4243
+ var y_big = ay_big.plus(m_big.times(v1y).round(16));
4244
+ var p = [x_big.toNumber(), y_big.toNumber()];
4245
+ return p;
4246
+ }
4247
+
4248
+ function findCrossIntersection_fast(ax, ay, bx, by, cx, cy, dx, dy) {
4249
+ var den = determinant2D(bx - ax, by - ay, dx - cx, dy - cy);
4250
+ var m = orient2D(cx, cy, dx, dy, ax, ay) / den;
4251
+ var p = [ax + m * (bx - ax), ay + m * (by - ay)];
4252
+ if (Math.abs(den) < 1e-25) {
4253
+ // changed from 1e-18 to 1e-25 (see geom ex1)
4254
+ // assume that collinear and near-collinear segment intersections have been
4255
+ // accounted for already.
4256
+ // TODO: is this really true?
4257
+ return null;
4258
+ }
4259
+ return p;
4260
+ }
4261
+
4262
+ function useRobustCross(ax, ay, bx, by, cx, cy, dx, dy) {
4263
+ // angle and seg length ratio thresholds were found by comparing
4264
+ // fast and robust outputs on sample data
4265
+ if (innerAngle(ax, ay, bx, by, cx, cy, dx, dy) < 0.1) return true;
4266
+ var len1 = distance2D(ax, ay, bx, by);
4267
+ var len2 = distance2D(cx, cy, dx, dy);
4268
+ var ratio = len1 < len2 ? len1 / len2 : len2 / len1 || 0;
4269
+ if (ratio < 0.001) return true;
4270
+ return false;
4271
+ }
4272
+
4273
+ // Returns smaller unsigned angle between two segments
4274
+ function innerAngle(ax, ay, bx, by, cx, cy, dx, dy) {
4275
+ var v1x = bx - ax;
4276
+ var v1y = by - ay;
4277
+ var v2x = dx - cx;
4278
+ var v2y = dy - cy;
4279
+ var dot = v1x * v2x + v1y * v2y;
4280
+ var mag1Sq = v1x * v1x + v1y * v1y;
4281
+ var mag2Sq = v2x * v2x + v2y * v2y;
4282
+ if (mag1Sq === 0 || mag2Sq === 0) {
4283
+ return 0;
4284
+ }
4285
+ var cosTheta = dot / Math.sqrt(mag1Sq * mag2Sq);
4286
+ var theta;
4287
+ if (cosTheta > 1 - 1e-14) {
4288
+ theta = 0;
4289
+ } else if (cosTheta < -1 + 1e-14) {
4290
+ theta = Math.PI;
4291
+ } else {
4292
+ theta = Math.acos(cosTheta);
4293
+ }
4294
+ if (theta >= Math.PI / 2) {
4295
+ theta = Math.PI - theta;
4296
+ }
4297
+ return theta;
4298
+ }
4299
+
3217
4300
  function testEndpointHit(epsSq, ax, ay, bx, by, cx, cy, dx, dy) {
3218
- return distanceSq(ax, ay, cx, cy) <= epsSq || distanceSq(ax, ay, dx, dy) <= epsSq ||
3219
- distanceSq(bx, by, cx, cy) <= epsSq || distanceSq(bx, by, dx, dy) <= epsSq;
4301
+ return distanceSq(ax, ay, cx, cy) <= epsSq ||
4302
+ distanceSq(ax, ay, dx, dy) <= epsSq ||
4303
+ distanceSq(bx, by, cx, cy) <= epsSq ||
4304
+ distanceSq(bx, by, dx, dy) <= epsSq;
3220
4305
  }
3221
4306
 
3222
4307
  function findPointSegTouches(epsSq, ax, ay, bx, by, cx, cy, dx, dy) {
@@ -3227,6 +4312,9 @@
3227
4312
  collectPointSegTouch(touches, epsSq, dx, dy, ax, ay, bx, by);
3228
4313
  if (touches.length === 0) return null;
3229
4314
  if (touches.length > 4) {
4315
+ // console.log('XX', touches.length)
4316
+ // console.log('Seg 1', getSegFeature(ax, ay, bx, by, true))
4317
+ // console.log('Seg 2', getSegFeature(cx, cy, dx, dy, false))
3230
4318
  // Geometrically, more than two touch intersections can not occur.
3231
4319
  // Is it possible that fp rounding or a bug might result in >2 touches?
3232
4320
  debug('Intersection detection error');
@@ -3243,10 +4331,10 @@
3243
4331
  var pa = distanceSq(ax, ay, px, py);
3244
4332
  var pb = distanceSq(bx, by, px, py);
3245
4333
  if (pa <= epsSq || pb <= epsSq) return; // ignore endpoint hits
4334
+ // console.log("Dist:", Math.sqrt(pab), "eps:", Math.sqrt(epsSq), "p:", px, py)
3246
4335
  arr.push(px, py); // T intersection at P and AB
3247
4336
  }
3248
4337
 
3249
-
3250
4338
  // Used by mapshaper-undershoots.js
3251
4339
  // TODO: make more robust, make sure result is compatible with segmentIntersection()
3252
4340
  // (rounding errors currently must be handled downstream)
@@ -3295,16 +4383,55 @@
3295
4383
  // when a segment is vertical or horizontal. This has caused problems when
3296
4384
  // repeatedly applying bbox clipping along the same segment
3297
4385
  var x = p[0],
3298
- y = p[1];
4386
+ y = p[1],
4387
+ s1out = false,
4388
+ s2out = false;
4389
+
3299
4390
  // assumes that segment ranges intersect
3300
- x = clampToCloseRange(x, ax, bx);
3301
- x = clampToCloseRange(x, cx, dx);
3302
- y = clampToCloseRange(y, ay, by);
3303
- y = clampToCloseRange(y, cy, dy);
4391
+ if (outsideRange(x, ax, bx)) {
4392
+ x = clampToClosestEndpoint(x, ax, bx);
4393
+ s1out = true;
4394
+ }
4395
+ if (outsideRange(x, cx, dx)) {
4396
+ x = clampToClosestEndpoint(x, cx, dx);
4397
+ s2out = true;
4398
+ }
4399
+ if (outsideRange(y, ay, by)) {
4400
+ y = clampToClosestEndpoint(y, ay, by);
4401
+ s1out = true;
4402
+ }
4403
+ if (outsideRange(y, cy, dy)) {
4404
+ y = clampToClosestEndpoint(y, cy, dy);
4405
+ s2out = true;
4406
+ }
4407
+ if ((s1out || s2out)) {
4408
+ debug('Clamping a segment intersection point');
4409
+ // console.log("angle:", innerAngle(ax, ay, bx, by, cx, cy, dx, dy))
4410
+ // console.log('Feature 1', getSegFeature(ax, ay, bx, by, s1out));
4411
+ // console.log('Feature 2', getSegFeature(cx, cy, dx, dy, s2out));
4412
+ // console.log('Point:', JSON.stringify({
4413
+ // type: 'Feature',
4414
+ // properties: {fill: 'red'},
4415
+ // geometry: {type: 'Point', coordinates: [p[0], p[1]]}
4416
+ // }));
4417
+ }
3304
4418
  p[0] = x;
3305
4419
  p[1] = y;
3306
4420
  }
3307
4421
 
4422
+ // function getSegFeature(x1, y1, x2, y2, hot) {
4423
+ // return JSON.stringify({
4424
+ // type: "Feature",
4425
+ // properties: {
4426
+ // stroke: hot ? "orange" : "blue"
4427
+ // },
4428
+ // geometry: {
4429
+ // type: "LineString",
4430
+ // coordinates: [[x1, y1], [x2, y2]]
4431
+ // }
4432
+ // });
4433
+ // }
4434
+
3308
4435
  // a: coordinate of point
3309
4436
  // b: endpoint coordinate of segment
3310
4437
  // c: other endpoint of segment
@@ -3320,16 +4447,13 @@
3320
4447
  return out;
3321
4448
  }
3322
4449
 
3323
- function clampToCloseRange(a, b, c) {
3324
- var lim;
3325
- if (outsideRange(a, b, c)) {
3326
- lim = Math.abs(a - b) < Math.abs(a - c) ? b : c;
3327
- if (Math.abs(a - lim) > 1e-15) {
3328
- debug("[clampToCloseRange()] large clamping interval", a, b, c);
3329
- }
3330
- a = lim;
4450
+ function clampToClosestEndpoint(a, b, c) {
4451
+ var lim = Math.abs(a - b) < Math.abs(a - c) ? b : c;
4452
+ var interval = Math.abs(a - lim);
4453
+ if (interval > 1e-15) {
4454
+ debug("[clampToClosestEndpoint()] large clamping interval:", interval);
3331
4455
  }
3332
- return a;
4456
+ return lim;
3333
4457
  }
3334
4458
 
3335
4459
  // Determinant of matrix
@@ -3339,6 +4463,10 @@
3339
4463
  return a * d - b * c;
3340
4464
  }
3341
4465
 
4466
+ function determinant2D_big(a, b, c, d) {
4467
+ return a.times(d).minus(b.times(c));
4468
+ }
4469
+
3342
4470
  // returns a positive value if the points a, b, and c are arranged in
3343
4471
  // counterclockwise order, a negative value if the points are in clockwise
3344
4472
  // order, and zero if the points are collinear.
@@ -3347,16 +4475,51 @@
3347
4475
  return determinant2D(ax - cx, ay - cy, bx - cx, by - cy);
3348
4476
  }
3349
4477
 
4478
+ function orient2D_big(ax, ay, bx, by, cx, cy) {
4479
+ var a = (ax.minus(cx)).times(by.minus(cy));
4480
+ var b = (ay.minus(cy)).times(bx.minus(cx));
4481
+ return a.minus(b);
4482
+ }
4483
+
4484
+ function orient2D_big2(ax, ay, bx, by, cx, cy) {
4485
+ return orient2D_big(Big(ax), Big(ay), Big(bx), Big(by), Big(cx), Big(cy));
4486
+ }
4487
+
4488
+
4489
+ // export function orient2D_v2(ax, ay, bx, by, cx, cy) {
4490
+ // return -orient2D_robust(ax, ay, bx, by, cx, cy);
4491
+ // }
4492
+
4493
+
3350
4494
  // Source: Sedgewick, _Algorithms in C_
3351
4495
  // (Other functions were tried that were more sensitive to floating point errors
3352
4496
  // than this function)
3353
- function segmentHit(ax, ay, bx, by, cx, cy, dx, dy) {
4497
+ function segmentHit_fast(ax, ay, bx, by, cx, cy, dx, dy) {
3354
4498
  return orient2D(ax, ay, bx, by, cx, cy) *
3355
4499
  orient2D(ax, ay, bx, by, dx, dy) <= 0 &&
3356
4500
  orient2D(cx, cy, dx, dy, ax, ay) *
3357
4501
  orient2D(cx, cy, dx, dy, bx, by) <= 0;
3358
4502
  }
3359
4503
 
4504
+ function segmentHit_big(ax, ay, bx, by, cx, cy, dx, dy) {
4505
+ return orient2D_big(ax, ay, bx, by, cx, cy).times(
4506
+ orient2D_big(ax, ay, bx, by, dx, dy)).lte(0) &&
4507
+ orient2D_big(cx, cy, dx, dy, ax, ay).times(
4508
+ orient2D_big(cx, cy, dx, dy, bx, by)).lte(0);
4509
+ }
4510
+
4511
+ function segmentHit_big2(ax, ay, bx, by, cx, cy, dx, dy) {
4512
+ return segmentHit_big(Big(ax), Big(ay), Big(bx), Big(by),
4513
+ Big(cx), Big(cy), Big(dx), Big(dy));
4514
+ }
4515
+
4516
+ // export function segmentHit_robust(ax, ay, bx, by, cx, cy, dx, dy) {
4517
+ // return -orient2D_robust(ax, ay, bx, by, cx, cy) *
4518
+ // -orient2D_robust(ax, ay, bx, by, dx, dy) <= 0 &&
4519
+ // -orient2D_robust(cx, cy, dx, dy, ax, ay) *
4520
+ // -orient2D_robust(cx, cy, dx, dy, bx, by) <= 0;
4521
+ // }
4522
+
3360
4523
  // Useful for determining if a segment that intersects another segment is
3361
4524
  // entering or leaving an enclosed buffer area
3362
4525
  // returns -1 if angle of p1p2 -> p3p4 is counter-clockwise (left turn)
@@ -3381,7 +4544,11 @@
3381
4544
  __proto__: null,
3382
4545
  findClosestPointOnSeg: findClosestPointOnSeg,
3383
4546
  orient2D: orient2D,
3384
- segmentHit: segmentHit,
4547
+ orient2D_big: orient2D_big,
4548
+ orient2D_big2: orient2D_big2,
4549
+ segmentHit_big: segmentHit_big,
4550
+ segmentHit_big2: segmentHit_big2,
4551
+ segmentHit_fast: segmentHit_fast,
3385
4552
  segmentIntersection: segmentIntersection,
3386
4553
  segmentTurn: segmentTurn
3387
4554
  });
@@ -3691,7 +4858,7 @@
3691
4858
  // editPart: callback function
3692
4859
  function editShapes(shapes, editPart) {
3693
4860
  for (var i=0, n=shapes.length; i<n; i++) {
3694
- shapes[i] = editShapeParts(shapes[i], editPart);
4861
+ shapes[i] = editShapeParts(shapes[i], editPart, i);
3695
4862
  }
3696
4863
  }
3697
4864
 
@@ -3699,8 +4866,9 @@
3699
4866
  // @cb: function(part, i, parts)
3700
4867
  // If @cb returns an array, it replaces the existing value
3701
4868
  // If @cb returns null, the path is removed from the feature
4869
+ // @shpId: (optional) id of shape
3702
4870
  //
3703
- function editShapeParts(parts, cb) {
4871
+ function editShapeParts(parts, cb, shpId) {
3704
4872
  if (!parts) return null; // null geometry not edited
3705
4873
  if (!utils.isArray(parts)) error("Expected an array, received:", parts);
3706
4874
  var nulls = 0,
@@ -3708,7 +4876,7 @@
3708
4876
  retn;
3709
4877
 
3710
4878
  for (var i=0; i<n; i++) {
3711
- retn = cb(parts[i], i, parts);
4879
+ retn = cb(parts[i], i, parts, shpId);
3712
4880
  if (retn === null) {
3713
4881
  nulls++;
3714
4882
  parts[i] = null;
@@ -5755,7 +6923,7 @@
5755
6923
  //
5756
6924
  this.forEach2 = function(cb) {
5757
6925
  for (var arcId=0, n=this.size(); arcId<n; arcId++) {
5758
- cb(_ii[arcId], _nn[arcId], _xx, _yy, _zz, arcId);
6926
+ cb(arcId, _ii[arcId], _nn[arcId], _xx, _yy, _zz);
5759
6927
  }
5760
6928
  };
5761
6929
 
@@ -5890,6 +7058,27 @@
5890
7058
  };
5891
7059
 
5892
7060
  this.arcIsDegenerate = function(arcId) {
7061
+ return this.arcHasZeroLength(arcId) || this.arcIsSpike(arcId);
7062
+ };
7063
+
7064
+ this.arcIsSpike = function(arcId) {
7065
+ var iter = this.getArcIter(arcId);
7066
+ var x0, y0;
7067
+ if (iter.hasNext()) {
7068
+ x0 = iter.x;
7069
+ y0 = iter.y;
7070
+ }
7071
+ iter.hasNext(); // ignore second point
7072
+ if (iter.hasNext()) {
7073
+ if (iter.x == x0 && iter.y == y0 && !iter.hasNext()) {
7074
+ // three-vertex arc, first two are the same
7075
+ return true;
7076
+ }
7077
+ }
7078
+ return false;
7079
+ };
7080
+
7081
+ this.arcHasZeroLength = function(arcId) {
5893
7082
  var iter = this.getArcIter(arcId);
5894
7083
  var i = 0,
5895
7084
  x, y;
@@ -6396,10 +7585,10 @@
6396
7585
  // Visit each point in the path, up to but not including the last point
6397
7586
  for (var i = start; i < end; i++) {
6398
7587
  if (pointIsArcEndpoint(i)) {
6399
- if (firstNodeId > -1) {
6400
- arcIds.push(addEdge(arcStartId, i));
6401
- } else {
7588
+ if (firstNodeId == -1) {
6402
7589
  firstNodeId = i;
7590
+ } else {
7591
+ arcIds.push(addEdge(arcStartId, i));
6403
7592
  }
6404
7593
  arcStartId = i;
6405
7594
  }
@@ -6990,7 +8179,7 @@
6990
8179
  yy2 = new Float64Array(n),
6991
8180
  ids2 = new Int32Array(n);
6992
8181
 
6993
- arcs.forEach2(function(i, n, xx, yy, zz, arcId) {
8182
+ arcs.forEach2(function(arcId, i, n, xx, yy, zz) {
6994
8183
  var start = i,
6995
8184
  end = i + n - 1,
6996
8185
  start2 = arcId * 2,
@@ -7041,7 +8230,7 @@
7041
8230
  layers.forEach(function(lyr) {
7042
8231
  // modify copies of the original shapes; original shapes should be unmodified
7043
8232
  // (need to test this)
7044
- lyr.shapes = lyr.shapes.map(function(shape) {
8233
+ lyr.shapes = lyr.shapes.map(function(shape, i) {
7045
8234
  return editShapeParts(shape && shape.concat(), translatePath);
7046
8235
  });
7047
8236
  });
@@ -12583,11 +13772,161 @@
12583
13772
  stringifyAsNDJSON: stringifyAsNDJSON
12584
13773
  });
12585
13774
 
13775
+ function findNearestVertices(p, shp, arcs) {
13776
+ var p2 = findNearestVertex(p[0], p[1], shp, arcs);
13777
+ return findVertexIds(p2.x, p2.y, arcs);
13778
+ }
13779
+
13780
+ function snapVerticesToPoint(ids, p, arcs) {
13781
+ var data = arcs.getVertexData();
13782
+ ids.forEach(function(idx) {
13783
+ setVertexCoords(p[0], p[1], idx, arcs);
13784
+ arcs.updateArcBounds(findArcIdFromVertexId(idx, data.ii));
13785
+ });
13786
+ }
13787
+
13788
+
13789
+ // p: point to snap
13790
+ // ids: ids of nearby vertices, possibly including an arc endpoint
13791
+ function snapPointToArcEndpoint(p, ids, arcs) {
13792
+ var p2, dx, dy;
13793
+ ids.forEach(function(idx) {
13794
+ if (vertexIsArcStart(idx, arcs)) {
13795
+ p2 = getVertexCoords(idx + 1, arcs);
13796
+ } else if (vertexIsArcEnd(idx, arcs)) {
13797
+ p2 = getVertexCoords(idx - 1, arcs);
13798
+ }
13799
+ });
13800
+ if (!p2) return;
13801
+ dx = p2[0] - p[0];
13802
+ dy = p2[1] - p[1];
13803
+ if (Math.abs(dx) > Math.abs(dy)) {
13804
+ p[1] = p2[1]; // snap y coord
13805
+ } else {
13806
+ p[0] = p2[0];
13807
+ }
13808
+ }
13809
+
13810
+ // Find ids of vertices with identical coordinates to x,y in an ArcCollection
13811
+ // Caveat: does not exclude vertices that are not visible at the
13812
+ // current level of simplification.
13813
+ function findVertexIds(x, y, arcs) {
13814
+ var data = arcs.getVertexData(),
13815
+ xx = data.xx,
13816
+ yy = data.yy,
13817
+ ids = [];
13818
+ for (var i=0, n=xx.length; i<n; i++) {
13819
+ if (xx[i] == x && yy[i] == y) ids.push(i);
13820
+ }
13821
+ return ids;
13822
+ }
13823
+
13824
+ function getVertexCoords(i, arcs) {
13825
+ var data = arcs.getVertexData();
13826
+ return [data.xx[i], data.yy[i]];
13827
+ }
13828
+
13829
+ function vertexIsArcEnd(idx, arcs) {
13830
+ // Test whether the vertex at index @idx is the endpoint of an arc
13831
+ var data = arcs.getVertexData(),
13832
+ ii = data.ii,
13833
+ nn = data.nn;
13834
+ for (var j=0, n=ii.length; j<n; j++) {
13835
+ if (idx === ii[j] + nn[j] - 1) return true;
13836
+ }
13837
+ return false;
13838
+ }
13839
+
13840
+ function vertexIsArcEndpoint(idx, arcs) {
13841
+ return vertexIsArcStart(idx, arcs) || vertexIsArcEnd(idx, arcs);
13842
+ }
13843
+
13844
+ function vertexIsArcStart(idx, arcs) {
13845
+ var ii = arcs.getVertexData().ii;
13846
+ for (var j=0, n=ii.length; j<n; j++) {
13847
+ if (idx === ii[j]) return true;
13848
+ }
13849
+ return false;
13850
+ }
13851
+
13852
+ function getArcStartCoords(arcId, arcs) {
13853
+ var coords = getArcEndpointCoords(arcId, arcs);
13854
+ return coords[0];
13855
+ }
13856
+
13857
+ function getArcEndCoords(arcId, arcs) {
13858
+ var coords = getArcEndpointCoords(arcId, arcs);
13859
+ return coords[1];
13860
+ }
13861
+
13862
+ function getArcEndpointCoords(arcId, arcs) {
13863
+ if (arcId < 0) {
13864
+ return getArcEndpointCoords(~arcId, arcs).reverse();
13865
+ }
13866
+ var data = arcs.getVertexData();
13867
+ var i = data.ii[arcId];
13868
+ var n = data.nn[arcId];
13869
+ var a = [data.xx[i], data.yy[i]];
13870
+ var b = [data.xx[i + n - 1], data.yy[i + n - 1]];
13871
+ return [a, b];
13872
+ }
13873
+
13874
+ function setVertexCoords(x, y, i, arcs) {
13875
+ var data = arcs.getVertexData();
13876
+ data.xx[i] = x;
13877
+ data.yy[i] = y;
13878
+ }
13879
+
13880
+ function findNearestVertex(x, y, shp, arcs, spherical) {
13881
+ var calcLen = spherical ? geom.greatCircleDistance : geom.distance2D,
13882
+ minLen = Infinity,
13883
+ minX, minY, dist, iter;
13884
+ for (var i=0; i<shp.length; i++) {
13885
+ iter = arcs.getShapeIter(shp[i]);
13886
+ while (iter.hasNext()) {
13887
+ dist = calcLen(x, y, iter.x, iter.y);
13888
+ if (dist < minLen) {
13889
+ minLen = dist;
13890
+ minX = iter.x;
13891
+ minY = iter.y;
13892
+ }
13893
+ }
13894
+ }
13895
+ return minLen < Infinity ? {x: minX, y: minY} : null;
13896
+ }
13897
+
13898
+ var VertexUtils = /*#__PURE__*/Object.freeze({
13899
+ __proto__: null,
13900
+ findNearestVertex: findNearestVertex,
13901
+ findNearestVertices: findNearestVertices,
13902
+ findVertexIds: findVertexIds,
13903
+ getArcEndCoords: getArcEndCoords,
13904
+ getArcEndpointCoords: getArcEndpointCoords,
13905
+ getArcStartCoords: getArcStartCoords,
13906
+ getVertexCoords: getVertexCoords,
13907
+ setVertexCoords: setVertexCoords,
13908
+ snapPointToArcEndpoint: snapPointToArcEndpoint,
13909
+ snapVerticesToPoint: snapVerticesToPoint,
13910
+ vertexIsArcEnd: vertexIsArcEnd,
13911
+ vertexIsArcEndpoint: vertexIsArcEndpoint,
13912
+ vertexIsArcStart: vertexIsArcStart
13913
+ });
13914
+
12586
13915
  function isValidArc(arcId, arcs) {
12587
13916
  // check for arcs with no vertices
12588
13917
  // TODO: also check for other kinds of degenerate arcs
12589
13918
  // (e.g. collapsed arcs consisting of identical points)
12590
- return arcs.getArcLength(arcId) > 1;
13919
+ var len = arcs.getArcLength(arcId);
13920
+ if (len >= 2 === false) {
13921
+ return false;
13922
+ }
13923
+ // if (len <=3) {
13924
+ // var endpoints = getArcEndpointCoords(arcId, arcs);
13925
+ // if (pointsAreEqual(endpoints[0], endpoints[1])) {
13926
+ // return false;
13927
+ // }
13928
+ // }
13929
+ return true;
12591
13930
  }
12592
13931
 
12593
13932
  // Return id of rightmost connected arc in relation to @fromArcId
@@ -12624,6 +13963,7 @@
12624
13963
  candId = ids[j];
12625
13964
  if (!isValidArc(candId, arcs)) {
12626
13965
  // skip empty arcs
13966
+ debug('skipping one arc:', candId, 'out of:', ids.length, ids);
12627
13967
  continue;
12628
13968
  }
12629
13969
  icand = arcs.indexOfVertex(candId, -2);
@@ -12634,6 +13974,10 @@
12634
13974
  continue;
12635
13975
  }
12636
13976
  code = chooseRighthandPath(fromX, fromY, nodeX, nodeY, xx[ito], yy[ito], xx[icand], yy[icand]);
13977
+
13978
+ if (xx[ito] == xx[icand] && yy[ito] == yy[icand]) {
13979
+ debug("Pathfinder warning: duplicate segments: i:", ito, "j:", icand, "ids:", [fromArcId].concat(ids));
13980
+ }
12637
13981
  if (code == 2) {
12638
13982
  ito = icand;
12639
13983
  toArcId = candId;
@@ -12643,26 +13987,22 @@
12643
13987
 
12644
13988
  if (toArcId == fromArcId) {
12645
13989
  // This shouldn't occur, assuming that other arcs are present
12646
- error("Pathfinder error");
13990
+ error("Pathfinder error", toArcId, fromArcId);
12647
13991
  }
12648
13992
  return toArcId;
12649
13993
  }
12650
13994
 
12651
- // TODO: consider using simpler internal.chooseRighthandPath2()
12652
13995
  // Returns 1 if node->a, return 2 if node->b, else return 0
12653
13996
  // TODO: better handling of identical angles (better -- avoid creating them)
12654
13997
  function chooseRighthandPath(fromX, fromY, nodeX, nodeY, ax, ay, bx, by) {
12655
13998
  var angleA = geom.signedAngle(fromX, fromY, nodeX, nodeY, ax, ay);
12656
13999
  var angleB = geom.signedAngle(fromX, fromY, nodeX, nodeY, bx, by);
14000
+ // should use arbitrary precision math to evaluate angles smaller than this
14001
+ // (all observed errors caused by fp rounding occured with smaller angles than this)
14002
+ var smallAngle = 0.001;
12657
14003
  var code;
12658
14004
  if (angleA <= 0 || angleB <= 0) {
12659
14005
  debug("[chooseRighthandPath()] 0 angle(s):", angleA, angleB);
12660
- if (angleA <= 0) {
12661
- debug(' A orient2D:', geom.orient2D(fromX, fromY, nodeX, nodeY, ax, ay));
12662
- }
12663
- if (angleB <= 0) {
12664
- debug(' B orient2D:', geom.orient2D(fromX, fromY, nodeX, nodeY, bx, by));
12665
- }
12666
14006
  // TODO: test against "from" segment
12667
14007
  if (angleA > 0) {
12668
14008
  code = 1;
@@ -12671,23 +14011,24 @@
12671
14011
  } else {
12672
14012
  code = 0;
12673
14013
  }
12674
- } else if (angleA < angleB) {
14014
+ } else if (angleA < angleB - smallAngle) {
12675
14015
  code = 1;
12676
- } else if (angleB < angleA) {
14016
+ } else if (angleB < angleA - smallAngle) {
12677
14017
  code = 2;
12678
14018
  } else if (isNaN(angleA) || isNaN(angleB)) {
12679
14019
  // probably a duplicate point, which should not occur
12680
14020
  error('Invalid node geometry');
12681
14021
  } else {
12682
- // Equal angles: use fallback test that is less sensitive to rounding error
12683
- code = chooseRighthandVector(ax - nodeX, ay - nodeY, bx - nodeX, by - nodeY);
12684
- debug('[chooseRighthandPath()] equal angles:', angleA, 'fallback test:', code);
14022
+ // Close-to-equal or equal angles: use more exact test.
14023
+ code = chooseBetweenClosePaths(nodeX, nodeY, ax, ay, bx, by);
14024
+ // debug('[chooseRighthandPath()] close-to-equal angles:', Math.abs(angleA) - Math.abs(angleB), 'hi-res code:', code);
12685
14025
  }
12686
14026
  return code;
12687
14027
  }
12688
14028
 
12689
- function chooseRighthandVector(ax, ay, bx, by) {
12690
- var orient = geom.orient2D(ax, ay, 0, 0, bx, by);
14029
+ function chooseBetweenClosePaths(nodeX, nodeY, ax, ay, bx, by) {
14030
+ // var orient = orient2D_big2(ax, ay, 0, 0, bx, by);
14031
+ var orient = orient2D_big2(ax, ay, nodeX, nodeY, bx, by);
12691
14032
  var code;
12692
14033
  if (orient > 0) {
12693
14034
  code = 2;
@@ -12701,7 +14042,7 @@
12701
14042
 
12702
14043
  var PathfinderUtils = /*#__PURE__*/Object.freeze({
12703
14044
  __proto__: null,
12704
- chooseRighthandVector: chooseRighthandVector,
14045
+ chooseBetweenClosePaths: chooseBetweenClosePaths,
12705
14046
  getRightmostArc: getRightmostArc
12706
14047
  });
12707
14048
 
@@ -13226,7 +14567,7 @@
13226
14567
  }
13227
14568
 
13228
14569
  // test two candidate segments for intersection
13229
- hit = geom.segmentIntersection(s1p1x, s1p1y, s1p2x, s1p2y,
14570
+ hit = segmentIntersection(s1p1x, s1p1y, s1p2x, s1p2y,
13230
14571
  s2p1x, s2p1y, s2p2x, s2p2y, tolerance);
13231
14572
  if (hit) {
13232
14573
  seg1 = [s1p1, s1p2];
@@ -13284,146 +14625,6 @@
13284
14625
  sortIntersections: sortIntersections
13285
14626
  });
13286
14627
 
13287
- function findNearestVertices(p, shp, arcs) {
13288
- var p2 = findNearestVertex(p[0], p[1], shp, arcs);
13289
- return findVertexIds(p2.x, p2.y, arcs);
13290
- }
13291
-
13292
- function snapVerticesToPoint(ids, p, arcs) {
13293
- var data = arcs.getVertexData();
13294
- ids.forEach(function(idx) {
13295
- setVertexCoords(p[0], p[1], idx, arcs);
13296
- arcs.updateArcBounds(findArcIdFromVertexId(idx, data.ii));
13297
- });
13298
- }
13299
-
13300
-
13301
- // p: point to snap
13302
- // ids: ids of nearby vertices, possibly including an arc endpoint
13303
- function snapPointToArcEndpoint(p, ids, arcs) {
13304
- var p2, dx, dy;
13305
- ids.forEach(function(idx) {
13306
- if (vertexIsArcStart(idx, arcs)) {
13307
- p2 = getVertexCoords(idx + 1, arcs);
13308
- } else if (vertexIsArcEnd(idx, arcs)) {
13309
- p2 = getVertexCoords(idx - 1, arcs);
13310
- }
13311
- });
13312
- if (!p2) return;
13313
- dx = p2[0] - p[0];
13314
- dy = p2[1] - p[1];
13315
- if (Math.abs(dx) > Math.abs(dy)) {
13316
- p[1] = p2[1]; // snap y coord
13317
- } else {
13318
- p[0] = p2[0];
13319
- }
13320
- }
13321
-
13322
- // Find ids of vertices with identical coordinates to x,y in an ArcCollection
13323
- // Caveat: does not exclude vertices that are not visible at the
13324
- // current level of simplification.
13325
- function findVertexIds(x, y, arcs) {
13326
- var data = arcs.getVertexData(),
13327
- xx = data.xx,
13328
- yy = data.yy,
13329
- ids = [];
13330
- for (var i=0, n=xx.length; i<n; i++) {
13331
- if (xx[i] == x && yy[i] == y) ids.push(i);
13332
- }
13333
- return ids;
13334
- }
13335
-
13336
- function getVertexCoords(i, arcs) {
13337
- var data = arcs.getVertexData();
13338
- return [data.xx[i], data.yy[i]];
13339
- }
13340
-
13341
- function vertexIsArcEnd(idx, arcs) {
13342
- // Test whether the vertex at index @idx is the endpoint of an arc
13343
- var data = arcs.getVertexData(),
13344
- ii = data.ii,
13345
- nn = data.nn;
13346
- for (var j=0, n=ii.length; j<n; j++) {
13347
- if (idx === ii[j] + nn[j] - 1) return true;
13348
- }
13349
- return false;
13350
- }
13351
-
13352
- function vertexIsArcEndpoint(idx, arcs) {
13353
- return vertexIsArcStart(idx, arcs) || vertexIsArcEnd(idx, arcs);
13354
- }
13355
-
13356
- function vertexIsArcStart(idx, arcs) {
13357
- var ii = arcs.getVertexData().ii;
13358
- for (var j=0, n=ii.length; j<n; j++) {
13359
- if (idx === ii[j]) return true;
13360
- }
13361
- return false;
13362
- }
13363
-
13364
- function getArcStartCoords(arcId, arcs) {
13365
- var coords = getArcEndpointCoords(arcId, arcs);
13366
- return coords[0];
13367
- }
13368
-
13369
- function getArcEndCoords(arcId, arcs) {
13370
- var coords = getArcEndpointCoords(arcId, arcs);
13371
- return coords[1];
13372
- }
13373
-
13374
- function getArcEndpointCoords(arcId, arcs) {
13375
- if (arcId < 0) {
13376
- return getArcEndpointCoords(~arcId, arcs).reverse();
13377
- }
13378
- var data = arcs.getVertexData();
13379
- var i = data.ii[arcId];
13380
- var n = data.nn[arcId];
13381
- var a = [data.xx[i], data.yy[i]];
13382
- var b = [data.xx[i + n - 1], data.yy[i + n - 1]];
13383
- return [a, b];
13384
- }
13385
-
13386
- function setVertexCoords(x, y, i, arcs) {
13387
- var data = arcs.getVertexData();
13388
- data.xx[i] = x;
13389
- data.yy[i] = y;
13390
- }
13391
-
13392
- function findNearestVertex(x, y, shp, arcs, spherical) {
13393
- var calcLen = spherical ? geom.greatCircleDistance : geom.distance2D,
13394
- minLen = Infinity,
13395
- minX, minY, dist, iter;
13396
- for (var i=0; i<shp.length; i++) {
13397
- iter = arcs.getShapeIter(shp[i]);
13398
- while (iter.hasNext()) {
13399
- dist = calcLen(x, y, iter.x, iter.y);
13400
- if (dist < minLen) {
13401
- minLen = dist;
13402
- minX = iter.x;
13403
- minY = iter.y;
13404
- }
13405
- }
13406
- }
13407
- return minLen < Infinity ? {x: minX, y: minY} : null;
13408
- }
13409
-
13410
- var VertexUtils = /*#__PURE__*/Object.freeze({
13411
- __proto__: null,
13412
- findNearestVertex: findNearestVertex,
13413
- findNearestVertices: findNearestVertices,
13414
- findVertexIds: findVertexIds,
13415
- getArcEndCoords: getArcEndCoords,
13416
- getArcEndpointCoords: getArcEndpointCoords,
13417
- getArcStartCoords: getArcStartCoords,
13418
- getVertexCoords: getVertexCoords,
13419
- setVertexCoords: setVertexCoords,
13420
- snapPointToArcEndpoint: snapPointToArcEndpoint,
13421
- snapVerticesToPoint: snapVerticesToPoint,
13422
- vertexIsArcEnd: vertexIsArcEnd,
13423
- vertexIsArcEndpoint: vertexIsArcEndpoint,
13424
- vertexIsArcStart: vertexIsArcStart
13425
- });
13426
-
13427
14628
  // arcs: ArcCollection containing original coordinates
13428
14629
  function getRepairFunction(arcs) {
13429
14630
  var arcsOrig = arcs.getCopy();
@@ -13855,6 +15056,9 @@
13855
15056
  getSliverTest(dataset.arcs, threshold, sliverControl) :
13856
15057
  getMinAreaTest(threshold, dataset);
13857
15058
  var label = getSliverLabel(getAreaLabel(threshold, crs), sliverControl > 0);
15059
+ if (opts.keep_shapes) {
15060
+ filter = keepShapes(filter);
15061
+ }
13858
15062
  return {
13859
15063
  threshold: threshold,
13860
15064
  filter: filter,
@@ -13862,6 +15066,23 @@
13862
15066
  };
13863
15067
  }
13864
15068
 
15069
+ // wrap path filter in a function that ensures at least one part of every shape
15070
+ // is retained.
15071
+ function keepShapes(filter) {
15072
+ var flags;
15073
+ return function(path, pathId, paths) {
15074
+ // console.log("keepShapes()", path, pathId, paths)
15075
+ if (pathId === 0) {
15076
+ flags = paths.map(path => filter(path));
15077
+ }
15078
+ if (flags.length > 0 && flags.every(Boolean)) {
15079
+ // kludge ... assumes that the first path is a valid ring (e.g. not a)
15080
+ flags[0] = false;
15081
+ }
15082
+ return flags[pathId];
15083
+ };
15084
+ }
15085
+
13865
15086
  function getSliverLabel(areaStr, variable) {
13866
15087
  if (variable) {
13867
15088
  areaStr = areaStr.replace(' ', '+ ') + ' variable';
@@ -16790,6 +18011,7 @@
16790
18011
  arcs.flatten();
16791
18012
 
16792
18013
  var changed = snapAndCut(dataset, snapDist);
18014
+
16793
18015
  // Detect topology again if coordinates have changed
16794
18016
  if (changed || opts.rebuild_topology) {
16795
18017
  buildTopology(dataset);
@@ -16811,9 +18033,10 @@
16811
18033
 
16812
18034
  function snapAndCut(dataset, snapDist) {
16813
18035
  var arcs = dataset.arcs;
16814
- var cutOpts = snapDist > 0 ? {} : {tolerance: 0};
18036
+ var cutOpts = snapDist > 0 ? {tolerance: snapDist} : {tolerance: 0};
16815
18037
  var coordsHaveChanged = false;
16816
18038
  var snapCount, dupeCount, cutCount;
18039
+ var maxPasses = 4, passCount = 0;
16817
18040
  snapCount = snapCoordsByInterval(arcs, snapDist);
16818
18041
  dupeCount = arcs.dedupCoords();
16819
18042
 
@@ -16825,22 +18048,28 @@
16825
18048
 
16826
18049
  // cut arcs at points where segments intersect
16827
18050
  cutCount = cutPathsAtIntersections(dataset, cutOpts);
18051
+ passCount++;
16828
18052
  if (cutCount > 0 || snapCount > 0 || dupeCount > 0) {
16829
18053
  coordsHaveChanged = true;
16830
18054
  }
18055
+
16831
18056
  // perform a second snap + cut pass if needed
16832
- if (cutCount > 0) {
16833
- cutCount = 0;
18057
+ while (cutCount > 0 && passCount < maxPasses) {
18058
+ passCount++;
16834
18059
  snapCount = snapCoordsByInterval(arcs, snapDist);
16835
- arcs.dedupCoords(); // need to do this here?
16836
- if (snapCount > 0) {
18060
+ dupeCount = arcs.dedupCoords();
18061
+ // cutCount = 0;
18062
+ if (snapCount > 0 || cutCount > 0) {
16837
18063
  cutCount = cutPathsAtIntersections(dataset, cutOpts);
16838
- }
16839
- if (cutCount > 0) {
16840
- arcs.dedupCoords(); // need to do this here?
16841
- debug('Second-pass vertices added:', cutCount, 'consider third pass?');
18064
+ debug("[snapAndCut] pass:", passCount, "snaps:", snapCount, "dupes:", dupeCount, "cuts:", cutCount);
16842
18065
  }
16843
18066
  }
18067
+
18068
+ // if (cutCount > 0) {
18069
+ // arcs.dedupCoords(); // need to do this here?
18070
+ // debug('Second-pass vertices added:', cutCount, 'consider third pass?');
18071
+ // }
18072
+
16844
18073
  return coordsHaveChanged;
16845
18074
  }
16846
18075
 
@@ -16876,17 +18105,6 @@
16876
18105
  }
16877
18106
  }
16878
18107
 
16879
- // Divides a collection of arcs at points where arc paths cross each other
16880
- // Returns array for remapping arc ids
16881
- function divideArcs(arcs, opts) {
16882
- var points = findClippingPoints(arcs, opts);
16883
- // TODO: avoid the following if no points need to be added
16884
- var map = insertCutPoints(points, arcs);
16885
- // segment-point intersections currently create duplicate points
16886
- // TODO: consider dedup in a later cleanup pass?
16887
- // arcs.dedupCoords();
16888
- return map;
16889
- }
16890
18108
 
16891
18109
  function cutPathsAtIntersections(dataset, opts) {
16892
18110
  var n = dataset.arcs.getPointCount();
@@ -16905,6 +18123,25 @@
16905
18123
  });
16906
18124
  }
16907
18125
 
18126
+
18127
+ // Divides a collection of arcs at points where arc paths cross each other
18128
+ // Returns array for remapping arc ids
18129
+ function divideArcs(arcs, opts) {
18130
+ var points = findClippingPoints(arcs, opts);
18131
+ // TODO: avoid the following if no points need to be added
18132
+ var map = insertCutPoints(points, arcs);
18133
+ // segment-point intersections currently create duplicate points
18134
+ // TODO: consider dedup in a later cleanup pass?
18135
+ // arcs.dedupCoords();
18136
+ return map;
18137
+ }
18138
+
18139
+ function findClippingPoints(arcs, opts) {
18140
+ var intersections = findSegmentIntersections(arcs, opts),
18141
+ data = arcs.getVertexData();
18142
+ return convertIntersectionsToCutPoints(intersections, data.xx, data.yy);
18143
+ }
18144
+
16908
18145
  // Inserts array of cutting points into an ArcCollection
16909
18146
  // Returns array for remapping arc ids
16910
18147
  function insertCutPoints(unfilteredPoints, arcs) {
@@ -17017,32 +18254,26 @@
17017
18254
  function filterSortedCutPoints(points, arcs) {
17018
18255
  var filtered = [],
17019
18256
  pointId = 0;
17020
- arcs.forEach2(function(i, n, xx, yy) {
17021
- var j = i + n - 1,
17022
- x0 = xx[i],
17023
- y0 = yy[i],
17024
- xn = xx[j],
17025
- yn = yy[j],
18257
+ arcs.forEach2(function(arcId, i, n, xx, yy) {
18258
+ var j = i + n - 1, // idx of second endpoint
18259
+ x1 = xx[i],
18260
+ y1 = yy[i],
18261
+ x2 = xx[j],
18262
+ y2 = yy[j],
17026
18263
  p, pp;
17027
18264
 
17028
18265
  while (pointId < points.length && points[pointId].i <= j) {
17029
18266
  p = points[pointId];
18267
+ pointId++;
17030
18268
  pp = filtered[filtered.length - 1]; // previous point
17031
- if (p.x == x0 && p.y == y0 || p.x == xn && p.y == yn) ; else if (pp && pp.x == p.x && pp.y == p.y && pp.i == p.i) ; else {
18269
+ if (p.x == x1 && p.y == y1 && p.i == i || p.x == x2 && p.y == y2 && p.i >= j - 1) ; else if (pp && pp.x == p.x && pp.y == p.y && pp.i == p.i) ; else {
17032
18270
  filtered.push(p);
17033
18271
  }
17034
- pointId++;
17035
18272
  }
17036
18273
  });
17037
18274
  return filtered;
17038
18275
  }
17039
18276
 
17040
- function findClippingPoints(arcs, opts) {
17041
- var intersections = findSegmentIntersections(arcs, opts),
17042
- data = arcs.getVertexData();
17043
- return convertIntersectionsToCutPoints(intersections, data.xx, data.yy);
17044
- }
17045
-
17046
18277
  var IntersectionCuts = /*#__PURE__*/Object.freeze({
17047
18278
  __proto__: null,
17048
18279
  addIntersectionCuts: addIntersectionCuts,
@@ -18772,203 +20003,6 @@
18772
20003
  catalog.getDatasets().push(o);
18773
20004
  }
18774
20005
 
18775
- // Apply rotation, scale and/or shift to some or all of the features in a dataset
18776
- //
18777
- cmd.affine = function(targetLayers, dataset, opts) {
18778
- // Need to separate the targeted shapes from any other shapes that share
18779
- // the same topology. So we duplicate any arcs that are shared by the targeted
18780
- // shapes and their topological neighbors and remap arc references in the
18781
- // neighbors to point to the copies.
18782
- // TODO: explore alternative: if some arcs are shared between transformed and
18783
- // non-transformed shapes, first remove topology, then tranform, then rebuild topology
18784
- //
18785
- var rotateArg = opts.rotate || 0;
18786
- var scaleArg = opts.scale || 1;
18787
- var shiftArg = opts.shift ? convertIntervalPair(opts.shift, getDatasetCRS(dataset)) : [0, 0];
18788
- var arcs = dataset.arcs;
18789
- var targetShapes = [];
18790
- var otherShapes = [];
18791
- var targetPoints = [];
18792
- var targetFlags, otherFlags, transform;
18793
- dataset.layers.filter(layerHasGeometry).forEach(function(lyr) {
18794
- var hits = [],
18795
- misses = [],
18796
- test;
18797
- if (targetLayers.indexOf(lyr) == -1) {
18798
- misses = lyr.shapes;
18799
- } else if (opts.where) {
18800
- test = compileFeatureExpression(opts.where, lyr, dataset.arcs);
18801
- lyr.shapes.forEach(function(shp, i) {
18802
- (test(i) ? hits : misses).push(shp);
18803
- });
18804
- } else {
18805
- hits = lyr.shapes;
18806
- }
18807
- if (lyr.geometry_type == 'point') {
18808
- targetPoints = targetPoints.concat(hits);
18809
- } else {
18810
- targetShapes = targetShapes.concat(hits);
18811
- otherShapes = otherShapes.concat(misses);
18812
- }
18813
- });
18814
- var anchorArg = getAffineAnchor({arcs: dataset.arcs, layers: [{
18815
- geometry_type: 'point', shapes: targetPoints}, {geometry_type: 'polyline',
18816
- shapes: targetShapes}]}, opts);
18817
- transform = getAffineTransform(rotateArg, scaleArg, shiftArg, anchorArg);
18818
- if (opts.fit_bbox) {
18819
- transform = getFitBoxTransform(opts.fit_bbox, targetPoints, targetShapes, arcs);
18820
- }
18821
- if (targetShapes.length > 0) {
18822
- targetFlags = new Uint8Array(arcs.size());
18823
- otherFlags = new Uint8Array(arcs.size());
18824
- countArcsInShapes(targetShapes, targetFlags);
18825
- if (otherShapes.length > 0) {
18826
- countArcsInShapes(otherShapes, otherFlags);
18827
- applyArrayMask(otherFlags, targetFlags);
18828
- dataset.arcs = duplicateSelectedArcs(otherShapes, arcs, otherFlags);
18829
- }
18830
- dataset.arcs.transformPoints(function(x, y, arcId) {
18831
- if (arcId < targetFlags.length && targetFlags[arcId] > 0) {
18832
- return transform(x, y);
18833
- }
18834
- });
18835
- }
18836
- forEachPoint(targetPoints, function(p) {
18837
- var p2 = transform(p[0], p[1]);
18838
- p[0] = p2[0];
18839
- p[1] = p2[1];
18840
- });
18841
- };
18842
-
18843
- function getAffineAnchor(dataset, opts) {
18844
- var anchor, bounds;
18845
- if (opts.anchor) {
18846
- anchor = opts.anchor;
18847
- } else {
18848
- // get bounds of selected shapes to calculate center of rotation/scale
18849
- bounds = getDatasetBounds(dataset);
18850
- anchor = [bounds.centerX(), bounds.centerY()];
18851
- }
18852
- return anchor;
18853
- }
18854
-
18855
- // TODO: handle problems with unprojected datasets
18856
- // option 1: don't allow affine transformation of unprojected data
18857
- // option 2: error if transformed data exceeds valid coordinate range
18858
- // source: http://mathworld.wolfram.com/AffineTransformation.html
18859
- function getAffineTransform(rotation, scale, shift, anchor) {
18860
- var angle = rotation * Math.PI / 180;
18861
- var a = scale * Math.cos(angle);
18862
- var b = -scale * Math.sin(angle);
18863
- return function(x, y) {
18864
- var x2 = a * (x - anchor[0]) - b * (y - anchor[1]) + shift[0] + anchor[0];
18865
- var y2 = b * (x - anchor[0]) + a * (y - anchor[1]) + shift[1] + anchor[1];
18866
- return [x2, y2];
18867
- };
18868
- }
18869
-
18870
- function getFitBoxTransform(bbox, points, shapes, arcs) {
18871
- var dataset = {
18872
- arcs: arcs,
18873
- layers: []
18874
- };
18875
- if (points && points.length) {
18876
- dataset.layers.push({
18877
- geometry_type: 'point',
18878
- shapes: points
18879
- });
18880
- }
18881
- if (shapes && shapes.length) {
18882
- dataset.layers.push({
18883
- geometry_type: 'polyline',
18884
- shapes: shapes
18885
- });
18886
- }
18887
- var frame = calcFrameData(dataset, {fit_bbox: bbox});
18888
- var fromBounds = new Bounds(frame.bbox);
18889
- var toBounds = new Bounds(frame.bbox2);
18890
- var fwd = fromBounds.getTransform(toBounds, false);
18891
- return function(x, y) {
18892
- return fwd.transform(x, y);
18893
- };
18894
- }
18895
-
18896
- function applyArrayMask(destArr, maskArr) {
18897
- for (var i=0, n=destArr.length; i<n; i++) {
18898
- if (maskArr[i] === 0) destArr[i] = 0;
18899
- }
18900
- }
18901
-
18902
- function duplicateSelectedArcs(shapes, arcs, flags) {
18903
- var arcCount = 0;
18904
- var vertexCount = 0;
18905
- var data = arcs.getVertexData();
18906
- var xx = [], yy = [], nn = [], map = [], n;
18907
- for (var i=0, len=flags.length; i<len; i++) {
18908
- if (flags[i] > 0) {
18909
- map[i] = arcs.size() + arcCount;
18910
- n = data.nn[i];
18911
- utils.copyElements(data.xx, data.ii[i], xx, vertexCount, n);
18912
- utils.copyElements(data.yy, data.ii[i], yy, vertexCount, n);
18913
- nn.push(n);
18914
- vertexCount += n;
18915
- arcCount++;
18916
- }
18917
- }
18918
- forEachArcId(shapes, function(id) {
18919
- var absId = absArcId(id);
18920
- if (flags[absId] > 0) {
18921
- return id < 0 ? ~map[absId] : map[absId];
18922
- }
18923
- });
18924
- return mergeArcs([arcs, new ArcCollection(nn, xx, yy)]);
18925
- }
18926
-
18927
- var roundCoord$2 = getRoundingFunction(0.01);
18928
-
18929
- function stringifyVertex(p) {
18930
- return ' ' + roundCoord$2(p[0]) + ' ' + roundCoord$2(p[1]);
18931
- }
18932
-
18933
- function isCubicCtrl(p) {
18934
- return p.length > 2 && p[2] == 'C';
18935
- }
18936
-
18937
- function stringifyPolygonCoords(coords) {
18938
- var parts = [];
18939
- for (var i=0; i<coords.length; i++) {
18940
- parts.push(stringifyLineStringCoords(coords[i]) + ' Z');
18941
- }
18942
- return parts.length > 0 ? parts.join(' ') : '';
18943
- }
18944
-
18945
- function stringifyLineStringCoords(coords) {
18946
- if (coords.length === 0) return '';
18947
- var d = 'M';
18948
- var fromCurve = false;
18949
- var p, i, n;
18950
- for (i=0, n=coords.length; i<n; i++) {
18951
- p = coords[i];
18952
- if (isCubicCtrl(p)) {
18953
- // TODO: add defensive check
18954
- d += ' C' + stringifyVertex(p) + stringifyVertex(coords[++i]) + stringifyVertex(coords[++i]);
18955
- fromCurve = true;
18956
- } else if (fromCurve) {
18957
- d += ' L' + stringifyVertex(p);
18958
- fromCurve = false;
18959
- } else {
18960
- d += stringifyVertex(p);
18961
- }
18962
- }
18963
- return d;
18964
- }
18965
-
18966
- var SvgPathUtils = /*#__PURE__*/Object.freeze({
18967
- __proto__: null,
18968
- stringifyLineStringCoords: stringifyLineStringCoords,
18969
- stringifyPolygonCoords: stringifyPolygonCoords
18970
- });
18971
-
18972
20006
  /* example patterns
18973
20007
  hatches 1px black 1px red 1px white
18974
20008
  1px black 1px red 1px white // same as above (hatches is default)
@@ -19557,7 +20591,7 @@
19557
20591
  }
19558
20592
 
19559
20593
  function importLineString(coords) {
19560
- var d = stringifyLineStringCoords(coords);
20594
+ var d = stringifyLineStringCoords$1(coords);
19561
20595
  return {
19562
20596
  tag: 'path',
19563
20597
  properties: {d: d}
@@ -19565,7 +20599,7 @@
19565
20599
  }
19566
20600
 
19567
20601
  function importMultiLineString(coords) {
19568
- var d = coords.map(stringifyLineStringCoords).join(' ');
20602
+ var d = coords.map(stringifyLineStringCoords$1).join(' ');
19569
20603
  return {
19570
20604
  tag: 'path',
19571
20605
  properties: {d: d}
@@ -19587,7 +20621,7 @@
19587
20621
  var o = {
19588
20622
  tag: 'path',
19589
20623
  properties: {
19590
- d: stringifyPolygonCoords(coords)
20624
+ d: stringifyPolygonCoords$1(coords)
19591
20625
  }
19592
20626
  };
19593
20627
  if (coords.length > 1) {
@@ -19596,6 +20630,23 @@
19596
20630
  return o;
19597
20631
  }
19598
20632
 
20633
+ function stringifyPolygonCoords$1(coords) {
20634
+ var parts = [];
20635
+ for (var i=0; i<coords.length; i++) {
20636
+ parts.push(stringifyLineStringCoords$1(coords[i]) + ' Z');
20637
+ }
20638
+ return parts.length > 0 ? parts.join(' ') : '';
20639
+ }
20640
+
20641
+ function stringifyLineStringCoords$1(coords) {
20642
+ if (coords.length === 0) return '';
20643
+ var d = 'M';
20644
+ for (var i=0, n=coords.length; i<n; i++) {
20645
+ d += ' ' + coords[i][0] + ' ' + coords[i][1];
20646
+ }
20647
+ return d;
20648
+ }
20649
+
19599
20650
  var GeojsonToSvg = /*#__PURE__*/Object.freeze({
19600
20651
  __proto__: null,
19601
20652
  flattenMultiPolygonCoords: flattenMultiPolygonCoords,
@@ -20485,7 +21536,7 @@
20485
21536
  opts = Object.assign({invert_y: true, margin: "1"}, opts);
20486
21537
  frame = getFrameData(dataset, opts);
20487
21538
  fitDatasetToFrame(dataset, frame);
20488
- setCoordinatePrecision(dataset, opts.precision || 0.0001);
21539
+ setCoordinatePrecision(dataset, opts.precision || 0.01);
20489
21540
 
20490
21541
  // error if one or more svg_data fields are not present in any layers
20491
21542
  if (opts.svg_data) validateSvgDataFields(dataset.layers, opts.svg_data);
@@ -22241,7 +23292,7 @@ ${svg}
22241
23292
  if (opts.presimplify) {
22242
23293
  fromZ = getPresimplifyFunction(bounds.width());
22243
23294
  }
22244
- arcs.forEach2(function(i, n, xx, yy, zz) {
23295
+ arcs.forEach2(function(arcId, i, n, xx, yy, zz) {
22245
23296
  var arc = [], p;
22246
23297
  for (var j=i + n; i<j; i++) {
22247
23298
  p = [xx[i], yy[i]];
@@ -24837,6 +25888,11 @@ ${svg}
24837
25888
  .option('sliver-control', sliverControlOpt)
24838
25889
  .option('snap-interval', snapIntervalOpt)
24839
25890
  .option('no-snap', noSnapOpt)
25891
+ // // not useful in -clean -- clean removes gaps, not slivers
25892
+ // .option('keep-shapes', {
25893
+ // type: 'flag',
25894
+ // describe: 'protect sliver polygons from complete removal'
25895
+ // })
24840
25896
  .option('allow-overlaps', {
24841
25897
  describe: 'allow polygons to overlap (disables gap fill)',
24842
25898
  type: 'flag'
@@ -24888,6 +25944,10 @@ ${svg}
24888
25944
  })
24889
25945
  .option('name', nameOpt)
24890
25946
  .option('no-snap', noSnapOpt)
25947
+ .option('no-cleanup', {
25948
+ // for debugging - no documentation
25949
+ type: 'flag'
25950
+ })
24891
25951
  .option('target', targetOpt)
24892
25952
  .option('no-replace', noReplaceOpt);
24893
25953
 
@@ -25123,6 +26183,10 @@ ${svg}
25123
26183
  .option('bbox', bboxOpt)
25124
26184
  .option('name', nameOpt)
25125
26185
  .option('no-snap', noSnapOpt)
26186
+ .option('no-cleanup', {
26187
+ // for debugging - no documentation
26188
+ type: 'flag'
26189
+ })
25126
26190
  .option('target', targetOpt)
25127
26191
  .option('no-replace', noReplaceOpt);
25128
26192
 
@@ -25233,6 +26297,10 @@ ${svg}
25233
26297
  type: 'flag',
25234
26298
  describe: 'delete features with null geometry'
25235
26299
  })
26300
+ .option('keep-shapes', {
26301
+ type: 'flag',
26302
+ describe: 'protect sliver polygons from complete removal'
26303
+ })
25236
26304
  .option('target', targetOpt);
25237
26305
 
25238
26306
  parser.command('graticule')
@@ -25440,6 +26508,8 @@ ${svg}
25440
26508
  .option('calc', calcOpt)
25441
26509
  .option('name', nameOpt)
25442
26510
  .option('target', targetOpt)
26511
+ .option('snap-interval', snapIntervalOpt)
26512
+ .option('no-snap', noSnapOpt)
25443
26513
  .option('no-replace', noReplaceOpt);
25444
26514
 
25445
26515
  parser.command('point-grid')
@@ -29835,6 +30905,158 @@ ${svg}
29835
30905
  stop('Unable to import coordinates');
29836
30906
  }
29837
30907
 
30908
+ // Apply rotation, scale and/or shift to some or all of the features in a dataset
30909
+ //
30910
+ cmd.affine = function(targetLayers, dataset, opts) {
30911
+ // Need to separate the targeted shapes from any other shapes that share
30912
+ // the same topology. So we duplicate any arcs that are shared by the targeted
30913
+ // shapes and their topological neighbors and remap arc references in the
30914
+ // neighbors to point to the copies.
30915
+ // TODO: explore alternative: if some arcs are shared between transformed and
30916
+ // non-transformed shapes, first remove topology, then tranform, then rebuild topology
30917
+ //
30918
+ var rotateArg = opts.rotate || 0;
30919
+ var scaleArg = opts.scale || 1;
30920
+ var shiftArg = opts.shift ? convertIntervalPair(opts.shift, getDatasetCRS(dataset)) : [0, 0];
30921
+ var arcs = dataset.arcs;
30922
+ var targetShapes = [];
30923
+ var otherShapes = [];
30924
+ var targetPoints = [];
30925
+ var targetFlags, otherFlags, transform;
30926
+ dataset.layers.filter(layerHasGeometry).forEach(function(lyr) {
30927
+ var hits = [],
30928
+ misses = [],
30929
+ test;
30930
+ if (targetLayers.indexOf(lyr) == -1) {
30931
+ misses = lyr.shapes;
30932
+ } else if (opts.where) {
30933
+ test = compileFeatureExpression(opts.where, lyr, dataset.arcs);
30934
+ lyr.shapes.forEach(function(shp, i) {
30935
+ (test(i) ? hits : misses).push(shp);
30936
+ });
30937
+ } else {
30938
+ hits = lyr.shapes;
30939
+ }
30940
+ if (lyr.geometry_type == 'point') {
30941
+ targetPoints = targetPoints.concat(hits);
30942
+ } else {
30943
+ targetShapes = targetShapes.concat(hits);
30944
+ otherShapes = otherShapes.concat(misses);
30945
+ }
30946
+ });
30947
+ var anchorArg = getAffineAnchor({arcs: dataset.arcs, layers: [{
30948
+ geometry_type: 'point', shapes: targetPoints}, {geometry_type: 'polyline',
30949
+ shapes: targetShapes}]}, opts);
30950
+ transform = getAffineTransform(rotateArg, scaleArg, shiftArg, anchorArg);
30951
+ if (opts.fit_bbox) {
30952
+ transform = getFitBoxTransform(opts.fit_bbox, targetPoints, targetShapes, arcs);
30953
+ }
30954
+ if (targetShapes.length > 0) {
30955
+ targetFlags = new Uint8Array(arcs.size());
30956
+ otherFlags = new Uint8Array(arcs.size());
30957
+ countArcsInShapes(targetShapes, targetFlags);
30958
+ if (otherShapes.length > 0) {
30959
+ countArcsInShapes(otherShapes, otherFlags);
30960
+ applyArrayMask(otherFlags, targetFlags);
30961
+ dataset.arcs = duplicateSelectedArcs(otherShapes, arcs, otherFlags);
30962
+ }
30963
+ dataset.arcs.transformPoints(function(x, y, arcId) {
30964
+ if (arcId < targetFlags.length && targetFlags[arcId] > 0) {
30965
+ return transform(x, y);
30966
+ }
30967
+ });
30968
+ }
30969
+ forEachPoint(targetPoints, function(p) {
30970
+ var p2 = transform(p[0], p[1]);
30971
+ p[0] = p2[0];
30972
+ p[1] = p2[1];
30973
+ });
30974
+ };
30975
+
30976
+ function getAffineAnchor(dataset, opts) {
30977
+ var anchor, bounds;
30978
+ if (opts.anchor) {
30979
+ anchor = opts.anchor;
30980
+ } else {
30981
+ // get bounds of selected shapes to calculate center of rotation/scale
30982
+ bounds = getDatasetBounds(dataset);
30983
+ anchor = [bounds.centerX(), bounds.centerY()];
30984
+ }
30985
+ return anchor;
30986
+ }
30987
+
30988
+ // TODO: handle problems with unprojected datasets
30989
+ // option 1: don't allow affine transformation of unprojected data
30990
+ // option 2: error if transformed data exceeds valid coordinate range
30991
+ // source: http://mathworld.wolfram.com/AffineTransformation.html
30992
+ function getAffineTransform(rotation, scale, shift, anchor) {
30993
+ var angle = rotation * Math.PI / 180;
30994
+ var a = scale * Math.cos(angle);
30995
+ var b = -scale * Math.sin(angle);
30996
+ return function(x, y) {
30997
+ var x2 = a * (x - anchor[0]) - b * (y - anchor[1]) + shift[0] + anchor[0];
30998
+ var y2 = b * (x - anchor[0]) + a * (y - anchor[1]) + shift[1] + anchor[1];
30999
+ return [x2, y2];
31000
+ };
31001
+ }
31002
+
31003
+ function getFitBoxTransform(bbox, points, shapes, arcs) {
31004
+ var dataset = {
31005
+ arcs: arcs,
31006
+ layers: []
31007
+ };
31008
+ if (points && points.length) {
31009
+ dataset.layers.push({
31010
+ geometry_type: 'point',
31011
+ shapes: points
31012
+ });
31013
+ }
31014
+ if (shapes && shapes.length) {
31015
+ dataset.layers.push({
31016
+ geometry_type: 'polyline',
31017
+ shapes: shapes
31018
+ });
31019
+ }
31020
+ var frame = calcFrameData(dataset, {fit_bbox: bbox});
31021
+ var fromBounds = new Bounds(frame.bbox);
31022
+ var toBounds = new Bounds(frame.bbox2);
31023
+ var fwd = fromBounds.getTransform(toBounds, false);
31024
+ return function(x, y) {
31025
+ return fwd.transform(x, y);
31026
+ };
31027
+ }
31028
+
31029
+ function applyArrayMask(destArr, maskArr) {
31030
+ for (var i=0, n=destArr.length; i<n; i++) {
31031
+ if (maskArr[i] === 0) destArr[i] = 0;
31032
+ }
31033
+ }
31034
+
31035
+ function duplicateSelectedArcs(shapes, arcs, flags) {
31036
+ var arcCount = 0;
31037
+ var vertexCount = 0;
31038
+ var data = arcs.getVertexData();
31039
+ var xx = [], yy = [], nn = [], map = [], n;
31040
+ for (var i=0, len=flags.length; i<len; i++) {
31041
+ if (flags[i] > 0) {
31042
+ map[i] = arcs.size() + arcCount;
31043
+ n = data.nn[i];
31044
+ utils.copyElements(data.xx, data.ii[i], xx, vertexCount, n);
31045
+ utils.copyElements(data.yy, data.ii[i], yy, vertexCount, n);
31046
+ nn.push(n);
31047
+ vertexCount += n;
31048
+ arcCount++;
31049
+ }
31050
+ }
31051
+ forEachArcId(shapes, function(id) {
31052
+ var absId = absArcId(id);
31053
+ if (flags[absId] > 0) {
31054
+ return id < 0 ? ~map[absId] : map[absId];
31055
+ }
31056
+ });
31057
+ return mergeArcs([arcs, new ArcCollection(nn, xx, yy)]);
31058
+ }
31059
+
29838
31060
  const epsilon = 1.1102230246251565e-16;
29839
31061
  const splitter = 134217729;
29840
31062
  const resulterrbound = (3 + 8 * epsilon) * epsilon;
@@ -30095,8 +31317,6 @@ ${svg}
30095
31317
  const detright = (ax - cx) * (by - cy);
30096
31318
  const det = detleft - detright;
30097
31319
 
30098
- if (detleft === 0 || detright === 0 || (detleft > 0) !== (detright > 0)) return det;
30099
-
30100
31320
  const detsum = Math.abs(detleft + detright);
30101
31321
  if (Math.abs(det) >= ccwerrboundA * detsum) return det;
30102
31322
 
@@ -34870,7 +36090,7 @@ ${svg}
34870
36090
  var ringTest = filterData.filter;
34871
36091
  var removed = 0;
34872
36092
  var pathFilter = function(path, i, paths) {
34873
- if (ringTest(path)) {
36093
+ if (ringTest(path, i, paths)) {
34874
36094
  removed++;
34875
36095
  return null;
34876
36096
  }
@@ -35010,6 +36230,7 @@ ${svg}
35010
36230
  // or overlapping rings. TODO: try to optimize or remove it for all cases
35011
36231
 
35012
36232
  // skipping shape cleanup when using the experimental fast bbox clipping option
36233
+ // if (!opts.bbox2 && !opts.no_cleanup) {
35013
36234
  if (!opts.bbox2) {
35014
36235
  // clean each target polygon by dissolving its rings
35015
36236
  targetShapes = targetShapes.map(dissolvePolygon);
@@ -35506,7 +36727,7 @@ ${svg}
35506
36727
  } else {
35507
36728
  nodes = new NodeCollection(mergedDataset.arcs);
35508
36729
  }
35509
- // clipLyr = mergedDataset.layers.pop();
36730
+
35510
36731
  return clipLayersByLayer(targetLayers, clipLyr, nodes, type, opts);
35511
36732
  }
35512
36733
 
@@ -40498,7 +41719,7 @@ ${svg}
40498
41719
 
40499
41720
  function createMeridianPart(x, ymin, ymax) {
40500
41721
  var coords = densifyPathByInterval([[x, ymin], [x, ymax]], precision);
40501
- meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord$1(x)}));
41722
+ meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord$2(x)}));
40502
41723
  }
40503
41724
 
40504
41725
  function createParallel(y) {
@@ -40508,7 +41729,7 @@ ${svg}
40508
41729
  }
40509
41730
 
40510
41731
  // remove tiny offsets
40511
- function roundCoord$1(x) {
41732
+ function roundCoord$2(x) {
40512
41733
  return +x.toFixed(3) || 0;
40513
41734
  }
40514
41735
 
@@ -44607,7 +45828,7 @@ ${svg}
44607
45828
  });
44608
45829
  };
44609
45830
 
44610
- var roundCoord = getRoundingFunction(0.01);
45831
+ var roundCoord$1 = getRoundingFunction(0.01);
44611
45832
 
44612
45833
  function getSymbolFillColor(d) {
44613
45834
  return d.fill || 'magenta';
@@ -44658,8 +45879,8 @@ ${svg}
44658
45879
 
44659
45880
  function roundCoordsForSVG(coords) {
44660
45881
  forEachSymbolCoord(coords, function(p) {
44661
- p[0] = roundCoord(p[0]);
44662
- p[1] = roundCoord(p[1]);
45882
+ p[0] = roundCoord$1(p[0]);
45883
+ p[1] = roundCoord$1(p[1]);
44663
45884
  });
44664
45885
  }
44665
45886
 
@@ -45384,7 +46605,7 @@ ${svg}
45384
46605
  // Filter arcs based on an array of thresholds
45385
46606
  function applyArcThresholds(arcs, thresholds) {
45386
46607
  arcs.getVertexData().zz;
45387
- arcs.forEach2(function(start, n, xx, yy, zz, arcId) {
46608
+ arcs.forEach2(function(arcId, start, n, xx, yy, zz) {
45388
46609
  var arcZ = thresholds[arcId];
45389
46610
  var z;
45390
46611
  for (var i=1; i<n-1; i++) {
@@ -46038,7 +47259,7 @@ ${svg}
46038
47259
  });
46039
47260
  }
46040
47261
 
46041
- var version = "0.6.108";
47262
+ var version = "0.6.110";
46042
47263
 
46043
47264
  // Parse command line args into commands and run them
46044
47265
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -46370,6 +47591,51 @@ ${svg}
46370
47591
  testCommands: testCommands
46371
47592
  });
46372
47593
 
47594
+ var roundCoord = getRoundingFunction(0.01);
47595
+
47596
+ function stringifyVertex(p) {
47597
+ return ' ' + roundCoord(p[0]) + ' ' + roundCoord(p[1]);
47598
+ }
47599
+
47600
+ function isCubicCtrl(p) {
47601
+ return p.length > 2 && p[2] == 'C';
47602
+ }
47603
+
47604
+ function stringifyPolygonCoords(coords) {
47605
+ var parts = [];
47606
+ for (var i=0; i<coords.length; i++) {
47607
+ parts.push(stringifyLineStringCoords(coords[i]) + ' Z');
47608
+ }
47609
+ return parts.length > 0 ? parts.join(' ') : '';
47610
+ }
47611
+
47612
+ function stringifyLineStringCoords(coords) {
47613
+ if (coords.length === 0) return '';
47614
+ var d = 'M';
47615
+ var fromCurve = false;
47616
+ var p, i, n;
47617
+ for (i=0, n=coords.length; i<n; i++) {
47618
+ p = coords[i];
47619
+ if (isCubicCtrl(p)) {
47620
+ // TODO: add defensive check
47621
+ d += ' C' + stringifyVertex(p) + stringifyVertex(coords[++i]) + stringifyVertex(coords[++i]);
47622
+ fromCurve = true;
47623
+ } else if (fromCurve) {
47624
+ d += ' L' + stringifyVertex(p);
47625
+ fromCurve = false;
47626
+ } else {
47627
+ d += stringifyVertex(p);
47628
+ }
47629
+ }
47630
+ return d;
47631
+ }
47632
+
47633
+ var SvgPathUtils = /*#__PURE__*/Object.freeze({
47634
+ __proto__: null,
47635
+ stringifyLineStringCoords: stringifyLineStringCoords,
47636
+ stringifyPolygonCoords: stringifyPolygonCoords
47637
+ });
47638
+
46373
47639
  // Return an array containing points from a path iterator, clipped to a bounding box
46374
47640
  // Currently using this function for clipping styled polygons in the GUI to speed up layer rendering.
46375
47641
  // Artifacts along the edges make this unsuitable for clipping datasets