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 +1784 -276
- package/package.json +2 -2
- package/www/mapshaper-gui.js +122 -19
- package/www/mapshaper.js +1784 -276
- package/www/modules.js +122 -10
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
7171
|
-
Math.asin(clamp$2(p[2], -1, 1)) * R2D$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
7439
|
-
Math.asin(Math.max(-1, Math.min(1, p[2]))) * R2D$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
8378
|
-
Math.asin(Math.max(-1, Math.min(1, p[2]))) * R2D$
|
|
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$
|
|
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$
|
|
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$
|
|
8581
|
-
q[1] * R2D$
|
|
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$
|
|
9169
|
-
p[1] * R2D$
|
|
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$
|
|
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$
|
|
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$
|
|
9510
|
-
[-60, -ASIN_ONE_THIRD * R2D$
|
|
9511
|
-
[60, -ASIN_ONE_THIRD * R2D$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
22619
|
-
|
|
22620
|
-
|
|
22621
|
-
|
|
22622
|
-
|
|
22623
|
-
|
|
22624
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22648
|
-
|
|
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
|
-
|
|
31646
|
-
|
|
31647
|
-
|
|
31648
|
-
|
|
31649
|
-
|
|
31650
|
-
|
|
31651
|
-
|
|
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
|
-
|
|
31657
|
-
}
|
|
31658
|
-
|
|
31659
|
-
|
|
31660
|
-
|
|
31661
|
-
|
|
31662
|
-
|
|
31663
|
-
|
|
31664
|
-
|
|
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
|
|
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(
|
|
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:
|
|
32689
|
+
content: writeLayer(writer, lyr, dataset, opts, compression, rowGroupOverride)
|
|
32505
32690
|
});
|
|
32506
32691
|
});
|
|
32507
32692
|
return files;
|
|
32508
32693
|
}
|
|
32509
32694
|
|
|
32510
|
-
|
|
32511
|
-
|
|
32512
|
-
|
|
32513
|
-
|
|
32514
|
-
|
|
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
|
-
|
|
32518
|
-
|
|
32519
|
-
|
|
32520
|
-
|
|
32521
|
-
|
|
32522
|
-
|
|
32523
|
-
|
|
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
|
-
|
|
32527
|
-
|
|
32528
|
-
|
|
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
|
-
|
|
32533
|
-
|
|
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
|
|
32539
|
-
var
|
|
32540
|
-
|
|
32541
|
-
|
|
32542
|
-
|
|
32543
|
-
|
|
32544
|
-
|
|
32545
|
-
|
|
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
|
|
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 <
|
|
32552
|
-
var
|
|
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
|
-
|
|
32562
|
-
break;
|
|
32859
|
+
return 'STRING';
|
|
32563
32860
|
}
|
|
32564
32861
|
}
|
|
32565
32862
|
}
|
|
32566
|
-
return
|
|
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(
|
|
32904
|
+
function getPropertyNames(properties, opts) {
|
|
32608
32905
|
var index = {};
|
|
32609
|
-
|
|
32610
|
-
|
|
32611
|
-
|
|
32612
|
-
|
|
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
|
-
|
|
32619
|
-
|
|
32620
|
-
|
|
32621
|
-
|
|
32622
|
-
|
|
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
|
|
33130
|
+
if (mod && mod.default && !isGeoParquetWriter(mod)) {
|
|
32678
33131
|
mod = mod.default;
|
|
32679
33132
|
}
|
|
32680
|
-
if (!mod
|
|
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
|
|
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, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
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' &&
|
|
32880
|
-
error('The
|
|
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]
|
|
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
|
|
38922
|
-
xx = new Float64Array(
|
|
38923
|
-
yy = new Float64Array(
|
|
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
|
-
|
|
41343
|
-
|
|
41344
|
-
|
|
41345
|
-
|
|
41346
|
-
|
|
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
|
-
|
|
41740
|
-
|
|
41741
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
41780
|
-
if (
|
|
41781
|
-
if (
|
|
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
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
55981
|
-
p[1] *= R2D$
|
|
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,
|
|
62381
|
-
//
|
|
62382
|
-
//
|
|
62383
|
-
//
|
|
62384
|
-
//
|
|
62385
|
-
//
|
|
62386
|
-
|
|
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
|
|
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
|
-
|
|
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 <
|
|
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) + ' (
|
|
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.
|
|
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,
|