mapshaper 0.6.117 → 0.6.118

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
@@ -19,7 +19,7 @@
19
19
  get expandoBuffer () { return expandoBuffer; },
20
20
  get extend () { return extend$1; },
21
21
  get extendBuffer () { return extendBuffer; },
22
- get find () { return find; },
22
+ get find () { return find$1; },
23
23
  get findMedian () { return findMedian; },
24
24
  get findQuantile () { return findQuantile; },
25
25
  get findRankByValue () { return findRankByValue; },
@@ -442,7 +442,7 @@
442
442
  }, true);
443
443
  }
444
444
 
445
- function find(arr, test, ctx) {
445
+ function find$1(arr, test, ctx) {
446
446
  var matches = arr.filter(test, ctx);
447
447
  return matches.length === 0 ? null : matches[0];
448
448
  }
@@ -6399,41 +6399,52 @@
6399
6399
  };
6400
6400
  }
6401
6401
 
6402
- // Used for building topology
6402
+ // Used for building topology.
6403
+ //
6404
+ // Every arc is stored as a reference into some source coordinate arrays
6405
+ // (srcXX/srcYY) plus an inclusive [start..end] index range. The caller
6406
+ // decides which buffer to hand in: the shared xx/yy for normal arcs, or
6407
+ // a freshly-allocated merged buffer for split/wrap-around arcs. ArcIndex
6408
+ // doesn't care which — no sentinels, no special cases in its own code.
6403
6409
  //
