mapshaper 0.7.49 → 0.7.50

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
@@ -336,7 +336,7 @@
336
336
  return arr;
337
337
  }
338
338
 
339
- function repeat(times, func) {
339
+ function repeat$1(times, func) {
340
340
  var values = [],
341
341
  val;
342
342
  for (var i=0; i<times; i++) {
@@ -1115,7 +1115,7 @@
1115
1115
  parseIntlNumber, parseNumber: parseNumber$1, parseString, pickOne, pluck,
1116
1116
  pluralSuffix, promisify,
1117
1117
  quicksort: quicksort$1, quicksortPartition,
1118
- range, regexEscape, reorderArray: reorderArray$1, repeat, repeatString,
1118
+ range, regexEscape, reorderArray: reorderArray$1, repeat: repeat$1, repeatString,
1119
1119
  replaceArray, rpad, rtrim,
1120
1120
  shuffle, some, sortArrayIndex, sortOn, splitLines, sum: sum$1,
1121
1121
  toArray: toArray$1, toBuffer, trim, trimQuotes,
@@ -1409,7 +1409,7 @@
1409
1409
  // path/bound helpers, forming a cycle).
1410
1410
 
1411
1411
 
1412
- function layerHasGeometry(lyr) {
1412
+ function layerHasGeometry$1(lyr) {
1413
1413
  return layerHasPaths(lyr) || layerHasPoints(lyr);
1414
1414
  }
1415
1415
 
@@ -2054,7 +2054,7 @@
2054
2054
  // also consider using ellipsoidal formulas where greater accuracy might be important.
2055
2055
  var R$3 = WGS84.SEMIMAJOR_AXIS;
2056
2056
  var D2R$8 = Math.PI / 180;
2057
- var R2D$7 = 180 / Math.PI;
2057
+ var R2D$8 = 180 / Math.PI;
2058
2058
 
2059
2059
  // Equirectangular projection
2060
2060
  function degreesToMeters(deg) {
@@ -2336,7 +2336,7 @@
2336
2336
  __proto__: null,
2337
2337
  D2R: D2R$8,
2338
2338
  R: R$3,
2339
- R2D: R2D$7,
2339
+ R2D: R2D$8,
2340
2340
  bearing: bearing,
2341
2341
  bearing2D: bearing2D,
2342
2342
  containsBounds: containsBounds,
@@ -6087,7 +6087,7 @@
6087
6087
  insertFieldValues: insertFieldValues,
6088
6088
  isolateLayer: isolateLayer,
6089
6089
  layerHasAttributeData: layerHasAttributeData,
6090
- layerHasGeometry: layerHasGeometry,
6090
+ layerHasGeometry: layerHasGeometry$1,
6091
6091
  layerHasNonNullData: layerHasNonNullData,
6092
6092
  layerHasNonNullShapes: layerHasNonNullShapes,
6093
6093
  layerHasPaths: layerHasPaths,
@@ -6572,7 +6572,7 @@
6572
6572
 
6573
6573
 
6574
6574
  var D2R$7 = Math.PI / 180;
6575
- var R2D$6 = 180 / Math.PI;
6575
+ var R2D$7 = 180 / Math.PI;
6576
6576
  var EPS$3 = 1e-12;
6577
6577
 
6578
6578
  // Build a forward-only spherical polyhedral projection.
@@ -6790,7 +6790,7 @@
6790
6790
  config.rotation[2] * D2R$7,
6791
6791
  true
6792
6792
  );
6793
- return [normalizeLongitude$1(q[0] * R2D$6 + lon0), q[1] * R2D$6];
6793
+ return [normalizeLongitude$1(q[0] * R2D$7 + lon0), q[1] * R2D$7];
6794
6794
  });
6795
6795
  var paths = splitPathAtAntimeridian$3(
6796
6796
  interpolateGreatCircle(endpoints[0], endpoints[1], 0.05)
@@ -7094,7 +7094,7 @@
7094
7094
  var av = degreesToVector$2(a[0], a[1]);
7095
7095
  var bv = degreesToVector$2(b[0], b[1]);
7096
7096
  var angle = Math.acos(clamp$2(dot$2(av, bv), -1, 1));
7097
- var n = Math.max(1, Math.ceil(angle * R2D$6 / interval));
7097
+ var n = Math.max(1, Math.ceil(angle * R2D$7 / interval));
7098
7098
  var sinAngle = Math.sin(angle);
7099
7099
  var points = [];
7100
7100
  for (var i = 0; i <= n; i++) {
@@ -7167,8 +7167,8 @@
7167
7167
 
7168
7168
  function vectorToDegrees$2(p) {
7169
7169
  return [
7170
- Math.atan2(p[1], p[0]) * R2D$6,
7171
- Math.asin(clamp$2(p[2], -1, 1)) * R2D$6
7170
+ Math.atan2(p[1], p[0]) * R2D$7,
7171
+ Math.asin(clamp$2(p[2], -1, 1)) * R2D$7
7172
7172
  ];
7173
7173
  }
7174
7174
 
@@ -7236,7 +7236,7 @@
7236
7236
 
7237
7237
 
7238
7238
  var D2R$6 = Math.PI / 180;
7239
- var R2D$5 = 180 / Math.PI;
7239
+ var R2D$6 = 180 / Math.PI;
7240
7240
  var SQRT3$2 = Math.sqrt(3);
7241
7241
  var GRAY_Z = Math.sqrt(5 + 2 * Math.sqrt(5)) / Math.sqrt(15);
7242
7242
  var GRAY_EL = Math.sqrt(8) / Math.sqrt(5 + Math.sqrt(5));
@@ -7297,12 +7297,12 @@
7297
7297
  xy.y = p[1];
7298
7298
  }
7299
7299
  };
7300
- P.__projection_topology = engine.getTopology(P.lam0 * R2D$5);
7300
+ P.__projection_topology = engine.getTopology(P.lam0 * R2D$6);
7301
7301
  P.__projected_outline = engine.outline;
7302
7302
  }
7303
7303
 
7304
7304
  function createAiroceanFaces() {
7305
- var theta = Math.atan(0.5) * R2D$5;
7305
+ var theta = Math.atan(0.5) * R2D$6;
7306
7306
  var vertices = [[0, 90], [0, -90]];
7307
7307
  var faces;
7308
7308
  var sites;
@@ -7435,8 +7435,8 @@
7435
7435
 
7436
7436
  function vectorToDegrees$1(p) {
7437
7437
  return [
7438
- Math.atan2(p[1], p[0]) * R2D$5,
7439
- Math.asin(Math.max(-1, Math.min(1, p[2]))) * R2D$5
7438
+ Math.atan2(p[1], p[0]) * R2D$6,
7439
+ Math.asin(Math.max(-1, Math.min(1, p[2]))) * R2D$6
7440
7440
  ];
7441
7441
  }
7442
7442
 
@@ -7485,7 +7485,7 @@
7485
7485
  */
7486
7486
 
7487
7487
  var D2R$5 = Math.PI / 180;
7488
- var R2D$4 = 180 / Math.PI;
7488
+ var R2D$5 = 180 / Math.PI;
7489
7489
 
7490
7490
  function createCahillKeyesRaw(mg) {
7491
7491
  return createCahillKeyesTransform(mg, false);
@@ -7497,12 +7497,12 @@
7497
7497
 
7498
7498
  return function(lambda, phi) {
7499
7499
  if (faceOnly) {
7500
- var lon = lambda * R2D$4;
7500
+ var lon = lambda * R2D$5;
7501
7501
  var side = lon < 0 ? -1 : lon > 0 ? 1 : 0;
7502
- var local = mp2xy(Math.abs(lon), Math.abs(phi * R2D$4));
7502
+ var local = mp2xy(Math.abs(lon), Math.abs(phi * R2D$5));
7503
7503
  return [local[0], side * local[1]];
7504
7504
  }
7505
- var res = ll2mp(lambda * R2D$4, phi * R2D$4);
7505
+ var res = ll2mp(lambda * R2D$5, phi * R2D$5);
7506
7506
  var xy = mp2xy(res[0], res[1]);
7507
7507
  var p = [xy[0], res[2] * xy[1]];
7508
7508
  return mj2g(p, res[3]);
@@ -7999,7 +7999,7 @@
7999
7999
 
8000
8000
 
8001
8001
  var D2R$3 = Math.PI / 180;
8002
- var R2D$3 = 180 / Math.PI;
8002
+ var R2D$4 = 180 / Math.PI;
8003
8003
  var RADIAL_BOUNDARY_STRENGTH = 1;
8004
8004
  var OCTAHEDRON = createOctahedron();
8005
8005
  var BUTTERFLY_PARENTS = [-1, 0, 0, 1, 0, 1, 4, 5];
@@ -8054,7 +8054,7 @@
8054
8054
  xy.y = p[1];
8055
8055
  }
8056
8056
  };
8057
- P.__projection_topology = engine.getTopology(P.lam0 * R2D$3);
8057
+ P.__projection_topology = engine.getTopology(P.lam0 * R2D$4);
8058
8058
  if (engine.removeOutlineExtremeConnectors) {
8059
8059
  P.__remove_outline_extreme_connectors = true;
8060
8060
  }
@@ -8374,8 +8374,8 @@
8374
8374
 
8375
8375
  function vectorToDegrees(p) {
8376
8376
  return [
8377
- Math.atan2(p[1], p[0]) * R2D$3,
8378
- Math.asin(Math.max(-1, Math.min(1, p[2]))) * R2D$3
8377
+ Math.atan2(p[1], p[0]) * R2D$4,
8378
+ Math.asin(Math.max(-1, Math.min(1, p[2]))) * R2D$4
8379
8379
  ];
8380
8380
  }
8381
8381
 
@@ -8430,7 +8430,7 @@
8430
8430
 
8431
8431
 
8432
8432
  var D2R$2 = Math.PI / 180;
8433
- var R2D$2 = 180 / Math.PI;
8433
+ var R2D$3 = 180 / Math.PI;
8434
8434
  var HALF_PI = Math.PI / 2;
8435
8435
  var SQRT2$1 = Math.sqrt(2);
8436
8436
  var SQRT3$1 = Math.sqrt(3);
@@ -8487,7 +8487,7 @@
8487
8487
  xy.x = p[0];
8488
8488
  xy.y = p[1];
8489
8489
  };
8490
- P.__projection_topology = engine.getTopology(P.lam0 * R2D$2);
8490
+ P.__projection_topology = engine.getTopology(P.lam0 * R2D$3);
8491
8491
  P.__projected_outline = engine.outline;
8492
8492
  }
8493
8493
 
@@ -8577,8 +8577,8 @@
8577
8577
  boundary: sector.sphericalBoundary.map(function(p) {
8578
8578
  var q = fromCanonical(p[0], p[1], orientation);
8579
8579
  return [
8580
- normalizeLongitude$1(q[0] * R2D$2 + lon0),
8581
- q[1] * R2D$2
8580
+ normalizeLongitude$1(q[0] * R2D$3 + lon0),
8581
+ q[1] * R2D$3
8582
8582
  ];
8583
8583
  })
8584
8584
  };
@@ -9165,8 +9165,8 @@
9165
9165
  var y = edge[0][1] + (edge[1][1] - edge[0][1]) * t;
9166
9166
  var p = inverse(x * EDGE_SCALE, y * EDGE_SCALE);
9167
9167
  path.push([
9168
- normalizeLongitude$1(p[0] * R2D$2 + lon0),
9169
- p[1] * R2D$2
9168
+ normalizeLongitude$1(p[0] * R2D$3 + lon0),
9169
+ p[1] * R2D$3
9170
9170
  ]);
9171
9171
  }
9172
9172
  return memo.concat(splitPathAtAntimeridian$2(path));
@@ -9462,11 +9462,11 @@
9462
9462
 
9463
9463
 
9464
9464
  var D2R$1 = Math.PI / 180;
9465
- var R2D$1 = 180 / Math.PI;
9465
+ var R2D$2 = 180 / Math.PI;
9466
9466
  var SQRT2 = Math.sqrt(2);
9467
9467
  var SQRT3 = Math.sqrt(3);
9468
9468
  var ASIN_ONE_THIRD = Math.asin(1 / 3);
9469
- var MARKLEY_LATITUDE = Math.acos(1 / 3) * 0.5 * R2D$1;
9469
+ var MARKLEY_LATITUDE = Math.acos(1 / 3) * 0.5 * R2D$2;
9470
9470
  var LAYOUT_PERIOD = 8;
9471
9471
  var MARKLEY_LAYOUT_PHASE = -2;
9472
9472
  var CALM_LAYOUT_PHASE = -1.5;
@@ -9506,9 +9506,9 @@
9506
9506
 
9507
9507
  var TETRAHEDRON_VERTICES = [
9508
9508
  [0, 90],
9509
- [-180, -ASIN_ONE_THIRD * R2D$1],
9510
- [-60, -ASIN_ONE_THIRD * R2D$1],
9511
- [60, -ASIN_ONE_THIRD * R2D$1]
9509
+ [-180, -ASIN_ONE_THIRD * R2D$2],
9510
+ [-60, -ASIN_ONE_THIRD * R2D$2],
9511
+ [60, -ASIN_ONE_THIRD * R2D$2]
9512
9512
  ];
9513
9513
 
9514
9514
  var TETRAHEDRON_FACES = [
@@ -9562,7 +9562,7 @@
9562
9562
  xy.x = p[0];
9563
9563
  xy.y = p[1];
9564
9564
  };
9565
- P.__projection_topology = engine.getTopology(P.lam0 * R2D$1);
9565
+ P.__projection_topology = engine.getTopology(P.lam0 * R2D$2);
9566
9566
  P.__projected_outline = engine.outline;
9567
9567
  P.__graticule_precision = 1;
9568
9568
  }
@@ -9787,7 +9787,7 @@
9787
9787
  var boundary = face.map(function(p) {
9788
9788
  var q = rotateSphericalRadians(
9789
9789
  p[0] * D2R$1, p[1] * D2R$1, rotation, true);
9790
- return [normalizeLongitude$1(q[0] * R2D$1), q[1] * R2D$1];
9790
+ return [normalizeLongitude$1(q[0] * R2D$2), q[1] * R2D$2];
9791
9791
  });
9792
9792
  boundary.push(boundary[0].concat());
9793
9793
  return {id: id, boundary: boundary};
@@ -9905,7 +9905,7 @@
9905
9905
  stats.failed_count++;
9906
9906
  continue;
9907
9907
  }
9908
- path.push([normalizeLongitude$1(p[0] * R2D$1 + lon0), p[1] * R2D$1]);
9908
+ path.push([normalizeLongitude$1(p[0] * R2D$2 + lon0), p[1] * R2D$2]);
9909
9909
  }
9910
9910
  return memo.concat(splitPathAtAntimeridian$1(path));
9911
9911
  }, []).map(function(path) {
@@ -10447,6 +10447,24 @@
10447
10447
  };
10448
10448
  }
10449
10449
 
10450
+ // Parse a CRS string that may well be unusable, without the failure reaching
10451
+ // the user: returns null instead of stopping. This is for CRS metadata read out
10452
+ // of a file, where mapshaper is guessing and the caller has a fallback. (In the
10453
+ // web app a stop() shows a modal popup as it throws, which is too much for a
10454
+ // guess that did not come off, and interrupts whatever the user was doing.)
10455
+ function tryParseCrsString(str) {
10456
+ var revertLogging = getLoggingSetter();
10457
+ var crs = null;
10458
+ setLoggingForCLI();
10459
+ try {
10460
+ crs = parseCrsString$1(str);
10461
+ } catch(e) {
10462
+ // an unparseable string is one of the expected outcomes here
10463
+ }
10464
+ revertLogging();
10465
+ return crs;
10466
+ }
10467
+
10450
10468
  function parseCrsString$1(str) {
10451
10469
  var defn = getProjDefn(str); // defn is a string or a Proj object
10452
10470
  var P;
@@ -10701,6 +10719,7 @@
10701
10719
  setDatasetCrsInfo: setDatasetCrsInfo,
10702
10720
  setProjectionLoader: setProjectionLoader,
10703
10721
  toLngLat: toLngLat,
10722
+ tryParseCrsString: tryParseCrsString,
10704
10723
  wkt1ToProj: wkt1ToProj,
10705
10724
  wkt2ToProj: wkt2ToProj,
10706
10725
  wktToProj: wktToProj
@@ -12867,7 +12886,7 @@
12867
12886
 
12868
12887
  function datasetHasGeometry(dataset) {
12869
12888
  return utils.some(dataset.layers, function(lyr) {
12870
- return layerHasGeometry(lyr);
12889
+ return layerHasGeometry$1(lyr);
12871
12890
  });
12872
12891
  }
12873
12892
 
@@ -14082,6 +14101,28 @@
14082
14101
  d: function () { return ~c; }
14083
14102
  };
14084
14103
  };
14104
+ // Adler32
14105
+ var adler = function () {
14106
+ var a = 1, b = 0;
14107
+ return {
14108
+ p: function (d) {
14109
+ // closures have awful performance
14110
+ var n = a, m = b;
14111
+ var l = d.length | 0;
14112
+ for (var i = 0; i != l;) {
14113
+ var e = Math.min(i + 2655, l);
14114
+ for (; i < e; ++i)
14115
+ m += n += d[i];
14116
+ n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16);
14117
+ }
14118
+ a = n, b = m;
14119
+ },
14120
+ d: function () {
14121
+ a %= 65521, b %= 65521;
14122
+ return (a & 255) << 24 | (a & 0xFF00) << 8 | (b & 255) << 8 | (b >> 8);
14123
+ }
14124
+ };
14125
+ };
14085
14126
  // deflate with opts
14086
14127
  var dopt = function (dat, opt, pre, post, st) {
14087
14128
  if (!st) {
@@ -14166,7 +14207,7 @@
14166
14207
  };
14167
14208
  // base async inflate fn
14168
14209
  var bInflt = function () { return [u8, u16, i32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, ec, hMap, max, bits, bits16, shft, slc, err, inflt, inflateSync, pbf, gopt]; };
14169
- var bDflt = function () { return [u8, u16, i32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; };
14210
+ var bDflt = function () { return [u8, u16, i32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync$1, pbf]; };
14170
14211
  // gzip extra
14171
14212
  var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; };
14172
14213
  // gunzip extra
@@ -14229,6 +14270,17 @@
14229
14270
  };
14230
14271
  // gzip header length
14231
14272
  var gzhl = function (o) { return 10 + (o.filename ? o.filename.length + 1 : 0); };
14273
+ // zlib header
14274
+ var zlh = function (c, o) {
14275
+ var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2;
14276
+ c[0] = 120, c[1] = (fl << 6) | (o.dictionary && 32);
14277
+ c[1] |= 31 - ((c[0] << 8) | c[1]) % 31;
14278
+ if (o.dictionary) {
14279
+ var h = adler();
14280
+ h.p(o.dictionary);
14281
+ wbytes(c, 2, h.d());
14282
+ }
14283
+ };
14232
14284
  function deflate(data, opts, cb) {
14233
14285
  if (!cb)
14234
14286
  cb = opts, opts = {};
@@ -14236,7 +14288,7 @@
14236
14288
  err(7);
14237
14289
  return cbify(data, opts, [
14238
14290
  bDflt,
14239
- ], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb);
14291
+ ], function (ev) { return pbf(deflateSync$1(ev.data[0], ev.data[1])); }, 0, cb);
14240
14292
  }
14241
14293
  /**
14242
14294
  * Compresses data with DEFLATE without any wrapper
@@ -14244,7 +14296,7 @@
14244
14296
  * @param opts The compression options
14245
14297
  * @returns The deflated version of the data
14246
14298
  */