6404
6410
  function ArcIndex(pointCount) {
6405
6411
  var hashTableSize = Math.floor(pointCount * 0.25 + 1),
6406
6412
  hash = getXYHash(hashTableSize),
6407
6413
  hashTable = new Int32Array(hashTableSize),
6408
6414
  chainIds = [],
6409
- arcs = [],
6415
+ arcSrcXX = [],
6416
+ arcSrcYY = [],
6417
+ arcStart = [],
6418
+ arcEnd = [],
6410
6419
  arcPoints = 0;
6411
6420
 
6412
- utils.initializeArray(hashTable, -1);
6421
+ initializeArray(hashTable, -1);
6413
6422
 
6414
- this.addArc = function(xx, yy) {
6415
- var end = xx.length - 1,
6416
- key = hash(xx[end], yy[end]),
6417
- chainId = hashTable[key],
6418
- arcId = arcs.length;
6423
+ // Register an arc whose coordinates are `srcXX[start..end]`, `srcYY[start..end]`
6424
+ // (inclusive). Returns the new arc id.
6425
+ this.addArc = function(srcXX, srcYY, start, end) {
6426
+ var arcId = arcSrcXX.length,
6427
+ key = hash(srcXX[end], srcYY[end]);
6428
+ chainIds.push(hashTable[key]);
6419
6429
  hashTable[key] = arcId;
6420
- arcs.push([xx, yy]);
6421
- arcPoints += xx.length;
6422
- chainIds.push(chainId);
6430
+ arcSrcXX.push(srcXX);
6431
+ arcSrcYY.push(srcYY);
6432
+ arcStart.push(start);
6433
+ arcEnd.push(end);
6434
+ arcPoints += end - start + 1;
6423
6435
  return arcId;
6424
6436
  };
6425
6437
 
6426
- // Look for a previously generated arc with the same sequence of coords, but in the
6427
- // opposite direction. (This program uses the convention of CW for space-enclosing rings, CCW for holes,
6428
- // so coincident boundaries should contain the same points in reverse sequence).
6438
+ // Look for a previously generated arc with the same sequence of coords, but
6439
+ // in the opposite direction. (This program uses the convention of CW for
6440
+ // space-enclosing rings, CCW for holes, so coincident boundaries should
6441
+ // contain the same points in reverse sequence.)
6429
6442
  //
6430
6443
  this.findDuplicateArc = function(xx, yy, start, end, getNext, getPrev) {
6431
- // First, look for a reverse match
6432
6444
  var arcId = findArcNeighbor(xx, yy, start, end, getNext);
6433
6445
  if (arcId === null) {
6434
- // Look for forward match
6435
- // (Abnormal topology, but we're accepting it because in-the-wild
6436
- // Shapefiles sometimes have duplicate paths)
6446
+ // Look for forward match (abnormal topology, but we accept it because
6447
+ // in-the-wild Shapefiles sometimes have duplicate paths).
6437
6448
  arcId = findArcNeighbor(xx, yy, end, start, getPrev);
6438
6449
  } else {
6439
6450
  arcId = ~arcId;
@@ -6445,17 +6456,14 @@
6445
6456
  var next = getNext(start),
6446
6457
  key = hash(xx[start], yy[start]),
6447
6458
  arcId = hashTable[key],
6448
- arcX, arcY, len;
6449
-
6459
+ sx, sy, s, e;
6450
6460
  while (arcId != -1) {
6451
- // check endpoints and one segment...
6452
- // it would be more rigorous but slower to identify a match
6453
- // by comparing all segments in the coordinate sequence
6454
- arcX = arcs[arcId][0];
6455
- arcY = arcs[arcId][1];
6456
- len = arcX.length;
6457
- if (arcX[0] === xx[end] && arcX[len-1] === xx[start] && arcX[len-2] === xx[next] &&
6458
- arcY[0] === yy[end] && arcY[len-1] === yy[start] && arcY[len-2] === yy[next]) {
6461
+ sx = arcSrcXX[arcId]; sy = arcSrcYY[arcId];
6462
+ s = arcStart[arcId]; e = arcEnd[arcId];
6463
+ // Check endpoints and one segment. A more rigorous match would compare
6464
+ // every segment, but that's slower and this is sufficient in practice.
6465
+ if (sx[s] === xx[end] && sx[e] === xx[start] && sx[e - 1] === xx[next] &&
6466
+ sy[s] === yy[end] && sy[e] === yy[start] && sy[e - 1] === yy[next]) {
6459
6467
  return arcId;
6460
6468
  }
6461
6469
  arcId = chainIds[arcId];
@@ -6464,22 +6472,31 @@
6464
6472
  }
6465
6473
 
6466
6474
  this.getVertexData = function() {
6467
- var xx = new Float64Array(arcPoints),
6468
- yy = new Float64Array(arcPoints),
6469
- nn = new Uint32Array(arcs.length),
6475
+ var arcCount = arcSrcXX.length,
6476
+ destXX = new Float64Array(arcPoints),
6477
+ destYY = new Float64Array(arcPoints),
6478
+ nn = new Uint32Array(arcCount),
6470
6479
  copied = 0,
6471
- arc, len;
6472
- for (var i=0, n=arcs.length; i<n; i++) {
6473
- arc = arcs[i];
6474
- len = arc[0].length;
6475
- utils.copyElements(arc[0], 0, xx, copied, len);
6476
- utils.copyElements(arc[1], 0, yy, copied, len);
6480
+ sx, sy, s, e, len, i;
6481
+ for (i = 0; i < arcCount; i++) {
6482
+ sx = arcSrcXX[i]; sy = arcSrcYY[i];
6483
+ s = arcStart[i]; e = arcEnd[i];
6484
+ len = e - s + 1;
6485
+ if (sx.subarray) {
6486
+ destXX.set(sx.subarray(s, e + 1), copied);
6487
+ destYY.set(sy.subarray(s, e + 1), copied);
6488
+ } else {
6489
+ for (var k = 0; k < len; k++) {
6490
+ destXX[copied + k] = sx[s + k];
6491
+ destYY[copied + k] = sy[s + k];
6492
+ }
6493
+ }
6477
6494
  nn[i] = len;
6478
6495
  copied += len;
6479
6496
  }
6480
6497
  return {
6481
- xx: xx,
6482
- yy: yy,
6498
+ xx: destXX,
6499
+ yy: destYY,
6483
6500
  nn: nn
6484
6501
  };
6485
6502
  };
@@ -6578,18 +6595,15 @@
6578
6595
  var pointCount = xx.length,
6579
6596
  chainIds = initPointChains(xx, yy),
6580
6597
  pathIds = initPathIds(pointCount, nn),
6598
+ pathIsRing = initPathIsRing(nn, xx, yy),
6599
+ isNode = computeIsNode(nn, xx, yy, chainIds, pathIds, pathIsRing),
6581
6600
  index = new ArcIndex(pointCount),
6582
- slice = usingTypedArrays() ? xx.subarray : Array.prototype.slice,
6583
6601
  paths, retn;
6584
6602
  paths = convertPaths(nn);
6585
6603
  retn = index.getVertexData();
6586
6604
  retn.paths = paths;
6587
6605
  return retn;
6588
6606
 
6589
- function usingTypedArrays() {
6590
- return !!(xx.subarray && yy.subarray);
6591
- }
6592
-
6593
6607
  function convertPaths(nn) {
6594
6608
  var paths = [],
6595
6609
  pointId = 0,
@@ -6602,28 +6616,28 @@
6602
6616
  return paths;
6603
6617
  }
6604
6618
 
6619
+ // Fast neighbour lookups using the precomputed pathIsRing cache.
6620
+ // Used by findDuplicateArc (via addEdge/addSplitEdge) and by addRing.
6605
6621
  function nextPoint(id) {
6606
6622
  var partId = pathIds[id],
6607
6623
  nextId = id + 1;
6608
6624
  if (nextId < pointCount && pathIds[nextId] === partId) {
6609
- return id + 1;
6625
+ return nextId;
6610
6626
  }
6611
- var len = nn[partId];
6612
- return sameXY(id, id - len + 1) ? id - len + 2 : -1;
6627
+ return pathIsRing[partId] ? id - nn[partId] + 2 : -1;
6613
6628
  }
6614
6629
 
6615
6630
  function prevPoint(id) {
6616
6631
  var partId = pathIds[id],
6617
6632
  prevId = id - 1;
6618
6633
  if (prevId >= 0 && pathIds[prevId] === partId) {
6619
- return id - 1;
6634
+ return prevId;
6620
6635
  }
6621
- var len = nn[partId];
6622
- return sameXY(id, id + len - 1) ? id + len - 2 : -1;
6636
+ return pathIsRing[partId] ? id + nn[partId] - 2 : -1;
6623
6637
  }
6624
6638
 
6625
- function sameXY(a, b) {
6626
- return xx[a] == xx[b] && yy[a] == yy[b];
6639
+ function pointIsArcEndpoint(id) {
6640
+ return isNode[id] === 1;
6627
6641
  }
6628
6642
 
6629
6643
  // Convert a non-topological path to one or more topological arcs
@@ -6666,49 +6680,9 @@
6666
6680
  return arcIds;
6667
6681
  }
6668
6682
 
6669
- // Test if a point @id is an endpoint of a topological path
6670
- function pointIsArcEndpoint(id) {
6671
- var id2 = chainIds[id],
6672
- prev = prevPoint(id),
6673
- next = nextPoint(id),
6674
- prev2, next2;
6675
- if (prev == -1 || next == -1) {
6676
- // @id is an endpoint if it is the start or end of an open path
6677
- return true;
6678
- }
6679
- while (id != id2) {
6680
- prev2 = prevPoint(id2);
6681
- next2 = nextPoint(id2);
6682
- if (prev2 == -1 || next2 == -1 || brokenEdge(prev, next, prev2, next2)) {
6683
- // there is a discontinuity at @id -- point is arc endpoint
6684
- return true;
6685
- }
6686
- id2 = chainIds[id2];
6687
- }
6688
- return false;
6689
- }
6690
-
6691
- // a and b are two vertices with the same x, y coordinates
6692
- // test if the segments on either side of them are also identical
6693
- function brokenEdge(aprev, anext, bprev, bnext) {
6694
- var apx = xx[aprev],
6695
- anx = xx[anext],
6696
- bpx = xx[bprev],
6697
- bnx = xx[bnext],
6698
- apy = yy[aprev],
6699
- any = yy[anext],
6700
- bpy = yy[bprev],
6701
- bny = yy[bnext];
6702
- if (apx == bnx && anx == bpx && apy == bny && any == bpy ||
6703
- apx == bpx && anx == bnx && apy == bpy && any == bny) {
6704
- return false;
6705
- }
6706
- return true;
6707
- }
6708
-
6709
6683
  function mergeArcParts(src, startId, endId, startId2, endId2) {
6710
6684
  var len = endId - startId + endId2 - startId2 + 2,
6711
- ArrayClass = usingTypedArrays() ? Float64Array : Array,
6685
+ ArrayClass = src.subarray ? Float64Array : Array,
6712
6686
  dest = new ArrayClass(len),
6713
6687
  j = 0, i;
6714
6688
  for (i=startId; i <= endId; i++) {
@@ -6723,8 +6697,12 @@
6723
6697
  function addSplitEdge(start1, end1, start2, end2) {
6724
6698
  var arcId = index.findDuplicateArc(xx, yy, start1, end2, nextPoint, prevPoint);
6725
6699
  if (arcId === null) {
6726
- arcId = index.addArc(mergeArcParts(xx, start1, end1, start2, end2),
6727
- mergeArcParts(yy, start1, end1, start2, end2));
6700
+ // Coordinates for a split (wrap-around) edge don't form a contiguous
6701
+ // slice of xx/yy, so we build a standalone buffer and hand it to the
6702
+ // index as the arc's source.
6703
+ var mx = mergeArcParts(xx, start1, end1, start2, end2),
6704
+ my = mergeArcParts(yy, start1, end1, start2, end2);
6705
+ arcId = index.addArc(mx, my, 0, mx.length - 1);
6728
6706
  }
6729
6707
  return arcId;
6730
6708
  }
@@ -6733,8 +6711,7 @@
6733
6711
  // search for a matching edge that has already been generated
6734
6712
  var arcId = index.findDuplicateArc(xx, yy, start, end, nextPoint, prevPoint);
6735
6713
  if (arcId === null) {
6736
- arcId = index.addArc(slice.call(xx, start, end + 1),
6737
- slice.call(yy, start, end + 1));
6714
+ arcId = index.addArc(xx, yy, start, end);
6738
6715
  }
6739
6716
  return arcId;
6740
6717
  }
@@ -6777,6 +6754,87 @@
6777
6754
  return pathIds;
6778
6755
  }
6779
6756
 
6757
+ // Per-path flag: 1 if the path is a closed ring (first vertex coincides with
6758
+ // last vertex), else 0. Computed once so that prevPoint()/nextPoint() don't
6759
+ // need to call sameXY() at path boundaries.
6760
+ //
6761
+ function initPathIsRing(nn, xx, yy) {
6762
+ var pathCount = nn.length,
6763
+ pathIsRing = new Uint8Array(pathCount),
6764
+ pstart = 0, len;
6765
+ for (var p = 0; p < pathCount; p++) {
6766
+ len = nn[p];
6767
+ if (len > 1 &&
6768
+ xx[pstart] === xx[pstart + len - 1] &&
6769
+ yy[pstart] === yy[pstart + len - 1]) {
6770
+ pathIsRing[p] = 1;
6771
+ }
6772
+ pstart += len;
6773
+ }
6774
+ return pathIsRing;
6775
+ }
6776
+
6777
+ // Decide, for every point, whether it is a topological node (an arc
6778
+ // endpoint). Being a node is a property of the entire coincident-point
6779
+ // chain: all points sharing a location agree on the answer. So we walk
6780
+ // each chain at most once — O(n) total — instead of doing the walk
6781
+ // independently at every point (O(n * K), quadratic per chain).
6782
+ //
6783
+ // A chain's points are nodes iff:
6784
+ // - any member has a missing neighbour (open-path endpoint), or
6785
+ // - two members disagree on the unordered pair of neighbour coords.
6786
+ //
6787
+ function computeIsNode(nn, xx, yy, chainIds, pathIds, pathIsRing) {
6788
+ var n = xx.length,
6789
+ isNode = new Uint8Array(n),
6790
+ done = new Uint8Array(n);
6791
+
6792
+ function nextPoint(id) {
6793
+ var part = pathIds[id], nid = id + 1;
6794
+ if (nid < n && pathIds[nid] === part) return nid;
6795
+ return pathIsRing[part] ? id - nn[part] + 2 : -1;
6796
+ }
6797
+
6798
+ function prevPoint(id) {
6799
+ var part = pathIds[id], pid = id - 1;
6800
+ if (pid >= 0 && pathIds[pid] === part) return pid;
6801
+ return pathIsRing[part] ? id + nn[part] - 2 : -1;
6802
+ }
6803
+
6804
+ for (var i = 0; i < n; i++) {
6805
+ if (done[i]) continue;
6806
+ var result = chainIsBroken(i);
6807
+ var id = i;
6808
+ do {
6809
+ isNode[id] = result;
6810
+ done[id] = 1;
6811
+ id = chainIds[id];
6812
+ } while (id !== i);
6813
+ }
6814
+ return isNode;
6815
+
6816
+ // Returns 1 if the chain containing `start` has any broken neighbour
6817
+ // signature (i.e. all members are nodes), else 0.
6818
+ function chainIsBroken(start) {
6819
+ var prev = prevPoint(start),
6820
+ next = nextPoint(start);
6821
+ if (prev === -1 || next === -1) return 1;
6822
+ var refPX = xx[prev], refPY = yy[prev],
6823
+ refNX = xx[next], refNY = yy[next];
6824
+ var id = chainIds[start];
6825
+ while (id !== start) {
6826
+ var p = prevPoint(id), q = nextPoint(id);
6827
+ if (p === -1 || q === -1) return 1;
6828
+ var px = xx[p], py = yy[p], qx = xx[q], qy = yy[q];
6829
+ var fwd = px === refPX && py === refPY && qx === refNX && qy === refNY;
6830
+ var rev = px === refNX && py === refNY && qx === refPX && qy === refPY;
6831
+ if (!fwd && !rev) return 1;
6832
+ id = chainIds[id];
6833
+ }
6834
+ return 0;
6835
+ }
6836
+ }
6837
+
6780
6838
  function replaceArcIds(src, replacements) {
6781
6839
  return src.map(function(shape) {
6782
6840
  return replaceArcsInShape(shape, replacements);
@@ -18724,13 +18782,13 @@
18724
18782
  // default is true iff layers contain attributes
18725
18783
  return utils.some(layers, function(lyr) {
18726
18784
  var fields = lyr.data ? lyr.data.getFields() : [];
18727
- var haveData = useFeatureProperties(fields, opts);
18785
+ var haveData = useFeatureProperties$1(fields, opts);
18728
18786
  var haveId = !!getIdField(fields, opts);
18729
18787
  return haveData || haveId;
18730
18788
  });
18731
18789
  }
18732
18790
 
18733
- function useFeatureProperties(fields, opts) {
18791
+ function useFeatureProperties$1(fields, opts) {
18734
18792
  return !(opts.drop_table || opts.cut_table || fields.length === 0 ||
18735
18793
  fields.length == 1 && fields[0] == GeoJSON.ID_FIELD);
18736
18794
  }
@@ -18739,7 +18797,7 @@
18739
18797
  var fields = table ? table.getFields() : [],
18740
18798
  idField = getIdField(fields, opts),
18741
18799
  properties, records;
18742
- if (!useFeatureProperties(fields, opts)) {
18800
+ if (!useFeatureProperties$1(fields, opts)) {
18743
18801
  return null;
18744
18802
  }
18745
18803
  records = table.getRecords();
@@ -24500,12 +24558,10 @@ ${svg}
24500
24558
  }
24501
24559
 
24502
24560
  async function exportLayerToGeoPackage(lyr, dataset, gpkg, opts) {
24503
- var features, fields, columns, tableName, targetSrs;
24561
+ var fields, columns, tableName, targetSrs, state;
24504
24562
  if (!lyr.geometry_type) return;
24505
- features = exportLayerAsGeoJSON(lyr, dataset, opts, true, null)
24506
- .filter(feat => !!(feat && feat.geometry));
24507
- if (features.length === 0) return;
24508
- fields = inferFieldTypes(features);
24563
+ fields = inferFieldTypesFromLayer(lyr, dataset, opts);
24564
+ if (!fields) return;
24509
24565
  tableName = getTableName(lyr.name);
24510
24566
  columns = fields.map(function(field) {
24511
24567
  return {
@@ -24522,8 +24578,11 @@ ${svg}
24522
24578
  // GeoJSON is EPSG:4326 and reprojects unless given EPSG:4326 metadata.
24523
24579
  // Mapshaper output coords are already in target CRS, so suppress reprojection.
24524
24580
  var srs = getSourceSrsWithoutReprojection(featureDao && featureDao.srs, targetSrs);
24525
- for (var i = 0; i < features.length; i++) {
24526
- var feat = features[i];
24581
+ state = initLayerFeatureExportState(lyr, dataset, opts);
24582
+ var featureIndex = 0;
24583
+ for (var i = 0; i < state.count; i++) {
24584
+ var feat = exportFeatureAtIndex(state, i);
24585
+ if (!feat || !feat.geometry) continue;
24527
24586
  var normalized = normalizeFeature(feat, fields);
24528
24587
  try {
24529
24588
  await gpkg.addGeoJSONFeatureToGeoPackageWithFeatureDaoAndSrs(
@@ -24533,8 +24592,9 @@ ${svg}
24533
24592
  );
24534
24593
  } catch (e) {
24535
24594
  throw Error('GeoPackage insert failed at layer "' + tableName +
24536
- '", feature ' + i + ': ' + e.message);
24595
+ '", feature ' + featureIndex + ': ' + e.message);
24537
24596
  }
24597
+ featureIndex++;
24538
24598
  }
24539
24599
  }
24540
24600
 
@@ -24561,9 +24621,11 @@ ${svg}
24561
24621
  stop$1('Unable to export GeoPackage bytes');
24562
24622
  }
24563
24623
 
24564
- function inferFieldTypes(features) {
24624
+ function inferFieldTypesFromLayer(lyr, dataset, opts) {
24625
+ var sawFeature = false;
24565
24626
  var index = {};
24566
- features.forEach(function(feat) {
24627
+ forEachLayerExportFeature(lyr, dataset, opts, function(feat) {
24628
+ sawFeature = true;
24567
24629
  var props = feat.properties || {};
24568
24630
  Object.keys(props).forEach(function(name) {
24569
24631
  if (!isSupportedPropertyName(name)) return;
@@ -24577,11 +24639,79 @@ ${svg}
24577
24639
  }
24578
24640
  });
24579
24641
  });
24642
+ if (!sawFeature) return null;
24580
24643
  return Object.keys(index).map(function(name) {
24581
24644
  return {name: name, type: index[name]};
24582
24645
  });
24583
24646
  }
24584
24647
 
24648
+ function forEachLayerExportFeature(lyr, dataset, opts, cb) {
24649
+ var state = initLayerFeatureExportState(lyr, dataset, opts);
24650
+ var featureIndex = 0;
24651
+ for (var i = 0; i < state.count; i++) {
24652
+ var feature = exportFeatureAtIndex(state, i);
24653
+ if (!feature || !feature.geometry) continue;
24654
+ cb(feature, featureIndex++, i);
24655
+ }
24656
+ }
24657
+
24658
+ function initLayerFeatureExportState(lyr, dataset, opts) {
24659
+ var records = lyr.data ? lyr.data.getRecords() : null;
24660
+ var fields = lyr.data ? lyr.data.getFields() : [];
24661
+ var shapes = lyr.shapes || null;
24662
+ if (records && shapes && records.length !== shapes.length) {
24663
+ stop$1('Mismatch between number of properties and number of shapes');
24664
+ }
24665
+ return {
24666
+ lyr: lyr,
24667
+ dataset: dataset,
24668
+ opts: opts || {},
24669
+ fields: fields,
24670
+ records: records,
24671
+ shapes: shapes,
24672
+ count: Math.max(records ? records.length : 0, shapes ? shapes.length : 0),
24673
+ idField: getIdField(fields, opts || {}),
24674
+ useProps: useFeatureProperties(fields, opts || {})
24675
+ };
24676
+ }
24677
+
24678
+ function exportFeatureAtIndex(state, i) {
24679
+ var shape = state.shapes ? state.shapes[i] : null;
24680
+ var geom = shape ? GeoJSON.exporters[state.lyr.geometry_type](shape, state.dataset.arcs, state.opts) : null;
24681
+ var rec = state.records ? state.records[i] : null;
24682
+ var props = null;
24683
+ if (state.useProps && rec) {
24684
+ if (state.idField == GeoJSON.ID_FIELD) {
24685
+ props = Object.assign({}, rec);
24686
+ delete props[state.idField];
24687
+ } else {
24688
+ props = rec;
24689
+ }
24690
+ }
24691
+ var feat = GeoJSON.toFeature(geom, props);
24692
+ if (state.idField && rec) {
24693
+ feat.id = state.idField in rec ? rec[state.idField] : null;
24694
+ }
24695
+ if (state.opts.no_null_props && !feat.properties) {
24696
+ feat.properties = {};
24697
+ }
24698
+ if (Array.isArray(state.opts.hoist) && feat.properties) {
24699
+ feat.properties = Object.assign({}, feat.properties);
24700
+ state.opts.hoist.forEach(function(field) {
24701
+ if (Object.prototype.hasOwnProperty.call(feat.properties, field)) {
24702
+ feat[field] = feat.properties[field];
24703
+ delete feat.properties[field];
24704
+ }
24705
+ });
24706
+ }
24707
+ return feat;
24708
+ }
24709
+
24710
+ function useFeatureProperties(fields, opts) {
24711
+ return !(opts.drop_table || opts.cut_table || fields.length === 0 ||
24712
+ fields.length == 1 && fields[0] == GeoJSON.ID_FIELD);
24713
+ }
24714
+
24585
24715
  function inferValueType(value) {
24586
24716
  if (value === null || value === undefined) return null;
24587
24717
  if (value instanceof Date) return 'DATETIME';
@@ -24600,10 +24730,6 @@ ${svg}
24600
24730
  }
24601
24731
 
24602
24732
  function normalizeFeature(feature, fields) {
24603
- fields.reduce(function(memo, field) {
24604
- memo[field.name] = field.type;
24605
- return memo;
24606
- }, {});
24607
24733
  var props = {};
24608
24734
  fields.forEach(function(field) {
24609
24735
  var value = feature.properties && Object.prototype.hasOwnProperty.call(feature.properties, field.name) ?
@@ -24622,11 +24748,8 @@ ${svg}
24622
24748
 
24623
24749
  function getExportSrs(dataset) {
24624
24750
  var info = dataset.info || {};
24625
- var gpkgCrs = normalizeSrsForInsert(info.geopackage_crs);
24751
+ var gpkgCrs = resolveExportSrs(normalizeSrsForInsert(info.geopackage_crs), info.wkt1);
24626
24752
  if (gpkgCrs) {
24627
- if (!gpkgCrs.definition && info.wkt1) {
24628
- gpkgCrs.definition = info.wkt1;
24629
- }
24630
24753
  return gpkgCrs;
24631
24754
  }
24632
24755
  var parsed = parseCrsString(info.crs_string);
@@ -24642,13 +24765,83 @@ ${svg}
24642
24765
  if (info.wkt1) {
24643
24766
  return buildCustomSrsFromWkt(info.wkt1);
24644
24767
  }
24768
+ // If the dataset has an in-memory CRS object (e.g. set by -proj) but no
24769
+ // metadata source, derive a WKT1 definition from it so a projected CRS
24770
+ // isn't lost on round-trip.
24771
+ var derivedWkt = getWktFromDatasetCrs(dataset);
24772
+ if (derivedWkt) {
24773
+ return buildCustomSrsFromWkt(derivedWkt);
24774
+ }
24775
+ // Unknown CRS fallback:
24776
+ // - probable decimal-degree coordinates => allow WGS84 assumption
24777
+ // - otherwise use undefined cartesian CRS (null/unknown projection)
24778
+ if (probablyDecimalDegreeBounds(getDatasetBounds(dataset))) {
24779
+ return {
24780
+ srs_id: 4326,
24781
+ organization: 'EPSG',
24782
+ organization_coordsys_id: 4326
24783
+ };
24784
+ }
24645
24785
  return {
24646
- srs_id: 4326,
24647
- organization: 'EPSG',
24648
- organization_coordsys_id: 4326
24786
+ srs_id: -1,
24787
+ srs_name: 'Undefined Cartesian SRS',
24788
+ organization: 'NONE',
24789
+ organization_coordsys_id: -1
24649
24790
  };
24650
24791
  }
24651
24792
 
24793
+ function getWktFromDatasetCrs(dataset) {
24794
+ // Only derive from an explicitly-set CRS object (e.g. from -proj).
24795
+ // This avoids building a custom SRS for lat/lon datasets that were
24796
+ // auto-defaulted to WGS84 by getDatasetCRS().
24797
+ var info = dataset && dataset.info;
24798
+ if (!info || !info.crs) return null;
24799
+ try {
24800
+ return crsToPrj(info.crs) || null;
24801
+ } catch (e) {
24802
+ return null;
24803
+ }
24804
+ }
24805
+
24806
+ function resolveExportSrs(srs, fallbackWkt) {
24807
+ if (!srs) return null;
24808
+ var out = Object.assign({}, srs);
24809
+ if (!out.definition && fallbackWkt) {
24810
+ out.definition = fallbackWkt;
24811
+ }
24812
+ if (out.srs_id == null && out.organization_coordsys_id != null) {
24813
+ out.srs_id = out.organization_coordsys_id;
24814
+ }
24815
+ if (out.srs_id == null) {
24816
+ var fromWkt = parseWktAuthority(out.definition);
24817
+ if (fromWkt) {
24818
+ out.srs_id = fromWkt.srs_id;
24819
+ if (!out.organization) out.organization = fromWkt.organization;
24820
+ if (out.organization_coordsys_id == null) {
24821
+ out.organization_coordsys_id = fromWkt.organization_coordsys_id;
24822
+ }
24823
+ }
24824
+ }
24825
+ if (out.srs_id == null && out.definition) {
24826
+ var custom = buildCustomSrsFromWkt(out.definition);
24827
+ out.srs_id = custom.srs_id;
24828
+ if (!out.srs_name) out.srs_name = custom.srs_name;
24829
+ if (!out.organization) out.organization = custom.organization;
24830
+ if (out.organization_coordsys_id == null) {
24831
+ out.organization_coordsys_id = custom.organization_coordsys_id;
24832
+ }
24833
+ if (!out.description) out.description = custom.description;
24834
+ }
24835
+ if (out.srs_id == null) return null;
24836
+ if (out.organization_coordsys_id == null) {
24837
+ out.organization_coordsys_id = out.srs_id;
24838
+ }
24839
+ if (!out.organization) {
24840
+ out.organization = 'NONE';
24841
+ }
24842
+ return out;
24843
+ }
24844
+
24652
24845
  function parseCrsString(str) {
24653
24846
  if (!str || typeof str != 'string') return null;
24654
24847
  var match = str.match(/^([a-z]+):(\d+)$/i);
@@ -24740,9 +24933,10 @@ ${svg}
24740
24933
  var geopackage = require$1('@ngageoint/geopackage');
24741
24934
  var spatialReferenceSystem = new geopackage.SpatialReferenceSystem();
24742
24935
  spatialReferenceSystem.srs_id = srs.srs_id;
24743
- spatialReferenceSystem.srs_name = srs.srs_name || (srs.organization + ':' + srs.organization_coordsys_id);
24744
- spatialReferenceSystem.organization = srs.organization;
24745
- spatialReferenceSystem.organization_coordsys_id = srs.organization_coordsys_id;
24936
+ spatialReferenceSystem.srs_name = srs.srs_name || (String(srs.organization || 'NONE') + ':' + srs.organization_coordsys_id);
24937
+ spatialReferenceSystem.organization = srs.organization || 'NONE';
24938
+ spatialReferenceSystem.organization_coordsys_id = srs.organization_coordsys_id != null ?
24939
+ srs.organization_coordsys_id : srs.srs_id;
24746
24940
  spatialReferenceSystem.definition = srs.definition;
24747
24941
  spatialReferenceSystem.description = srs.description || null;
24748
24942
  gpkg.createSpatialReferenceSystem(spatialReferenceSystem);
@@ -24751,7 +24945,9 @@ ${svg}
24751
24945
  function getSourceSrsWithoutReprojection(tableSrs, fallbackSrs) {
24752
24946
  var target = normalizeSrsForInsert(tableSrs) || normalizeSrsForInsert(fallbackSrs);
24753
24947
  return {
24754
- srs_id: target.srs_id,
24948
+ // geopackage writer rejects negative srs_id for source metadata;
24949
+ // use 0 (undefined geographic) when target is undefined cartesian (-1).
24950
+ srs_id: target.srs_id < 0 ? 0 : target.srs_id,
24755
24951
  organization: 'EPSG',
24756
24952
  organization_coordsys_id: 4326
24757
24953
  };
@@ -24828,18 +25024,21 @@ ${svg}
24828
25024
  if (normalized.organization_coordsys_id == null && normalized.organizationCoordsysId != null) {
24829
25025
  normalized.organization_coordsys_id = normalized.organizationCoordsysId;
24830
25026
  }
24831
- if (normalized.srs_id == null) {
24832
- normalized.srs_id = normalized.organization_coordsys_id || 4326;
25027
+ if (normalized.srs_id != null) {
25028
+ normalized.srs_id = +normalized.srs_id;
25029
+ if (!Number.isFinite(normalized.srs_id)) {
25030
+ normalized.srs_id = null;
25031
+ }
24833
25032
  }
24834
- normalized.srs_id = +normalized.srs_id || 4326;
24835
- if (!normalized.organization) {
24836
- normalized.organization = 'EPSG';
25033
+ if (normalized.organization) {
25034
+ normalized.organization = String(normalized.organization).toUpperCase();
24837
25035
  }
24838
- normalized.organization = String(normalized.organization).toUpperCase();
24839
- if (normalized.organization_coordsys_id == null) {
24840
- normalized.organization_coordsys_id = normalized.srs_id || 4326;
25036
+ if (normalized.organization_coordsys_id != null) {
25037
+ normalized.organization_coordsys_id = +normalized.organization_coordsys_id;
25038
+ if (!Number.isFinite(normalized.organization_coordsys_id)) {
25039
+ normalized.organization_coordsys_id = null;
25040
+ }
24841
25041
  }
24842
- normalized.organization_coordsys_id = +normalized.organization_coordsys_id || 4326;
24843
25042
  return normalized;
24844
25043
  }
24845
25044
 
@@ -26869,6 +27068,10 @@ ${svg}
26869
27068
  .option('name', {
26870
27069
  describe: 'rename the imported layer(s)'
26871
27070
  })
27071
+ .option('layers', {
27072
+ type: 'strings',
27073
+ describe: '[GPKG] comma-sep. list of layers to import'
27074
+ })
26872
27075
  .option('geometry-type', {
26873
27076
  // undocumented; GeoJSON import rejects all but one kind of geometry
26874
27077
  // describe: '[GeoJSON] Import one kind of geometry (point|polygon|polyline)'
@@ -31463,24 +31666,35 @@ ${svg}
31463
31666
  var geopackage = require$1('@ngageoint/geopackage');
31464
31667
  var gpkg;
31465
31668
  var datasets;
31466
- var source;
31467
31669
  var tmpPath = null;
31468
31670
 
31469
31671
  if (!geopackage || !geopackage.GeoPackageAPI) {
31470
31672
  stop$1('GeoPackage library is not loaded');
31471
31673
  }
31472
31674
 
31473
- if (utils.isString(content)) {
31474
- source = content;
31475
- } else if (!runningInBrowser()) {
31476
- tmpPath = writeGeoPackageTempFile(content);
31477
- source = tmpPath;
31478
- } else {
31479
- source = new Uint8Array(content);
31480
- }
31481
- gpkg = await geopackage.GeoPackageAPI.open(source);
31675
+ ({gpkg, tmpPath} = await openGeoPackage(content, geopackage));
31676
+ var availableLayers;
31677
+ var filterApplied;
31482
31678
  try {
31483
- datasets = readFeatureTableDatasets(gpkg, opts);
31679
+ try {
31680
+ ({datasets, availableLayers, filterApplied} = readFeatureTableDatasets(gpkg, opts));
31681
+ } catch (e) {
31682
+ if (!runningInBrowser() && isLocalCsProjError(e)) {
31683
+ gpkg.close();
31684
+ if (tmpPath) {
31685
+ sanitizeGeoPackageCrsMetadata(tmpPath);
31686
+ } else if (utils.isString(content)) {
31687
+ tmpPath = copyGeoPackageTempFile(content);
31688
+ sanitizeGeoPackageCrsMetadata(tmpPath);
31689
+ } else {
31690
+ throw e;
31691
+ }
31692
+ gpkg = await geopackage.GeoPackageAPI.open(tmpPath);
31693
+ ({datasets, availableLayers, filterApplied} = readFeatureTableDatasets(gpkg, opts));
31694
+ } else {
31695
+ throw e;
31696
+ }
31697
+ }
31484
31698
  } finally {
31485
31699
  gpkg.close();
31486
31700
  removeTempGeoPackageFile(tmpPath);
@@ -31489,13 +31703,72 @@ ${svg}
31489
31703
  if (datasets.length === 0) {
31490
31704
  return {
31491
31705
  layers: [{name: '', data: null}],
31492
- info: {}
31706
+ info: {
31707
+ _gpkg_available_layers: availableLayers,
31708
+ _gpkg_placeholder: !!filterApplied
31709
+ }
31493
31710
  };
31494
31711
  }
31495
31712
 
31496
31713
  await initProjLib(datasets);
31714
+ var merged = mergeGeoPackageDatasets(datasets);
31715
+ var mergedArr = Array.isArray(merged) ? merged : [merged];
31716
+ mergedArr.forEach(function(ds) {
31717
+ ds.info = ds.info || {};
31718
+ ds.info._gpkg_available_layers = availableLayers;
31719
+ });
31720
+ return merged;
31721
+ }
31497
31722
 
31498
- return mergeDatasets(datasets);
31723
+ async function openGeoPackage(content, geopackage) {
31724
+ var source;
31725
+ var tmpPath = null;
31726
+ if (utils.isString(content)) {
31727
+ if (!runningInBrowser()) {
31728
+ try {
31729
+ return {
31730
+ gpkg: await geopackage.GeoPackageAPI.open(content),
31731
+ tmpPath: null
31732
+ };
31733
+ } catch (e) {
31734
+ if (!isLocalCsProjError(e)) throw e;
31735
+ tmpPath = copyGeoPackageTempFile(content);
31736
+ sanitizeGeoPackageCrsMetadata(tmpPath);
31737
+ return {
31738
+ gpkg: await geopackage.GeoPackageAPI.open(tmpPath),
31739
+ tmpPath: tmpPath
31740
+ };
31741
+ }
31742
+ }
31743
+ source = content;
31744
+ } else if (!runningInBrowser()) {
31745
+ tmpPath = writeGeoPackageTempFile(content);
31746
+ try {
31747
+ return {
31748
+ gpkg: await geopackage.GeoPackageAPI.open(tmpPath),
31749
+ tmpPath: tmpPath
31750
+ };
31751
+ } catch (e) {
31752
+ if (!isLocalCsProjError(e)) throw e;
31753
+ sanitizeGeoPackageCrsMetadata(tmpPath);
31754
+ return {
31755
+ gpkg: await geopackage.GeoPackageAPI.open(tmpPath),
31756
+ tmpPath: tmpPath
31757
+ };
31758
+ }
31759
+ } else {
31760
+ source = new Uint8Array(content);
31761
+ }
31762
+ return {
31763
+ gpkg: await geopackage.GeoPackageAPI.open(source),
31764
+ tmpPath: null
31765
+ };
31766
+ }
31767
+
31768
+ function isLocalCsProjError(err) {
31769
+ var msg = err && err.message ? String(err.message) : '';
31770
+ return msg.includes("havn't handled \"_\" in keyword yet") ||
31771
+ msg.includes('LOCAL_CS');
31499
31772
  }
31500
31773
 
31501
31774
  function writeGeoPackageTempFile(content) {
@@ -31504,10 +31777,40 @@ ${svg}
31504
31777
  var path = require$1('path');
31505
31778
  var unique = Date.now() + '-' + process.pid + '-' + Math.random().toString(36).slice(2);
31506
31779
  var tmpPath = path.join(os.tmpdir(), 'mapshaper-gpkg-import-' + unique + '.gpkg');
31507
- fs.writeFileSync(tmpPath, Buffer.from(new Uint8Array(content)));
31780
+ if (Buffer.isBuffer(content)) {
31781
+ fs.writeFileSync(tmpPath, content);
31782
+ } else if (content instanceof Uint8Array) {
31783
+ fs.writeFileSync(tmpPath, Buffer.from(content.buffer, content.byteOffset, content.byteLength));
31784
+ } else if (content instanceof ArrayBuffer) {
31785
+ fs.writeFileSync(tmpPath, Buffer.from(content));
31786
+ } else {
31787
+ fs.writeFileSync(tmpPath, Buffer.from(new Uint8Array(content)));
31788
+ }
31789
+ return tmpPath;
31790
+ }
31791
+
31792
+ function copyGeoPackageTempFile(filepath) {
31793
+ var fs = require$1('fs');
31794
+ var os = require$1('os');
31795
+ var path = require$1('path');
31796
+ var unique = Date.now() + '-' + process.pid + '-' + Math.random().toString(36).slice(2);
31797
+ var tmpPath = path.join(os.tmpdir(), 'mapshaper-gpkg-import-' + unique + '.gpkg');
31798
+ fs.copyFileSync(filepath, tmpPath);
31508
31799
  return tmpPath;
31509
31800
  }
31510
31801
 
31802
+ function sanitizeGeoPackageCrsMetadata(filepath) {
31803
+ var Database = require$1('better-sqlite3');
31804
+ var db = new Database(filepath);
31805
+ try {
31806
+ // GDAL may write LOCAL_CS definitions that proj4 can't parse.
31807
+ // Normalize to 'undefined' so GeoPackageAPI.open() won't throw.
31808
+ db.prepare("UPDATE gpkg_spatial_ref_sys SET definition = 'undefined' WHERE definition LIKE 'LOCAL_CS[%'").run();
31809
+ } finally {
31810
+ db.close();
31811
+ }
31812
+ }
31813
+
31511
31814
  function removeTempGeoPackageFile(filepath) {
31512
31815
  if (!filepath) return;
31513
31816
  var fs = require$1('fs');
@@ -31526,13 +31829,207 @@ ${svg}
31526
31829
  }
31527
31830
 
31528
31831
  function readFeatureTableDatasets(gpkg, opts) {
31529
- var tables = gpkg.getFeatureTables() || [];
31530
- return tables.map(function(table) {
31832
+ var selected = getSelectedGeoPackageLayers(opts);
31833
+ var availableLayers = gpkg.getFeatureTables() || [];
31834
+ var tables = filterGeoPackageTables(availableLayers, selected);
31835
+ var filterApplied = Array.isArray(selected) && selected.length > 0;
31836
+ var datasets = tables.map(function(table) {
31531
31837
  return readFeatureTable(gpkg, table, opts);
31532
31838
  });
31839
+ return {datasets: datasets, availableLayers: availableLayers, filterApplied: filterApplied};
31840
+ }
31841
+
31842
+ function getSelectedGeoPackageLayers(opts) {
31843
+ if (!opts) return null;
31844
+ return opts.gpkg_layers || opts.layers || null;
31845
+ }
31846
+
31847
+ function filterGeoPackageTables(tables, selected) {
31848
+ if (!Array.isArray(selected) || selected.length === 0) return tables;
31849
+ var index = selected.reduce(function(memo, name) {
31850
+ memo[name] = true;
31851
+ return memo;
31852
+ }, {});
31853
+ return tables.filter(function(name) {
31854
+ return !!index[name];
31855
+ });
31856
+ }
31857
+
31858
+ function mergeGeoPackageDatasets(datasets) {
31859
+ var groups = groupGeoPackageDatasets(datasets);
31860
+ var merged = groups.reduce(function(memo, group) {
31861
+ return memo.concat(mergeGeoPackageDatasetGroup(group));
31862
+ }, []);
31863
+ return merged.length == 1 ? merged[0] : merged;
31864
+ }
31865
+
31866
+ function mergeGeoPackageDatasetGroup(group) {
31867
+ var pathDatasets = [];
31868
+ var pointDatasets = [];
31869
+ var dataDatasets = [];
31870
+
31871
+ group.forEach(function(dataset) {
31872
+ var kind = getDatasetGeometryKind(dataset);
31873
+ if (kind == 'path') {
31874
+ pathDatasets.push(dataset);
31875
+ } else if (kind == 'point') {
31876
+ pointDatasets.push(dataset);
31877
+ } else {
31878
+ dataDatasets.push(dataset);
31879
+ }
31880
+ });
31881
+
31882
+ var output = [];
31883
+ if (dataDatasets.length > 0) {
31884
+ output.push(dataDatasets.length == 1 ? dataDatasets[0] : mergeDatasets(dataDatasets));
31885
+ }
31886
+ if (pointDatasets.length > 0) {
31887
+ output.push(pointDatasets.length == 1 ? pointDatasets[0] : mergeDatasets(pointDatasets));
31888
+ }
31889
+ if (pathDatasets.length > 0) {
31890
+ var groupedPaths = groupPathDatasetsBySharedArcs(pathDatasets);
31891
+ if (groupedPaths.mergedDataset) {
31892
+ output.push(groupedPaths.mergedDataset);
31893
+ } else {
31894
+ groupedPaths.components.forEach(function(component) {
31895
+ output.push(component.length == 1 ? component[0] : mergeDatasets(component));
31896
+ });
31897
+ }
31898
+ }
31899
+ return output;
31900
+ }
31901
+
31902
+ function groupGeoPackageDatasets(datasets) {
31903
+ var index = {};
31904
+ datasets.forEach(function(dataset) {
31905
+ var key = getGeoPackageDatasetGroupKey(dataset);
31906
+ if (!index[key]) index[key] = [];
31907
+ index[key].push(dataset);
31908
+ });
31909
+ return Object.keys(index).map(function(key) {
31910
+ return index[key];
31911
+ });
31912
+ }
31913
+
31914
+ function getGeoPackageDatasetGroupKey(dataset) {
31915
+ var info = dataset.info || {};
31916
+ var crs = info.geopackage_crs || null;
31917
+ if (isDataOnlyDataset(dataset)) {
31918
+ return 'data-only';
31919
+ }
31920
+ if (crs) {
31921
+ var org = normalizeCrsOrg(crs.organization);
31922
+ var code = normalizeNumericCode(crs.organization_coordsys_id);
31923
+ if (isTrustedCrsAuthority(org) && code !== null && !(org == 'NONE' && code <= 0)) {
31924
+ return 'crs:' + org + ':' + code;
31925
+ }
31926
+ if (isUsableWkt1Definition(crs.definition)) {
31927
+ return 'wkt:' + crs.definition;
31928
+ }
31929
+ }
31930
+ return probablyDecimalDegreeBounds(getDatasetBounds(dataset)) ?
31931
+ 'unknown:unprojected' :
31932
+ 'unknown:projected';
31933
+ }
31934
+
31935
+ function isDataOnlyDataset(dataset) {
31936
+ return (dataset.layers || []).every(function(lyr) {
31937
+ return !lyr.geometry_type;
31938
+ });
31939
+ }
31940
+
31941
+ function getDatasetGeometryKind(dataset) {
31942
+ if (isDataOnlyDataset(dataset)) return 'data';
31943
+ if (datasetHasPaths(dataset)) return 'path';
31944
+ var hasPoints = (dataset.layers || []).some(function(lyr) {
31945
+ return layerHasPoints(lyr);
31946
+ });
31947
+ return hasPoints ? 'point' : 'data';
31948
+ }
31949
+
31950
+ function groupPathDatasetsBySharedArcs(datasets) {
31951
+ if (datasets.length < 2) {
31952
+ return {
31953
+ mergedDataset: datasets[0] || null,
31954
+ components: []
31955
+ };
31956
+ }
31957
+ var copy = datasets.map(copyDatasetForExport);
31958
+ var merged = mergeDatasets(copy);
31959
+ if (!merged.arcs || merged.arcs.size() === 0) {
31960
+ return {
31961
+ mergedDataset: null,
31962
+ components: datasets.map(function(dataset) { return [dataset]; })
31963
+ };
31964
+ }
31965
+ buildTopology(merged);
31966
+ var parent = datasets.map(function(_, i) { return i; });
31967
+ var arcOwner = new Map();
31968
+ merged.layers.forEach(function(layer, layerIdx) {
31969
+ var seen = new Set();
31970
+ forEachArcId(layer.shapes || [], function(arcId) {
31971
+ seen.add(arcId < 0 ? ~arcId : arcId);
31972
+ });
31973
+ seen.forEach(function(absId) {
31974
+ if (!arcOwner.has(absId)) {
31975
+ arcOwner.set(absId, layerIdx);
31976
+ } else {
31977
+ union(parent, layerIdx, arcOwner.get(absId));
31978
+ }
31979
+ });
31980
+ });
31981
+ var components = {};
31982
+ datasets.forEach(function(dataset, i) {
31983
+ var root = find(parent, i);
31984
+ if (!components[root]) components[root] = [];
31985
+ components[root].push(dataset);
31986
+ });
31987
+ var grouped = Object.keys(components).map(function(key) {
31988
+ return components[key];
31989
+ });
31990
+ if (grouped.length == 1) {
31991
+ return {
31992
+ mergedDataset: merged,
31993
+ components: []
31994
+ };
31995
+ }
31996
+ return {
31997
+ mergedDataset: null,
31998
+ components: grouped
31999
+ };
32000
+ }
32001
+
32002
+ function find(parent, i) {
32003
+ var p = parent[i];
32004
+ if (p !== i) {
32005
+ parent[i] = find(parent, p);
32006
+ }
32007
+ return parent[i];
31533
32008
  }
31534
32009
 
31535
- function convertOrgToProj4(crs) {
32010
+ function union(parent, a, b) {
32011
+ var ra = find(parent, a);
32012
+ var rb = find(parent, b);
32013
+ if (ra !== rb) {
32014
+ parent[rb] = ra;
32015
+ }
32016
+ }
32017
+
32018
+ function normalizeNumericCode(val) {
32019
+ var n = +val;
32020
+ return Number.isFinite(n) ? n : null;
32021
+ }
32022
+
32023
+ function normalizeCrsOrg(org) {
32024
+ if (!org || org == 'undefined') return null;
32025
+ return String(org).toUpperCase();
32026
+ }
32027
+
32028
+ function isTrustedCrsAuthority(org) {
32029
+ return org == 'EPSG' || org == 'ESRI' || org == 'NONE';
32030
+ }
32031
+
32032
+ function convertOrgToProjString(crs) {
31536
32033
  var org = crs.organization.toLowerCase();
31537
32034
  if (org == 'epsg' || org == 'esri') {
31538
32035
  return org + ':' + crs.organization_coordsys_id;
@@ -31560,14 +32057,25 @@ ${svg}
31560
32057
  });
31561
32058
  dataset.info = dataset.info || {};
31562
32059
  dataset.info.geopackage_crs = crs;
31563
- if (crs?.definition && crs.definition !== 'undefined') {
32060
+ if (isUsableWkt1Definition(crs?.definition)) {
31564
32061
  dataset.info.wkt1 = crs.definition;
31565
32062
  } else if (crs?.organization && crs.organization !== 'undefined') {
31566
- dataset.info.crs_string = convertOrgToProj4(crs);
32063
+ var crsString = convertOrgToProjString(crs);
32064
+ if (crsString) {
32065
+ dataset.info.crs_string = crsString;
32066
+ }
31567
32067
  }
31568
32068
  return dataset;
31569
32069
  }
31570
32070
 
32071
+ function isUsableWkt1Definition(defn) {
32072
+ if (!defn || defn === 'undefined') return false;
32073
+ // GDAL may emit LOCAL_CS["Undefined SRS", ...] for null CRS;
32074
+ // this is not a usable projected/geographic CRS for mapshaper.
32075
+ if (/^\s*LOCAL_CS\[/i.test(defn)) return false;
32076
+ return true;
32077
+ }
32078
+
31571
32079
  function convertFeatureRow(featureRow) {
31572
32080
  var feature = {
31573
32081
  type: 'Feature',
@@ -31583,8 +32091,7 @@ ${svg}
31583
32091
  // skip feature if geometry can't be parsed
31584
32092
  return null;
31585
32093
  }
31586
- if (!feature.geometry) return null;
31587
- var geomColName = featureRow.geometryColumn.name;
32094
+ var geomColName = featureRow.geometryColumn && featureRow.geometryColumn.name;
31588
32095
  for (var key in featureRow.values) {
31589
32096
  if (Object.prototype.hasOwnProperty.call(featureRow.values, key) &&
31590
32097
  key !== geomColName) {
@@ -32363,6 +32870,11 @@ ${svg}
32363
32870
  } else {
32364
32871
  return importContent(obj, opts);
32365
32872
  }
32873
+ if (Array.isArray(dataset)) {
32874
+ return dataset.map(function(ds) {
32875
+ return finalizeImportedDataset(ds, dataFmt, data, opts);
32876
+ });
32877
+ }
32366
32878
  return finalizeImportedDataset(dataset, dataFmt, data, opts);
32367
32879
  }
32368
32880
 
@@ -32564,12 +33076,18 @@ ${svg}
32564
33076
 
32565
33077
  cmd.importFiles = async function(catalog, opts) {
32566
33078
  var files = opts.files || [];
32567
- var dataset;
33079
+ var dataset, datasets, target;
32568
33080
 
32569
33081
  if (opts.stdin) {
32570
33082
  dataset = await importFileAsync('/dev/stdin', opts);
32571
- catalog.addDataset(dataset);
32572
- return dataset;
33083
+ datasets = normalizeImportedDatasets(dataset);
33084
+ catalog.addDatasets(datasets);
33085
+ if (datasets.length > 1) {
33086
+ catalog.setDefaultTargets(datasets.map(function(ds) {
33087
+ return {dataset: ds, layers: ds.layers};
33088
+ }));
33089
+ }
33090
+ return normalizeImportedTarget(datasets);
32573
33091
  }
32574
33092
 
32575
33093
  if (files.length > 0 === false) {
@@ -32604,14 +33122,22 @@ ${svg}
32604
33122
  } else {
32605
33123
  dataset = await importFilesTogetherAsync(files, opts);
32606
33124
  }
33125
+ datasets = normalizeImportedDatasets(dataset);
33126
+ datasets = validateAndCleanGpkgSelection(datasets, opts);
32607
33127
 
32608
33128
  if (opts.merge_files && files.length > 1) {
32609
33129
  // TODO: deprecate and remove this option (use -merge-layers cmd instead)
32610
- dataset.layers = cmd.mergeLayers(dataset.layers);
33130
+ datasets[0].layers = cmd.mergeLayers(datasets[0].layers);
32611
33131
  }
32612
33132
 
32613
- catalog.addDataset(dataset);
32614
- return dataset;
33133
+ catalog.addDatasets(datasets);
33134
+ if (datasets.length > 1) {
33135
+ catalog.setDefaultTargets(datasets.map(function(ds) {
33136
+ return {dataset: ds, layers: ds.layers};
33137
+ }));
33138
+ }
33139
+ target = normalizeImportedTarget(datasets);
33140
+ return target;
32615
33141
  };
32616
33142
 
32617
33143
  // replace any JSON data objects with filenames and cache the data
@@ -32712,7 +33238,8 @@ ${svg}
32712
33238
 
32713
33239
  cli.checkFileExists(path, cache);
32714
33240
 
32715
- if ((fileType == 'shp' || fileType == 'json' || fileType == 'text' || fileType == 'dbf') && !cached) {
33241
+ if ((fileType == 'shp' || fileType == 'json' || fileType == 'text' || fileType == 'dbf' ||
33242
+ fileType == 'gpkg') && !cached) {
32716
33243
  // these file types are read incrementally
32717
33244
  content = null;
32718
33245
 
@@ -32815,17 +33342,20 @@ ${svg}
32815
33342
  // Import multiple files to a single dataset
32816
33343
  function importFilesTogether(files, opts) {
32817
33344
  var unbuiltTopology = false;
32818
- var datasets = files.map(function(fname) {
33345
+ var datasets = files.reduce(function(memo, fname) {
32819
33346
  // import without topology or snapping
32820
33347
  var importOpts = utils.defaults({no_topology: true, snap: false, snap_interval: null, files: [fname]}, opts);
32821
- var dataset = importFile(fname, importOpts);
33348
+ var imported = normalizeImportedDatasets(importFile(fname, importOpts));
32822
33349
  // check if dataset contains non-topological paths
32823
33350
  // TODO: may also need to rebuild topology if multiple topojson files are merged
32824
- if (dataset.arcs && dataset.arcs.size() > 0 && dataset.info.input_formats[0] != 'topojson') {
32825
- unbuiltTopology = true;
32826
- }
32827
- return dataset;
32828
- });
33351
+ imported.forEach(function(dataset) {
33352
+ if (dataset.arcs && dataset.arcs.size() > 0 && dataset.info.input_formats[0] != 'topojson') {
33353
+ unbuiltTopology = true;
33354
+ }
33355
+ memo.push(dataset);
33356
+ });
33357
+ return memo;
33358
+ }, []);
32829
33359
  var combined = mergeDatasets(datasets);
32830
33360
  // Build topology, if needed
32831
33361
  // TODO: consider updating topology of TopoJSON files instead of concatenating arcs
@@ -32843,12 +33373,15 @@ ${svg}
32843
33373
  for (var fname of files) {
32844
33374
  // import without topology or snapping
32845
33375
  var importOpts = utils.defaults({no_topology: true, snap: false, snap_interval: null, files: [fname]}, opts);
32846
- var dataset = await importFileAsync(fname, importOpts);
32847
- if (dataset.arcs && dataset.arcs.size() > 0 && dataset.info.input_formats[0] != 'topojson') {
32848
- unbuiltTopology = true;
32849
- }
32850
- datasets.push(dataset);
33376
+ var imported = normalizeImportedDatasets(await importFileAsync(fname, importOpts));
33377
+ imported.forEach(function(dataset) {
33378
+ if (dataset.arcs && dataset.arcs.size() > 0 && dataset.info.input_formats[0] != 'topojson') {
33379
+ unbuiltTopology = true;
33380
+ }
33381
+ datasets.push(dataset);
33382
+ });
32851
33383
  }
33384
+ datasets = validateAndCleanGpkgSelection(datasets, opts);
32852
33385
  var combined = mergeDatasets(datasets);
32853
33386
  if (unbuiltTopology && !opts.no_topology) {
32854
33387
  cleanPathsAfterImport(combined, opts);
@@ -32857,6 +33390,60 @@ ${svg}
32857
33390
  return combined;
32858
33391
  }
32859
33392
 
33393
+ // Validate the GeoPackage layers= selection across all imported datasets,
33394
+ // remove any placeholder datasets produced by filter misses, and strip the
33395
+ // bookkeeping metadata attached by the GeoPackage importer.
33396
+ function validateAndCleanGpkgSelection(datasets, opts) {
33397
+ var availableSet = new Set();
33398
+ var importedSet = new Set();
33399
+ var sawGpkg = false;
33400
+ datasets.forEach(function(ds) {
33401
+ var info = ds && ds.info;
33402
+ if (!info || !Array.isArray(info._gpkg_available_layers)) return;
33403
+ sawGpkg = true;
33404
+ info._gpkg_available_layers.forEach(function(name) { availableSet.add(name); });
33405
+ if (!info._gpkg_placeholder) {
33406
+ (ds.layers || []).forEach(function(lyr) {
33407
+ if (lyr && lyr.name) importedSet.add(lyr.name);
33408
+ });
33409
+ }
33410
+ });
33411
+ if (sawGpkg && Array.isArray(opts.layers) && opts.layers.length > 0) {
33412
+ var missing = opts.layers.filter(function(name) {
33413
+ return !importedSet.has(name);
33414
+ });
33415
+ if (missing.length > 0) {
33416
+ stop$1(
33417
+ 'Missing GeoPackage layer(s): ' + missing.join(', ') + '\n' +
33418
+ 'Existing layers: ' + Array.from(availableSet).join(' ')
33419
+ );
33420
+ }
33421
+ }
33422
+ var cleaned = datasets.filter(function(ds) {
33423
+ return !(ds && ds.info && ds.info._gpkg_placeholder);
33424
+ });
33425
+ cleaned.forEach(function(ds) {
33426
+ if (ds && ds.info) {
33427
+ delete ds.info._gpkg_available_layers;
33428
+ delete ds.info._gpkg_placeholder;
33429
+ }
33430
+ });
33431
+ return cleaned;
33432
+ }
33433
+
33434
+ function normalizeImportedDatasets(datasetOrArray) {
33435
+ return Array.isArray(datasetOrArray) ? datasetOrArray : [datasetOrArray];
33436
+ }
33437
+
33438
+ function normalizeImportedTarget(datasets) {
33439
+ if (datasets.length == 1) return datasets[0];
33440
+ return {
33441
+ layers: datasets.reduce(function(memo, dataset) {
33442
+ return memo.concat(dataset.layers);
33443
+ }, [])
33444
+ };
33445
+ }
33446
+
32860
33447
  function getUnsupportedFileMessage(path) {
32861
33448
  var ext = getFileExtension(path);
32862
33449
  var msg = 'Unable to import ' + path;
@@ -50192,7 +50779,7 @@ ${svg}
50192
50779
  });
50193
50780
  }
50194
50781
 
50195
- var version = "0.6.117";
50782
+ var version = "0.6.118";
50196
50783
 
50197
50784
  // Parse command line args into commands and run them
50198
50785
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.