14247
- function deflateSync(data, opts) {
14299
+ function deflateSync$1(data, opts) {
14248
14300
  return dopt(data, opts || {}, 0, 0);
14249
14301
  }
14250
14302
  function inflate(data, opts, cb) {
@@ -14313,6 +14365,20 @@
14313
14365
  err(6, 'invalid gzip data');
14314
14366
  return inflt(data.subarray(st, -8), { i: 2 }, opts && opts.out || new u8(gzl(data)), opts && opts.dictionary);
14315
14367
  }
14368
+ /**
14369
+ * Compress data with Zlib
14370
+ * @param data The data to compress
14371
+ * @param opts The compression options
14372
+ * @returns The zlib-compressed version of the data
14373
+ */
14374
+ function zlibSync(data, opts) {
14375
+ if (!opts)
14376
+ opts = {};
14377
+ var a = adler();
14378
+ a.p(data);
14379
+ var d = dopt(data, opts, opts.dictionary ? 6 : 2, 4);
14380
+ return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d;
14381
+ }
14316
14382
  // flatten a directory structure
14317
14383
  var fltn = function (d, p, t, o) {
14318
14384
  for (var k in d) {
@@ -14568,7 +14634,7 @@
14568
14634
  cbl(null, file);
14569
14635
  else if (size < 160000) {
14570
14636
  try {
14571
- cbl(null, deflateSync(file, p));
14637
+ cbl(null, deflateSync$1(file, p));
14572
14638
  }
14573
14639
  catch (e) {
14574
14640
  cbl(e, null);
@@ -14606,7 +14672,7 @@
14606
14672
  var exl = exfl(p.extra);
14607
14673
  if (s > 65535)
14608
14674
  err(11);
14609
- var d = compression ? deflateSync(file, p) : file, l = d.length;
14675
+ var d = compression ? deflateSync$1(file, p) : file, l = d.length;
14610
14676
  var c = crc();
14611
14677
  c.p(file);
14612
14678
  files.push(mrg(p, {
@@ -17475,6 +17541,12 @@
17475
17541
  sortIntersections: sortIntersections
17476
17542
  });
17477
17543
 
17544
+ // Reverting an arc can leave it crossing a neighbor that is still modified, so
17545
+ // the check is repeated; each pass reverts more arcs than the last, and the
17546
+ // unmodified geometry is the floor.
17547
+ var MAX_REPAIR_PASSES = 10;
17548
+
17549
+
17478
17550
  // arcs: ArcCollection containing original coordinates
17479
17551
  function getRepairFunction(arcs) {
17480
17552
  var arcsOrig = arcs.getCopy();
@@ -17536,6 +17608,73 @@
17536
17608
  });
17537
17609
  }
17538
17610
 
17611
+ // Remove intersections from a whole-collection edit (such as smoothing) by
17612
+ // putting the arcs that cross back the way they were. Reverting per arc, rather
17613
+ // than per vertex, is what suits an edit that resamples a path: the modified and
17614
+ // original versions of an arc need not share a vertex count.
17615
+ //
17616
+ // arcs: modified arcs, edited in place
17617
+ // arcsOrig: the same arcs, in the same order, before the edit
17618
+ // Returns {reverted: number of arcs put back, remaining: crossings left}
17619
+ function repairCrossedArcs(arcs, arcsOrig) {
17620
+ var intersections = findSegmentIntersections(arcs, {});
17621
+ var reverted = null;
17622
+ var pass = 0;
17623
+ while (intersections.length > 0 && pass++ < MAX_REPAIR_PASSES) {
17624
+ reverted = reverted || new Uint8Array(arcs.size());
17625
+ // Nothing new to try: the arcs that cross are already unmodified.
17626
+ if (markCrossedArcs(intersections, arcs, reverted) === 0) break;
17627
+ revertArcs(arcs, arcsOrig, reverted);
17628
+ intersections = findSegmentIntersections(arcs, {});
17629
+ }
17630
+ return {
17631
+ reverted: reverted ? countMarked(reverted) : 0,
17632
+ remaining: intersections.length
17633
+ };
17634
+ }
17635
+
17636
+ // Flags the arcs taking part in each intersection; returns how many were newly
17637
+ // flagged.
17638
+ function markCrossedArcs(intersections, arcs, marked) {
17639
+ var ii = arcs.getVertexData().ii;
17640
+ var added = 0;
17641
+ intersections.forEach(function(o) {
17642
+ [findArcIdFromVertexId(o.a[0], ii), findArcIdFromVertexId(o.b[0], ii)]
17643
+ .forEach(function(arcId) {
17644
+ if (marked[arcId]) return;
17645
+ marked[arcId] = 1;
17646
+ added++;
17647
+ });
17648
+ });
17649
+ return added;
17650
+ }
17651
+
17652
+ function revertArcs(arcs, arcsOrig, marked) {
17653
+ var mod = arcs.getVertexData();
17654
+ var orig = arcsOrig.getVertexData();
17655
+ var nn = [], xx = [], yy = [];
17656
+ var i, j, src, start, len;
17657
+ for (i = 0; i < marked.length; i++) {
17658
+ src = marked[i] ? orig : mod;
17659
+ start = src.ii[i];
17660
+ len = src.nn[i];
17661
+ nn.push(len);
17662
+ for (j = 0; j < len; j++) {
17663
+ xx.push(src.xx[start + j]);
17664
+ yy.push(src.yy[start + j]);
17665
+ }
17666
+ }
17667
+ arcs.updateVertexData(nn, xx, yy);
17668
+ }
17669
+
17670
+ function countMarked(marked) {
17671
+ var count = 0;
17672
+ for (var i = 0; i < marked.length; i++) {
17673
+ if (marked[i]) count++;
17674
+ }
17675
+ return count;
17676
+ }
17677
+
17539
17678
  // idx: index of an arc endpoint
17540
17679
  function findMatchingEndpoints(idx, data) {
17541
17680
  var ii = data.ii, nn = data.nn, xx = data.xx, yy = data.yy;
@@ -17555,6 +17694,13 @@
17555
17694
  return matches;
17556
17695
  }
17557
17696
 
17697
+ var SegmentIntersectionRepair = /*#__PURE__*/Object.freeze({
17698
+ __proto__: null,
17699
+ getRepairFunction: getRepairFunction,
17700
+ repairCrossedArcs: repairCrossedArcs,
17701
+ repairSegmentIntersections: repairSegmentIntersections
17702
+ });
17703
+
17558
17704
  function roundToSignificantDigits(n, d) {
17559
17705
  return +n.toPrecision(d);
17560
17706
  }
@@ -22299,7 +22445,7 @@
22299
22445
  return;
22300
22446
  }
22301
22447
  layers.forEach(function(lyr) {
22302
- if (!layerHasGeometry(lyr)) return;
22448
+ if (!layerHasGeometry$1(lyr)) return;
22303
22449
  if (lyr.geometry_type == 'polygon' && opts.rewind) {
22304
22450
  profileStart('rewindPolygons');
22305
22451
  rewindPolygons(lyr, dataset.arcs);
@@ -22595,33 +22741,35 @@
22595
22741
  });
22596
22742
  }
22597
22743
 
22598
- // Return an array of Features or Geometries as objects or strings
22744
+ // Returns a cursor for generating a layer's Features (or Geometries) one at a
22745
+ // time. A caller that can consume features incrementally should use this
22746
+ // instead of exportLayerAsGeoJSON(), which holds the entire layer in memory --
22747
+ // the GeoJSON form of a coordinate is many times larger than its binary form,
22748
+ // so materializing a whole layer of detailed geometry is usually the largest
22749
+ // allocation an exporter makes.
22599
22750
  //
22600
- function exportLayerAsGeoJSON(lyr, dataset, opts, asFeatures, ofmt) {
22751
+ // getFeature() returns null for a null geometry when asFeatures is false, so
22752
+ // that such shapes can be left out of a GeometryCollection.
22753
+ function getFeatureCursor(lyr, dataset, opts, asFeatures) {
22601
22754
  var properties = exportProperties(lyr.data, opts),
22602
22755
  shapes = lyr.shapes,
22603
22756
  ids = exportIds(lyr.data, opts),
22604
- stringify;
22605
-
22606
- if (opts.ndjson) {
22607
- stringify = stringifyAsNDJSON;
22608
- } else if (opts.prettify) {
22609
- stringify = getFormattedStringify(['bbox', 'coordinates']);
22610
- } else {
22611
- stringify = JSON.stringify;
22612
- }
22757
+ exporter = GeoJSON.exporters[lyr.geometry_type];
22613
22758
 
22614
22759
  if (properties && shapes && properties.length !== shapes.length) {
22615
22760
  error("Mismatch between number of properties and number of shapes");
22616
22761
  }
22617
22762
 
22618
- return (shapes || properties || []).reduce(function(memo, o, i) {
22619
- var shape = shapes ? shapes[i] : null,
22620
- exporter = GeoJSON.exporters[lyr.geometry_type],
22621
- geom = shape ? exporter(shape, dataset.arcs, opts) : null,
22622
- obj = null;
22623
-
22624
- if (asFeatures) {
22763
+ return {
22764
+ length: (shapes || properties || []).length,
22765
+ properties: properties,
22766
+ getFeature: function(i) {
22767
+ var shape = shapes ? shapes[i] : null,
22768
+ geom = shape ? exporter(shape, dataset.arcs, opts) : null,
22769
+ obj;
22770
+ if (!asFeatures) {
22771
+ return geom || null;
22772
+ }
22625
22773
  obj = composeFeature(geom, properties ? properties[i] : null, opts);
22626
22774
  if (ids) {
22627
22775
  obj.id = ids[i];
@@ -22629,24 +22777,40 @@
22629
22777
  if (opts.no_null_props && !obj.properties) {
22630
22778
  obj.properties = {};
22631
22779
  }
22632
- } else if (!geom) {
22633
- return memo; // don't add null objects to GeometryCollection
22634
- } else {
22635
- obj = geom;
22780
+ return obj;
22636
22781
  }
22782
+ };
22783
+ }
22784
+
22785
+ // Return an array of Features or Geometries as objects or strings
22786
+ //
22787
+ function exportLayerAsGeoJSON(lyr, dataset, opts, asFeatures, ofmt) {
22788
+ var cursor = getFeatureCursor(lyr, dataset, opts, asFeatures),
22789
+ items = [],
22790
+ stringify, obj, i;
22791
+
22792
+ if (opts.ndjson) {
22793
+ stringify = stringifyAsNDJSON;
22794
+ } else if (opts.prettify) {
22795
+ stringify = getFormattedStringify(['bbox', 'coordinates']);
22796
+ } else {
22797
+ stringify = JSON.stringify;
22798
+ }
22799
+
22800
+ for (i = 0; i < cursor.length; i++) {
22801
+ obj = cursor.getFeature(i);
22802
+ if (!asFeatures && !obj) continue; // don't add null objects to GeometryCollection
22637
22803
  if (ofmt) {
22638
22804
  // stringify features as soon as they are generated, to reduce the
22639
22805
  // number of JS objects in memory (so larger files can be exported)
22640
22806
  obj = stringify(obj);
22641
22807
  if (ofmt == 'buffer') {
22642
22808
  obj = encodeString(obj, 'utf8');
22643
- // obj = stringToBuffer(obj);
22644
- // obj = new Buffer(obj, 'utf8');
22645
22809
  }
22646
22810
  }
22647
- memo.push(obj);
22648
- return memo;
22649
- }, []);
22811
+ items.push(obj);
22812
+ }
22813
+ return items;
22650
22814
  }
22651
22815
 
22652
22816
  function composeFeature(geom, properties, opts) {
@@ -22967,6 +23131,7 @@
22967
23131
  exportLayerAsGeoJSON: exportLayerAsGeoJSON,
22968
23132
  exportProperties: exportProperties,
22969
23133
  getDatasetBbox: getDatasetBbox,
23134
+ getFeatureCursor: getFeatureCursor,
22970
23135
  getIdField: getIdField,
22971
23136
  preserveOriginalCRS: preserveOriginalCRS,
22972
23137
  useFeatureCollection: useFeatureCollection,
@@ -28525,6 +28690,19 @@ ${svg}
28525
28690
  return require('zlib').gzipSync(content, opts);
28526
28691
  }
28527
28692
 
28693
+ // Compress to a zlib stream (a deflate stream wrapped in a zlib header and
28694
+ // checksum). This is what TIFF's Deflate compression expects, unlike the gzip
28695
+ // wrapper used for .gz files.
28696
+ function deflateSync(content, opts) {
28697
+ if (typeof content == 'string') {
28698
+ content = strToU8(content);
28699
+ }
28700
+ if (runningInBrowser()) {
28701
+ return zlibSync(content, opts);
28702
+ }
28703
+ return require('zlib').deflateSync(content, opts);
28704
+ }
28705
+
28528
28706
  async function gzipAsync(content, opts) {
28529
28707
  if (typeof content == 'string') {
28530
28708
  content = strToU8(content);
@@ -28559,6 +28737,7 @@ ${svg}
28559
28737
 
28560
28738
  var Gzip = /*#__PURE__*/Object.freeze({
28561
28739
  __proto__: null,
28740
+ deflateSync: deflateSync,
28562
28741
  gunzipAsync: gunzipAsync,
28563
28742
  gunzipSync: gunzipSync,
28564
28743
  gzipAsync: gzipAsync,
@@ -31642,56 +31821,89 @@ ${svg}
31642
31821
  return builder.asUint8Array();
31643
31822
  }
31644
31823
 
31645
- function serializeWithColumns(geojson, columns) {
31646
- var headerMeta = {
31647
- geometryType: inferGeometryType(geojson.features),
31648
- columns: columns,
31649
- featuresCount: geojson.features.length};
31650
- var header = buildHeader(headerMeta);
31651
- var features = geojson.features.map(function(feature) {
31824
+ // source: a feature cursor, {length, getFeature(i)}. Features are pulled and
31825
+ // encoded one at a time rather than as a collection, because the GeoJSON form
31826
+ // of a layer is several times larger than the FlatGeobuf it encodes into.
31827
+ function serializeWithColumns(source, columns) {
31828
+ if (source.length === 0) {
31829
+ throw new Error('Could not infer geometry type for collection of features.');
31830
+ }
31831
+ // The header precedes the features and declares the collection's geometry
31832
+ // type, but that type isn't known until every feature has been seen. Assume
31833
+ // the layer is homogeneous, which nearly all are, and encode again as
31834
+ // Unknown in the rare case that it isn't.
31835
+ return encodeCollection(source, columns, null) ||
31836
+ encodeCollection(source, columns, GeometryType.Unknown);
31837
+ }
31838
+
31839
+ // Returns null if forcedType is null and the features turn out to have more
31840
+ // than one geometry type.
31841
+ function encodeCollection(source, columns, forcedType) {
31842
+ var headerMeta = null;
31843
+ var sink = null;
31844
+ for (var i = 0; i < source.length; i++) {
31845
+ var feature = source.getFeature(i);
31846
+ var type = GeometryType[feature.geometry.type] || GeometryType.Unknown;
31847
+ if (!headerMeta) {
31848
+ headerMeta = {
31849
+ geometryType: forcedType === null ? type : forcedType,
31850
+ columns: columns,
31851
+ envelope: null,
31852
+ featuresCount: source.length,
31853
+ indexNodeSize: 0,
31854
+ crs: null,
31855
+ title: null,
31856
+ description: null,
31857
+ metadata: null
31858
+ };
31859
+ sink = new ByteSink(magicbytes.length + estimateEncodedBytes(source));
31860
+ sink.append(magicbytes);
31861
+ sink.append(buildHeader(headerMeta));
31862
+ } else if (forcedType === null && type != headerMeta.geometryType) {
31863
+ return null;
31864
+ }
31652
31865
  var geometry = feature.geometry.type == 'GeometryCollection' ?
31653
31866
  parseGC(feature.geometry) :
31654
31867
  parseGeometry(feature.geometry);
31655
31868
  omitRedundantGeometryType(geometry, headerMeta.geometryType);
31656
- return buildFeature(geometry, normalizeProperties(feature.properties, columns), headerMeta);
31657
- });
31658
- var featureBytes = features.reduce(function(sum, feature) {
31659
- return sum + feature.length;
31660
- }, 0);
31661
- var output = new Uint8Array(magicbytes.length + header.length + featureBytes);
31662
- var offset = magicbytes.length;
31663
- output.set(header, offset);
31664
- offset += header.length;
31665
- features.forEach(function(feature) {
31666
- output.set(feature, offset);
31667
- offset += feature.length;
31668
- });
31669
- output.set(magicbytes);
31670
- return output;
31869
+ sink.append(buildFeature(geometry, normalizeProperties(feature.properties, columns), headerMeta));
31870
+ }
31871
+ return sink.toBytes();
31872
+ }
31873
+
31874
+ // Encoding the first feature is the only sample available before the buffer
31875
+ // has to be allocated; overshooting costs a resize, which is why it is loose.
31876
+ function estimateEncodedBytes(source) {
31877
+ return Math.max(source.length * 64, 1024);
31671
31878
  }
31672
31879
 
31880
+ function ByteSink(initialSize) {
31881
+ this.bytes = new Uint8Array(initialSize);
31882
+ this.length = 0;
31883
+ }
31884
+
31885
+ ByteSink.prototype.append = function(chunk) {
31886
+ if (this.length + chunk.length > this.bytes.length) {
31887
+ var size = this.bytes.length;
31888
+ while (size < this.length + chunk.length) size *= 2;
31889
+ var grown = new Uint8Array(size);
31890
+ grown.set(this.bytes.subarray(0, this.length));
31891
+ this.bytes = grown;
31892
+ }
31893
+ this.bytes.set(chunk, this.length);
31894
+ this.length += chunk.length;
31895
+ };
31896
+
31897
+ ByteSink.prototype.toBytes = function() {
31898
+ return this.bytes.subarray(0, this.length);
31899
+ };
31900
+
31673
31901
  function omitRedundantGeometryType(geometry, headerType) {
31674
31902
  if (headerType != GeometryType.Unknown && geometry.type == headerType) {
31675
31903
  geometry.type = GeometryType.Unknown;
31676
31904
  }
31677
31905
  }
31678
31906
 
31679
- function inferGeometryType(features) {
31680
- var type;
31681
- features.forEach(function(feature) {
31682
- var next = GeometryType[feature.geometry.type] || GeometryType.Unknown;
31683
- if (type === undefined) {
31684
- type = next;
31685
- } else if (type != next) {
31686
- type = GeometryType.Unknown;
31687
- }
31688
- });
31689
- if (type === undefined) {
31690
- throw new Error('Could not infer geometry type for collection of features.');
31691
- }
31692
- return type;
31693
- }
31694
-
31695
31907
  function normalizeProperties(properties, columns) {
31696
31908
  var copy = {};
31697
31909
  properties = properties || {};
@@ -31751,9 +31963,12 @@ ${svg}
31751
31963
  }
31752
31964
  var crsMeta = resolveOutputCRS(dataset);
31753
31965
  return dataset.layers.map(function(lyr) {
31754
- var geojson = getFeatureCollection(lyr, dataset, opts);
31966
+ var cursor = getFeatureCursor(lyr, dataset, opts, true);
31967
+ if (cursor.length === 0) {
31968
+ stop$1('FlatGeobuf export does not support empty layers');
31969
+ }
31755
31970
  var columns = getFlatGeobufColumns(lyr);
31756
- var content = serializeWithColumns(geojson, columns);
31971
+ var content = serializeWithColumns(cursor, columns);
31757
31972
  var filename = lyr.name + '.' + extension;
31758
31973
  if (crsMeta) {
31759
31974
  content = rewriteHeaderWithCRS(content, crsMeta);
@@ -31767,17 +31982,6 @@ ${svg}
31767
31982
  });
31768
31983
  }
31769
31984
 
31770
- function getFeatureCollection(lyr, dataset, opts) {
31771
- var features = exportLayerAsGeoJSON(lyr, dataset, opts, true, null);
31772
- if (features.length === 0) {
31773
- stop$1('FlatGeobuf export does not support empty layers');
31774
- }
31775
- return {
31776
- type: 'FeatureCollection',
31777
- features: features
31778
- };
31779
- }
31780
-
31781
31985
  function getFlatGeobufColumns(lyr) {
31782
31986
  var records = lyr.data ? lyr.data.getRecords() : [];
31783
31987
  return lyr.data ? lyr.data.getFields().map(function(name) {
@@ -32473,83 +32677,177 @@ ${svg}
32473
32677
  async function exportGeoParquet(dataset, opts, filenameOverride) {
32474
32678
  var writer = await loadGeoParquetWriter();
32475
32679
  var compression = await getGeoParquetCompression(opts);
32680
+ var rowGroupOverride = validateRowGroupSize(opts.rowgroup);
32476
32681
  var extension = opts.extension || 'parquet';
32477
32682
  var files = [];
32478
32683
  if (opts.file) {
32479
32684
  extension = getFileExtension(opts.file) || extension;
32480
32685
  }
32481
32686
  dataset.layers.forEach(function(lyr) {
32482
- var features = exportLayerAsGeoJSON(lyr, dataset, opts, true, null);
32483
- var hasGeometry = features.some(function(feat) {
32484
- return !!feat.geometry;
32485
- });
32486
- var output = buildGeoParquetColumns(features, hasGeometry);
32487
- var writeOptions = {
32488
- columnData: output.columnData,
32489
- codec: compression.codec,
32490
- compressors: compression.compressors,
32491
- pageSize: compression.pageSize
32492
- };
32493
- if (hasGeometry) {
32494
- writeOptions.kvMetadata = [{
32495
- key: 'geo',
32496
- value: JSON.stringify(buildGeoMetadata(features, dataset))
32497
- }];
32498
- } else {
32499
- warn('GeoParquet export: layer has no geometry; writing attribute data only.');
32500
- }
32501
- var content = writer.parquetWriteBuffer(writeOptions);
32502
32687
  files.push({
32503
32688
  filename: filenameOverride || (lyr.name + '.' + extension),
32504
- content: content
32689
+ content: writeLayer(writer, lyr, dataset, opts, compression, rowGroupOverride)
32505
32690
  });
32506
32691
  });
32507
32692
  return files;
32508
32693
  }
32509
32694
 
32510
- function buildGeoParquetColumns(features, includeGeometry) {
32511
- var geometryName = 'geometry';
32512
- var names = getPropertyNames(features);
32513
- var columnData = [];
32514
- if (features.length === 0) {
32695
+ var GEOMETRY_COLUMN = 'geometry';
32696
+
32697
+ // Rows are converted and handed to the writer one row group at a time. The
32698
+ // GeoJSON form of a geometry is several times larger than the WKB the writer
32699
+ // encodes it into, so materializing a whole layer of it was the largest
32700
+ // allocation the export made -- larger than the dataset and the output file
32701
+ // combined. Converting a group at a time makes that cost a function of the
32702
+ // row group size rather than of the layer size.
32703
+ function writeLayer(writer, lyr, dataset, opts, compression, rowGroupOverride) {
32704
+ var cursor = getFeatureCursor(lyr, dataset, opts, true);
32705
+ var rowCount = cursor.length;
32706
+ if (rowCount === 0) {
32515
32707
  stop$1('GeoParquet export requires at least one record');
32516
32708
  }
32517
- if (includeGeometry) {
32518
- columnData.push({
32519
- name: geometryName,
32520
- data: features.map(function(feat) {
32521
- return feat.geometry || null;
32522
- }),
32523
- type: 'GEOMETRY'
32709
+ var hasGeometry = layerHasGeometry(lyr);
32710
+ var fields = getFieldTypes(cursor, opts);
32711
+ if (!hasGeometry && fields.length === 0) {
32712
+ stop$1('GeoParquet export requires geometry or attribute data');
32713
+ }
32714
+ if (!hasGeometry) {
32715
+ warn('GeoParquet export: layer has no geometry; writing attribute data only.');
32716
+ }
32717
+ var crs = hasGeometry ? getGeoMetadataCrs(dataset) : null;
32718
+ var byteWriter = new writer.ByteWriter();
32719
+ var pq = new writer.ParquetWriter({
32720
+ writer: byteWriter,
32721
+ schema: buildSchema(writer, fields, hasGeometry, crs),
32722
+ codec: compression.codec,
32723
+ compressors: compression.compressors
32724
+ });
32725
+ var geometryTypes = {};
32726
+ var plan = getRowGroupSize(cursor, fields, hasGeometry, rowCount, rowGroupOverride);
32727
+ eachRowGroupRange(rowCount, plan, function(start, end) {
32728
+ pq.write({
32729
+ columnData: buildChunkColumns(cursor, fields, hasGeometry, start, end, geometryTypes),
32730
+ rowGroupSize: end - start,
32731
+ pageSize: compression.pageSize
32524
32732
  });
32733
+ });
32734
+ if (hasGeometry) {
32735
+ // Set on the writer rather than passed to the constructor: the geometry
32736
+ // types aren't known until every group has been converted.
32737
+ pq.kvMetadata = [{
32738
+ key: 'geo',
32739
+ value: JSON.stringify(buildGeoMetadata(Object.keys(geometryTypes), crs))
32740
+ }];
32525
32741
  }
32526
- names.forEach(function(name) {
32527
- var values = features.map(function(feat) {
32528
- return feat.properties ? feat.properties[name] : null;
32742
+ avoidIntegerBboxBounds(pq.row_groups);
32743
+ pq.finish();
32744
+ return byteWriter.getBuffer();
32745
+ }
32746
+
32747
+ // hyparquet-writer (0.14) infers each thrift wire type from the runtime value, so
32748
+ // a bounding box bound that lands on a whole number is written as I32 where the
32749
+ // Parquet spec requires a double. Readers that parse geospatial statistics
32750
+ // (pyarrow 21+, GDAL 3.11+) then fail to deserialize the footer and cannot open
32751
+ // the file at all -- which rounded, snapped or integer-grid coordinates make easy
32752
+ // to hit. Widening each bound to the adjacent double forces the double path.
32753
+ // Reported upstream; remove once the writer carries declared field types.
32754
+ function avoidIntegerBboxBounds(rowGroups) {
32755
+ (rowGroups || []).forEach(function(group) {
32756
+ (group.columns || []).forEach(function(col) {
32757
+ var stats = col.meta_data && col.meta_data.geospatial_statistics;
32758
+ if (!stats || !stats.bbox) return;
32759
+ if (!widenIntegerBounds(stats.bbox)) {
32760
+ // Past 2^53 doubles are spaced too widely to step off an integer. Drop
32761
+ // the box rather than write a footer that no reader can parse.
32762
+ delete stats.bbox;
32763
+ }
32529
32764
  });
32530
- columnData.push(buildAttributeColumn(name, values));
32531
32765
  });
32532
- if (columnData.length === 0) {
32533
- stop$1('GeoParquet export requires geometry or attribute data');
32766
+ }
32767
+
32768
+ // Nudges whole-number bounds outward, so that the box still covers every
32769
+ // geometry in the group: an oversized box only costs a reader the chance to skip
32770
+ // the group, while an undersized one would hide rows that match a filter.
32771
+ // Returns false if any bound could not be made non-integral.
32772
+ function widenIntegerBounds(bbox) {
32773
+ return Object.keys(bbox).every(function(key) {
32774
+ var val = bbox[key];
32775
+ var widened;
32776
+ if (!Number.isInteger(val)) return true;
32777
+ widened = nextDouble(val, /min$/.test(key) ? -1 : 1);
32778
+ if (Number.isInteger(widened)) return false;
32779
+ bbox[key] = widened;
32780
+ return true;
32781
+ });
32782
+ }
32783
+
32784
+ var nextDoubleFloats = new Float64Array(1);
32785
+ var nextDoubleBits = new BigInt64Array(nextDoubleFloats.buffer);
32786
+
32787
+ // Returns the adjacent double to @val in the direction of @sign (-1 down, 1 up).
32788
+ function nextDouble(val, sign) {
32789
+ if (!Number.isFinite(val)) return val;
32790
+ if (val === 0) return sign > 0 ? Number.MIN_VALUE : -Number.MIN_VALUE;
32791
+ nextDoubleFloats[0] = val;
32792
+ // Incrementing the bit pattern moves a positive double up and a negative one
32793
+ // further down, so the step direction depends on the sign of the value.
32794
+ nextDoubleBits[0] += (val > 0) === (sign > 0) ? 1n : -1n;
32795
+ return nextDoubleFloats[0];
32796
+ }
32797
+
32798
+ // A shape that survives to this point can still export as null geometry if all
32799
+ // of its paths collapse, in which case the column is written as nulls.
32800
+ function layerHasGeometry(lyr) {
32801
+ if (!lyr.geometry_type || !lyr.shapes) return false;
32802
+ return lyr.shapes.some(Boolean);
32803
+ }
32804
+
32805
+ function eachRowGroupRange(rowCount, plan, cb) {
32806
+ var sizes = Array.isArray(plan) ? plan : [plan];
32807
+ var start = 0;
32808
+ for (var i = 0; start < rowCount; i++) {
32809
+ var end = Math.min(start + sizes[Math.min(i, sizes.length - 1)], rowCount);
32810
+ cb(start, end);
32811
+ start = end;
32534
32812
  }
32535
- return {columnData: columnData, geometryColumn: geometryName};
32536
32813
  }
32537
32814
 
32538
- function buildAttributeColumn(name, values) {
32539
- var info = inferColumnType(values);
32540
- return {
32541
- name: name,
32542
- data: values.map(function(value) {
32543
- return normalizeFieldValue(value, info.type);
32544
- }),
32545
- type: info.type
32546
- };
32815
+ function buildChunkColumns(cursor, fields, hasGeometry, start, end, geometryTypes) {
32816
+ var n = end - start;
32817
+ var geometry = hasGeometry ? new Array(n) : null;
32818
+ var values = fields.map(function() { return new Array(n); });
32819
+ for (var i = 0; i < n; i++) {
32820
+ var feat = cursor.getFeature(start + i);
32821
+ var props = feat.properties;
32822
+ if (hasGeometry) {
32823
+ var geom = feat.geometry || null;
32824
+ geometry[i] = geom;
32825
+ if (geom && geom.type) geometryTypes[geom.type] = true;
32826
+ }
32827
+ for (var j = 0; j < fields.length; j++) {
32828
+ values[j][i] = normalizeFieldValue(props ? props[fields[j].name] : null, fields[j].type);
32829
+ }
32830
+ }
32831
+ var columnData = hasGeometry ? [{name: GEOMETRY_COLUMN, data: geometry}] : [];
32832
+ fields.forEach(function(field, j) {
32833
+ columnData.push({name: field.name, data: values[j]});
32834
+ });
32835
+ return columnData;
32547
32836
  }
32548
32837
 
32549
- function inferColumnType(values) {
32838
+ function getFieldTypes(cursor, opts) {
32839
+ var properties = cursor.properties;
32840
+ if (!properties) return [];
32841
+ return getPropertyNames(properties, opts).map(function(name) {
32842
+ return {name: name, type: inferColumnType(properties, name)};
32843
+ });
32844
+ }
32845
+
32846
+ function inferColumnType(properties, name) {
32550
32847
  var type = null;
32551
- for (var i = 0; i < values.length; i++) {
32552
- var valueType = inferValueType(values[i]);
32848
+ for (var i = 0; i < properties.length; i++) {
32849
+ var props = properties[i];
32850
+ var valueType = inferValueType(props ? props[name] : null);
32553
32851
  if (!valueType) continue;
32554
32852
  if (!type) {
32555
32853
  type = valueType;
@@ -32558,12 +32856,11 @@ ${svg}
32558
32856
  (valueType == 'INT32' || valueType == 'DOUBLE')) {
32559
32857
  type = 'DOUBLE';
32560
32858
  } else {
32561
- type = 'STRING';
32562
- break;
32859
+ return 'STRING';
32563
32860
  }
32564
32861
  }
32565
32862
  }
32566
- return {type: type || 'STRING'};
32863
+ return type || 'STRING';
32567
32864
  }
32568
32865
 
32569
32866
  function inferValueType(value) {
@@ -32604,22 +32901,172 @@ ${svg}
32604
32901
  return String(value);
32605
32902
  }
32606
32903
 
32607
- function getPropertyNames(features) {
32904
+ function getPropertyNames(properties, opts) {
32608
32905
  var index = {};
32609
- features.forEach(function(feat) {
32610
- var props = feat.properties || {};
32611
- Object.keys(props).forEach(function(name) {
32612
- index[name] = true;
32906
+ // Hoisted fields are moved to the root of a GeoJSON Feature, so they are not
32907
+ // among a feature's properties and don't become columns.
32908
+ var hoisted = Array.isArray(opts.hoist) ? opts.hoist : [];
32909
+ properties.forEach(function(props) {
32910
+ Object.keys(props || {}).forEach(function(name) {
32911
+ if (hoisted.indexOf(name) == -1) index[name] = true;
32613
32912
  });
32614
32913
  });
32615
32914
  return Object.keys(index);
32616
32915
  }
32617
32916
 
32618
- function buildGeoMetadata(features, dataset) {
32619
- var geomTypes = utils.uniq(features.map(function(feat) {
32620
- return feat.geometry && feat.geometry.type || null;
32621
- }).filter(Boolean));
32622
- var crs = getGeoMetadataCrs(dataset);
32917
+ // A reader has to materialize an entire row group, so the meaningful unit is
32918
+ // bytes, not rows: a row of point geometry runs to a few dozen bytes where a
32919
+ // row of detailed polygon geometry can be tens of kilobytes. Sizing by a fixed
32920
+ // row count therefore lands hundreds of times off in either direction, so the
32921
+ // group size is derived from an estimate of the encoded row size instead.
32922
+ // The estimate ignores Parquet's own encoding, which only ever shrinks a
32923
+ // column, so groups tend to come out at half the target or less.
32924
+ //
32925
+ // The target is well below the 128MB that Hadoop-era guidance suggests, which
32926
+ // assumed a row group should fill an HDFS block. Mapshaper holds a whole
32927
+ // dataset in memory and needs roughly twenty times the output size to write
32928
+ // it, so it tops out around a couple of hundred megabytes -- at 128MB nearly
32929
+ // every file it can produce would be a single row group, giving readers no
32930
+ // parallelism and nothing to skip when filtering. Measured on a 23MB layer,
32931
+ // splitting one group into eight costs 0.03% of file size, so the smaller
32932
+ // target is close to free.
32933
+ var ROW_GROUP_TARGET_BYTES = 16 * 1024 * 1024;
32934
+ // A small leading group lets a reader fetching byte ranges over HTTP show the
32935
+ // start of the table without pulling a full-sized group.
32936
+ var ROW_GROUP_PREVIEW_BYTES = 1024 * 1024;
32937
+ var ROW_GROUP_MAX_ROWS = 1000000;
32938
+ var ROW_GROUP_SAMPLE_ROWS = 1000;
32939
+
32940
+ function getRowGroupSize(cursor, fields, hasGeometry, rowCount, override) {
32941
+ if (override) return override;
32942
+ var bytesPerRow = estimateRowBytes(cursor, fields, hasGeometry, rowCount);
32943
+ if (!bytesPerRow) return rowCount; // one group rather than the writer's default
32944
+ return planRowGroups(rowCount, bytesPerRow);
32945
+ }
32946
+
32947
+ // Returns a rowGroupSize for the Parquet writer: either a row count, or a
32948
+ // [preview, bulk] pair where the bulk size repeats for the rest of the file.
32949
+ function planRowGroups(rowCount, bytesPerRow) {
32950
+ var bulkRows = clampRowCount(ROW_GROUP_TARGET_BYTES / bytesPerRow, ROW_GROUP_MAX_ROWS);
32951
+ // Keep the preview well below a full group, otherwise splitting it off buys
32952
+ // a range reader nothing.
32953
+ var previewRows = clampRowCount(ROW_GROUP_PREVIEW_BYTES / bytesPerRow, Math.floor(bulkRows / 4));
32954
+ if (rowCount <= previewRows * 4) {
32955
+ return bulkRows;
32956
+ }
32957
+ // Spread the remaining rows evenly so that the file doesn't end with a runt
32958
+ // group holding a handful of rows.
32959
+ var rest = rowCount - previewRows;
32960
+ var groups = Math.ceil(rest / bulkRows);
32961
+ return [previewRows, Math.ceil(rest / groups)];
32962
+ }
32963
+
32964
+ function clampRowCount(rows, max) {
32965
+ if (!(rows > 1)) return 1;
32966
+ if (max >= 1 && rows > max) return max;
32967
+ return Math.floor(rows);
32968
+ }
32969
+
32970
+ // Converts the sampled rows one at a time and discards them, so that sizing
32971
+ // the row groups doesn't itself materialize the layer.
32972
+ function estimateRowBytes(cursor, fields, hasGeometry, rowCount) {
32973
+ // Sample at a stride rather than taking a prefix: layers are often sorted or
32974
+ // clustered, so the first rows are not representative.
32975
+ var step = Math.max(1, Math.floor(rowCount / ROW_GROUP_SAMPLE_ROWS));
32976
+ var total = 0;
32977
+ var sampled = 0;
32978
+ for (var i = 0; i < rowCount; i += step) {
32979
+ var feat = cursor.getFeature(i);
32980
+ var props = feat.properties;
32981
+ if (hasGeometry) {
32982
+ total += estimateValueBytes(feat.geometry || null);
32983
+ }
32984
+ for (var j = 0; j < fields.length; j++) {
32985
+ total += estimateValueBytes(
32986
+ normalizeFieldValue(props ? props[fields[j].name] : null, fields[j].type));
32987
+ }
32988
+ sampled++;
32989
+ }
32990
+ return sampled > 0 ? total / sampled : 0;
32991
+ }
32992
+
32993
+ function estimateValueBytes(value) {
32994
+ if (value === null || value === undefined) return 1;
32995
+ if (typeof value == 'string') return value.length + 4;
32996
+ if (typeof value == 'number' || typeof value == 'bigint') return 8;
32997
+ if (typeof value == 'boolean') return 1;
32998
+ if (value instanceof Date) return 8;
32999
+ if (value instanceof Uint8Array) return value.byteLength + 4;
33000
+ if (utils.isObject(value) && utils.isString(value.type)) {
33001
+ return estimateWkbBytes(value);
33002
+ }
33003
+ return 32; // a JSON-encoded column; the exact width doesn't change the sizing
33004
+ }
33005
+
33006
+ // WKB spends 1 byte on the byte order and 4 on the geometry type, 4 more on
33007
+ // each nested element count, and 16 on each XY position.
33008
+ function estimateWkbBytes(geom) {
33009
+ if (!geom || !geom.type) return 1;
33010
+ if (geom.type == 'GeometryCollection') {
33011
+ return (geom.geometries || []).reduce(function(memo, part) {
33012
+ return memo + estimateWkbBytes(part);
33013
+ }, 9);
33014
+ }
33015
+ return 5 + estimateCoordinateBytes(geom.coordinates);
33016
+ }
33017
+
33018
+ function estimateCoordinateBytes(coords) {
33019
+ if (!Array.isArray(coords)) return 0;
33020
+ if (typeof coords[0] == 'number') return 16;
33021
+ var bytes = 4;
33022
+ for (var i = 0; i < coords.length; i++) {
33023
+ bytes += estimateCoordinateBytes(coords[i]);
33024
+ }
33025
+ return bytes;
33026
+ }
33027
+
33028
+ function validateRowGroupSize(rowgroup) {
33029
+ if (rowgroup === undefined) return undefined;
33030
+ if (rowgroup >= 1 && Math.floor(rowgroup) === rowgroup) {
33031
+ return rowgroup;
33032
+ }
33033
+ stop$1('The rowgroup= option must be a positive integer number of rows');
33034
+ }
33035
+
33036
+ // The schema is derived from the column types alone, before any rows are
33037
+ // converted, because each row group is written as it is built and they all
33038
+ // have to share one schema.
33039
+ //
33040
+ // A reader that understands the Parquet 2.11 GEOMETRY logical type takes the
33041
+ // CRS from the logical type and ignores the "geo" metadata. When the logical
33042
+ // type carries no CRS the spec default is OGC:CRS84, so projected data was
33043
+ // being reported as WGS 84. Write the CRS in both places, as GDAL does, so
33044
+ // that GeoParquet 1.x readers keep working.
33045
+ function buildSchema(writer, fields, hasGeometry, crs) {
33046
+ var columnData = hasGeometry ?
33047
+ [{name: GEOMETRY_COLUMN, type: 'GEOMETRY'}] : [];
33048
+ var overrides;
33049
+ fields.forEach(function(field) {
33050
+ columnData.push({name: field.name, type: field.type});
33051
+ });
33052
+ if (hasGeometry && crs) {
33053
+ overrides = {};
33054
+ overrides[GEOMETRY_COLUMN] = {
33055
+ name: GEOMETRY_COLUMN,
33056
+ type: 'BYTE_ARRAY',
33057
+ repetition_type: 'OPTIONAL',
33058
+ logical_type: {type: 'GEOMETRY', crs: JSON.stringify(crs)}
33059
+ };
33060
+ // An overridden column must not also declare a type.
33061
+ columnData[0] = {name: GEOMETRY_COLUMN};
33062
+ }
33063
+ return writer.schemaFromColumnData({
33064
+ columnData: columnData,
33065
+ schemaOverrides: overrides
33066
+ });
33067
+ }
33068
+
33069
+ function buildGeoMetadata(geomTypes, crs) {
32623
33070
  var geomMeta = {
32624
33071
  encoding: 'WKB',
32625
33072
  geometry_types: geomTypes
@@ -32671,13 +33118,19 @@ ${svg}
32671
33118
  return null;
32672
33119
  }
32673
33120
 
33121
+ function isGeoParquetWriter(mod) {
33122
+ return !!mod && typeof mod.ParquetWriter == 'function' &&
33123
+ typeof mod.ByteWriter == 'function' &&
33124
+ typeof mod.schemaFromColumnData == 'function';
33125
+ }
33126
+
32674
33127
  async function loadGeoParquetWriter() {
32675
33128
  if (runningInBrowser()) {
32676
33129
  var mod = require$1('hyparquet-writer');
32677
- if (mod && mod.default && !mod.parquetWriteBuffer) {
33130
+ if (mod && mod.default && !isGeoParquetWriter(mod)) {
32678
33131
  mod = mod.default;
32679
33132
  }
32680
- if (!mod || !mod.parquetWriteBuffer) {
33133
+ if (!isGeoParquetWriter(mod)) {
32681
33134
  stop$1('GeoParquet writer library is not loaded');
32682
33135
  }
32683
33136
  return mod;
@@ -32686,7 +33139,7 @@ ${svg}
32686
33139
  writerPromise = dynamicImportModule$2('hyparquet-writer');
32687
33140
  }
32688
33141
  var nodeMod = await writerPromise;
32689
- return nodeMod.default && !nodeMod.parquetWriteBuffer ? nodeMod.default : nodeMod;
33142
+ return nodeMod.default && !isGeoParquetWriter(nodeMod) ? nodeMod.default : nodeMod;
32690
33143
  }
32691
33144
 
32692
33145
  async function getGeoParquetCompression(opts) {
@@ -32770,6 +33223,960 @@ ${svg}
32770
33223
  return codec;
32771
33224
  }
32772
33225
 
33226
+ // A minimal TIFF/GeoTIFF encoder, used by -o format=geotiff.
33227
+ //
33228
+ // This is hand-rolled rather than delegating to the geotiff dependency, whose
33229
+ // writer silently corrupts signed integer samples (its type table omits
33230
+ // Int8/16/32Array, so it falls back to writing 64-bit floats while the tags
33231
+ // still declare integer samples), tags data as Deflate without compressing it,
33232
+ // and builds its IFD in a fixed 1000-byte buffer that long ASCII tags overrun.
33233
+ //
33234
+ // Output is a classic (32-bit) little-endian TIFF with band-interleaved,
33235
+ // strip-based data, which is what our grids already look like in memory.
33236
+
33237
+
33238
+ var TIFF_TYPES = {
33239
+ ASCII: 2,
33240
+ SHORT: 3,
33241
+ LONG: 4,
33242
+ DOUBLE: 12
33243
+ };
33244
+
33245
+ var TYPE_SIZES = {
33246
+ 2: 1,
33247
+ 3: 2,
33248
+ 4: 4,
33249
+ 12: 8
33250
+ };
33251
+
33252
+ // Baseline tags, plus the GeoTIFF and GDAL extensions we need. Tags must appear
33253
+ // in the IFD in ascending numeric order.
33254
+ var TAGS = {
33255
+ ImageWidth: 256,
33256
+ ImageLength: 257,
33257
+ BitsPerSample: 258,
33258
+ Compression: 259,
33259
+ PhotometricInterpretation: 262,
33260
+ StripOffsets: 273,
33261
+ SamplesPerPixel: 277,
33262
+ RowsPerStrip: 278,
33263
+ StripByteCounts: 279,
33264
+ PlanarConfiguration: 284,
33265
+ Software: 305,
33266
+ ExtraSamples: 338,
33267
+ SampleFormat: 339,
33268
+ ModelPixelScale: 33550,
33269
+ ModelTiepoint: 33922,
33270
+ GeoKeyDirectory: 34735,
33271
+ GeoDoubleParams: 34736,
33272
+ GDAL_NODATA: 42113
33273
+ };
33274
+
33275
+ // The geo keys this writer knows how to emit, each with its id and its value
33276
+ // type. A key's type is fixed by the GeoTIFF spec: the short-valued keys hold
33277
+ // codes and the double-valued ones hold measurements, in degrees or in the
33278
+ // projection's linear units.
33279
+ var GEO_KEYS = {
33280
+ GTModelTypeGeoKey: [1024, 'short'],
33281
+ GTRasterTypeGeoKey: [1025, 'short'],
33282
+ GeographicTypeGeoKey: [2048, 'short'],
33283
+ GeogGeodeticDatumGeoKey: [2050, 'short'],
33284
+ GeogAngularUnitsGeoKey: [2054, 'short'],
33285
+ GeogEllipsoidGeoKey: [2056, 'short'],
33286
+ GeogSemiMajorAxisGeoKey: [2057, 'double'],
33287
+ GeogSemiMinorAxisGeoKey: [2058, 'double'],
33288
+ GeogInvFlatteningGeoKey: [2059, 'double'],
33289
+ GeogPrimeMeridianLongGeoKey: [2061, 'double'],
33290
+ ProjectedCSTypeGeoKey: [3072, 'short'],
33291
+ ProjectionGeoKey: [3074, 'short'],
33292
+ ProjCoordTransGeoKey: [3075, 'short'],
33293
+ ProjLinearUnitsGeoKey: [3076, 'short'],
33294
+ ProjLinearUnitSizeGeoKey: [3077, 'double'],
33295
+ ProjStdParallel1GeoKey: [3078, 'double'],
33296
+ ProjStdParallel2GeoKey: [3079, 'double'],
33297
+ ProjNatOriginLongGeoKey: [3080, 'double'],
33298
+ ProjNatOriginLatGeoKey: [3081, 'double'],
33299
+ ProjFalseEastingGeoKey: [3082, 'double'],
33300
+ ProjFalseNorthingGeoKey: [3083, 'double'],
33301
+ ProjFalseOriginLongGeoKey: [3084, 'double'],
33302
+ ProjFalseOriginLatGeoKey: [3085, 'double'],
33303
+ ProjFalseOriginEastingGeoKey: [3086, 'double'],
33304
+ ProjFalseOriginNorthingGeoKey: [3087, 'double'],
33305
+ ProjCenterLongGeoKey: [3088, 'double'],
33306
+ ProjCenterLatGeoKey: [3089, 'double'],
33307
+ ProjScaleAtNatOriginGeoKey: [3092, 'double'],
33308
+ ProjScaleAtCenterGeoKey: [3093, 'double'],
33309
+ ProjAzimuthAngleGeoKey: [3094, 'double'],
33310
+ ProjStraightVertPoleLongGeoKey: [3095, 'double'],
33311
+ ProjRectifiedGridAngleGeoKey: [3096, 'double']
33312
+ };
33313
+
33314
+ var COMPRESSION_NONE = 1;
33315
+ var COMPRESSION_DEFLATE = 8;
33316
+ var PHOTOMETRIC_BLACK_IS_ZERO = 1;
33317
+ var PHOTOMETRIC_RGB = 2;
33318
+ var PLANAR_CONFIG_CHUNKY = 1; // band-interleaved, matching grid.samples
33319
+ var EXTRA_SAMPLE_UNSPECIFIED = 0;
33320
+ var EXTRA_SAMPLE_UNASSOCIATED_ALPHA = 2;
33321
+ var SAMPLE_FORMAT_UINT = 1;
33322
+ var SAMPLE_FORMAT_INT = 2;
33323
+ var SAMPLE_FORMAT_FLOAT = 3;
33324
+
33325
+ // Rows are grouped into strips of about this size, so that a large raster is
33326
+ // compressed in independent chunks instead of one enormous one. Each strip
33327
+ // costs 8 bytes of tag data, so this trades a small header against the memory
33328
+ // a single compression call has to hold.
33329
+ var TARGET_STRIP_BYTES = 256 * 1024;
33330
+
33331
+ // grid: a mapshaper raster grid (width, height, bands, samples, bbox, nodata)
33332
+ // opts.geoKeys: GeoTIFF geo keys to write, e.g. {ProjectedCSTypeGeoKey: 32615}
33333
+ // opts.compress: false to store samples uncompressed
33334
+ // Returns a Uint8Array holding the whole file.
33335
+ function encodeGeoTIFF(grid, opts) {
33336
+ opts = opts || {};
33337
+ var compress = opts.compress !== false;
33338
+ var rowsPerStrip = getRowsPerStrip(grid);
33339
+ var strips = encodeStrips(grid, rowsPerStrip, compress);
33340
+ var entries = getTagEntries(grid, strips, rowsPerStrip, compress, opts.geoKeys);
33341
+ var layout = getFileLayout(entries, strips);
33342
+ var bytes = new Uint8Array(layout.fileSize);
33343
+ var view = new DataView(bytes.buffer);
33344
+ setStripOffsets(entries, strips, layout.stripDataOffset);
33345
+ writeHeader(view);
33346
+ writeIfd(view, bytes, entries, layout);
33347
+ writeStrips(bytes, strips, layout.stripDataOffset);
33348
+ return bytes;
33349
+ }
33350
+
33351
+ function writeHeader(view) {
33352
+ view.setUint8(0, 0x49); // 'II' -- little-endian byte order
33353
+ view.setUint8(1, 0x49);
33354
+ view.setUint16(2, 42, true); // TIFF magic number
33355
+ view.setUint32(4, 8, true); // offset of the first IFD
33356
+ }
33357
+
33358
+ function writeIfd(view, bytes, entries, layout) {
33359
+ var offset = layout.ifdOffset;
33360
+ var valueOffset = layout.valueDataOffset;
33361
+ view.setUint16(offset, entries.length, true);
33362
+ offset += 2;
33363
+ entries.forEach(function(entry) {
33364
+ var size = getEntryDataSize(entry);
33365
+ view.setUint16(offset, entry.tag, true);
33366
+ view.setUint16(offset + 2, entry.type, true);
33367
+ view.setUint32(offset + 4, entry.count, true);
33368
+ if (size <= 4) {
33369
+ // Small values live in the entry itself, left-aligned in the 4 bytes.
33370
+ writeEntryValues(view, bytes, entry, offset + 8);
33371
+ } else {
33372
+ view.setUint32(offset + 8, valueOffset, true);
33373
+ writeEntryValues(view, bytes, entry, valueOffset);
33374
+ valueOffset += size + (size % 2); // value blocks start on a word boundary
33375
+ }
33376
+ offset += 12;
33377
+ });
33378
+ view.setUint32(offset, 0, true); // no second IFD
33379
+ }
33380
+
33381
+ function writeEntryValues(view, bytes, entry, offset) {
33382
+ var values = entry.values;
33383
+ if (entry.type == TIFF_TYPES.ASCII) {
33384
+ writeAsciiValue(bytes, values, offset);
33385
+ return;
33386
+ }
33387
+ for (var i = 0; i < values.length; i++) {
33388
+ if (entry.type == TIFF_TYPES.SHORT) {
33389
+ view.setUint16(offset + i * 2, values[i], true);
33390
+ } else if (entry.type == TIFF_TYPES.LONG) {
33391
+ view.setUint32(offset + i * 4, values[i], true);
33392
+ } else if (entry.type == TIFF_TYPES.DOUBLE) {
33393
+ view.setFloat64(offset + i * 8, values[i], true);
33394
+ } else {
33395
+ error('Unsupported TIFF field type:', entry.type);
33396
+ }
33397
+ }
33398
+ }
33399
+
33400
+ // ASCII fields are NUL-terminated, and the terminator is counted.
33401
+ function writeAsciiValue(bytes, str, offset) {
33402
+ for (var i = 0; i < str.length; i++) {
33403
+ bytes[offset + i] = str.charCodeAt(i) & 0x7f;
33404
+ }
33405
+ bytes[offset + str.length] = 0;
33406
+ }
33407
+
33408
+ function getEntryDataSize(entry) {
33409
+ return TYPE_SIZES[entry.type] * entry.count;
33410
+ }
33411
+
33412
+ function getFileLayout(entries, strips) {
33413
+ var ifdOffset = 8;
33414
+ var ifdSize = 2 + entries.length * 12 + 4;
33415
+ var valueDataOffset = ifdOffset + ifdSize;
33416
+ var valueDataSize = entries.reduce(function(memo, entry) {
33417
+ var size = getEntryDataSize(entry);
33418
+ return size <= 4 ? memo : memo + size + (size % 2);
33419
+ }, 0);
33420
+ var stripDataOffset = valueDataOffset + valueDataSize;
33421
+ var stripDataSize = strips.reduce(function(memo, strip) {
33422
+ return memo + strip.length;
33423
+ }, 0);
33424
+ return {
33425
+ ifdOffset: ifdOffset,
33426
+ valueDataOffset: valueDataOffset,
33427
+ stripDataOffset: stripDataOffset,
33428
+ fileSize: stripDataOffset + stripDataSize
33429
+ };
33430
+ }
33431
+
33432
+ // StripOffsets can only be filled in once the size of the tag data is known.
33433
+ function setStripOffsets(entries, strips, stripDataOffset) {
33434
+ var entry = findEntry(entries, TAGS.StripOffsets);
33435
+ var offset = stripDataOffset;
33436
+ strips.forEach(function(strip, i) {
33437
+ entry.values[i] = offset;
33438
+ offset += strip.length;
33439
+ });
33440
+ }
33441
+
33442
+ function writeStrips(bytes, strips, stripDataOffset) {
33443
+ var offset = stripDataOffset;
33444
+ strips.forEach(function(strip) {
33445
+ bytes.set(strip, offset);
33446
+ offset += strip.length;
33447
+ });
33448
+ }
33449
+
33450
+ function findEntry(entries, tag) {
33451
+ return entries.filter(function(entry) {
33452
+ return entry.tag == tag;
33453
+ })[0];
33454
+ }
33455
+
33456
+ function encodeStrips(grid, rowsPerStrip, compress) {
33457
+ var sampleBytes = getSampleBytes(grid.samples);
33458
+ var bytesPerRow = getBytesPerRow(grid);
33459
+ var strips = [];
33460
+ var row = 0;
33461
+ var start, end, chunk;
33462
+ while (row < grid.height) {
33463
+ start = row * bytesPerRow;
33464
+ end = Math.min(start + rowsPerStrip * bytesPerRow, sampleBytes.length);
33465
+ chunk = sampleBytes.subarray(start, end);
33466
+ strips.push(compress ? deflateSync(chunk) : chunk);
33467
+ row += rowsPerStrip;
33468
+ }
33469
+ return strips;
33470
+ }
33471
+
33472
+ function getRowsPerStrip(grid) {
33473
+ var rows = Math.floor(TARGET_STRIP_BYTES / Math.max(getBytesPerRow(grid), 1));
33474
+ return Math.max(1, Math.min(rows, grid.height));
33475
+ }
33476
+
33477
+ function getBytesPerRow(grid) {
33478
+ return getBytesPerSample(grid.samples) * grid.bands * grid.width;
33479
+ }
33480
+
33481
+ // TIFF stores samples in the file's byte order, which is little-endian here.
33482
+ // Typed arrays use the platform's order, so a byte view can be handed straight
33483
+ // to the encoder on a little-endian machine (all common ones) and needs
33484
+ // swapping otherwise.
33485
+ function getSampleBytes(samples) {
33486
+ var bytes = new Uint8Array(samples.buffer, samples.byteOffset, samples.byteLength);
33487
+ var size = getBytesPerSample(samples);
33488
+ if (size == 1 || platformIsLittleEndian()) return bytes;
33489
+ return swapByteOrder(bytes, size);
33490
+ }
33491
+
33492
+ function platformIsLittleEndian() {
33493
+ var probe = new Uint16Array([1]);
33494
+ return new Uint8Array(probe.buffer)[0] === 1;
33495
+ }
33496
+
33497
+ function swapByteOrder(bytes, size) {
33498
+ var swapped = new Uint8Array(bytes.length);
33499
+ for (var i = 0; i < bytes.length; i += size) {
33500
+ for (var j = 0; j < size; j++) {
33501
+ swapped[i + j] = bytes[i + size - 1 - j];
33502
+ }
33503
+ }
33504
+ return swapped;
33505
+ }
33506
+
33507
+ function getBytesPerSample(samples) {
33508
+ return samples.BYTES_PER_ELEMENT || 1;
33509
+ }
33510
+
33511
+ function getSampleFormat(samples) {
33512
+ if (samples instanceof Float32Array || samples instanceof Float64Array) {
33513
+ return SAMPLE_FORMAT_FLOAT;
33514
+ }
33515
+ if (samples instanceof Int8Array || samples instanceof Int16Array ||
33516
+ samples instanceof Int32Array) {
33517
+ return SAMPLE_FORMAT_INT;
33518
+ }
33519
+ if (samples instanceof Uint8Array || samples instanceof Uint8ClampedArray ||
33520
+ samples instanceof Uint16Array || samples instanceof Uint32Array) {
33521
+ return SAMPLE_FORMAT_UINT;
33522
+ }
33523
+ error('Unsupported raster sample type');
33524
+ }
33525
+
33526
+ function getTagEntries(grid, strips, rowsPerStrip, compress, geoKeys) {
33527
+ var bands = grid.bands;
33528
+ var bytesPerSample = getBytesPerSample(grid.samples);
33529
+ var entries = [
33530
+ long(TAGS.ImageWidth, [grid.width]),
33531
+ long(TAGS.ImageLength, [grid.height]),
33532
+ short(TAGS.BitsPerSample, repeat(bytesPerSample * 8, bands)),
33533
+ short(TAGS.Compression, [compress ? COMPRESSION_DEFLATE : COMPRESSION_NONE]),
33534
+ short(TAGS.PhotometricInterpretation, [getPhotometricInterpretation$1(bands)]),
33535
+ long(TAGS.StripOffsets, repeat(0, strips.length)), // filled in after layout
33536
+ short(TAGS.SamplesPerPixel, [bands]),
33537
+ long(TAGS.RowsPerStrip, [rowsPerStrip]),
33538
+ long(TAGS.StripByteCounts, strips.map(function(strip) {
33539
+ return strip.length;
33540
+ })),
33541
+ short(TAGS.PlanarConfiguration, [PLANAR_CONFIG_CHUNKY]),
33542
+ ascii(TAGS.Software, 'mapshaper'),
33543
+ short(TAGS.SampleFormat, repeat(getSampleFormat(grid.samples), bands))
33544
+ ];
33545
+ var extraSamples = getExtraSamples(bands);
33546
+ if (extraSamples) {
33547
+ entries.push(short(TAGS.ExtraSamples, extraSamples));
33548
+ }
33549
+ entries = entries.concat(getGeoreferencingEntries(grid, geoKeys));
33550
+ if (grid.nodata !== null && grid.nodata !== undefined) {
33551
+ // GDAL stores this value as text, and reads it back with a float parser
33552
+ // that accepts both integers and "nan".
33553
+ entries.push(ascii(TAGS.GDAL_NODATA, String(grid.nodata)));
33554
+ }
33555
+ return entries.sort(function(a, b) {
33556
+ return a.tag - b.tag;
33557
+ });
33558
+ }
33559
+
33560
+ // Three or more bands are rendered as RGB(A) elsewhere in mapshaper, so they
33561
+ // are tagged that way here; anything else is a measurement raster whose first
33562
+ // band is the one to display.
33563
+ function getPhotometricInterpretation$1(bands) {
33564
+ return bands >= 3 ? PHOTOMETRIC_RGB : PHOTOMETRIC_BLACK_IS_ZERO;
33565
+ }
33566
+
33567
+ // Bands beyond the ones the photometric interpretation accounts for have to be
33568
+ // declared as extra samples. A fourth band is treated as alpha, matching the
33569
+ // renderer; other extras are left unspecified rather than guessing at them.
33570
+ function getExtraSamples(bands) {
33571
+ var colorBands = bands >= 3 ? 3 : 1;
33572
+ var extras = repeat(EXTRA_SAMPLE_UNSPECIFIED, bands - colorBands);
33573
+ if (extras.length === 0) return null;
33574
+ if (bands == 4) extras[0] = EXTRA_SAMPLE_UNASSOCIATED_ALPHA;
33575
+ return extras;
33576
+ }
33577
+
33578
+ function getGeoreferencingEntries(grid, geoKeys) {
33579
+ var bbox = grid.bbox;
33580
+ var dx = Math.abs(bbox[2] - bbox[0]) / grid.width;
33581
+ var dy = Math.abs(bbox[3] - bbox[1]) / grid.height;
33582
+ var entries = [
33583
+ // Pixel scale is unsigned; the tiepoint anchors the grid at its top-left
33584
+ // corner, which is where row 0 of the samples begins.
33585
+ double(TAGS.ModelPixelScale, [dx, dy, 0]),
33586
+ double(TAGS.ModelTiepoint, [0, 0, 0, bbox[0], bbox[3], 0])
33587
+ ];
33588
+ return entries.concat(getGeoKeyEntries(geoKeys));
33589
+ }
33590
+
33591
+ // The geo keys live in a directory tag of their own: a four-short header
33592
+ // (version, revision, minor revision, key count) followed by four shorts per
33593
+ // key. A short-valued key keeps its value in the directory; a double-valued one
33594
+ // points into the GeoDoubleParams tag, which holds them all end to end.
33595
+ function getGeoKeyEntries(geoKeys) {
33596
+ var names = Object.keys(geoKeys || {}).filter(function(name) {
33597
+ return name in GEO_KEYS && isFinite(geoKeys[name]);
33598
+ }).sort(function(a, b) {
33599
+ return GEO_KEYS[a][0] - GEO_KEYS[b][0];
33600
+ });
33601
+ var values = [1, 1, 0, names.length];
33602
+ var doubles = [];
33603
+ if (names.length === 0) return [];
33604
+ names.forEach(function(name) {
33605
+ var id = GEO_KEYS[name][0];
33606
+ if (GEO_KEYS[name][1] == 'double') {
33607
+ values.push(id, TAGS.GeoDoubleParams, 1, doubles.length);
33608
+ doubles.push(geoKeys[name]);
33609
+ } else {
33610
+ values.push(id, 0, 1, geoKeys[name]);
33611
+ }
33612
+ });
33613
+ var entries = [short(TAGS.GeoKeyDirectory, values)];
33614
+ if (doubles.length > 0) {
33615
+ entries.push(double(TAGS.GeoDoubleParams, doubles));
33616
+ }
33617
+ return entries;
33618
+ }
33619
+
33620
+ function repeat(val, count) {
33621
+ var arr = [];
33622
+ for (var i = 0; i < count; i++) arr.push(val);
33623
+ return arr;
33624
+ }
33625
+
33626
+ function short(tag, values) {
33627
+ return {tag: tag, type: TIFF_TYPES.SHORT, count: values.length, values: values};
33628
+ }
33629
+
33630
+ function long(tag, values) {
33631
+ return {tag: tag, type: TIFF_TYPES.LONG, count: values.length, values: values};
33632
+ }
33633
+
33634
+ function double(tag, values) {
33635
+ return {tag: tag, type: TIFF_TYPES.DOUBLE, count: values.length, values: values};
33636
+ }
33637
+
33638
+ function ascii(tag, str) {
33639
+ return {tag: tag, type: TIFF_TYPES.ASCII, count: str.length + 1, values: str};
33640
+ }
33641
+
33642
+ var GeoTIFFEncode = /*#__PURE__*/Object.freeze({
33643
+ __proto__: null,
33644
+ encodeGeoTIFF: encodeGeoTIFF
33645
+ });
33646
+
33647
+ // Translate between mapshaper's CRS objects and the GeoTIFF geo keys that
33648
+ // describe a projection, in both directions.
33649
+ //
33650
+ // A GeoTIFF names a projection either by an authority code or by spelling it
33651
+ // out: a coordinate transformation code plus the parameters it takes. This
33652
+ // module handles the second, longer form, which is the only way to write a
33653
+ // projection that has no EPSG code, and the only way to read one back.
33654
+ //
33655
+ // The key-by-key layout follows what GDAL writes for the same projection, since
33656
+ // that is what other software is used to reading.
33657
+
33658
+
33659
+ var R2D$1 = 180 / Math.PI;
33660
+
33661
+ // The coordinate transformation codes of the GeoTIFF spec, and where each
33662
+ // projection's parameters go. Projections missing from this table, including
33663
+ // mapshaper's interrupted and polyhedral ones, have no GeoTIFF representation.
33664
+ //
33665
+ // ct: the ProjCoordTransGeoKey code
33666
+ // proj: the proj4 name to read the code back as, when it differs from the entry
33667
+ // origin: the geo keys that carry lon_0 and lat_0
33668
+ // falseXY: the pair that carries x_0 and y_0
33669
+ // scale: the key that carries k_0, for projections that take one
33670
+ // parallels: whether the standard-parallel keys carry lat_ts or lat_1 and lat_2
33671
+ // azimuth: the projection is skewed by an +alpha angle
33672
+ // encodeOnly: another entry with the same code is the one to read it back as
33673
+ var PROJECTIONS = {
33674
+ tmerc: {ct: 1, origin: 'nat', falseXY: 'nat', scale: 'nat'},
33675
+ utm: {ct: 1, origin: 'nat', falseXY: 'nat', scale: 'nat', proj: 'tmerc', encodeOnly: true},
33676
+ // Oblique Mercator comes in two variants, which differ in whether the false
33677
+ // easting and northing are applied at the projection center or at the point
33678
+ // where its skewed axis crosses the equator. GDAL identifies the first by its
33679
+ // EPSG method code rather than by a GeoTIFF transformation code.
33680
+ omerc: {ct: 9815, origin: 'center', falseXY: 'nat', scale: 'center', azimuth: true},
33681
+ omercNoUoff: {ct: 3, origin: 'center', falseXY: 'nat', scale: 'center', azimuth: true, proj: 'omerc', noUoff: true},
33682
+ merc: {ct: 7, origin: 'nat', falseXY: 'nat', scale: 'nat'},
33683
+ // Mercator's scale can be set either by a scale factor at the equator or by a
33684
+ // parallel of true scale, and the two are alternatives.
33685
+ mercStdParallel: {ct: 7, origin: 'nat', falseXY: 'nat', parallels: 'lat_ts', proj: 'merc'},
33686
+ lcc: {ct: 8, origin: 'falseOrigin', falseXY: 'falseOrigin', parallels: 'lat_1_2'},
33687
+ // With a single standard parallel the cone touches the globe, and the scale
33688
+ // factor at that parallel becomes a parameter.
33689
+ lcc1sp: {ct: 9, origin: 'nat', falseXY: 'nat', scale: 'nat', proj: 'lcc', tangentParallel: true},
33690
+ laea: {ct: 10, origin: 'center', falseXY: 'nat'},
33691
+ aea: {ct: 11, origin: 'nat', falseXY: 'nat', parallels: 'lat_1_2'},
33692
+ aeqd: {ct: 12, origin: 'center', falseXY: 'nat'},
33693
+ eqdc: {ct: 13, origin: 'nat', falseXY: 'nat', parallels: 'lat_1_2'},
33694
+ stere: {ct: 14, origin: 'center', falseXY: 'nat', scale: 'nat'},
33695
+ sterePolar: {ct: 15, origin: 'polar', falseXY: 'nat', scale: 'nat', proj: 'stere'},
33696
+ sterea: {ct: 16, origin: 'nat', falseXY: 'nat', scale: 'nat'},
33697
+ eqc: {ct: 17, origin: 'center', falseXY: 'nat', parallels: 'lat_ts'},
33698
+ cass: {ct: 18, origin: 'nat', falseXY: 'nat'},
33699
+ gnom: {ct: 19, origin: 'center', falseXY: 'nat'},
33700
+ mill: {ct: 20, origin: 'center', falseXY: 'nat'},
33701
+ ortho: {ct: 21, origin: 'center', falseXY: 'nat'},
33702
+ poly: {ct: 22, origin: 'nat', falseXY: 'nat', scale: 'nat'},
33703
+ robin: {ct: 23, origin: 'centerLong', falseXY: 'nat'},
33704
+ sinu: {ct: 24, origin: 'centerLong', falseXY: 'nat'},
33705
+ vandg: {ct: 25, origin: 'centerLong', falseXY: 'nat'}
33706
+ };
33707
+
33708
+ var ORIGIN_KEYS = {
33709
+ nat: ['ProjNatOriginLongGeoKey', 'ProjNatOriginLatGeoKey'],
33710
+ center: ['ProjCenterLongGeoKey', 'ProjCenterLatGeoKey'],
33711
+ centerLong: ['ProjCenterLongGeoKey', null],
33712
+ falseOrigin: ['ProjFalseOriginLongGeoKey', 'ProjFalseOriginLatGeoKey'],
33713
+ // A polar stereographic projection's longitude is the meridian running down
33714
+ // the map, and its latitude is the parallel of true scale.
33715
+ polar: ['ProjStraightVertPoleLongGeoKey', 'ProjNatOriginLatGeoKey']
33716
+ };
33717
+
33718
+ var FALSE_XY_KEYS = {
33719
+ nat: ['ProjFalseEastingGeoKey', 'ProjFalseNorthingGeoKey'],
33720
+ falseOrigin: ['ProjFalseOriginEastingGeoKey', 'ProjFalseOriginNorthingGeoKey']
33721
+ };
33722
+
33723
+ var SCALE_KEYS = {
33724
+ nat: 'ProjScaleAtNatOriginGeoKey',
33725
+ center: 'ProjScaleAtCenterGeoKey'
33726
+ };
33727
+
33728
+ // Geographic CRS codes for the datums that mapshaper can name.
33729
+ var DATUM_CODES = {
33730
+ WGS84: 4326,
33731
+ NAD83: 4269,
33732
+ NAD27: 4267
33733
+ };
33734
+
33735
+ var LINEAR_UNIT_CODES = [
33736
+ [1, 9001], // metre
33737
+ [0.3048, 9002], // international foot
33738
+ [0.3048006096012192, 9003] // US survey foot
33739
+ ];
33740
+
33741
+ var USER_DEFINED = 32767;
33742
+ var ANGULAR_DEGREE = 9102;
33743
+ var MODEL_TYPE_PROJECTED$1 = 1;
33744
+ var MODEL_TYPE_GEOGRAPHIC$1 = 2;
33745
+
33746
+ // The proj4 parameters that describe a projection's shape and placement, as
33747
+ // opposed to the ellipsoid it sits on. These are the ones this module claims;
33748
+ // everything else in a proj4 string is left alone.
33749
+ var PROJECTION_PARAMS = ['proj', 'lat_0', 'lon_0', 'lonc', 'lat_1', 'lat_2',
33750
+ 'lat_ts', 'k', 'k_0', 'x_0', 'y_0', 'alpha', 'gamma', 'zone', 'south',
33751
+ 'no_uoff', 'no_off', 'no_rot'];
33752
+
33753
+ // Returns geo keys describing a CRS, or null if it has no GeoTIFF
33754
+ // representation. Callers add the raster-type key.
33755
+ function getCrsGeoKeys(crs) {
33756
+ var proj4 = getProj4(crs);
33757
+ var params = proj4 ? parseProj4Params(proj4) : null;
33758
+ // A mixed projection is several projections in one frame, which a GeoTIFF
33759
+ // cannot describe. Its proj4 string mentions only the main one, so it has to
33760
+ // be excluded by hand rather than by failing to match the table.
33761
+ if (!params || crs.__mixed_crs) return null;
33762
+ return crs.is_latlong ? getGeographicGeoKeys(crs, params) :
33763
+ getProjectedGeoKeys(crs, params);
33764
+ }
33765
+
33766
+ function getProj4(crs) {
33767
+ try {
33768
+ return crsToProj4(crs);
33769
+ } catch(e) {
33770
+ return null;
33771
+ }
33772
+ }
33773
+
33774
+ function getGeographicGeoKeys(crs, params) {
33775
+ return Object.assign({
33776
+ GTModelTypeGeoKey: MODEL_TYPE_GEOGRAPHIC$1
33777
+ }, getDatumGeoKeys(crs, params));
33778
+ }
33779
+
33780
+ function getProjectedGeoKeys(crs, params) {
33781
+ var defn = getEncodingDefn(crs, params);
33782
+ var keys;
33783
+ if (!defn) return null;
33784
+ keys = Object.assign({
33785
+ GTModelTypeGeoKey: MODEL_TYPE_PROJECTED$1
33786
+ }, getDatumGeoKeys(crs, params), {
33787
+ // The projection is spelled out in the keys below rather than named by a
33788
+ // code, which is what "user-defined" announces.
33789
+ ProjectedCSTypeGeoKey: USER_DEFINED,
33790
+ ProjectionGeoKey: USER_DEFINED,
33791
+ ProjCoordTransGeoKey: defn.ct
33792
+ }, getLinearUnitGeoKeys(crs));
33793
+ Object.assign(keys, getProjectionParamGeoKeys(crs, params, defn));
33794
+ return keys;
33795
+ }
33796
+
33797
+ // Picks the table entry for a projection, including the cases where one proj4
33798
+ // name maps to two coordinate transformations.
33799
+ function getEncodingDefn(crs, params) {
33800
+ var name = params.proj;
33801
+ if (name == 'lcc' && !hasSecondParallel(params)) {
33802
+ name = 'lcc1sp';
33803
+ } else if (name == 'stere' && isPolarLatitude(crs.phi0 * R2D$1)) {
33804
+ name = 'sterePolar';
33805
+ } else if (name == 'merc' && isFinite(params.lat_ts)) {
33806
+ name = 'mercStdParallel';
33807
+ } else if (name == 'omerc' && params.no_uoff) {
33808
+ name = 'omercNoUoff';
33809
+ }
33810
+ return PROJECTIONS[name] || null;
33811
+ }
33812
+
33813
+ function hasSecondParallel(params) {
33814
+ return isFinite(params.lat_2) && params.lat_2 !== params.lat_1;
33815
+ }
33816
+
33817
+ function isPolarLatitude(deg) {
33818
+ return Math.abs(deg) > 89.999;
33819
+ }
33820
+
33821
+ function getProjectionParamGeoKeys(crs, params, defn) {
33822
+ var origin = ORIGIN_KEYS[defn.origin];
33823
+ var falseXY = FALSE_XY_KEYS[defn.falseXY];
33824
+ var keys = {};
33825
+ keys[origin[0]] = getOriginLongitude(crs, params, defn);
33826
+ if (origin[1]) keys[origin[1]] = getOriginLatitude(crs, params, defn);
33827
+ keys[falseXY[0]] = crs.x0;
33828
+ keys[falseXY[1]] = crs.y0;
33829
+ if (defn.scale) keys[SCALE_KEYS[defn.scale]] = crs.k0;
33830
+ if (defn.parallels == 'lat_ts') {
33831
+ keys.ProjStdParallel1GeoKey = getTrueScaleLatitude(params);
33832
+ } else if (defn.parallels == 'lat_1_2') {
33833
+ keys.ProjStdParallel1GeoKey = isFinite(params.lat_1) ? params.lat_1 : 0;
33834
+ keys.ProjStdParallel2GeoKey = isFinite(params.lat_2) ? params.lat_2 : params.lat_1;
33835
+ }
33836
+ if (defn.azimuth) {
33837
+ keys.ProjAzimuthAngleGeoKey = params.alpha;
33838
+ // The angle from the skew axis to the map's vertical. Absent, it equals the
33839
+ // azimuth, which is how proj4 treats it too.
33840
+ keys.ProjRectifiedGridAngleGeoKey = isFinite(params.gamma) ?
33841
+ params.gamma : params.alpha;
33842
+ }
33843
+ return keys;
33844
+ }
33845
+
33846
+ // An oblique Mercator's center longitude is given by +lonc, which proj4 keeps
33847
+ // separate from the +lon_0 of other projections.
33848
+ function getOriginLongitude(crs, params, defn) {
33849
+ if (defn.azimuth && isFinite(params.lonc)) return params.lonc;
33850
+ return degrees$1(crs.lam0);
33851
+ }
33852
+
33853
+ // A polar stereographic projection is anchored at a pole, so the latitude in
33854
+ // its keys is the parallel of true scale rather than the origin.
33855
+ function getOriginLatitude(crs, params, defn) {
33856
+ if (defn.origin == 'polar' && isFinite(params.lat_ts)) return params.lat_ts;
33857
+ return degrees$1(crs.phi0);
33858
+ }
33859
+
33860
+ // An angle in degrees, cleaned up: the CRS object holds radians, so a whole
33861
+ // number of degrees comes back as 96.00000000000001 without this. Nine decimal
33862
+ // places is a fraction of a millimeter on the ground.
33863
+ function degrees$1(rad) {
33864
+ return round(rad * R2D$1, 9);
33865
+ }
33866
+
33867
+ function round(val, digits) {
33868
+ var k = Math.pow(10, digits);
33869
+ return Math.round(val * k) / k;
33870
+ }
33871
+
33872
+ function getTrueScaleLatitude(params) {
33873
+ if (isFinite(params.lat_ts)) return params.lat_ts;
33874
+ if (isFinite(params.lat_1)) return params.lat_1;
33875
+ return 0;
33876
+ }
33877
+
33878
+ // The ellipsoid and datum, which a projected CRS carries as well as a
33879
+ // geographic one.
33880
+ function getDatumGeoKeys(crs, params) {
33881
+ var code = DATUM_CODES[String(params.datum).toUpperCase()] || 0;
33882
+ var keys = {
33883
+ GeographicTypeGeoKey: code || USER_DEFINED,
33884
+ GeogAngularUnitsGeoKey: ANGULAR_DEGREE,
33885
+ // Written even when the datum is named by a code, so that a reader which
33886
+ // does not recognize the code still has the shape of the earth.
33887
+ GeogSemiMajorAxisGeoKey: crs.a,
33888
+ GeogPrimeMeridianLongGeoKey: degrees$1(crs.from_greenwich || 0)
33889
+ };
33890
+ if (!code) {
33891
+ keys.GeogGeodeticDatumGeoKey = USER_DEFINED;
33892
+ keys.GeogEllipsoidGeoKey = USER_DEFINED;
33893
+ }
33894
+ if (crs.es > 0) {
33895
+ keys.GeogInvFlatteningGeoKey = round(1 / (1 - Math.sqrt(1 - crs.es)), 9);
33896
+ } else {
33897
+ keys.GeogSemiMinorAxisGeoKey = crs.a; // a sphere
33898
+ }
33899
+ return keys;
33900
+ }
33901
+
33902
+ function getLinearUnitGeoKeys(crs) {
33903
+ var size = crs.to_meter || 1;
33904
+ var match = LINEAR_UNIT_CODES.filter(function(pair) {
33905
+ return Math.abs(pair[0] - size) < 1e-12;
33906
+ })[0];
33907
+ if (match) return {ProjLinearUnitsGeoKey: match[1]};
33908
+ return {
33909
+ ProjLinearUnitsGeoKey: USER_DEFINED,
33910
+ ProjLinearUnitSizeGeoKey: size
33911
+ };
33912
+ }
33913
+
33914
+ // Reads back the projection that getCrsGeoKeys() writes: returns the proj4
33915
+ // parameters naming the projection and placing it on the globe, as a string, or
33916
+ // null if the keys describe a projection this module does not know.
33917
+ //
33918
+ // The ellipsoid, datum and units are not included, because a caller reading a
33919
+ // file written by other software has better sources for those.
33920
+ function getGeoKeyProjection(keys) {
33921
+ var defn = getDecodingDefn(keys);
33922
+ var origin, falseXY, params;
33923
+ if (!defn) return null;
33924
+ origin = ORIGIN_KEYS[defn.origin];
33925
+ falseXY = FALSE_XY_KEYS[defn.falseXY];
33926
+ params = {proj: defn.proj || getProjectionName(defn)};
33927
+ addProjParam(params, defn.azimuth ? 'lonc' : 'lon_0', keys[origin[0]]);
33928
+ if (origin[1]) {
33929
+ Object.assign(params, getDecodedLatitude(keys[origin[1]], defn));
33930
+ }
33931
+ addProjParam(params, 'x_0', keys[falseXY[0]]);
33932
+ addProjParam(params, 'y_0', keys[falseXY[1]]);
33933
+ if (defn.scale) addProjParam(params, 'k_0', keys[SCALE_KEYS[defn.scale]]);
33934
+ if (defn.parallels == 'lat_ts') {
33935
+ addProjParam(params, 'lat_ts', keys.ProjStdParallel1GeoKey);
33936
+ } else if (defn.parallels == 'lat_1_2') {
33937
+ addProjParam(params, 'lat_1', keys.ProjStdParallel1GeoKey);
33938
+ addProjParam(params, 'lat_2', keys.ProjStdParallel2GeoKey);
33939
+ }
33940
+ if (defn.azimuth) {
33941
+ addProjParam(params, 'alpha', keys.ProjAzimuthAngleGeoKey);
33942
+ addProjParam(params, 'gamma', keys.ProjRectifiedGridAngleGeoKey);
33943
+ if (defn.noUoff) params.no_uoff = true;
33944
+ }
33945
+ return formatProj4Params(params);
33946
+ }
33947
+
33948
+ function getDecodingDefn(keys) {
33949
+ var ct = keys && keys.ProjCoordTransGeoKey;
33950
+ var names;
33951
+ if (!ct || ct == USER_DEFINED) return null;
33952
+ if (ct == PROJECTIONS.merc.ct) {
33953
+ return isFinite(keys.ProjStdParallel1GeoKey) ?
33954
+ PROJECTIONS.mercStdParallel : PROJECTIONS.merc;
33955
+ }
33956
+ names = Object.keys(PROJECTIONS).filter(function(name) {
33957
+ return PROJECTIONS[name].ct === ct && !PROJECTIONS[name].encodeOnly;
33958
+ });
33959
+ return names.length == 1 ? PROJECTIONS[names[0]] : null;
33960
+ }
33961
+
33962
+ function getProjectionName(defn) {
33963
+ return Object.keys(PROJECTIONS).filter(function(name) {
33964
+ return PROJECTIONS[name] === defn;
33965
+ })[0];
33966
+ }
33967
+
33968
+ // The latitude in a polar stereographic projection's keys is the parallel of
33969
+ // true scale; which pole the map is centered on follows from its sign.
33970
+ function getDecodedLatitude(val, defn) {
33971
+ var params = {};
33972
+ if (!isFinite(val)) return params;
33973
+ if (defn.origin == 'polar') {
33974
+ params.lat_ts = val;
33975
+ params.lat_0 = val < 0 ? -90 : 90;
33976
+ } else {
33977
+ params.lat_0 = val;
33978
+ }
33979
+ if (defn.tangentParallel) {
33980
+ // A one-parallel conic touches the globe at its origin latitude, which
33981
+ // proj4 wants stated as the standard parallel.
33982
+ params.lat_1 = params.lat_0;
33983
+ }
33984
+ return params;
33985
+ }
33986
+
33987
+ function addProjParam(params, name, val) {
33988
+ if (isFinite(val)) params[name] = val;
33989
+ }
33990
+
33991
+ // Replaces the projection parameters of a proj4 string with a different set,
33992
+ // leaving its ellipsoid, datum and unit parameters in place. Used to correct a
33993
+ // projection that another parser has read loosely.
33994
+ function replaceProj4Projection(str, projectionStr) {
33995
+ var params = parseProj4Params(str) || {};
33996
+ var kept = {};
33997
+ Object.keys(params).forEach(function(name) {
33998
+ if (PROJECTION_PARAMS.indexOf(name) == -1) kept[name] = params[name];
33999
+ });
34000
+ return projectionStr + ' ' + formatProj4Params(kept);
34001
+ }
34002
+
34003
+ function formatProj4Params(params) {
34004
+ return Object.keys(params).map(function(name) {
34005
+ return params[name] === true ? '+' + name : '+' + name + '=' + params[name];
34006
+ }).join(' ');
34007
+ }
34008
+
34009
+ // Reads the parameters that mproj keeps inside a projection's own state, and so
34010
+ // does not expose on the CRS object, out of the proj4 string that describes it.
34011
+ // Numeric values come back as numbers, flags as true.
34012
+ function parseProj4Params(str) {
34013
+ var params = {};
34014
+ String(str).split(/\s+/).forEach(function(part) {
34015
+ var match = /^\+([a-z_0-9]+)(?:=(.*))?$/i.exec(part);
34016
+ var val;
34017
+ if (!match) return;
34018
+ val = match[2];
34019
+ params[match[1]] = val === undefined ? true :
34020
+ val !== '' && isFinite(Number(val)) ? Number(val) : val;
34021
+ });
34022
+ return params.proj ? params : null;
34023
+ }
34024
+
34025
+ var GeoTIFFGeoKeys = /*#__PURE__*/Object.freeze({
34026
+ __proto__: null,
34027
+ getCrsGeoKeys: getCrsGeoKeys,
34028
+ getGeoKeyProjection: getGeoKeyProjection,
34029
+ replaceProj4Projection: replaceProj4Projection
34030
+ });
34031
+
34032
+ // GeoTIFF's model type: is the raster referenced to a projected or a
34033
+ // geographic CRS?
34034
+ var MODEL_TYPE_PROJECTED = 1;
34035
+ var MODEL_TYPE_GEOGRAPHIC = 2;
34036
+ // The raster covers areas, i.e. a coordinate refers to a pixel's corner rather
34037
+ // than its center. This matches how mapshaper's grid bbox is defined.
34038
+ var RASTER_TYPE_AREA = 1;
34039
+ // GDAL's "persistent auxiliary metadata" sidecar, which it reads for any raster
34040
+ // it opens. Unlike the .prj file that accompanies a shapefile, this is a
34041
+ // sidecar that GDAL-based software (QGIS included) actually consults for a
34042
+ // GeoTIFF's CRS.
34043
+ var AUX_EXT = '.aux.xml';
34044
+
34045
+ function exportGeoTIFF(dataset, opts) {
34046
+ var extension = opts.extension ||
34047
+ (opts.file ? getFileExtension(opts.file) : '') || 'tif';
34048
+ var crs = getOutputCrs(dataset);
34049
+ var files = [];
34050
+ dataset.layers.forEach(function(lyr) {
34051
+ var filename = lyr.name + '.' + extension;
34052
+ files.push({
34053
+ filename: filename,
34054
+ content: encodeRasterLayer(lyr, crs, opts)
34055
+ });
34056
+ // A projection that GeoTIFF has no way to describe travels in a sidecar
34057
+ // file instead.
34058
+ if (crs.code || crs.geoKeys) return;
34059
+ if (crs.wkt) {
34060
+ files.push({
34061
+ filename: filename + AUX_EXT,
34062
+ content: getAuxXml(crs.wkt)
34063
+ });
34064
+ message('Wrote the CRS of', filename, 'to a', AUX_EXT, 'file, because GeoTIFF has no way to describe this projection. Keep the two files together.');
34065
+ } else {
34066
+ message('Wrote', filename, 'without CRS metadata (mapshaper could not derive it for this dataset). Other software will read the coordinates without knowing what they refer to.');
34067
+ }
34068
+ });
34069
+ return files;
34070
+ }
34071
+
34072
+ function encodeRasterLayer(lyr, crs, opts) {
34073
+ var grid = layerHasRaster(lyr) ? getRasterGrid(lyr.raster) : null;
34074
+ if (!grid || !grid.samples) {
34075
+ stop$1('GeoTIFF output requires a raster layer with pixel data');
34076
+ }
34077
+ if (!grid.bbox) {
34078
+ stop$1('Unable to export a raster layer without georeferencing');
34079
+ }
34080
+ if (!(grid.width > 0 && grid.height > 0) ||
34081
+ grid.samples.length < grid.width * grid.height * grid.bands) {
34082
+ stop$1('Unable to export a raster layer with incomplete pixel data');
34083
+ }
34084
+ if (rasterGridIsRotated(grid)) {
34085
+ // Georeferencing is written as a tiepoint plus a pixel scale, which cannot
34086
+ // express rotation or skew.
34087
+ stop$1('Exporting a rotated or skewed raster is not supported');
34088
+ }
34089
+ return encodeGeoTIFF(grid, {
34090
+ geoKeys: getGeoKeys(crs),
34091
+ compress: getCompressionSetting(opts)
34092
+ });
34093
+ }
34094
+
34095
+ function getCompressionSetting(opts) {
34096
+ var arg = opts.compression;
34097
+ if (!arg || arg == 'deflate') return true;
34098
+ if (arg == 'none') return false;
34099
+ stop$1('Unsupported GeoTIFF compression:', arg);
34100
+ }
34101
+
34102
+ function getGeoKeys(crs) {
34103
+ var keys = {GTRasterTypeGeoKey: RASTER_TYPE_AREA};
34104
+ if (crs.code) {
34105
+ // An EPSG code says everything, including which datum realization the
34106
+ // coordinates are in, so it is preferred to spelling the projection out.
34107
+ keys.GTModelTypeGeoKey = crs.isLatLng ? MODEL_TYPE_GEOGRAPHIC : MODEL_TYPE_PROJECTED;
34108
+ keys[crs.isLatLng ? 'GeographicTypeGeoKey' : 'ProjectedCSTypeGeoKey'] = crs.code;
34109
+ return keys;
34110
+ }
34111
+ // Otherwise the projection is written out parameter by parameter. Failing
34112
+ // that, the raster is georeferenced but says nothing about what its
34113
+ // coordinates refer to, and the CRS goes in a sidecar file.
34114
+ return Object.assign(keys, crs.geoKeys || {});
34115
+ }
34116
+
34117
+ // Describe the dataset's CRS in the forms the writer can use: an EPSG code,
34118
+ // a set of geo keys spelling the projection out, and a WKT string for the
34119
+ // sidecar file. Returns {code, geoKeys, isLatLng, wkt}, any member of which may
34120
+ // be missing.
34121
+ function getOutputCrs(dataset) {
34122
+ var info = (dataset && dataset.info) || {};
34123
+ var crsInfo = tryGetCrsInfo(dataset);
34124
+ var crs = crsInfo && crsInfo.crs;
34125
+ var code = getEpsgCode(info, crs);
34126
+ return {
34127
+ code: code,
34128
+ geoKeys: code || !crs ? null : getCrsGeoKeys(crs),
34129
+ isLatLng: !!crs && isLatLngCRS(crs),
34130
+ wkt: getOutputWkt(info, crs)
34131
+ };
34132
+ }
34133
+
34134
+ function tryGetCrsInfo(dataset) {
34135
+ try {
34136
+ // Besides reading the dataset's CRS metadata, this infers WGS-84 from
34137
+ // lat-long-looking bounds.
34138
+ return getDatasetCrsInfo(dataset);
34139
+ } catch(e) {
34140
+ return null;
34141
+ }
34142
+ }
34143
+
34144
+ function getEpsgCode(info, crs) {
34145
+ var authority = parseAuthorityCodeString(info.crs_string) ||
34146
+ parseAuthorityCodeFromWkt(info.wkt1);
34147
+ if (authority && authority.org == 'EPSG') return authority.code;
34148
+ if (info.geopackage_crs &&
34149
+ String(info.geopackage_crs.organization || '').toUpperCase() == 'EPSG') {
34150
+ return info.geopackage_crs.organization_coordsys_id ||
34151
+ info.geopackage_crs.srs_id || null;
34152
+ }
34153
+ // The two CRSes that mapshaper can recognize from a projection definition
34154
+ // alone; they also cover its wgs84 and webmercator aliases, which arrive here
34155
+ // without a code.
34156
+ if (crs && isWGS84(crs)) return 4326;
34157
+ if (crs && isWebMercator(crs)) return 3857;
34158
+ return null;
34159
+ }
34160
+
34161
+ function getOutputWkt(info, crs) {
34162
+ if (info.wkt1) return info.wkt1; // the CRS as the user supplied it
34163
+ if (!crs) return null;
34164
+ try {
34165
+ // WKT2 carries more of a projection's parameters than WKT1 does.
34166
+ return crsToWkt2(crs) || crsToPrj(crs) || null;
34167
+ } catch(e) {
34168
+ return null;
34169
+ }
34170
+ }
34171
+
34172
+ function getAuxXml(wkt) {
34173
+ return '<PAMDataset>\n <SRS>' + escapeXml(wkt) + '</SRS>\n</PAMDataset>\n';
34174
+ }
34175
+
34176
+ function escapeXml(str) {
34177
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
34178
+ }
34179
+
32773
34180
  function getOutputFormat(dataset, opts) {
32774
34181
  var outFile = opts.file || null,
32775
34182
  inFmt = dataset.info && dataset.info.input_formats && dataset.info.input_formats[0],
@@ -32815,6 +34222,8 @@ ${svg}
32815
34222
  format = 'flatgeobuf';
32816
34223
  } else if (ext == 'parquet' || ext == 'geoparquet') {
32817
34224
  format = 'geoparquet';
34225
+ } else if (ext == 'tif' || ext == 'tiff') {
34226
+ format = 'geotiff';
32818
34227
  } else if (ext == 'svg') {
32819
34228
  format = 'svg';
32820
34229
  } else if (ext == 'kml' || ext == 'kmz') {
@@ -32876,8 +34285,11 @@ ${svg}
32876
34285
  var format = getOutputFormat(datasets[0], opts);
32877
34286
  var files;
32878
34287
  validateRasterExportFormat(datasets, format);
32879
- if (format != 'geoparquet' && (opts.compression || opts.level !== undefined)) {
32880
- error('The compression= and level= options only apply to GeoParquet output');
34288
+ if (format != 'geoparquet' && opts.level !== undefined) {
34289
+ error('The level= option only applies to GeoParquet output');
34290
+ }
34291
+ if (format != 'geoparquet' && format != 'geotiff' && opts.compression) {
34292
+ error('The compression= option only applies to GeoParquet and GeoTIFF output');
32881
34293
  }
32882
34294
  if (format == PACKAGE_EXT) {
32883
34295
  opts = utils.defaults({compact: true}, opts);
@@ -32946,9 +34358,15 @@ ${svg}
32946
34358
  }
32947
34359
 
32948
34360
  function validateRasterExportFormat(datasets, format) {
34361
+ if (format == 'geotiff') {
34362
+ if (datasetsHaveVectorLayers(datasets)) {
34363
+ stop$1('GeoTIFF output requires raster layers; use -target to select them');
34364
+ }
34365
+ return;
34366
+ }
32949
34367
  if (!datasetsHaveRasterLayers(datasets)) return;
32950
34368
  if (format == 'svg' || format == PACKAGE_EXT) return;
32951
- stop$1('Raster layers can only be exported as SVG or ' + PACKAGE_EXT + ' files');
34369
+ stop$1('Raster layers can only be exported as GeoTIFF, SVG or ' + PACKAGE_EXT + ' files');
32952
34370
  }
32953
34371
 
32954
34372
  function datasetsHaveRasterLayers(datasets) {
@@ -32957,6 +34375,14 @@ ${svg}
32957
34375
  });
32958
34376
  }
32959
34377
 
34378
+ function datasetsHaveVectorLayers(datasets) {
34379
+ return datasets.some(function(dataset) {
34380
+ return dataset.layers && dataset.layers.some(function(lyr) {
34381
+ return !layerHasRaster(lyr);
34382
+ });
34383
+ });
34384
+ }
34385
+
32960
34386
  // Return an array of objects with 'filename' and 'content' members.
32961
34387
  //
32962
34388
  function exportFileContent(dataset, opts) {
@@ -33017,7 +34443,8 @@ ${svg}
33017
34443
  json: exportJSON,
33018
34444
  flatgeobuf: exportFlatGeobuf,
33019
34445
  svg: exportSVG,
33020
- kml: exportKML
34446
+ kml: exportKML,
34447
+ geotiff: exportGeoTIFF
33021
34448
  };
33022
34449
 
33023
34450
 
@@ -34014,7 +35441,7 @@ ${svg}
34014
35441
  });
34015
35442
 
34016
35443
  function isSupportedOutputFormat(fmt) {
34017
- var types = ['geojson', 'topojson', 'json', 'dsv', 'dbf', 'shapefile', 'svg', 'kml', PACKAGE_EXT, 'flatgeobuf', 'geopackage', 'geoparquet'];
35444
+ var types = ['geojson', 'topojson', 'json', 'dsv', 'dbf', 'shapefile', 'svg', 'kml', PACKAGE_EXT, 'flatgeobuf', 'geopackage', 'geoparquet', 'geotiff'];
34018
35445
  return types.indexOf(fmt) > -1;
34019
35446
  }
34020
35447
 
@@ -34032,6 +35459,7 @@ ${svg}
34032
35459
  flatgeobuf: 'Flatgeobuf',
34033
35460
  geopackage: 'GeoPackage',
34034
35461
  geoparquet: 'GeoParquet',
35462
+ geotiff: 'GeoTIFF',
34035
35463
  svg: 'SVG'
34036
35464
  }[fmt] || '';
34037
35465
  }
@@ -35447,7 +36875,7 @@ ${svg}
35447
36875
  }
35448
36876
  })
35449
36877
  .option('format', {
35450
- describe: 'options: shapefile,geojson,topojson,flatgeobuf,geopackage,geoparquet,json,dbf,csv,tsv,svg'
36878
+ describe: 'options: shapefile,geojson,topojson,flatgeobuf,geopackage,geoparquet,geotiff,json,dbf,csv,tsv,svg'
35451
36879
  })
35452
36880
  .option('target', targetOpt)
35453
36881
  .option('force', {
@@ -35641,12 +37069,16 @@ ${svg}
35641
37069
  describe: '[CSV] export numbers with decimal commas not points'
35642
37070
  })
35643
37071
  .option('compression', {
35644
- describe: '[GeoParquet] options: snappy,zstd,none (default is snappy)'
37072
+ describe: '[GeoParquet] snappy,zstd,none (default snappy) [GeoTIFF] deflate,none'
35645
37073
  })
35646
37074
  .option('level', {
35647
37075
  describe: '[GeoParquet] zstd compression level',
35648
37076
  type: 'integer'
35649
37077
  })
37078
+ .option('rowgroup', {
37079
+ describe: '[GeoParquet] rows per row group (default: sized by data)',
37080
+ type: 'integer'
37081
+ })
35650
37082
  .option('show-all', {
35651
37083
  type: 'flag',
35652
37084
  describe: '[Snapshot] show all layers in Web UI'
@@ -38916,11 +40348,13 @@ ${svg}
38916
40348
  // Import path data from a non-topological source (Shapefile, GeoJSON, etc)
38917
40349
  // in preparation for identifying topology.
38918
40350
  // @opts.reserved_points -- estimate of points in dataset, for pre-allocating buffers
40351
+ // (applied when the first path arrives, so that a layer of points -- which
40352
+ // keeps its coords in shapes, not in these buffers -- never allocates it)
38919
40353
  //
38920
40354
  function PathImporter(opts) {
38921
- var bufSize = opts.reserved_points > 0 ? opts.reserved_points : 20000,
38922
- xx = new Float64Array(bufSize),
38923
- yy = new Float64Array(bufSize),
40355
+ var reservedPoints = opts.reserved_points > 0 ? opts.reserved_points : 0,
40356
+ xx = new Float64Array(20000),
40357
+ yy = new Float64Array(20000),
38924
40358
  shapes = [],
38925
40359
  properties = [],
38926
40360
  nn = [],
@@ -39064,7 +40498,7 @@ ${svg}
39064
40498
 
39065
40499
  function checkBuffers(needed) {
39066
40500
  if (needed > xx.length) {
39067
- var newLen = Math.max(needed, Math.ceil(xx.length * 1.5));
40501
+ var newLen = Math.max(needed, reservedPoints, Math.ceil(xx.length * 1.5));
39068
40502
  xx = utils.extendBuffer(xx, newLen, pointId);
39069
40503
  yy = utils.extendBuffer(yy, newLen, pointId);
39070
40504
  }
@@ -40216,8 +41650,25 @@ ${svg}
40216
41650
  return fmt;
40217
41651
  }
40218
41652
 
41653
+ // Guess the number of coordinate pairs in a GeoJSON file from its size, so that
41654
+ // the importer can size its coordinate buffers up front instead of growing them
41655
+ // a dozen times over (each growth copies the coords accumulated so far and
41656
+ // leaves the previous buffer behind, which is what makes peak memory during
41657
+ // import roughly double the size of the coordinate data).
41658
+ //
41659
+ // A pair runs from ~16 bytes ("[-122.42,37.77],") to ~42 at full double
41660
+ // precision, so this sits in the middle and errs low: undershooting just means
41661
+ // the buffers resume growing from a larger starting point, while overshooting
41662
+ // allocates memory that is never used. An attribute-heavy file overshoots, but
41663
+ // only reserves more than the growth it replaces past ~69 bytes per pair.
41664
+ function estimateGeoJSONPointCount(byteLength) {
41665
+ return Math.round(byteLength / 32);
41666
+ }
41667
+
40219
41668
  function importGeoJSONFile(fileReader, opts) {
40220
- var importer = new GeoJSONParser(opts);
41669
+ var importer = new GeoJSONParser(utils.defaults({}, opts, {
41670
+ reserved_points: estimateGeoJSONPointCount(fileReader.size())
41671
+ }));
40221
41672
  // For collections, parseGeoJSON() returns the top-level object with its
40222
41673
  // features/geometries array nulled out but all other members intact.
40223
41674
  var obj = parseGeoJSON(fileReader, importer.parseObject);
@@ -41339,13 +42790,11 @@ ${svg}
41339
42790
  if (utils.isString(crs)) {
41340
42791
  return parseAuthorityString(crs);
41341
42792
  }
41342
- var match = parseAuthorityObject(crs.id);
41343
- if (match) return match;
41344
- if (crs.base_crs && utils.isObject(crs.base_crs)) {
41345
- match = parseAuthorityObject(crs.base_crs.id);
41346
- if (match) return match;
41347
- }
41348
- return null;
42793
+ // base_crs identifies the geographic CRS that a projected CRS is derived
42794
+ // from, not the projected CRS itself, so it can't stand in for a missing
42795
+ // top-level id -- doing so reinterpreted projected coordinates as lat/long.
42796
+ // Without an id the caller falls back to converting the PROJJSON to Proj4.
42797
+ return parseAuthorityObject(crs.id);
41349
42798
  }
41350
42799
 
41351
42800
  function parseAuthorityObject(id) {
@@ -41733,20 +43182,31 @@ ${svg}
41733
43182
  };
41734
43183
  }
41735
43184
 
43185
+ // Reads the CRS a GeoTIFF claims, if mapshaper can make sense of it. A file
43186
+ // with no usable CRS metadata still imports: the raster keeps the coordinates
43187
+ // in its own georeferencing, and only the things that need to know what those
43188
+ // coordinates mean (reprojecting, basemaps, measuring) are unavailable. So a
43189
+ // CRS that cannot be read is reported and set aside, never raised as an error.
41736
43190
  async function importGeoTIFFCrs(dataset, image) {
41737
43191
  var crsInfo = await getGeoTIFFCrsInfo(image);
41738
43192
  var crsString = crsInfo.crsString;
41739
- if (!crsString) {
41740
- warnOnce(getGeoTIFFCrsWarning(crsInfo));
41741
- return;
43193
+ var crs = null;
43194
+ if (crsString) {
43195
+ try {
43196
+ await initProjLibrary({crs: crsString});
43197
+ } catch(e) {
43198
+ // A projection resource that would not load leaves the string to be
43199
+ // parsed with whatever is already known.
43200
+ }
43201
+ crs = tryParseCrsString(crsString);
41742
43202
  }
41743
- try {
41744
- await initProjLibrary({crs: crsString});
41745
- setDatasetCrsInfo(dataset, getCrsInfo(crsString));
41746
- } catch(e) {
43203
+ if (!crs) {
41747
43204
  dataset.info = dataset.info || {};
41748
- warnOnce(getGeoTIFFCrsWarning(crsInfo, e.message || e));
43205
+ warnOnce(getGeoTIFFCrsWarning(crsInfo, crsString ?
43206
+ 'Unable to use projection ' + crsString : null));
43207
+ return;
41749
43208
  }
43209
+ setDatasetCrsInfo(dataset, {crs_string: crsString, crs: crs});
41750
43210
  }
41751
43211
 
41752
43212
  async function getGeoTIFFCrsInfo(image) {
@@ -41774,17 +43234,37 @@ ${svg}
41774
43234
  return code > 0 && code != 32767;
41775
43235
  }
41776
43236
 
43237
+ // Reads a projection that the file spells out in its geo keys, rather than
43238
+ // naming by a code.
43239
+ //
43240
+ // The bulk of the work is done by the geokeys-to-proj4 library, which knows the
43241
+ // EPSG database and so reads datums, ellipsoids and units thoroughly. Its
43242
+ // reading of the projection parameters themselves is loose, though: it drops
43243
+ // the central meridian of a polar stereographic projection, the azimuth of an
43244
+ // oblique Mercator, and the true-scale parallel of a Mercator. So the
43245
+ // projection part of its answer is replaced with mapshaper's own reading of the
43246
+ // same keys, for the projections mapshaper knows how to write.
41777
43247
  async function getGeoTIFFCustomProjection(keys) {
41778
43248
  var converted = await getGeoTIFFProj4FromGeoKeys(keys);
41779
- if (converted.crsString) return converted;
41780
- if (keys.ProjectedCSTypeGeoKey != 32767 && keys.ProjectionGeoKey != 32767) return converted;
41781
- if (keys.ProjCoordTransGeoKey == 11) {
41782
- return {
41783
- crsString: getGeoTIFFAlbersProjection(keys),
41784
- warning: converted.warning
41785
- };
43249
+ var projection = getGeoKeyProjection(keys);
43250
+ if (!projection) return converted;
43251
+ if (converted.crsString) {
43252
+ return {crsString: replaceProj4Projection(converted.crsString, projection)};
41786
43253
  }
41787
- return converted;
43254
+ return {
43255
+ crsString: getGeoTIFFEllipsoidProjection(keys, projection),
43256
+ warning: converted.warning
43257
+ };
43258
+ }
43259
+
43260
+ // A last resort for a file whose datum the library could not read: the
43261
+ // projection as mapshaper reads it, on an ellipsoid taken straight from the
43262
+ // geo keys.
43263
+ function getGeoTIFFEllipsoidProjection(keys, projection) {
43264
+ var units = getGeoTIFFLinearUnits(keys);
43265
+ var ellipsoid = getGeoTIFFEllipsoid(keys);
43266
+ if (!units || !ellipsoid) return null;
43267
+ return projection + ' ' + ellipsoid + ' ' + units;
41788
43268
  }
41789
43269
 
41790
43270
  async function getGeoTIFFProj4FromGeoKeys(keys) {
@@ -41813,27 +43293,6 @@ ${svg}
41813
43293
  });
41814
43294
  }
41815
43295
 
41816
- function getGeoTIFFAlbersProjection(keys) {
41817
- var units = getGeoTIFFLinearUnits(keys);
41818
- var ellipsoid = getGeoTIFFEllipsoid(keys);
41819
- if (!units || !ellipsoid ||
41820
- !isFinite(keys.ProjStdParallel1GeoKey) || !isFinite(keys.ProjStdParallel2GeoKey) ||
41821
- !isFinite(keys.ProjNatOriginLatGeoKey) || !isFinite(keys.ProjNatOriginLongGeoKey)) {
41822
- return null;
41823
- }
41824
- return [
41825
- '+proj=aea',
41826
- '+lat_1=' + keys.ProjStdParallel1GeoKey,
41827
- '+lat_2=' + keys.ProjStdParallel2GeoKey,
41828
- '+lat_0=' + keys.ProjNatOriginLatGeoKey,
41829
- '+lon_0=' + keys.ProjNatOriginLongGeoKey,
41830
- '+x_0=' + (keys.ProjFalseEastingGeoKey || 0),
41831
- '+y_0=' + (keys.ProjFalseNorthingGeoKey || 0),
41832
- ellipsoid,
41833
- units
41834
- ].join(' ');
41835
- }
41836
-
41837
43296
  function getGeoTIFFLinearUnits(keys) {
41838
43297
  var code = keys.ProjLinearUnitsGeoKey;
41839
43298
  if (!code || code == 9001) return '+units=m';
@@ -44269,7 +45728,7 @@ ${svg}
44269
45728
  var targetPoints = [];
44270
45729
  var targetPointLayers = [];
44271
45730
  var targetFlags, otherFlags, transform;
44272
- dataset.layers.filter(layerHasGeometry).forEach(function(lyr) {
45731
+ dataset.layers.filter(layerHasGeometry$1).forEach(function(lyr) {
44273
45732
  var hits = [],
44274
45733
  misses = [],
44275
45734
  test;
@@ -47203,7 +48662,7 @@ ${svg}
47203
48662
 
47204
48663
  function stopIfBufferReachesPole(shape, dist) {
47205
48664
  var maxAbsLat = 0;
47206
- var angularDist = dist / R$3 * R2D$7;
48665
+ var angularDist = dist / R$3 * R2D$8;
47207
48666
  (shape || []).forEach(function(path) {
47208
48667
  latLngPathIter.init(path);
47209
48668
  while (latLngPathIter.hasNext()) {
@@ -53996,7 +55455,7 @@ ${svg}
53996
55455
  var maxAbsLat = Math.max(Math.abs(bounds.ymin), Math.abs(bounds.ymax));
53997
55456
  var maxPositiveDistance = getMaxPositiveBufferDistance(lyr, dataset, opts);
53998
55457
  if (!(maxPositiveDistance > 0)) return false;
53999
- return maxAbsLat + maxPositiveDistance / R$3 * R2D$7 >= 90 - POLAR_BUFFER_MARGIN_DEGREES;
55458
+ return maxAbsLat + maxPositiveDistance / R$3 * R2D$8 >= 90 - POLAR_BUFFER_MARGIN_DEGREES;
54000
55459
  }
54001
55460
 
54002
55461
  function getMaxPositiveBufferDistance(lyr, dataset, opts) {
@@ -55248,7 +56707,7 @@ ${svg}
55248
56707
  }
55249
56708
 
55250
56709
  function getCoordinateDistance(distance, arcs) {
55251
- return arcs.isPlanar() ? distance : distance / R$3 * R2D$7;
56710
+ return arcs.isPlanar() ? distance : distance / R$3 * R2D$8;
55252
56711
  }
55253
56712
 
55254
56713
  // @shapeIndex: chunk-bounds index of the source shape (see buildShapeSegmentIndex)
@@ -55709,7 +57168,7 @@ ${svg}
55709
57168
  geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset);
55710
57169
 
55711
57170
  } else {
55712
- if (!layerHasGeometry(targetLyr)) {
57171
+ if (!layerHasGeometry$1(targetLyr)) {
55713
57172
  stop$1("Layer is missing geometric shapes");
55714
57173
  }
55715
57174
  geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
@@ -55977,8 +57436,8 @@ ${svg}
55977
57436
  p[1] *= D2R$8;
55978
57437
  var rotate = inv ? rotatePointInv : rotatePoint;
55979
57438
  rotate(p, a, b, c);
55980
- p[0] *= R2D$7;
55981
- p[1] *= R2D$7;
57439
+ p[0] *= R2D$8;
57440
+ p[1] *= R2D$8;
55982
57441
  return p;
55983
57442
  };
55984
57443
  }
@@ -56885,7 +58344,7 @@ ${svg}
56885
58344
  // polygon layer, @clipData). This avoids performing unnecessary intersection
56886
58345
  // tests on each line segment.
56887
58346
  var layers = dataset.layers.filter(function(lyr) {
56888
- return layerHasGeometry(lyr) && !layerIsFullyEnclosed(lyr, dataset, clipData);
58347
+ return layerHasGeometry$1(lyr) && !layerIsFullyEnclosed(lyr, dataset, clipData);
56889
58348
  });
56890
58349
  if (layers.length > 0) {
56891
58350
  clipLayersInPlace(layers, clipData, dataset, 'clip', getInternalClipOpts());
@@ -58726,7 +60185,7 @@ ${svg}
58726
60185
 
58727
60186
  cmd.proj = function(dataset, catalog, opts, targetLayers) {
58728
60187
  var srcInfo, destInfo, destStr;
58729
- var implicitlyProjectedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasGeometry);
60188
+ var implicitlyProjectedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasGeometry$1);
58730
60189
  // A preserved GeoJSON "crs" member becomes invalid once the CRS changes.
58731
60190
  deleteGeoJSONMetadataCRS(dataset);
58732
60191
  if (opts.init) {
@@ -62377,13 +63836,34 @@ ${svg}
62377
63836
  var MAX_CONTOUR_LEVELS = 2000;
62378
63837
  var TARGET_LEVEL_COUNT = 15;
62379
63838
 
62380
- // Marching squares traces a staircase with treads one pixel wide, so smoothing
62381
- // at one pixel is what removes the stepping. Measured against an analytic cone
62382
- // quantized to whole meters (as elevation models usually are), one pixel gives
62383
- // the lowest deviation from the true contour: 0.16 px, against 0.29 px
62384
- // unsmoothed and 0.18 px at 1.5 px. On unquantized float data, where there is
62385
- // no staircase to remove, the cost is under 0.1 px.
62386
- var SMOOTHING_PIXELS = 1;
63839
+ // Marching squares traces a staircase with treads about one pixel wide, and the
63840
+ // smoother halves the amplitude of detail at roughly five times the distance it
63841
+ // is given, so a quarter of a pixel is already aimed squarely at the stepping.
63842
+ //
63843
+ // The distance also sets how far the line may move, and that is what limits it
63844
+ // from above: neighboring contours are as far apart as the contour interval
63845
+ // divided by the local gradient, which on a coarse grid over steep ground is a
63846
+ // small fraction of a pixel. (In test/data/features/contours/sample2_detail.tif
63847
+ // -- 11x11 pixels -- lines run as close as 0.1 px.) A distance of one pixel
63848
+ // therefore let lines walk over their neighbors: 40 crossings on that file and
63849
+ // 346 on a 1200x1053 DEM, against none at a quarter pixel and one pixel of
63850
+ // visible detail lost for nothing.
63851
+ var SMOOTHING_PIXELS = 0.25;
63852
+
63853
+ // Smallest gap, as a fraction of a cell, that a traced vertex is kept from the
63854
+ // grid point at either end of the edge it crosses.
63855
+ //
63856
+ // A sample whose value is exactly the contour level -- common, since elevations
63857
+ // are usually whole units and levels are round numbers -- puts the crossing
63858
+ // exactly on that grid point. Every cell around the point does the same, so two
63859
+ // branches of the contour meet there instead of passing by. Touching is
63860
+ // harmless in the traced line, but smoothing moves the branches and turns each
63861
+ // touch into a crossing, and no smoothing distance is small enough to avoid it.
63862
+ // Holding vertices a tenth of a cell clear leaves the branches far enough apart
63863
+ // to survive smoothing: on a 1200x1053 DEM this took the crossings that
63864
+ // survived from 67 to 2. Only applied when the lines are going to be smoothed;
63865
+ // with no-smoothing the crossings are placed exactly.
63866
+ var CORNER_CLEARANCE = 0.1;
62387
63867
 
62388
63868
  // A degree of longitude vanishes at the poles; keep the implied pixel width
62389
63869
  // from collapsing to zero there.
@@ -62442,7 +63922,7 @@ ${svg}
62442
63922
  levels = getContourLevels(grid, band, opts);
62443
63923
  return {
62444
63924
  levels: levels,
62445
- lines: traceRasterContours(grid, band, levels)
63925
+ lines: traceRasterContours(grid, band, levels, getCornerClearance(opts))
62446
63926
  };
62447
63927
  }
62448
63928
 
@@ -62576,14 +64056,21 @@ ${svg}
62576
64056
  return {min: min, max: max};
62577
64057
  }
62578
64058
 
62579
- function traceRasterContours(grid, band, levels) {
64059
+ function getCornerClearance(opts) {
64060
+ return opts && opts.no_smoothing ? 0 : CORNER_CLEARANCE;
64061
+ }
64062
+
64063
+ // clearance: see CORNER_CLEARANCE; 0 places crossings exactly.
64064
+ function traceRasterContours(grid, band, levels, clearance) {
62580
64065
  var segments = collectContourSegments(grid, band, levels);
62581
64066
  var toMapXY = getLatticeToMapTransform(grid);
62582
64067
  var hCount = (grid.width - 1) * grid.height;
62583
64068
  var lines = [];
64069
+ clearance = clearance || 0;
62584
64070
  segments.forEach(function(levelSegments, i) {
62585
64071
  stitchContourSegments(levelSegments).forEach(function(path) {
62586
- var coords = getPathCoords(path, grid, band, levels[i], hCount, toMapXY);
64072
+ var coords = getPathCoords(path, grid, band, levels[i], hCount, toMapXY,
64073
+ clearance);
62587
64074
  if (coords.length > 1) {
62588
64075
  lines.push({value: levels[i], coords: coords});
62589
64076
  }
@@ -62724,12 +64211,12 @@ ${svg}
62724
64211
  return path;
62725
64212
  }
62726
64213
 
62727
- function getPathCoords(path, grid, band, level, hCount, toMapXY) {
64214
+ function getPathCoords(path, grid, band, level, hCount, toMapXY, clearance) {
62728
64215
  var coords = [];
62729
64216
  var prev = null;
62730
64217
  var point, xy;
62731
64218
  for (var i = 0; i < path.length; i++) {
62732
- point = getEdgeCrossing(grid, band, level, path[i], hCount);
64219
+ point = getEdgeCrossing(grid, band, level, path[i], hCount, clearance);
62733
64220
  xy = toMapXY(point[0], point[1]);
62734
64221
  // A corner value exactly equal to the level puts two crossings in the
62735
64222
  // same place; keeping both would emit zero-length segments.
@@ -62743,7 +64230,7 @@ ${svg}
62743
64230
  // Returns the crossing point in lattice coordinates. The result depends only
62744
64231
  // on the edge and the level, so both cells sharing an edge compute the same
62745
64232
  // point, down to the last bit.
62746
- function getEdgeCrossing(grid, band, level, edgeId, hCount) {
64233
+ function getEdgeCrossing(grid, band, level, edgeId, hCount, clearance) {
62747
64234
  var W = grid.width;
62748
64235
  var bands = grid.bands;
62749
64236
  var samples = grid.samples;
@@ -62753,21 +64240,23 @@ ${svg}
62753
64240
  x = edgeId - y * (W - 1);
62754
64241
  v0 = samples[(y * W + x) * bands + band];
62755
64242
  v1 = samples[(y * W + x + 1) * bands + band];
62756
- return [x + interpolateCrossing(level, v0, v1), y];
64243
+ return [x + interpolateCrossing(level, v0, v1, clearance), y];
62757
64244
  }
62758
64245
  edgeId -= hCount;
62759
64246
  y = Math.floor(edgeId / W);
62760
64247
  x = edgeId - y * W;
62761
64248
  v0 = samples[(y * W + x) * bands + band];
62762
64249
  v1 = samples[((y + 1) * W + x) * bands + band];
62763
- return [x, y + interpolateCrossing(level, v0, v1)];
64250
+ return [x, y + interpolateCrossing(level, v0, v1, clearance)];
62764
64251
  }
62765
64252
 
62766
- function interpolateCrossing(level, v0, v1) {
64253
+ // clearance: fraction of the edge kept free at either end. See CORNER_CLEARANCE.
64254
+ function interpolateCrossing(level, v0, v1, clearance) {
64255
+ var hi = 1 - clearance;
62767
64256
  var t;
62768
64257
  if (v1 === v0) return 0.5;
62769
64258
  t = (level - v0) / (v1 - v0);
62770
- return t < 0 ? 0 : t > 1 ? 1 : t;
64259
+ return t < clearance ? clearance : t > hi ? hi : t;
62771
64260
  }
62772
64261
 
62773
64262
  // Lattice coordinates are indexed from the center of the first pixel, not the
@@ -62792,6 +64281,7 @@ ${svg}
62792
64281
  getContourBand: getContourBand,
62793
64282
  getContourLevels: getContourLevels,
62794
64283
  getContourSmoothingDistance: getContourSmoothingDistance,
64284
+ getCornerClearance: getCornerClearance,
62795
64285
  getRasterContourLines: getRasterContourLines,
62796
64286
  getRasterSampleRange: getRasterSampleRange,
62797
64287
  traceRasterContours: traceRasterContours,
@@ -62829,12 +64319,14 @@ ${svg}
62829
64319
  function smoothContourDataset(dataset, grid) {
62830
64320
  var crs = getDatasetCRS(dataset);
62831
64321
  var distance = getContourSmoothingDistance(grid, crs);
64322
+ var unsmoothed, repair;
62832
64323
  if (!(distance > 0)) {
62833
64324
  message('Skipped contour smoothing: unable to determine the pixel size');
62834
64325
  return;
62835
64326
  }
62836
64327
  message('Smoothing contours with an auto-selected interval of ' +
62837
- formatSmoothingDistance(distance, crs) + ' (one pixel)');
64328
+ formatSmoothingDistance(distance, crs) + ' (a quarter of a pixel)');
64329
+ unsmoothed = dataset.arcs.getCopy();
62838
64330
  cmd.smooth(dataset, {
62839
64331
  distance: distance,
62840
64332
  // The contour staircase is an artifact, so there are no real corners to
@@ -62842,6 +64334,19 @@ ${svg}
62842
64334
  no_corners: true,
62843
64335
  no_prefilter: true
62844
64336
  }, dataset.layers);
64337
+ // Contour lines never cross, so any crossing is smoothing pulling a line over
64338
+ // its neighbor. The interval is small enough that this is rare, and where it
64339
+ // happens the lines involved are better left as they were traced.
64340
+ repair = repairCrossedArcs(dataset.arcs, unsmoothed);
64341
+ if (repair.reverted > 0) {
64342
+ message('Left ' + repair.reverted + ' contour ' +
64343
+ (repair.reverted == 1 ? 'line' : 'lines') +
64344
+ ' unsmoothed, to keep lines from crossing');
64345
+ }
64346
+ if (repair.remaining > 0) {
64347
+ message('Unable to remove ' + repair.remaining + ' crossing' +
64348
+ (repair.remaining == 1 ? '' : 's') + ' between contour lines');
64349
+ }
62845
64350
  }
62846
64351
 
62847
64352
  function formatSmoothingDistance(distance, crs) {
@@ -66068,7 +67573,7 @@ ${svg}
66068
67573
  }
66069
67574
 
66070
67575
  cmd.filterGeom = function(lyr, arcs, opts) {
66071
- if (!layerHasGeometry(lyr)) {
67576
+ if (!layerHasGeometry$1(lyr)) {
66072
67577
  stop$1("Layer is missing geometry");
66073
67578
  }
66074
67579
  if (opts.bbox) {
@@ -74186,7 +75691,7 @@ ${svg}
74186
75691
  return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
74187
75692
  }
74188
75693
 
74189
- var version = "0.7.49";
75694
+ var version = "0.7.50";
74190
75695
 
74191
75696
  // Parse command line args into commands and run them
74192
75697
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -76076,6 +77581,8 @@ ${svg}
76076
77581
  JoinTables,
76077
77582
  JsonImport,
76078
77583
  JsonTable,
77584
+ GeoTIFFEncode,
77585
+ GeoTIFFGeoKeys,
76079
77586
  KeepShapes,
76080
77587
  LatLon,
76081
77588
  LayerUtils,
@@ -76127,6 +77634,7 @@ ${svg}
76127
77634
  Scalebar,
76128
77635
  SegmentGeom,
76129
77636
  SegmentIntersection,
77637
+ SegmentIntersectionRepair,
76130
77638
  ShapeIter$1,
76131
77639
  ShapeUtils,
76132
77640
  ShpCommon,