mapshaper 0.7.49 → 0.7.51
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 +1996 -281
- package/package.json +3 -3
- package/www/mapshaper-gui.js +196 -30
- package/www/mapshaper.js +1996 -281
- package/www/modules.js +122 -10
package/www/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,40 @@
|
|
|
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
|
+
|
|
10468
|
+
// The WKT counterpart of tryParseCrsString(), for a projection read out of a
|
|
10469
|
+
// .prj or .aux.xml file, in either WKT dialect. Returns the proj4 equivalent of
|
|
10470
|
+
// the WKT, or null.
|
|
10471
|
+
function tryParseWktToProj(str) {
|
|
10472
|
+
var revertLogging = getLoggingSetter();
|
|
10473
|
+
var proj = null;
|
|
10474
|
+
setLoggingForCLI();
|
|
10475
|
+
try {
|
|
10476
|
+
proj = wktToProj(str);
|
|
10477
|
+
} catch(e) {
|
|
10478
|
+
// WKT that cannot be converted is one of the expected outcomes here
|
|
10479
|
+
}
|
|
10480
|
+
revertLogging();
|
|
10481
|
+
return proj || null;
|
|
10482
|
+
}
|
|
10483
|
+
|
|
10450
10484
|
function parseCrsString$1(str) {
|
|
10451
10485
|
var defn = getProjDefn(str); // defn is a string or a Proj object
|
|
10452
10486
|
var P;
|
|
@@ -10701,6 +10735,8 @@
|
|
|
10701
10735
|
setDatasetCrsInfo: setDatasetCrsInfo,
|
|
10702
10736
|
setProjectionLoader: setProjectionLoader,
|
|
10703
10737
|
toLngLat: toLngLat,
|
|
10738
|
+
tryParseCrsString: tryParseCrsString,
|
|
10739
|
+
tryParseWktToProj: tryParseWktToProj,
|
|
10704
10740
|
wkt1ToProj: wkt1ToProj,
|
|
10705
10741
|
wkt2ToProj: wkt2ToProj,
|
|
10706
10742
|
wktToProj: wktToProj
|
|
@@ -12867,7 +12903,7 @@
|
|
|
12867
12903
|
|
|
12868
12904
|
function datasetHasGeometry(dataset) {
|
|
12869
12905
|
return utils.some(dataset.layers, function(lyr) {
|
|
12870
|
-
return layerHasGeometry(lyr);
|
|
12906
|
+
return layerHasGeometry$1(lyr);
|
|
12871
12907
|
});
|
|
12872
12908
|
}
|
|
12873
12909
|
|
|
@@ -13110,6 +13146,67 @@
|
|
|
13110
13146
|
toLowerCaseExtension: toLowerCaseExtension
|
|
13111
13147
|
});
|
|
13112
13148
|
|
|
13149
|
+
// GDAL's "persistent auxiliary metadata" sidecar, named after the whole raster
|
|
13150
|
+
// filename rather than replacing its extension (world.tif -> world.tif.aux.xml).
|
|
13151
|
+
// Unlike the .prj file that accompanies a shapefile, this is a sidecar that
|
|
13152
|
+
// GDAL-based software (QGIS included) actually consults for a GeoTIFF's CRS, so
|
|
13153
|
+
// it is where mapshaper puts a projection that GeoTIFF itself cannot describe.
|
|
13154
|
+
var AUX_EXT = '.aux.xml';
|
|
13155
|
+
|
|
13156
|
+
function getAuxFilename(filename) {
|
|
13157
|
+
return filename + AUX_EXT;
|
|
13158
|
+
}
|
|
13159
|
+
|
|
13160
|
+
function isAuxFilename(filename) {
|
|
13161
|
+
return /\.aux\.xml$/i.test(filename || '');
|
|
13162
|
+
}
|
|
13163
|
+
|
|
13164
|
+
// Returns the raster a sidecar belongs to: world.tif.aux.xml -> world.tif
|
|
13165
|
+
function getAuxSourceFilename(filename) {
|
|
13166
|
+
return String(filename).replace(/\.aux\.xml$/i, '');
|
|
13167
|
+
}
|
|
13168
|
+
|
|
13169
|
+
function formatAuxXml(wkt) {
|
|
13170
|
+
return '<PAMDataset>\n <SRS>' + escapeXml(wkt) + '</SRS>\n</PAMDataset>\n';
|
|
13171
|
+
}
|
|
13172
|
+
|
|
13173
|
+
// The CRS a sidecar carries, or null. Everything else GDAL writes to these
|
|
13174
|
+
// files -- statistics, band metadata, overviews -- describes data that
|
|
13175
|
+
// mapshaper reads from the raster itself.
|
|
13176
|
+
function parseAuxCrsString(content) {
|
|
13177
|
+
var xml = typeof content == 'string' ? content :
|
|
13178
|
+
content ? decodeString(content, 'utf8') : '';
|
|
13179
|
+
var match = /<SRS\b[^>]*>([\s\S]*?)<\/SRS>/i.exec(xml);
|
|
13180
|
+
var srs = match ? unescapeXml(match[1]).trim() : '';
|
|
13181
|
+
return srs || null;
|
|
13182
|
+
}
|
|
13183
|
+
|
|
13184
|
+
// GDAL states the SRS in WKT, but a hand-written sidecar may hold an EPSG code
|
|
13185
|
+
// or a proj4 definition instead, which mapshaper can use as they are.
|
|
13186
|
+
function auxCrsIsWkt(str) {
|
|
13187
|
+
return /^\s*[A-Z_]+\[/i.test(str || '');
|
|
13188
|
+
}
|
|
13189
|
+
|
|
13190
|
+
function escapeXml(str) {
|
|
13191
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
13192
|
+
}
|
|
13193
|
+
|
|
13194
|
+
function unescapeXml(str) {
|
|
13195
|
+
return str.replace(/</g, '<').replace(/>/g, '>')
|
|
13196
|
+
.replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, '&');
|
|
13197
|
+
}
|
|
13198
|
+
|
|
13199
|
+
var GeoTIFFAux = /*#__PURE__*/Object.freeze({
|
|
13200
|
+
__proto__: null,
|
|
13201
|
+
AUX_EXT: AUX_EXT,
|
|
13202
|
+
auxCrsIsWkt: auxCrsIsWkt,
|
|
13203
|
+
formatAuxXml: formatAuxXml,
|
|
13204
|
+
getAuxFilename: getAuxFilename,
|
|
13205
|
+
getAuxSourceFilename: getAuxSourceFilename,
|
|
13206
|
+
isAuxFilename: isAuxFilename,
|
|
13207
|
+
parseAuxCrsString: parseAuxCrsString
|
|
13208
|
+
});
|
|
13209
|
+
|
|
13113
13210
|
// Constants shared between the .msx (mapshaper snapshot) writer in
|
|
13114
13211
|
// mapshaper-pack.mjs and the file-type registry in
|
|
13115
13212
|
// io/mapshaper-file-types.mjs. Living here lets file-types reference the
|
|
@@ -13123,7 +13220,10 @@
|
|
|
13123
13220
|
function guessInputFileType(file) {
|
|
13124
13221
|
var ext = getFileExtension(file || '').toLowerCase(),
|
|
13125
13222
|
type = null;
|
|
13126
|
-
if (
|
|
13223
|
+
if (isAuxFilename(file)) {
|
|
13224
|
+
// Matched ahead of the extension, which is the raster's own (.tif.aux.xml).
|
|
13225
|
+
type = 'aux';
|
|
13226
|
+
} else if (ext == 'dbf' || ext == 'shp' || ext == 'kml' || ext == 'svg' || ext == 'fgb' || ext == 'gpkg' || ext == 'png') {
|
|
13127
13227
|
type = ext;
|
|
13128
13228
|
} else if (ext == 'jpg' || ext == 'jpeg') {
|
|
13129
13229
|
type = 'jpeg';
|
|
@@ -13147,7 +13247,8 @@
|
|
|
13147
13247
|
|
|
13148
13248
|
// File types that can be imported but are not convertible to datasets
|
|
13149
13249
|
function isAuxiliaryInputFileType(type) {
|
|
13150
|
-
return type == 'prj' || type == 'shx' || type == 'cpg' || type == 'world'
|
|
13250
|
+
return type == 'prj' || type == 'shx' || type == 'cpg' || type == 'world' ||
|
|
13251
|
+
type == 'aux';
|
|
13151
13252
|
}
|
|
13152
13253
|
|
|
13153
13254
|
function isRasterImageInputType(type) {
|
|
@@ -14082,6 +14183,28 @@
|
|
|
14082
14183
|
d: function () { return ~c; }
|
|
14083
14184
|
};
|
|
14084
14185
|
};
|
|
14186
|
+
// Adler32
|
|
14187
|
+
var adler = function () {
|
|
14188
|
+
var a = 1, b = 0;
|
|
14189
|
+
return {
|
|
14190
|
+
p: function (d) {
|
|
14191
|
+
// closures have awful performance
|
|
14192
|
+
var n = a, m = b;
|
|
14193
|
+
var l = d.length | 0;
|
|
14194
|
+
for (var i = 0; i != l;) {
|
|
14195
|
+
var e = Math.min(i + 2655, l);
|
|
14196
|
+
for (; i < e; ++i)
|
|
14197
|
+
m += n += d[i];
|
|
14198
|
+
n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16);
|
|
14199
|
+
}
|
|
14200
|
+
a = n, b = m;
|
|
14201
|
+
},
|
|
14202
|
+
d: function () {
|
|
14203
|
+
a %= 65521, b %= 65521;
|
|
14204
|
+
return (a & 255) << 24 | (a & 0xFF00) << 8 | (b & 255) << 8 | (b >> 8);
|
|
14205
|
+
}
|
|
14206
|
+
};
|
|
14207
|
+
};
|
|
14085
14208
|
// deflate with opts
|
|
14086
14209
|
var dopt = function (dat, opt, pre, post, st) {
|
|
14087
14210
|
if (!st) {
|
|
@@ -14166,7 +14289,7 @@
|
|
|
14166
14289
|
};
|
|
14167
14290
|
// base async inflate fn
|
|
14168
14291
|
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]; };
|
|
14292
|
+
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
14293
|
// gzip extra
|
|
14171
14294
|
var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; };
|
|
14172
14295
|
// gunzip extra
|
|
@@ -14229,6 +14352,17 @@
|
|
|
14229
14352
|
};
|
|
14230
14353
|
// gzip header length
|
|
14231
14354
|
var gzhl = function (o) { return 10 + (o.filename ? o.filename.length + 1 : 0); };
|
|
14355
|
+
// zlib header
|
|
14356
|
+
var zlh = function (c, o) {
|
|
14357
|
+
var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2;
|
|
14358
|
+
c[0] = 120, c[1] = (fl << 6) | (o.dictionary && 32);
|
|
14359
|
+
c[1] |= 31 - ((c[0] << 8) | c[1]) % 31;
|
|
14360
|
+
if (o.dictionary) {
|
|
14361
|
+
var h = adler();
|
|
14362
|
+
h.p(o.dictionary);
|
|
14363
|
+
wbytes(c, 2, h.d());
|
|
14364
|
+
}
|
|
14365
|
+
};
|
|
14232
14366
|
function deflate(data, opts, cb) {
|
|
14233
14367
|
if (!cb)
|
|
14234
14368
|
cb = opts, opts = {};
|
|
@@ -14236,7 +14370,7 @@
|
|
|
14236
14370
|
err(7);
|
|
14237
14371
|
return cbify(data, opts, [
|
|
14238
14372
|
bDflt,
|
|
14239
|
-
], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb);
|
|
14373
|
+
], function (ev) { return pbf(deflateSync$1(ev.data[0], ev.data[1])); }, 0, cb);
|
|
14240
14374
|
}
|
|
14241
14375
|
/**
|
|
14242
14376
|
* Compresses data with DEFLATE without any wrapper
|
|
@@ -14244,7 +14378,7 @@
|
|
|
14244
14378
|
* @param opts The compression options
|
|
14245
14379
|
* @returns The deflated version of the data
|
|
14246
14380
|
*/
|
|
14247
|
-
function deflateSync(data, opts) {
|
|
14381
|
+
function deflateSync$1(data, opts) {
|
|
14248
14382
|
return dopt(data, opts || {}, 0, 0);
|
|
14249
14383
|
}
|
|
14250
14384
|
function inflate(data, opts, cb) {
|
|
@@ -14313,6 +14447,20 @@
|
|
|
14313
14447
|
err(6, 'invalid gzip data');
|
|
14314
14448
|
return inflt(data.subarray(st, -8), { i: 2 }, opts && opts.out || new u8(gzl(data)), opts && opts.dictionary);
|
|
14315
14449
|
}
|
|
14450
|
+
/**
|
|
14451
|
+
* Compress data with Zlib
|
|
14452
|
+
* @param data The data to compress
|
|
14453
|
+
* @param opts The compression options
|
|
14454
|
+
* @returns The zlib-compressed version of the data
|
|
14455
|
+
*/
|
|
14456
|
+
function zlibSync(data, opts) {
|
|
14457
|
+
if (!opts)
|
|
14458
|
+
opts = {};
|
|
14459
|
+
var a = adler();
|
|
14460
|
+
a.p(data);
|
|
14461
|
+
var d = dopt(data, opts, opts.dictionary ? 6 : 2, 4);
|
|
14462
|
+
return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d;
|
|
14463
|
+
}
|
|
14316
14464
|
// flatten a directory structure
|
|
14317
14465
|
var fltn = function (d, p, t, o) {
|
|
14318
14466
|
for (var k in d) {
|
|
@@ -14568,7 +14716,7 @@
|
|
|
14568
14716
|
cbl(null, file);
|
|
14569
14717
|
else if (size < 160000) {
|
|
14570
14718
|
try {
|
|
14571
|
-
cbl(null, deflateSync(file, p));
|
|
14719
|
+
cbl(null, deflateSync$1(file, p));
|
|
14572
14720
|
}
|
|
14573
14721
|
catch (e) {
|
|
14574
14722
|
cbl(e, null);
|
|
@@ -14606,7 +14754,7 @@
|
|
|
14606
14754
|
var exl = exfl(p.extra);
|
|
14607
14755
|
if (s > 65535)
|
|
14608
14756
|
err(11);
|
|
14609
|
-
var d = compression ? deflateSync(file, p) : file, l = d.length;
|
|
14757
|
+
var d = compression ? deflateSync$1(file, p) : file, l = d.length;
|
|
14610
14758
|
var c = crc();
|
|
14611
14759
|
c.p(file);
|
|
14612
14760
|
files.push(mrg(p, {
|
|
@@ -17475,6 +17623,12 @@
|
|
|
17475
17623
|
sortIntersections: sortIntersections
|
|
17476
17624
|
});
|
|
17477
17625
|
|
|
17626
|
+
// Reverting an arc can leave it crossing a neighbor that is still modified, so
|
|
17627
|
+
// the check is repeated; each pass reverts more arcs than the last, and the
|
|
17628
|
+
// unmodified geometry is the floor.
|
|
17629
|
+
var MAX_REPAIR_PASSES = 10;
|
|
17630
|
+
|
|
17631
|
+
|
|
17478
17632
|
// arcs: ArcCollection containing original coordinates
|
|
17479
17633
|
function getRepairFunction(arcs) {
|
|
17480
17634
|
var arcsOrig = arcs.getCopy();
|
|
@@ -17536,6 +17690,73 @@
|
|
|
17536
17690
|
});
|
|
17537
17691
|
}
|
|
17538
17692
|
|
|
17693
|
+
// Remove intersections from a whole-collection edit (such as smoothing) by
|
|
17694
|
+
// putting the arcs that cross back the way they were. Reverting per arc, rather
|
|
17695
|
+
// than per vertex, is what suits an edit that resamples a path: the modified and
|
|
17696
|
+
// original versions of an arc need not share a vertex count.
|
|
17697
|
+
//
|
|
17698
|
+
// arcs: modified arcs, edited in place
|
|
17699
|
+
// arcsOrig: the same arcs, in the same order, before the edit
|
|
17700
|
+
// Returns {reverted: number of arcs put back, remaining: crossings left}
|
|
17701
|
+
function repairCrossedArcs(arcs, arcsOrig) {
|
|
17702
|
+
var intersections = findSegmentIntersections(arcs, {});
|
|
17703
|
+
var reverted = null;
|
|
17704
|
+
var pass = 0;
|
|
17705
|
+
while (intersections.length > 0 && pass++ < MAX_REPAIR_PASSES) {
|
|
17706
|
+
reverted = reverted || new Uint8Array(arcs.size());
|
|
17707
|
+
// Nothing new to try: the arcs that cross are already unmodified.
|
|
17708
|
+
if (markCrossedArcs(intersections, arcs, reverted) === 0) break;
|
|
17709
|
+
revertArcs(arcs, arcsOrig, reverted);
|
|
17710
|
+
intersections = findSegmentIntersections(arcs, {});
|
|
17711
|
+
}
|
|
17712
|
+
return {
|
|
17713
|
+
reverted: reverted ? countMarked(reverted) : 0,
|
|
17714
|
+
remaining: intersections.length
|
|
17715
|
+
};
|
|
17716
|
+
}
|
|
17717
|
+
|
|
17718
|
+
// Flags the arcs taking part in each intersection; returns how many were newly
|
|
17719
|
+
// flagged.
|
|
17720
|
+
function markCrossedArcs(intersections, arcs, marked) {
|
|
17721
|
+
var ii = arcs.getVertexData().ii;
|
|
17722
|
+
var added = 0;
|
|
17723
|
+
intersections.forEach(function(o) {
|
|
17724
|
+
[findArcIdFromVertexId(o.a[0], ii), findArcIdFromVertexId(o.b[0], ii)]
|
|
17725
|
+
.forEach(function(arcId) {
|
|
17726
|
+
if (marked[arcId]) return;
|
|
17727
|
+
marked[arcId] = 1;
|
|
17728
|
+
added++;
|
|
17729
|
+
});
|
|
17730
|
+
});
|
|
17731
|
+
return added;
|
|
17732
|
+
}
|
|
17733
|
+
|
|
17734
|
+
function revertArcs(arcs, arcsOrig, marked) {
|
|
17735
|
+
var mod = arcs.getVertexData();
|
|
17736
|
+
var orig = arcsOrig.getVertexData();
|
|
17737
|
+
var nn = [], xx = [], yy = [];
|
|
17738
|
+
var i, j, src, start, len;
|
|
17739
|
+
for (i = 0; i < marked.length; i++) {
|
|
17740
|
+
src = marked[i] ? orig : mod;
|
|
17741
|
+
start = src.ii[i];
|
|
17742
|
+
len = src.nn[i];
|
|
17743
|
+
nn.push(len);
|
|
17744
|
+
for (j = 0; j < len; j++) {
|
|
17745
|
+
xx.push(src.xx[start + j]);
|
|
17746
|
+
yy.push(src.yy[start + j]);
|
|
17747
|
+
}
|
|
17748
|
+
}
|
|
17749
|
+
arcs.updateVertexData(nn, xx, yy);
|
|
17750
|
+
}
|
|
17751
|
+
|
|
17752
|
+
function countMarked(marked) {
|
|
17753
|
+
var count = 0;
|
|
17754
|
+
for (var i = 0; i < marked.length; i++) {
|
|
17755
|
+
if (marked[i]) count++;
|
|
17756
|
+
}
|
|
17757
|
+
return count;
|
|
17758
|
+
}
|
|
17759
|
+
|
|
17539
17760
|
// idx: index of an arc endpoint
|
|
17540
17761
|
function findMatchingEndpoints(idx, data) {
|
|
17541
17762
|
var ii = data.ii, nn = data.nn, xx = data.xx, yy = data.yy;
|
|
@@ -17555,6 +17776,13 @@
|
|
|
17555
17776
|
return matches;
|
|
17556
17777
|
}
|
|
17557
17778
|
|
|
17779
|
+
var SegmentIntersectionRepair = /*#__PURE__*/Object.freeze({
|
|
17780
|
+
__proto__: null,
|
|
17781
|
+
getRepairFunction: getRepairFunction,
|
|
17782
|
+
repairCrossedArcs: repairCrossedArcs,
|
|
17783
|
+
repairSegmentIntersections: repairSegmentIntersections
|
|
17784
|
+
});
|
|
17785
|
+
|
|
17558
17786
|
function roundToSignificantDigits(n, d) {
|
|
17559
17787
|
return +n.toPrecision(d);
|
|
17560
17788
|
}
|
|
@@ -22299,7 +22527,7 @@
|
|
|
22299
22527
|
return;
|
|
22300
22528
|
}
|
|
22301
22529
|
layers.forEach(function(lyr) {
|
|
22302
|
-
if (!layerHasGeometry(lyr)) return;
|
|
22530
|
+
if (!layerHasGeometry$1(lyr)) return;
|
|
22303
22531
|
if (lyr.geometry_type == 'polygon' && opts.rewind) {
|
|
22304
22532
|
profileStart('rewindPolygons');
|
|
22305
22533
|
rewindPolygons(lyr, dataset.arcs);
|
|
@@ -22595,33 +22823,35 @@
|
|
|
22595
22823
|
});
|
|
22596
22824
|
}
|
|
22597
22825
|
|
|
22598
|
-
//
|
|
22826
|
+
// Returns a cursor for generating a layer's Features (or Geometries) one at a
|
|
22827
|
+
// time. A caller that can consume features incrementally should use this
|
|
22828
|
+
// instead of exportLayerAsGeoJSON(), which holds the entire layer in memory --
|
|
22829
|
+
// the GeoJSON form of a coordinate is many times larger than its binary form,
|
|
22830
|
+
// so materializing a whole layer of detailed geometry is usually the largest
|
|
22831
|
+
// allocation an exporter makes.
|
|
22599
22832
|
//
|
|
22600
|
-
|
|
22833
|
+
// getFeature() returns null for a null geometry when asFeatures is false, so
|
|
22834
|
+
// that such shapes can be left out of a GeometryCollection.
|
|
22835
|
+
function getFeatureCursor(lyr, dataset, opts, asFeatures) {
|
|
22601
22836
|
var properties = exportProperties(lyr.data, opts),
|
|
22602
22837
|
shapes = lyr.shapes,
|
|
22603
22838
|
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
|
-
}
|
|
22839
|
+
exporter = GeoJSON.exporters[lyr.geometry_type];
|
|
22613
22840
|
|
|
22614
22841
|
if (properties && shapes && properties.length !== shapes.length) {
|
|
22615
22842
|
error("Mismatch between number of properties and number of shapes");
|
|
22616
22843
|
}
|
|
22617
22844
|
|
|
22618
|
-
return
|
|
22619
|
-
|
|
22620
|
-
|
|
22621
|
-
|
|
22622
|
-
|
|
22623
|
-
|
|
22624
|
-
|
|
22845
|
+
return {
|
|
22846
|
+
length: (shapes || properties || []).length,
|
|
22847
|
+
properties: properties,
|
|
22848
|
+
getFeature: function(i) {
|
|
22849
|
+
var shape = shapes ? shapes[i] : null,
|
|
22850
|
+
geom = shape ? exporter(shape, dataset.arcs, opts) : null,
|
|
22851
|
+
obj;
|
|
22852
|
+
if (!asFeatures) {
|
|
22853
|
+
return geom || null;
|
|
22854
|
+
}
|
|
22625
22855
|
obj = composeFeature(geom, properties ? properties[i] : null, opts);
|
|
22626
22856
|
if (ids) {
|
|
22627
22857
|
obj.id = ids[i];
|
|
@@ -22629,24 +22859,40 @@
|
|
|
22629
22859
|
if (opts.no_null_props && !obj.properties) {
|
|
22630
22860
|
obj.properties = {};
|
|
22631
22861
|
}
|
|
22632
|
-
|
|
22633
|
-
return memo; // don't add null objects to GeometryCollection
|
|
22634
|
-
} else {
|
|
22635
|
-
obj = geom;
|
|
22862
|
+
return obj;
|
|
22636
22863
|
}
|
|
22864
|
+
};
|
|
22865
|
+
}
|
|
22866
|
+
|
|
22867
|
+
// Return an array of Features or Geometries as objects or strings
|
|
22868
|
+
//
|
|
22869
|
+
function exportLayerAsGeoJSON(lyr, dataset, opts, asFeatures, ofmt) {
|
|
22870
|
+
var cursor = getFeatureCursor(lyr, dataset, opts, asFeatures),
|
|
22871
|
+
items = [],
|
|
22872
|
+
stringify, obj, i;
|
|
22873
|
+
|
|
22874
|
+
if (opts.ndjson) {
|
|
22875
|
+
stringify = stringifyAsNDJSON;
|
|
22876
|
+
} else if (opts.prettify) {
|
|
22877
|
+
stringify = getFormattedStringify(['bbox', 'coordinates']);
|
|
22878
|
+
} else {
|
|
22879
|
+
stringify = JSON.stringify;
|
|
22880
|
+
}
|
|
22881
|
+
|
|
22882
|
+
for (i = 0; i < cursor.length; i++) {
|
|
22883
|
+
obj = cursor.getFeature(i);
|
|
22884
|
+
if (!asFeatures && !obj) continue; // don't add null objects to GeometryCollection
|
|
22637
22885
|
if (ofmt) {
|
|
22638
22886
|
// stringify features as soon as they are generated, to reduce the
|
|
22639
22887
|
// number of JS objects in memory (so larger files can be exported)
|
|
22640
22888
|
obj = stringify(obj);
|
|
22641
22889
|
if (ofmt == 'buffer') {
|
|
22642
22890
|
obj = encodeString(obj, 'utf8');
|
|
22643
|
-
// obj = stringToBuffer(obj);
|
|
22644
|
-
// obj = new Buffer(obj, 'utf8');
|
|
22645
22891
|
}
|
|
22646
22892
|
}
|
|
22647
|
-
|
|
22648
|
-
|
|
22649
|
-
|
|
22893
|
+
items.push(obj);
|
|
22894
|
+
}
|
|
22895
|
+
return items;
|
|
22650
22896
|
}
|
|
22651
22897
|
|
|
22652
22898
|
function composeFeature(geom, properties, opts) {
|
|
@@ -22967,6 +23213,7 @@
|
|
|
22967
23213
|
exportLayerAsGeoJSON: exportLayerAsGeoJSON,
|
|
22968
23214
|
exportProperties: exportProperties,
|
|
22969
23215
|
getDatasetBbox: getDatasetBbox,
|
|
23216
|
+
getFeatureCursor: getFeatureCursor,
|
|
22970
23217
|
getIdField: getIdField,
|
|
22971
23218
|
preserveOriginalCRS: preserveOriginalCRS,
|
|
22972
23219
|
useFeatureCollection: useFeatureCollection,
|
|
@@ -28525,6 +28772,19 @@ ${svg}
|
|
|
28525
28772
|
return require('zlib').gzipSync(content, opts);
|
|
28526
28773
|
}
|
|
28527
28774
|
|
|
28775
|
+
// Compress to a zlib stream (a deflate stream wrapped in a zlib header and
|
|
28776
|
+
// checksum). This is what TIFF's Deflate compression expects, unlike the gzip
|
|
28777
|
+
// wrapper used for .gz files.
|
|
28778
|
+
function deflateSync(content, opts) {
|
|
28779
|
+
if (typeof content == 'string') {
|
|
28780
|
+
content = strToU8(content);
|
|
28781
|
+
}
|
|
28782
|
+
if (runningInBrowser()) {
|
|
28783
|
+
return zlibSync(content, opts);
|
|
28784
|
+
}
|
|
28785
|
+
return require('zlib').deflateSync(content, opts);
|
|
28786
|
+
}
|
|
28787
|
+
|
|
28528
28788
|
async function gzipAsync(content, opts) {
|
|
28529
28789
|
if (typeof content == 'string') {
|
|
28530
28790
|
content = strToU8(content);
|
|
@@ -28559,6 +28819,7 @@ ${svg}
|
|
|
28559
28819
|
|
|
28560
28820
|
var Gzip = /*#__PURE__*/Object.freeze({
|
|
28561
28821
|
__proto__: null,
|
|
28822
|
+
deflateSync: deflateSync,
|
|
28562
28823
|
gunzipAsync: gunzipAsync,
|
|
28563
28824
|
gunzipSync: gunzipSync,
|
|
28564
28825
|
gzipAsync: gzipAsync,
|
|
@@ -31642,56 +31903,89 @@ ${svg}
|
|
|
31642
31903
|
return builder.asUint8Array();
|
|
31643
31904
|
}
|
|
31644
31905
|
|
|
31645
|
-
|
|
31646
|
-
|
|
31647
|
-
|
|
31648
|
-
|
|
31649
|
-
|
|
31650
|
-
|
|
31651
|
-
|
|
31906
|
+
// source: a feature cursor, {length, getFeature(i)}. Features are pulled and
|
|
31907
|
+
// encoded one at a time rather than as a collection, because the GeoJSON form
|
|
31908
|
+
// of a layer is several times larger than the FlatGeobuf it encodes into.
|
|
31909
|
+
function serializeWithColumns(source, columns) {
|
|
31910
|
+
if (source.length === 0) {
|
|
31911
|
+
throw new Error('Could not infer geometry type for collection of features.');
|
|
31912
|
+
}
|
|
31913
|
+
// The header precedes the features and declares the collection's geometry
|
|
31914
|
+
// type, but that type isn't known until every feature has been seen. Assume
|
|
31915
|
+
// the layer is homogeneous, which nearly all are, and encode again as
|
|
31916
|
+
// Unknown in the rare case that it isn't.
|
|
31917
|
+
return encodeCollection(source, columns, null) ||
|
|
31918
|
+
encodeCollection(source, columns, GeometryType.Unknown);
|
|
31919
|
+
}
|
|
31920
|
+
|
|
31921
|
+
// Returns null if forcedType is null and the features turn out to have more
|
|
31922
|
+
// than one geometry type.
|
|
31923
|
+
function encodeCollection(source, columns, forcedType) {
|
|
31924
|
+
var headerMeta = null;
|
|
31925
|
+
var sink = null;
|
|
31926
|
+
for (var i = 0; i < source.length; i++) {
|
|
31927
|
+
var feature = source.getFeature(i);
|
|
31928
|
+
var type = GeometryType[feature.geometry.type] || GeometryType.Unknown;
|
|
31929
|
+
if (!headerMeta) {
|
|
31930
|
+
headerMeta = {
|
|
31931
|
+
geometryType: forcedType === null ? type : forcedType,
|
|
31932
|
+
columns: columns,
|
|
31933
|
+
envelope: null,
|
|
31934
|
+
featuresCount: source.length,
|
|
31935
|
+
indexNodeSize: 0,
|
|
31936
|
+
crs: null,
|
|
31937
|
+
title: null,
|
|
31938
|
+
description: null,
|
|
31939
|
+
metadata: null
|
|
31940
|
+
};
|
|
31941
|
+
sink = new ByteSink(magicbytes.length + estimateEncodedBytes(source));
|
|
31942
|
+
sink.append(magicbytes);
|
|
31943
|
+
sink.append(buildHeader(headerMeta));
|
|
31944
|
+
} else if (forcedType === null && type != headerMeta.geometryType) {
|
|
31945
|
+
return null;
|
|
31946
|
+
}
|
|
31652
31947
|
var geometry = feature.geometry.type == 'GeometryCollection' ?
|
|
31653
31948
|
parseGC(feature.geometry) :
|
|
31654
31949
|
parseGeometry(feature.geometry);
|
|
31655
31950
|
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;
|
|
31951
|
+
sink.append(buildFeature(geometry, normalizeProperties(feature.properties, columns), headerMeta));
|
|
31952
|
+
}
|
|
31953
|
+
return sink.toBytes();
|
|
31954
|
+
}
|
|
31955
|
+
|
|
31956
|
+
// Encoding the first feature is the only sample available before the buffer
|
|
31957
|
+
// has to be allocated; overshooting costs a resize, which is why it is loose.
|
|
31958
|
+
function estimateEncodedBytes(source) {
|
|
31959
|
+
return Math.max(source.length * 64, 1024);
|
|
31671
31960
|
}
|
|
31672
31961
|
|
|
31962
|
+
function ByteSink(initialSize) {
|
|
31963
|
+
this.bytes = new Uint8Array(initialSize);
|
|
31964
|
+
this.length = 0;
|
|
31965
|
+
}
|
|
31966
|
+
|
|
31967
|
+
ByteSink.prototype.append = function(chunk) {
|
|
31968
|
+
if (this.length + chunk.length > this.bytes.length) {
|
|
31969
|
+
var size = this.bytes.length;
|
|
31970
|
+
while (size < this.length + chunk.length) size *= 2;
|
|
31971
|
+
var grown = new Uint8Array(size);
|
|
31972
|
+
grown.set(this.bytes.subarray(0, this.length));
|
|
31973
|
+
this.bytes = grown;
|
|
31974
|
+
}
|
|
31975
|
+
this.bytes.set(chunk, this.length);
|
|
31976
|
+
this.length += chunk.length;
|
|
31977
|
+
};
|
|
31978
|
+
|
|
31979
|
+
ByteSink.prototype.toBytes = function() {
|
|
31980
|
+
return this.bytes.subarray(0, this.length);
|
|
31981
|
+
};
|
|
31982
|
+
|
|
31673
31983
|
function omitRedundantGeometryType(geometry, headerType) {
|
|
31674
31984
|
if (headerType != GeometryType.Unknown && geometry.type == headerType) {
|
|
31675
31985
|
geometry.type = GeometryType.Unknown;
|
|
31676
31986
|
}
|
|
31677
31987
|
}
|
|
31678
31988
|
|
|
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
31989
|
function normalizeProperties(properties, columns) {
|
|
31696
31990
|
var copy = {};
|
|
31697
31991
|
properties = properties || {};
|
|
@@ -31751,9 +32045,12 @@ ${svg}
|
|
|
31751
32045
|
}
|
|
31752
32046
|
var crsMeta = resolveOutputCRS(dataset);
|
|
31753
32047
|
return dataset.layers.map(function(lyr) {
|
|
31754
|
-
var
|
|
32048
|
+
var cursor = getFeatureCursor(lyr, dataset, opts, true);
|
|
32049
|
+
if (cursor.length === 0) {
|
|
32050
|
+
stop$1('FlatGeobuf export does not support empty layers');
|
|
32051
|
+
}
|
|
31755
32052
|
var columns = getFlatGeobufColumns(lyr);
|
|
31756
|
-
var content = serializeWithColumns(
|
|
32053
|
+
var content = serializeWithColumns(cursor, columns);
|
|
31757
32054
|
var filename = lyr.name + '.' + extension;
|
|
31758
32055
|
if (crsMeta) {
|
|
31759
32056
|
content = rewriteHeaderWithCRS(content, crsMeta);
|
|
@@ -31767,17 +32064,6 @@ ${svg}
|
|
|
31767
32064
|
});
|
|
31768
32065
|
}
|
|
31769
32066
|
|
|
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
32067
|
function getFlatGeobufColumns(lyr) {
|
|
31782
32068
|
var records = lyr.data ? lyr.data.getRecords() : [];
|
|
31783
32069
|
return lyr.data ? lyr.data.getFields().map(function(name) {
|
|
@@ -32473,83 +32759,177 @@ ${svg}
|
|
|
32473
32759
|
async function exportGeoParquet(dataset, opts, filenameOverride) {
|
|
32474
32760
|
var writer = await loadGeoParquetWriter();
|
|
32475
32761
|
var compression = await getGeoParquetCompression(opts);
|
|
32762
|
+
var rowGroupOverride = validateRowGroupSize(opts.rowgroup);
|
|
32476
32763
|
var extension = opts.extension || 'parquet';
|
|
32477
32764
|
var files = [];
|
|
32478
32765
|
if (opts.file) {
|
|
32479
32766
|
extension = getFileExtension(opts.file) || extension;
|
|
32480
32767
|
}
|
|
32481
32768
|
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
32769
|
files.push({
|
|
32503
32770
|
filename: filenameOverride || (lyr.name + '.' + extension),
|
|
32504
|
-
content:
|
|
32771
|
+
content: writeLayer(writer, lyr, dataset, opts, compression, rowGroupOverride)
|
|
32505
32772
|
});
|
|
32506
32773
|
});
|
|
32507
32774
|
return files;
|
|
32508
32775
|
}
|
|
32509
32776
|
|
|
32510
|
-
|
|
32511
|
-
|
|
32512
|
-
|
|
32513
|
-
|
|
32514
|
-
|
|
32777
|
+
var GEOMETRY_COLUMN = 'geometry';
|
|
32778
|
+
|
|
32779
|
+
// Rows are converted and handed to the writer one row group at a time. The
|
|
32780
|
+
// GeoJSON form of a geometry is several times larger than the WKB the writer
|
|
32781
|
+
// encodes it into, so materializing a whole layer of it was the largest
|
|
32782
|
+
// allocation the export made -- larger than the dataset and the output file
|
|
32783
|
+
// combined. Converting a group at a time makes that cost a function of the
|
|
32784
|
+
// row group size rather than of the layer size.
|
|
32785
|
+
function writeLayer(writer, lyr, dataset, opts, compression, rowGroupOverride) {
|
|
32786
|
+
var cursor = getFeatureCursor(lyr, dataset, opts, true);
|
|
32787
|
+
var rowCount = cursor.length;
|
|
32788
|
+
if (rowCount === 0) {
|
|
32515
32789
|
stop$1('GeoParquet export requires at least one record');
|
|
32516
32790
|
}
|
|
32517
|
-
|
|
32518
|
-
|
|
32519
|
-
|
|
32520
|
-
|
|
32521
|
-
|
|
32522
|
-
|
|
32523
|
-
|
|
32791
|
+
var hasGeometry = layerHasGeometry(lyr);
|
|
32792
|
+
var fields = getFieldTypes(cursor, opts);
|
|
32793
|
+
if (!hasGeometry && fields.length === 0) {
|
|
32794
|
+
stop$1('GeoParquet export requires geometry or attribute data');
|
|
32795
|
+
}
|
|
32796
|
+
if (!hasGeometry) {
|
|
32797
|
+
warn('GeoParquet export: layer has no geometry; writing attribute data only.');
|
|
32798
|
+
}
|
|
32799
|
+
var crs = hasGeometry ? getGeoMetadataCrs(dataset) : null;
|
|
32800
|
+
var byteWriter = new writer.ByteWriter();
|
|
32801
|
+
var pq = new writer.ParquetWriter({
|
|
32802
|
+
writer: byteWriter,
|
|
32803
|
+
schema: buildSchema(writer, fields, hasGeometry, crs),
|
|
32804
|
+
codec: compression.codec,
|
|
32805
|
+
compressors: compression.compressors
|
|
32806
|
+
});
|
|
32807
|
+
var geometryTypes = {};
|
|
32808
|
+
var plan = getRowGroupSize(cursor, fields, hasGeometry, rowCount, rowGroupOverride);
|
|
32809
|
+
eachRowGroupRange(rowCount, plan, function(start, end) {
|
|
32810
|
+
pq.write({
|
|
32811
|
+
columnData: buildChunkColumns(cursor, fields, hasGeometry, start, end, geometryTypes),
|
|
32812
|
+
rowGroupSize: end - start,
|
|
32813
|
+
pageSize: compression.pageSize
|
|
32524
32814
|
});
|
|
32815
|
+
});
|
|
32816
|
+
if (hasGeometry) {
|
|
32817
|
+
// Set on the writer rather than passed to the constructor: the geometry
|
|
32818
|
+
// types aren't known until every group has been converted.
|
|
32819
|
+
pq.kvMetadata = [{
|
|
32820
|
+
key: 'geo',
|
|
32821
|
+
value: JSON.stringify(buildGeoMetadata(Object.keys(geometryTypes), crs))
|
|
32822
|
+
}];
|
|
32525
32823
|
}
|
|
32526
|
-
|
|
32527
|
-
|
|
32528
|
-
|
|
32824
|
+
avoidIntegerBboxBounds(pq.row_groups);
|
|
32825
|
+
pq.finish();
|
|
32826
|
+
return byteWriter.getBuffer();
|
|
32827
|
+
}
|
|
32828
|
+
|
|
32829
|
+
// hyparquet-writer (0.14) infers each thrift wire type from the runtime value, so
|
|
32830
|
+
// a bounding box bound that lands on a whole number is written as I32 where the
|
|
32831
|
+
// Parquet spec requires a double. Readers that parse geospatial statistics
|
|
32832
|
+
// (pyarrow 21+, GDAL 3.11+) then fail to deserialize the footer and cannot open
|
|
32833
|
+
// the file at all -- which rounded, snapped or integer-grid coordinates make easy
|
|
32834
|
+
// to hit. Widening each bound to the adjacent double forces the double path.
|
|
32835
|
+
// Reported upstream; remove once the writer carries declared field types.
|
|
32836
|
+
function avoidIntegerBboxBounds(rowGroups) {
|
|
32837
|
+
(rowGroups || []).forEach(function(group) {
|
|
32838
|
+
(group.columns || []).forEach(function(col) {
|
|
32839
|
+
var stats = col.meta_data && col.meta_data.geospatial_statistics;
|
|
32840
|
+
if (!stats || !stats.bbox) return;
|
|
32841
|
+
if (!widenIntegerBounds(stats.bbox)) {
|
|
32842
|
+
// Past 2^53 doubles are spaced too widely to step off an integer. Drop
|
|
32843
|
+
// the box rather than write a footer that no reader can parse.
|
|
32844
|
+
delete stats.bbox;
|
|
32845
|
+
}
|
|
32529
32846
|
});
|
|
32530
|
-
columnData.push(buildAttributeColumn(name, values));
|
|
32531
32847
|
});
|
|
32532
|
-
|
|
32533
|
-
|
|
32848
|
+
}
|
|
32849
|
+
|
|
32850
|
+
// Nudges whole-number bounds outward, so that the box still covers every
|
|
32851
|
+
// geometry in the group: an oversized box only costs a reader the chance to skip
|
|
32852
|
+
// the group, while an undersized one would hide rows that match a filter.
|
|
32853
|
+
// Returns false if any bound could not be made non-integral.
|
|
32854
|
+
function widenIntegerBounds(bbox) {
|
|
32855
|
+
return Object.keys(bbox).every(function(key) {
|
|
32856
|
+
var val = bbox[key];
|
|
32857
|
+
var widened;
|
|
32858
|
+
if (!Number.isInteger(val)) return true;
|
|
32859
|
+
widened = nextDouble(val, /min$/.test(key) ? -1 : 1);
|
|
32860
|
+
if (Number.isInteger(widened)) return false;
|
|
32861
|
+
bbox[key] = widened;
|
|
32862
|
+
return true;
|
|
32863
|
+
});
|
|
32864
|
+
}
|
|
32865
|
+
|
|
32866
|
+
var nextDoubleFloats = new Float64Array(1);
|
|
32867
|
+
var nextDoubleBits = new BigInt64Array(nextDoubleFloats.buffer);
|
|
32868
|
+
|
|
32869
|
+
// Returns the adjacent double to @val in the direction of @sign (-1 down, 1 up).
|
|
32870
|
+
function nextDouble(val, sign) {
|
|
32871
|
+
if (!Number.isFinite(val)) return val;
|
|
32872
|
+
if (val === 0) return sign > 0 ? Number.MIN_VALUE : -Number.MIN_VALUE;
|
|
32873
|
+
nextDoubleFloats[0] = val;
|
|
32874
|
+
// Incrementing the bit pattern moves a positive double up and a negative one
|
|
32875
|
+
// further down, so the step direction depends on the sign of the value.
|
|
32876
|
+
nextDoubleBits[0] += (val > 0) === (sign > 0) ? 1n : -1n;
|
|
32877
|
+
return nextDoubleFloats[0];
|
|
32878
|
+
}
|
|
32879
|
+
|
|
32880
|
+
// A shape that survives to this point can still export as null geometry if all
|
|
32881
|
+
// of its paths collapse, in which case the column is written as nulls.
|
|
32882
|
+
function layerHasGeometry(lyr) {
|
|
32883
|
+
if (!lyr.geometry_type || !lyr.shapes) return false;
|
|
32884
|
+
return lyr.shapes.some(Boolean);
|
|
32885
|
+
}
|
|
32886
|
+
|
|
32887
|
+
function eachRowGroupRange(rowCount, plan, cb) {
|
|
32888
|
+
var sizes = Array.isArray(plan) ? plan : [plan];
|
|
32889
|
+
var start = 0;
|
|
32890
|
+
for (var i = 0; start < rowCount; i++) {
|
|
32891
|
+
var end = Math.min(start + sizes[Math.min(i, sizes.length - 1)], rowCount);
|
|
32892
|
+
cb(start, end);
|
|
32893
|
+
start = end;
|
|
32534
32894
|
}
|
|
32535
|
-
return {columnData: columnData, geometryColumn: geometryName};
|
|
32536
32895
|
}
|
|
32537
32896
|
|
|
32538
|
-
function
|
|
32539
|
-
var
|
|
32540
|
-
|
|
32541
|
-
|
|
32542
|
-
|
|
32543
|
-
|
|
32544
|
-
|
|
32545
|
-
|
|
32546
|
-
|
|
32897
|
+
function buildChunkColumns(cursor, fields, hasGeometry, start, end, geometryTypes) {
|
|
32898
|
+
var n = end - start;
|
|
32899
|
+
var geometry = hasGeometry ? new Array(n) : null;
|
|
32900
|
+
var values = fields.map(function() { return new Array(n); });
|
|
32901
|
+
for (var i = 0; i < n; i++) {
|
|
32902
|
+
var feat = cursor.getFeature(start + i);
|
|
32903
|
+
var props = feat.properties;
|
|
32904
|
+
if (hasGeometry) {
|
|
32905
|
+
var geom = feat.geometry || null;
|
|
32906
|
+
geometry[i] = geom;
|
|
32907
|
+
if (geom && geom.type) geometryTypes[geom.type] = true;
|
|
32908
|
+
}
|
|
32909
|
+
for (var j = 0; j < fields.length; j++) {
|
|
32910
|
+
values[j][i] = normalizeFieldValue(props ? props[fields[j].name] : null, fields[j].type);
|
|
32911
|
+
}
|
|
32912
|
+
}
|
|
32913
|
+
var columnData = hasGeometry ? [{name: GEOMETRY_COLUMN, data: geometry}] : [];
|
|
32914
|
+
fields.forEach(function(field, j) {
|
|
32915
|
+
columnData.push({name: field.name, data: values[j]});
|
|
32916
|
+
});
|
|
32917
|
+
return columnData;
|
|
32547
32918
|
}
|
|
32548
32919
|
|
|
32549
|
-
function
|
|
32920
|
+
function getFieldTypes(cursor, opts) {
|
|
32921
|
+
var properties = cursor.properties;
|
|
32922
|
+
if (!properties) return [];
|
|
32923
|
+
return getPropertyNames(properties, opts).map(function(name) {
|
|
32924
|
+
return {name: name, type: inferColumnType(properties, name)};
|
|
32925
|
+
});
|
|
32926
|
+
}
|
|
32927
|
+
|
|
32928
|
+
function inferColumnType(properties, name) {
|
|
32550
32929
|
var type = null;
|
|
32551
|
-
for (var i = 0; i <
|
|
32552
|
-
var
|
|
32930
|
+
for (var i = 0; i < properties.length; i++) {
|
|
32931
|
+
var props = properties[i];
|
|
32932
|
+
var valueType = inferValueType(props ? props[name] : null);
|
|
32553
32933
|
if (!valueType) continue;
|
|
32554
32934
|
if (!type) {
|
|
32555
32935
|
type = valueType;
|
|
@@ -32558,12 +32938,11 @@ ${svg}
|
|
|
32558
32938
|
(valueType == 'INT32' || valueType == 'DOUBLE')) {
|
|
32559
32939
|
type = 'DOUBLE';
|
|
32560
32940
|
} else {
|
|
32561
|
-
|
|
32562
|
-
break;
|
|
32941
|
+
return 'STRING';
|
|
32563
32942
|
}
|
|
32564
32943
|
}
|
|
32565
32944
|
}
|
|
32566
|
-
return
|
|
32945
|
+
return type || 'STRING';
|
|
32567
32946
|
}
|
|
32568
32947
|
|
|
32569
32948
|
function inferValueType(value) {
|
|
@@ -32604,22 +32983,172 @@ ${svg}
|
|
|
32604
32983
|
return String(value);
|
|
32605
32984
|
}
|
|
32606
32985
|
|
|
32607
|
-
function getPropertyNames(
|
|
32986
|
+
function getPropertyNames(properties, opts) {
|
|
32608
32987
|
var index = {};
|
|
32609
|
-
|
|
32610
|
-
|
|
32611
|
-
|
|
32612
|
-
|
|
32988
|
+
// Hoisted fields are moved to the root of a GeoJSON Feature, so they are not
|
|
32989
|
+
// among a feature's properties and don't become columns.
|
|
32990
|
+
var hoisted = Array.isArray(opts.hoist) ? opts.hoist : [];
|
|
32991
|
+
properties.forEach(function(props) {
|
|
32992
|
+
Object.keys(props || {}).forEach(function(name) {
|
|
32993
|
+
if (hoisted.indexOf(name) == -1) index[name] = true;
|
|
32613
32994
|
});
|
|
32614
32995
|
});
|
|
32615
32996
|
return Object.keys(index);
|
|
32616
32997
|
}
|
|
32617
32998
|
|
|
32618
|
-
|
|
32619
|
-
|
|
32620
|
-
|
|
32621
|
-
|
|
32622
|
-
|
|
32999
|
+
// A reader has to materialize an entire row group, so the meaningful unit is
|
|
33000
|
+
// bytes, not rows: a row of point geometry runs to a few dozen bytes where a
|
|
33001
|
+
// row of detailed polygon geometry can be tens of kilobytes. Sizing by a fixed
|
|
33002
|
+
// row count therefore lands hundreds of times off in either direction, so the
|
|
33003
|
+
// group size is derived from an estimate of the encoded row size instead.
|
|
33004
|
+
// The estimate ignores Parquet's own encoding, which only ever shrinks a
|
|
33005
|
+
// column, so groups tend to come out at half the target or less.
|
|
33006
|
+
//
|
|
33007
|
+
// The target is well below the 128MB that Hadoop-era guidance suggests, which
|
|
33008
|
+
// assumed a row group should fill an HDFS block. Mapshaper holds a whole
|
|
33009
|
+
// dataset in memory and needs roughly twenty times the output size to write
|
|
33010
|
+
// it, so it tops out around a couple of hundred megabytes -- at 128MB nearly
|
|
33011
|
+
// every file it can produce would be a single row group, giving readers no
|
|
33012
|
+
// parallelism and nothing to skip when filtering. Measured on a 23MB layer,
|
|
33013
|
+
// splitting one group into eight costs 0.03% of file size, so the smaller
|
|
33014
|
+
// target is close to free.
|
|
33015
|
+
var ROW_GROUP_TARGET_BYTES = 16 * 1024 * 1024;
|
|
33016
|
+
// A small leading group lets a reader fetching byte ranges over HTTP show the
|
|
33017
|
+
// start of the table without pulling a full-sized group.
|
|
33018
|
+
var ROW_GROUP_PREVIEW_BYTES = 1024 * 1024;
|
|
33019
|
+
var ROW_GROUP_MAX_ROWS = 1000000;
|
|
33020
|
+
var ROW_GROUP_SAMPLE_ROWS = 1000;
|
|
33021
|
+
|
|
33022
|
+
function getRowGroupSize(cursor, fields, hasGeometry, rowCount, override) {
|
|
33023
|
+
if (override) return override;
|
|
33024
|
+
var bytesPerRow = estimateRowBytes(cursor, fields, hasGeometry, rowCount);
|
|
33025
|
+
if (!bytesPerRow) return rowCount; // one group rather than the writer's default
|
|
33026
|
+
return planRowGroups(rowCount, bytesPerRow);
|
|
33027
|
+
}
|
|
33028
|
+
|
|
33029
|
+
// Returns a rowGroupSize for the Parquet writer: either a row count, or a
|
|
33030
|
+
// [preview, bulk] pair where the bulk size repeats for the rest of the file.
|
|
33031
|
+
function planRowGroups(rowCount, bytesPerRow) {
|
|
33032
|
+
var bulkRows = clampRowCount(ROW_GROUP_TARGET_BYTES / bytesPerRow, ROW_GROUP_MAX_ROWS);
|
|
33033
|
+
// Keep the preview well below a full group, otherwise splitting it off buys
|
|
33034
|
+
// a range reader nothing.
|
|
33035
|
+
var previewRows = clampRowCount(ROW_GROUP_PREVIEW_BYTES / bytesPerRow, Math.floor(bulkRows / 4));
|
|
33036
|
+
if (rowCount <= previewRows * 4) {
|
|
33037
|
+
return bulkRows;
|
|
33038
|
+
}
|
|
33039
|
+
// Spread the remaining rows evenly so that the file doesn't end with a runt
|
|
33040
|
+
// group holding a handful of rows.
|
|
33041
|
+
var rest = rowCount - previewRows;
|
|
33042
|
+
var groups = Math.ceil(rest / bulkRows);
|
|
33043
|
+
return [previewRows, Math.ceil(rest / groups)];
|
|
33044
|
+
}
|
|
33045
|
+
|
|
33046
|
+
function clampRowCount(rows, max) {
|
|
33047
|
+
if (!(rows > 1)) return 1;
|
|
33048
|
+
if (max >= 1 && rows > max) return max;
|
|
33049
|
+
return Math.floor(rows);
|
|
33050
|
+
}
|
|
33051
|
+
|
|
33052
|
+
// Converts the sampled rows one at a time and discards them, so that sizing
|
|
33053
|
+
// the row groups doesn't itself materialize the layer.
|
|
33054
|
+
function estimateRowBytes(cursor, fields, hasGeometry, rowCount) {
|
|
33055
|
+
// Sample at a stride rather than taking a prefix: layers are often sorted or
|
|
33056
|
+
// clustered, so the first rows are not representative.
|
|
33057
|
+
var step = Math.max(1, Math.floor(rowCount / ROW_GROUP_SAMPLE_ROWS));
|
|
33058
|
+
var total = 0;
|
|
33059
|
+
var sampled = 0;
|
|
33060
|
+
for (var i = 0; i < rowCount; i += step) {
|
|
33061
|
+
var feat = cursor.getFeature(i);
|
|
33062
|
+
var props = feat.properties;
|
|
33063
|
+
if (hasGeometry) {
|
|
33064
|
+
total += estimateValueBytes(feat.geometry || null);
|
|
33065
|
+
}
|
|
33066
|
+
for (var j = 0; j < fields.length; j++) {
|
|
33067
|
+
total += estimateValueBytes(
|
|
33068
|
+
normalizeFieldValue(props ? props[fields[j].name] : null, fields[j].type));
|
|
33069
|
+
}
|
|
33070
|
+
sampled++;
|
|
33071
|
+
}
|
|
33072
|
+
return sampled > 0 ? total / sampled : 0;
|
|
33073
|
+
}
|
|
33074
|
+
|
|
33075
|
+
function estimateValueBytes(value) {
|
|
33076
|
+
if (value === null || value === undefined) return 1;
|
|
33077
|
+
if (typeof value == 'string') return value.length + 4;
|
|
33078
|
+
if (typeof value == 'number' || typeof value == 'bigint') return 8;
|
|
33079
|
+
if (typeof value == 'boolean') return 1;
|
|
33080
|
+
if (value instanceof Date) return 8;
|
|
33081
|
+
if (value instanceof Uint8Array) return value.byteLength + 4;
|
|
33082
|
+
if (utils.isObject(value) && utils.isString(value.type)) {
|
|
33083
|
+
return estimateWkbBytes(value);
|
|
33084
|
+
}
|
|
33085
|
+
return 32; // a JSON-encoded column; the exact width doesn't change the sizing
|
|
33086
|
+
}
|
|
33087
|
+
|
|
33088
|
+
// WKB spends 1 byte on the byte order and 4 on the geometry type, 4 more on
|
|
33089
|
+
// each nested element count, and 16 on each XY position.
|
|
33090
|
+
function estimateWkbBytes(geom) {
|
|
33091
|
+
if (!geom || !geom.type) return 1;
|
|
33092
|
+
if (geom.type == 'GeometryCollection') {
|
|
33093
|
+
return (geom.geometries || []).reduce(function(memo, part) {
|
|
33094
|
+
return memo + estimateWkbBytes(part);
|
|
33095
|
+
}, 9);
|
|
33096
|
+
}
|
|
33097
|
+
return 5 + estimateCoordinateBytes(geom.coordinates);
|
|
33098
|
+
}
|
|
33099
|
+
|
|
33100
|
+
function estimateCoordinateBytes(coords) {
|
|
33101
|
+
if (!Array.isArray(coords)) return 0;
|
|
33102
|
+
if (typeof coords[0] == 'number') return 16;
|
|
33103
|
+
var bytes = 4;
|
|
33104
|
+
for (var i = 0; i < coords.length; i++) {
|
|
33105
|
+
bytes += estimateCoordinateBytes(coords[i]);
|
|
33106
|
+
}
|
|
33107
|
+
return bytes;
|
|
33108
|
+
}
|
|
33109
|
+
|
|
33110
|
+
function validateRowGroupSize(rowgroup) {
|
|
33111
|
+
if (rowgroup === undefined) return undefined;
|
|
33112
|
+
if (rowgroup >= 1 && Math.floor(rowgroup) === rowgroup) {
|
|
33113
|
+
return rowgroup;
|
|
33114
|
+
}
|
|
33115
|
+
stop$1('The rowgroup= option must be a positive integer number of rows');
|
|
33116
|
+
}
|
|
33117
|
+
|
|
33118
|
+
// The schema is derived from the column types alone, before any rows are
|
|
33119
|
+
// converted, because each row group is written as it is built and they all
|
|
33120
|
+
// have to share one schema.
|
|
33121
|
+
//
|
|
33122
|
+
// A reader that understands the Parquet 2.11 GEOMETRY logical type takes the
|
|
33123
|
+
// CRS from the logical type and ignores the "geo" metadata. When the logical
|
|
33124
|
+
// type carries no CRS the spec default is OGC:CRS84, so projected data was
|
|
33125
|
+
// being reported as WGS 84. Write the CRS in both places, as GDAL does, so
|
|
33126
|
+
// that GeoParquet 1.x readers keep working.
|
|
33127
|
+
function buildSchema(writer, fields, hasGeometry, crs) {
|
|
33128
|
+
var columnData = hasGeometry ?
|
|
33129
|
+
[{name: GEOMETRY_COLUMN, type: 'GEOMETRY'}] : [];
|
|
33130
|
+
var overrides;
|
|
33131
|
+
fields.forEach(function(field) {
|
|
33132
|
+
columnData.push({name: field.name, type: field.type});
|
|
33133
|
+
});
|
|
33134
|
+
if (hasGeometry && crs) {
|
|
33135
|
+
overrides = {};
|
|
33136
|
+
overrides[GEOMETRY_COLUMN] = {
|
|
33137
|
+
name: GEOMETRY_COLUMN,
|
|
33138
|
+
type: 'BYTE_ARRAY',
|
|
33139
|
+
repetition_type: 'OPTIONAL',
|
|
33140
|
+
logical_type: {type: 'GEOMETRY', crs: JSON.stringify(crs)}
|
|
33141
|
+
};
|
|
33142
|
+
// An overridden column must not also declare a type.
|
|
33143
|
+
columnData[0] = {name: GEOMETRY_COLUMN};
|
|
33144
|
+
}
|
|
33145
|
+
return writer.schemaFromColumnData({
|
|
33146
|
+
columnData: columnData,
|
|
33147
|
+
schemaOverrides: overrides
|
|
33148
|
+
});
|
|
33149
|
+
}
|
|
33150
|
+
|
|
33151
|
+
function buildGeoMetadata(geomTypes, crs) {
|
|
32623
33152
|
var geomMeta = {
|
|
32624
33153
|
encoding: 'WKB',
|
|
32625
33154
|
geometry_types: geomTypes
|
|
@@ -32671,13 +33200,19 @@ ${svg}
|
|
|
32671
33200
|
return null;
|
|
32672
33201
|
}
|
|
32673
33202
|
|
|
33203
|
+
function isGeoParquetWriter(mod) {
|
|
33204
|
+
return !!mod && typeof mod.ParquetWriter == 'function' &&
|
|
33205
|
+
typeof mod.ByteWriter == 'function' &&
|
|
33206
|
+
typeof mod.schemaFromColumnData == 'function';
|
|
33207
|
+
}
|
|
33208
|
+
|
|
32674
33209
|
async function loadGeoParquetWriter() {
|
|
32675
33210
|
if (runningInBrowser()) {
|
|
32676
33211
|
var mod = require$1('hyparquet-writer');
|
|
32677
|
-
if (mod && mod.default && !mod
|
|
33212
|
+
if (mod && mod.default && !isGeoParquetWriter(mod)) {
|
|
32678
33213
|
mod = mod.default;
|
|
32679
33214
|
}
|
|
32680
|
-
if (!mod
|
|
33215
|
+
if (!isGeoParquetWriter(mod)) {
|
|
32681
33216
|
stop$1('GeoParquet writer library is not loaded');
|
|
32682
33217
|
}
|
|
32683
33218
|
return mod;
|
|
@@ -32686,7 +33221,7 @@ ${svg}
|
|
|
32686
33221
|
writerPromise = dynamicImportModule$2('hyparquet-writer');
|
|
32687
33222
|
}
|
|
32688
33223
|
var nodeMod = await writerPromise;
|
|
32689
|
-
return nodeMod.default && !nodeMod
|
|
33224
|
+
return nodeMod.default && !isGeoParquetWriter(nodeMod) ? nodeMod.default : nodeMod;
|
|
32690
33225
|
}
|
|
32691
33226
|
|
|
32692
33227
|
async function getGeoParquetCompression(opts) {
|
|
@@ -32770,6 +33305,947 @@ ${svg}
|
|
|
32770
33305
|
return codec;
|
|
32771
33306
|
}
|
|
32772
33307
|
|
|
33308
|
+
// A minimal TIFF/GeoTIFF encoder, used by -o format=geotiff.
|
|
33309
|
+
//
|
|
33310
|
+
// This is hand-rolled rather than delegating to the geotiff dependency, whose
|
|
33311
|
+
// writer silently corrupts signed integer samples (its type table omits
|
|
33312
|
+
// Int8/16/32Array, so it falls back to writing 64-bit floats while the tags
|
|
33313
|
+
// still declare integer samples), tags data as Deflate without compressing it,
|
|
33314
|
+
// and builds its IFD in a fixed 1000-byte buffer that long ASCII tags overrun.
|
|
33315
|
+
//
|
|
33316
|
+
// Output is a classic (32-bit) little-endian TIFF with band-interleaved,
|
|
33317
|
+
// strip-based data, which is what our grids already look like in memory.
|
|
33318
|
+
|
|
33319
|
+
|
|
33320
|
+
var TIFF_TYPES = {
|
|
33321
|
+
ASCII: 2,
|
|
33322
|
+
SHORT: 3,
|
|
33323
|
+
LONG: 4,
|
|
33324
|
+
DOUBLE: 12
|
|
33325
|
+
};
|
|
33326
|
+
|
|
33327
|
+
var TYPE_SIZES = {
|
|
33328
|
+
2: 1,
|
|
33329
|
+
3: 2,
|
|
33330
|
+
4: 4,
|
|
33331
|
+
12: 8
|
|
33332
|
+
};
|
|
33333
|
+
|
|
33334
|
+
// Baseline tags, plus the GeoTIFF and GDAL extensions we need. Tags must appear
|
|
33335
|
+
// in the IFD in ascending numeric order.
|
|
33336
|
+
var TAGS = {
|
|
33337
|
+
ImageWidth: 256,
|
|
33338
|
+
ImageLength: 257,
|
|
33339
|
+
BitsPerSample: 258,
|
|
33340
|
+
Compression: 259,
|
|
33341
|
+
PhotometricInterpretation: 262,
|
|
33342
|
+
StripOffsets: 273,
|
|
33343
|
+
SamplesPerPixel: 277,
|
|
33344
|
+
RowsPerStrip: 278,
|
|
33345
|
+
StripByteCounts: 279,
|
|
33346
|
+
PlanarConfiguration: 284,
|
|
33347
|
+
Software: 305,
|
|
33348
|
+
ExtraSamples: 338,
|
|
33349
|
+
SampleFormat: 339,
|
|
33350
|
+
ModelPixelScale: 33550,
|
|
33351
|
+
ModelTiepoint: 33922,
|
|
33352
|
+
GeoKeyDirectory: 34735,
|
|
33353
|
+
GeoDoubleParams: 34736,
|
|
33354
|
+
GDAL_NODATA: 42113
|
|
33355
|
+
};
|
|
33356
|
+
|
|
33357
|
+
// The geo keys this writer knows how to emit, each with its id and its value
|
|
33358
|
+
// type. A key's type is fixed by the GeoTIFF spec: the short-valued keys hold
|
|
33359
|
+
// codes and the double-valued ones hold measurements, in degrees or in the
|
|
33360
|
+
// projection's linear units.
|
|
33361
|
+
var GEO_KEYS = {
|
|
33362
|
+
GTModelTypeGeoKey: [1024, 'short'],
|
|
33363
|
+
GTRasterTypeGeoKey: [1025, 'short'],
|
|
33364
|
+
GeographicTypeGeoKey: [2048, 'short'],
|
|
33365
|
+
GeogGeodeticDatumGeoKey: [2050, 'short'],
|
|
33366
|
+
GeogAngularUnitsGeoKey: [2054, 'short'],
|
|
33367
|
+
GeogEllipsoidGeoKey: [2056, 'short'],
|
|
33368
|
+
GeogSemiMajorAxisGeoKey: [2057, 'double'],
|
|
33369
|
+
GeogSemiMinorAxisGeoKey: [2058, 'double'],
|
|
33370
|
+
GeogInvFlatteningGeoKey: [2059, 'double'],
|
|
33371
|
+
GeogPrimeMeridianLongGeoKey: [2061, 'double'],
|
|
33372
|
+
ProjectedCSTypeGeoKey: [3072, 'short'],
|
|
33373
|
+
ProjectionGeoKey: [3074, 'short'],
|
|
33374
|
+
ProjCoordTransGeoKey: [3075, 'short'],
|
|
33375
|
+
ProjLinearUnitsGeoKey: [3076, 'short'],
|
|
33376
|
+
ProjLinearUnitSizeGeoKey: [3077, 'double'],
|
|
33377
|
+
ProjStdParallel1GeoKey: [3078, 'double'],
|
|
33378
|
+
ProjStdParallel2GeoKey: [3079, 'double'],
|
|
33379
|
+
ProjNatOriginLongGeoKey: [3080, 'double'],
|
|
33380
|
+
ProjNatOriginLatGeoKey: [3081, 'double'],
|
|
33381
|
+
ProjFalseEastingGeoKey: [3082, 'double'],
|
|
33382
|
+
ProjFalseNorthingGeoKey: [3083, 'double'],
|
|
33383
|
+
ProjFalseOriginLongGeoKey: [3084, 'double'],
|
|
33384
|
+
ProjFalseOriginLatGeoKey: [3085, 'double'],
|
|
33385
|
+
ProjFalseOriginEastingGeoKey: [3086, 'double'],
|
|
33386
|
+
ProjFalseOriginNorthingGeoKey: [3087, 'double'],
|
|
33387
|
+
ProjCenterLongGeoKey: [3088, 'double'],
|
|
33388
|
+
ProjCenterLatGeoKey: [3089, 'double'],
|
|
33389
|
+
ProjScaleAtNatOriginGeoKey: [3092, 'double'],
|
|
33390
|
+
ProjScaleAtCenterGeoKey: [3093, 'double'],
|
|
33391
|
+
ProjAzimuthAngleGeoKey: [3094, 'double'],
|
|
33392
|
+
ProjStraightVertPoleLongGeoKey: [3095, 'double'],
|
|
33393
|
+
ProjRectifiedGridAngleGeoKey: [3096, 'double']
|
|
33394
|
+
};
|
|
33395
|
+
|
|
33396
|
+
var COMPRESSION_NONE = 1;
|
|
33397
|
+
var COMPRESSION_DEFLATE = 8;
|
|
33398
|
+
var PHOTOMETRIC_BLACK_IS_ZERO = 1;
|
|
33399
|
+
var PHOTOMETRIC_RGB = 2;
|
|
33400
|
+
var PLANAR_CONFIG_CHUNKY = 1; // band-interleaved, matching grid.samples
|
|
33401
|
+
var EXTRA_SAMPLE_UNSPECIFIED = 0;
|
|
33402
|
+
var EXTRA_SAMPLE_UNASSOCIATED_ALPHA = 2;
|
|
33403
|
+
var SAMPLE_FORMAT_UINT = 1;
|
|
33404
|
+
var SAMPLE_FORMAT_INT = 2;
|
|
33405
|
+
var SAMPLE_FORMAT_FLOAT = 3;
|
|
33406
|
+
|
|
33407
|
+
// Rows are grouped into strips of about this size, so that a large raster is
|
|
33408
|
+
// compressed in independent chunks instead of one enormous one. Each strip
|
|
33409
|
+
// costs 8 bytes of tag data, so this trades a small header against the memory
|
|
33410
|
+
// a single compression call has to hold.
|
|
33411
|
+
var TARGET_STRIP_BYTES = 256 * 1024;
|
|
33412
|
+
|
|
33413
|
+
// grid: a mapshaper raster grid (width, height, bands, samples, bbox, nodata)
|
|
33414
|
+
// opts.geoKeys: GeoTIFF geo keys to write, e.g. {ProjectedCSTypeGeoKey: 32615}
|
|
33415
|
+
// opts.compress: false to store samples uncompressed
|
|
33416
|
+
// Returns a Uint8Array holding the whole file.
|
|
33417
|
+
function encodeGeoTIFF(grid, opts) {
|
|
33418
|
+
opts = opts || {};
|
|
33419
|
+
var compress = opts.compress !== false;
|
|
33420
|
+
var rowsPerStrip = getRowsPerStrip(grid);
|
|
33421
|
+
var strips = encodeStrips(grid, rowsPerStrip, compress);
|
|
33422
|
+
var entries = getTagEntries(grid, strips, rowsPerStrip, compress, opts.geoKeys);
|
|
33423
|
+
var layout = getFileLayout(entries, strips);
|
|
33424
|
+
var bytes = new Uint8Array(layout.fileSize);
|
|
33425
|
+
var view = new DataView(bytes.buffer);
|
|
33426
|
+
setStripOffsets(entries, strips, layout.stripDataOffset);
|
|
33427
|
+
writeHeader(view);
|
|
33428
|
+
writeIfd(view, bytes, entries, layout);
|
|
33429
|
+
writeStrips(bytes, strips, layout.stripDataOffset);
|
|
33430
|
+
return bytes;
|
|
33431
|
+
}
|
|
33432
|
+
|
|
33433
|
+
function writeHeader(view) {
|
|
33434
|
+
view.setUint8(0, 0x49); // 'II' -- little-endian byte order
|
|
33435
|
+
view.setUint8(1, 0x49);
|
|
33436
|
+
view.setUint16(2, 42, true); // TIFF magic number
|
|
33437
|
+
view.setUint32(4, 8, true); // offset of the first IFD
|
|
33438
|
+
}
|
|
33439
|
+
|
|
33440
|
+
function writeIfd(view, bytes, entries, layout) {
|
|
33441
|
+
var offset = layout.ifdOffset;
|
|
33442
|
+
var valueOffset = layout.valueDataOffset;
|
|
33443
|
+
view.setUint16(offset, entries.length, true);
|
|
33444
|
+
offset += 2;
|
|
33445
|
+
entries.forEach(function(entry) {
|
|
33446
|
+
var size = getEntryDataSize(entry);
|
|
33447
|
+
view.setUint16(offset, entry.tag, true);
|
|
33448
|
+
view.setUint16(offset + 2, entry.type, true);
|
|
33449
|
+
view.setUint32(offset + 4, entry.count, true);
|
|
33450
|
+
if (size <= 4) {
|
|
33451
|
+
// Small values live in the entry itself, left-aligned in the 4 bytes.
|
|
33452
|
+
writeEntryValues(view, bytes, entry, offset + 8);
|
|
33453
|
+
} else {
|
|
33454
|
+
view.setUint32(offset + 8, valueOffset, true);
|
|
33455
|
+
writeEntryValues(view, bytes, entry, valueOffset);
|
|
33456
|
+
valueOffset += size + (size % 2); // value blocks start on a word boundary
|
|
33457
|
+
}
|
|
33458
|
+
offset += 12;
|
|
33459
|
+
});
|
|
33460
|
+
view.setUint32(offset, 0, true); // no second IFD
|
|
33461
|
+
}
|
|
33462
|
+
|
|
33463
|
+
function writeEntryValues(view, bytes, entry, offset) {
|
|
33464
|
+
var values = entry.values;
|
|
33465
|
+
if (entry.type == TIFF_TYPES.ASCII) {
|
|
33466
|
+
writeAsciiValue(bytes, values, offset);
|
|
33467
|
+
return;
|
|
33468
|
+
}
|
|
33469
|
+
for (var i = 0; i < values.length; i++) {
|
|
33470
|
+
if (entry.type == TIFF_TYPES.SHORT) {
|
|
33471
|
+
view.setUint16(offset + i * 2, values[i], true);
|
|
33472
|
+
} else if (entry.type == TIFF_TYPES.LONG) {
|
|
33473
|
+
view.setUint32(offset + i * 4, values[i], true);
|
|
33474
|
+
} else if (entry.type == TIFF_TYPES.DOUBLE) {
|
|
33475
|
+
view.setFloat64(offset + i * 8, values[i], true);
|
|
33476
|
+
} else {
|
|
33477
|
+
error('Unsupported TIFF field type:', entry.type);
|
|
33478
|
+
}
|
|
33479
|
+
}
|
|
33480
|
+
}
|
|
33481
|
+
|
|
33482
|
+
// ASCII fields are NUL-terminated, and the terminator is counted.
|
|
33483
|
+
function writeAsciiValue(bytes, str, offset) {
|
|
33484
|
+
for (var i = 0; i < str.length; i++) {
|
|
33485
|
+
bytes[offset + i] = str.charCodeAt(i) & 0x7f;
|
|
33486
|
+
}
|
|
33487
|
+
bytes[offset + str.length] = 0;
|
|
33488
|
+
}
|
|
33489
|
+
|
|
33490
|
+
function getEntryDataSize(entry) {
|
|
33491
|
+
return TYPE_SIZES[entry.type] * entry.count;
|
|
33492
|
+
}
|
|
33493
|
+
|
|
33494
|
+
function getFileLayout(entries, strips) {
|
|
33495
|
+
var ifdOffset = 8;
|
|
33496
|
+
var ifdSize = 2 + entries.length * 12 + 4;
|
|
33497
|
+
var valueDataOffset = ifdOffset + ifdSize;
|
|
33498
|
+
var valueDataSize = entries.reduce(function(memo, entry) {
|
|
33499
|
+
var size = getEntryDataSize(entry);
|
|
33500
|
+
return size <= 4 ? memo : memo + size + (size % 2);
|
|
33501
|
+
}, 0);
|
|
33502
|
+
var stripDataOffset = valueDataOffset + valueDataSize;
|
|
33503
|
+
var stripDataSize = strips.reduce(function(memo, strip) {
|
|
33504
|
+
return memo + strip.length;
|
|
33505
|
+
}, 0);
|
|
33506
|
+
return {
|
|
33507
|
+
ifdOffset: ifdOffset,
|
|
33508
|
+
valueDataOffset: valueDataOffset,
|
|
33509
|
+
stripDataOffset: stripDataOffset,
|
|
33510
|
+
fileSize: stripDataOffset + stripDataSize
|
|
33511
|
+
};
|
|
33512
|
+
}
|
|
33513
|
+
|
|
33514
|
+
// StripOffsets can only be filled in once the size of the tag data is known.
|
|
33515
|
+
function setStripOffsets(entries, strips, stripDataOffset) {
|
|
33516
|
+
var entry = findEntry(entries, TAGS.StripOffsets);
|
|
33517
|
+
var offset = stripDataOffset;
|
|
33518
|
+
strips.forEach(function(strip, i) {
|
|
33519
|
+
entry.values[i] = offset;
|
|
33520
|
+
offset += strip.length;
|
|
33521
|
+
});
|
|
33522
|
+
}
|
|
33523
|
+
|
|
33524
|
+
function writeStrips(bytes, strips, stripDataOffset) {
|
|
33525
|
+
var offset = stripDataOffset;
|
|
33526
|
+
strips.forEach(function(strip) {
|
|
33527
|
+
bytes.set(strip, offset);
|
|
33528
|
+
offset += strip.length;
|
|
33529
|
+
});
|
|
33530
|
+
}
|
|
33531
|
+
|
|
33532
|
+
function findEntry(entries, tag) {
|
|
33533
|
+
return entries.filter(function(entry) {
|
|
33534
|
+
return entry.tag == tag;
|
|
33535
|
+
})[0];
|
|
33536
|
+
}
|
|
33537
|
+
|
|
33538
|
+
function encodeStrips(grid, rowsPerStrip, compress) {
|
|
33539
|
+
var sampleBytes = getSampleBytes(grid.samples);
|
|
33540
|
+
var bytesPerRow = getBytesPerRow(grid);
|
|
33541
|
+
var strips = [];
|
|
33542
|
+
var row = 0;
|
|
33543
|
+
var start, end, chunk;
|
|
33544
|
+
while (row < grid.height) {
|
|
33545
|
+
start = row * bytesPerRow;
|
|
33546
|
+
end = Math.min(start + rowsPerStrip * bytesPerRow, sampleBytes.length);
|
|
33547
|
+
chunk = sampleBytes.subarray(start, end);
|
|
33548
|
+
strips.push(compress ? deflateSync(chunk) : chunk);
|
|
33549
|
+
row += rowsPerStrip;
|
|
33550
|
+
}
|
|
33551
|
+
return strips;
|
|
33552
|
+
}
|
|
33553
|
+
|
|
33554
|
+
function getRowsPerStrip(grid) {
|
|
33555
|
+
var rows = Math.floor(TARGET_STRIP_BYTES / Math.max(getBytesPerRow(grid), 1));
|
|
33556
|
+
return Math.max(1, Math.min(rows, grid.height));
|
|
33557
|
+
}
|
|
33558
|
+
|
|
33559
|
+
function getBytesPerRow(grid) {
|
|
33560
|
+
return getBytesPerSample(grid.samples) * grid.bands * grid.width;
|
|
33561
|
+
}
|
|
33562
|
+
|
|
33563
|
+
// TIFF stores samples in the file's byte order, which is little-endian here.
|
|
33564
|
+
// Typed arrays use the platform's order, so a byte view can be handed straight
|
|
33565
|
+
// to the encoder on a little-endian machine (all common ones) and needs
|
|
33566
|
+
// swapping otherwise.
|
|
33567
|
+
function getSampleBytes(samples) {
|
|
33568
|
+
var bytes = new Uint8Array(samples.buffer, samples.byteOffset, samples.byteLength);
|
|
33569
|
+
var size = getBytesPerSample(samples);
|
|
33570
|
+
if (size == 1 || platformIsLittleEndian()) return bytes;
|
|
33571
|
+
return swapByteOrder(bytes, size);
|
|
33572
|
+
}
|
|
33573
|
+
|
|
33574
|
+
function platformIsLittleEndian() {
|
|
33575
|
+
var probe = new Uint16Array([1]);
|
|
33576
|
+
return new Uint8Array(probe.buffer)[0] === 1;
|
|
33577
|
+
}
|
|
33578
|
+
|
|
33579
|
+
function swapByteOrder(bytes, size) {
|
|
33580
|
+
var swapped = new Uint8Array(bytes.length);
|
|
33581
|
+
for (var i = 0; i < bytes.length; i += size) {
|
|
33582
|
+
for (var j = 0; j < size; j++) {
|
|
33583
|
+
swapped[i + j] = bytes[i + size - 1 - j];
|
|
33584
|
+
}
|
|
33585
|
+
}
|
|
33586
|
+
return swapped;
|
|
33587
|
+
}
|
|
33588
|
+
|
|
33589
|
+
function getBytesPerSample(samples) {
|
|
33590
|
+
return samples.BYTES_PER_ELEMENT || 1;
|
|
33591
|
+
}
|
|
33592
|
+
|
|
33593
|
+
function getSampleFormat(samples) {
|
|
33594
|
+
if (samples instanceof Float32Array || samples instanceof Float64Array) {
|
|
33595
|
+
return SAMPLE_FORMAT_FLOAT;
|
|
33596
|
+
}
|
|
33597
|
+
if (samples instanceof Int8Array || samples instanceof Int16Array ||
|
|
33598
|
+
samples instanceof Int32Array) {
|
|
33599
|
+
return SAMPLE_FORMAT_INT;
|
|
33600
|
+
}
|
|
33601
|
+
if (samples instanceof Uint8Array || samples instanceof Uint8ClampedArray ||
|
|
33602
|
+
samples instanceof Uint16Array || samples instanceof Uint32Array) {
|
|
33603
|
+
return SAMPLE_FORMAT_UINT;
|
|
33604
|
+
}
|
|
33605
|
+
error('Unsupported raster sample type');
|
|
33606
|
+
}
|
|
33607
|
+
|
|
33608
|
+
function getTagEntries(grid, strips, rowsPerStrip, compress, geoKeys) {
|
|
33609
|
+
var bands = grid.bands;
|
|
33610
|
+
var bytesPerSample = getBytesPerSample(grid.samples);
|
|
33611
|
+
var entries = [
|
|
33612
|
+
long(TAGS.ImageWidth, [grid.width]),
|
|
33613
|
+
long(TAGS.ImageLength, [grid.height]),
|
|
33614
|
+
short(TAGS.BitsPerSample, repeat(bytesPerSample * 8, bands)),
|
|
33615
|
+
short(TAGS.Compression, [compress ? COMPRESSION_DEFLATE : COMPRESSION_NONE]),
|
|
33616
|
+
short(TAGS.PhotometricInterpretation, [getPhotometricInterpretation$1(bands)]),
|
|
33617
|
+
long(TAGS.StripOffsets, repeat(0, strips.length)), // filled in after layout
|
|
33618
|
+
short(TAGS.SamplesPerPixel, [bands]),
|
|
33619
|
+
long(TAGS.RowsPerStrip, [rowsPerStrip]),
|
|
33620
|
+
long(TAGS.StripByteCounts, strips.map(function(strip) {
|
|
33621
|
+
return strip.length;
|
|
33622
|
+
})),
|
|
33623
|
+
short(TAGS.PlanarConfiguration, [PLANAR_CONFIG_CHUNKY]),
|
|
33624
|
+
ascii(TAGS.Software, 'mapshaper'),
|
|
33625
|
+
short(TAGS.SampleFormat, repeat(getSampleFormat(grid.samples), bands))
|
|
33626
|
+
];
|
|
33627
|
+
var extraSamples = getExtraSamples(bands);
|
|
33628
|
+
if (extraSamples) {
|
|
33629
|
+
entries.push(short(TAGS.ExtraSamples, extraSamples));
|
|
33630
|
+
}
|
|
33631
|
+
entries = entries.concat(getGeoreferencingEntries(grid, geoKeys));
|
|
33632
|
+
if (grid.nodata !== null && grid.nodata !== undefined) {
|
|
33633
|
+
// GDAL stores this value as text, and reads it back with a float parser
|
|
33634
|
+
// that accepts both integers and "nan".
|
|
33635
|
+
entries.push(ascii(TAGS.GDAL_NODATA, String(grid.nodata)));
|
|
33636
|
+
}
|
|
33637
|
+
return entries.sort(function(a, b) {
|
|
33638
|
+
return a.tag - b.tag;
|
|
33639
|
+
});
|
|
33640
|
+
}
|
|
33641
|
+
|
|
33642
|
+
// Three or more bands are rendered as RGB(A) elsewhere in mapshaper, so they
|
|
33643
|
+
// are tagged that way here; anything else is a measurement raster whose first
|
|
33644
|
+
// band is the one to display.
|
|
33645
|
+
function getPhotometricInterpretation$1(bands) {
|
|
33646
|
+
return bands >= 3 ? PHOTOMETRIC_RGB : PHOTOMETRIC_BLACK_IS_ZERO;
|
|
33647
|
+
}
|
|
33648
|
+
|
|
33649
|
+
// Bands beyond the ones the photometric interpretation accounts for have to be
|
|
33650
|
+
// declared as extra samples. A fourth band is treated as alpha, matching the
|
|
33651
|
+
// renderer; other extras are left unspecified rather than guessing at them.
|
|
33652
|
+
function getExtraSamples(bands) {
|
|
33653
|
+
var colorBands = bands >= 3 ? 3 : 1;
|
|
33654
|
+
var extras = repeat(EXTRA_SAMPLE_UNSPECIFIED, bands - colorBands);
|
|
33655
|
+
if (extras.length === 0) return null;
|
|
33656
|
+
if (bands == 4) extras[0] = EXTRA_SAMPLE_UNASSOCIATED_ALPHA;
|
|
33657
|
+
return extras;
|
|
33658
|
+
}
|
|
33659
|
+
|
|
33660
|
+
function getGeoreferencingEntries(grid, geoKeys) {
|
|
33661
|
+
var bbox = grid.bbox;
|
|
33662
|
+
var dx = Math.abs(bbox[2] - bbox[0]) / grid.width;
|
|
33663
|
+
var dy = Math.abs(bbox[3] - bbox[1]) / grid.height;
|
|
33664
|
+
var entries = [
|
|
33665
|
+
// Pixel scale is unsigned; the tiepoint anchors the grid at its top-left
|
|
33666
|
+
// corner, which is where row 0 of the samples begins.
|
|
33667
|
+
double(TAGS.ModelPixelScale, [dx, dy, 0]),
|
|
33668
|
+
double(TAGS.ModelTiepoint, [0, 0, 0, bbox[0], bbox[3], 0])
|
|
33669
|
+
];
|
|
33670
|
+
return entries.concat(getGeoKeyEntries(geoKeys));
|
|
33671
|
+
}
|
|
33672
|
+
|
|
33673
|
+
// The geo keys live in a directory tag of their own: a four-short header
|
|
33674
|
+
// (version, revision, minor revision, key count) followed by four shorts per
|
|
33675
|
+
// key. A short-valued key keeps its value in the directory; a double-valued one
|
|
33676
|
+
// points into the GeoDoubleParams tag, which holds them all end to end.
|
|
33677
|
+
function getGeoKeyEntries(geoKeys) {
|
|
33678
|
+
var names = Object.keys(geoKeys || {}).filter(function(name) {
|
|
33679
|
+
return name in GEO_KEYS && isFinite(geoKeys[name]);
|
|
33680
|
+
}).sort(function(a, b) {
|
|
33681
|
+
return GEO_KEYS[a][0] - GEO_KEYS[b][0];
|
|
33682
|
+
});
|
|
33683
|
+
var values = [1, 1, 0, names.length];
|
|
33684
|
+
var doubles = [];
|
|
33685
|
+
if (names.length === 0) return [];
|
|
33686
|
+
names.forEach(function(name) {
|
|
33687
|
+
var id = GEO_KEYS[name][0];
|
|
33688
|
+
if (GEO_KEYS[name][1] == 'double') {
|
|
33689
|
+
values.push(id, TAGS.GeoDoubleParams, 1, doubles.length);
|
|
33690
|
+
doubles.push(geoKeys[name]);
|
|
33691
|
+
} else {
|
|
33692
|
+
values.push(id, 0, 1, geoKeys[name]);
|
|
33693
|
+
}
|
|
33694
|
+
});
|
|
33695
|
+
var entries = [short(TAGS.GeoKeyDirectory, values)];
|
|
33696
|
+
if (doubles.length > 0) {
|
|
33697
|
+
entries.push(double(TAGS.GeoDoubleParams, doubles));
|
|
33698
|
+
}
|
|
33699
|
+
return entries;
|
|
33700
|
+
}
|
|
33701
|
+
|
|
33702
|
+
function repeat(val, count) {
|
|
33703
|
+
var arr = [];
|
|
33704
|
+
for (var i = 0; i < count; i++) arr.push(val);
|
|
33705
|
+
return arr;
|
|
33706
|
+
}
|
|
33707
|
+
|
|
33708
|
+
function short(tag, values) {
|
|
33709
|
+
return {tag: tag, type: TIFF_TYPES.SHORT, count: values.length, values: values};
|
|
33710
|
+
}
|
|
33711
|
+
|
|
33712
|
+
function long(tag, values) {
|
|
33713
|
+
return {tag: tag, type: TIFF_TYPES.LONG, count: values.length, values: values};
|
|
33714
|
+
}
|
|
33715
|
+
|
|
33716
|
+
function double(tag, values) {
|
|
33717
|
+
return {tag: tag, type: TIFF_TYPES.DOUBLE, count: values.length, values: values};
|
|
33718
|
+
}
|
|
33719
|
+
|
|
33720
|
+
function ascii(tag, str) {
|
|
33721
|
+
return {tag: tag, type: TIFF_TYPES.ASCII, count: str.length + 1, values: str};
|
|
33722
|
+
}
|
|
33723
|
+
|
|
33724
|
+
var GeoTIFFEncode = /*#__PURE__*/Object.freeze({
|
|
33725
|
+
__proto__: null,
|
|
33726
|
+
encodeGeoTIFF: encodeGeoTIFF
|
|
33727
|
+
});
|
|
33728
|
+
|
|
33729
|
+
// Translate between mapshaper's CRS objects and the GeoTIFF geo keys that
|
|
33730
|
+
// describe a projection, in both directions.
|
|
33731
|
+
//
|
|
33732
|
+
// A GeoTIFF names a projection either by an authority code or by spelling it
|
|
33733
|
+
// out: a coordinate transformation code plus the parameters it takes. This
|
|
33734
|
+
// module handles the second, longer form, which is the only way to write a
|
|
33735
|
+
// projection that has no EPSG code, and the only way to read one back.
|
|
33736
|
+
//
|
|
33737
|
+
// The key-by-key layout follows what GDAL writes for the same projection, since
|
|
33738
|
+
// that is what other software is used to reading.
|
|
33739
|
+
|
|
33740
|
+
|
|
33741
|
+
var R2D$1 = 180 / Math.PI;
|
|
33742
|
+
|
|
33743
|
+
// The coordinate transformation codes of the GeoTIFF spec, and where each
|
|
33744
|
+
// projection's parameters go. Projections missing from this table, including
|
|
33745
|
+
// mapshaper's interrupted and polyhedral ones, have no GeoTIFF representation.
|
|
33746
|
+
//
|
|
33747
|
+
// ct: the ProjCoordTransGeoKey code
|
|
33748
|
+
// proj: the proj4 name to read the code back as, when it differs from the entry
|
|
33749
|
+
// origin: the geo keys that carry lon_0 and lat_0
|
|
33750
|
+
// falseXY: the pair that carries x_0 and y_0
|
|
33751
|
+
// scale: the key that carries k_0, for projections that take one
|
|
33752
|
+
// parallels: whether the standard-parallel keys carry lat_ts or lat_1 and lat_2
|
|
33753
|
+
// azimuth: the projection is skewed by an +alpha angle
|
|
33754
|
+
// encodeOnly: another entry with the same code is the one to read it back as
|
|
33755
|
+
var PROJECTIONS = {
|
|
33756
|
+
tmerc: {ct: 1, origin: 'nat', falseXY: 'nat', scale: 'nat'},
|
|
33757
|
+
utm: {ct: 1, origin: 'nat', falseXY: 'nat', scale: 'nat', proj: 'tmerc', encodeOnly: true},
|
|
33758
|
+
// Oblique Mercator comes in two variants, which differ in whether the false
|
|
33759
|
+
// easting and northing are applied at the projection center or at the point
|
|
33760
|
+
// where its skewed axis crosses the equator. GDAL identifies the first by its
|
|
33761
|
+
// EPSG method code rather than by a GeoTIFF transformation code.
|
|
33762
|
+
omerc: {ct: 9815, origin: 'center', falseXY: 'nat', scale: 'center', azimuth: true},
|
|
33763
|
+
omercNoUoff: {ct: 3, origin: 'center', falseXY: 'nat', scale: 'center', azimuth: true, proj: 'omerc', noUoff: true},
|
|
33764
|
+
merc: {ct: 7, origin: 'nat', falseXY: 'nat', scale: 'nat'},
|
|
33765
|
+
// Mercator's scale can be set either by a scale factor at the equator or by a
|
|
33766
|
+
// parallel of true scale, and the two are alternatives.
|
|
33767
|
+
mercStdParallel: {ct: 7, origin: 'nat', falseXY: 'nat', parallels: 'lat_ts', proj: 'merc'},
|
|
33768
|
+
lcc: {ct: 8, origin: 'falseOrigin', falseXY: 'falseOrigin', parallels: 'lat_1_2'},
|
|
33769
|
+
// With a single standard parallel the cone touches the globe, and the scale
|
|
33770
|
+
// factor at that parallel becomes a parameter.
|
|
33771
|
+
lcc1sp: {ct: 9, origin: 'nat', falseXY: 'nat', scale: 'nat', proj: 'lcc', tangentParallel: true},
|
|
33772
|
+
laea: {ct: 10, origin: 'center', falseXY: 'nat'},
|
|
33773
|
+
aea: {ct: 11, origin: 'nat', falseXY: 'nat', parallels: 'lat_1_2'},
|
|
33774
|
+
aeqd: {ct: 12, origin: 'center', falseXY: 'nat'},
|
|
33775
|
+
eqdc: {ct: 13, origin: 'nat', falseXY: 'nat', parallels: 'lat_1_2'},
|
|
33776
|
+
stere: {ct: 14, origin: 'center', falseXY: 'nat', scale: 'nat'},
|
|
33777
|
+
sterePolar: {ct: 15, origin: 'polar', falseXY: 'nat', scale: 'nat', proj: 'stere'},
|
|
33778
|
+
sterea: {ct: 16, origin: 'nat', falseXY: 'nat', scale: 'nat'},
|
|
33779
|
+
eqc: {ct: 17, origin: 'center', falseXY: 'nat', parallels: 'lat_ts'},
|
|
33780
|
+
cass: {ct: 18, origin: 'nat', falseXY: 'nat'},
|
|
33781
|
+
gnom: {ct: 19, origin: 'center', falseXY: 'nat'},
|
|
33782
|
+
mill: {ct: 20, origin: 'center', falseXY: 'nat'},
|
|
33783
|
+
ortho: {ct: 21, origin: 'center', falseXY: 'nat'},
|
|
33784
|
+
poly: {ct: 22, origin: 'nat', falseXY: 'nat', scale: 'nat'},
|
|
33785
|
+
robin: {ct: 23, origin: 'centerLong', falseXY: 'nat'},
|
|
33786
|
+
sinu: {ct: 24, origin: 'centerLong', falseXY: 'nat'},
|
|
33787
|
+
vandg: {ct: 25, origin: 'centerLong', falseXY: 'nat'}
|
|
33788
|
+
};
|
|
33789
|
+
|
|
33790
|
+
var ORIGIN_KEYS = {
|
|
33791
|
+
nat: ['ProjNatOriginLongGeoKey', 'ProjNatOriginLatGeoKey'],
|
|
33792
|
+
center: ['ProjCenterLongGeoKey', 'ProjCenterLatGeoKey'],
|
|
33793
|
+
centerLong: ['ProjCenterLongGeoKey', null],
|
|
33794
|
+
falseOrigin: ['ProjFalseOriginLongGeoKey', 'ProjFalseOriginLatGeoKey'],
|
|
33795
|
+
// A polar stereographic projection's longitude is the meridian running down
|
|
33796
|
+
// the map, and its latitude is the parallel of true scale.
|
|
33797
|
+
polar: ['ProjStraightVertPoleLongGeoKey', 'ProjNatOriginLatGeoKey']
|
|
33798
|
+
};
|
|
33799
|
+
|
|
33800
|
+
var FALSE_XY_KEYS = {
|
|
33801
|
+
nat: ['ProjFalseEastingGeoKey', 'ProjFalseNorthingGeoKey'],
|
|
33802
|
+
falseOrigin: ['ProjFalseOriginEastingGeoKey', 'ProjFalseOriginNorthingGeoKey']
|
|
33803
|
+
};
|
|
33804
|
+
|
|
33805
|
+
var SCALE_KEYS = {
|
|
33806
|
+
nat: 'ProjScaleAtNatOriginGeoKey',
|
|
33807
|
+
center: 'ProjScaleAtCenterGeoKey'
|
|
33808
|
+
};
|
|
33809
|
+
|
|
33810
|
+
// Geographic CRS codes for the datums that mapshaper can name.
|
|
33811
|
+
var DATUM_CODES = {
|
|
33812
|
+
WGS84: 4326,
|
|
33813
|
+
NAD83: 4269,
|
|
33814
|
+
NAD27: 4267
|
|
33815
|
+
};
|
|
33816
|
+
|
|
33817
|
+
var LINEAR_UNIT_CODES = [
|
|
33818
|
+
[1, 9001], // metre
|
|
33819
|
+
[0.3048, 9002], // international foot
|
|
33820
|
+
[0.3048006096012192, 9003] // US survey foot
|
|
33821
|
+
];
|
|
33822
|
+
|
|
33823
|
+
var USER_DEFINED = 32767;
|
|
33824
|
+
var ANGULAR_DEGREE = 9102;
|
|
33825
|
+
var MODEL_TYPE_PROJECTED$1 = 1;
|
|
33826
|
+
var MODEL_TYPE_GEOGRAPHIC$1 = 2;
|
|
33827
|
+
|
|
33828
|
+
// The proj4 parameters that describe a projection's shape and placement, as
|
|
33829
|
+
// opposed to the ellipsoid it sits on. These are the ones this module claims;
|
|
33830
|
+
// everything else in a proj4 string is left alone.
|
|
33831
|
+
var PROJECTION_PARAMS = ['proj', 'lat_0', 'lon_0', 'lonc', 'lat_1', 'lat_2',
|
|
33832
|
+
'lat_ts', 'k', 'k_0', 'x_0', 'y_0', 'alpha', 'gamma', 'zone', 'south',
|
|
33833
|
+
'no_uoff', 'no_off', 'no_rot'];
|
|
33834
|
+
|
|
33835
|
+
// Returns geo keys describing a CRS, or null if it has no GeoTIFF
|
|
33836
|
+
// representation. Callers add the raster-type key.
|
|
33837
|
+
function getCrsGeoKeys(crs) {
|
|
33838
|
+
var proj4 = getProj4(crs);
|
|
33839
|
+
var params = proj4 ? parseProj4Params(proj4) : null;
|
|
33840
|
+
// A mixed projection is several projections in one frame, which a GeoTIFF
|
|
33841
|
+
// cannot describe. Its proj4 string mentions only the main one, so it has to
|
|
33842
|
+
// be excluded by hand rather than by failing to match the table.
|
|
33843
|
+
if (!params || crs.__mixed_crs) return null;
|
|
33844
|
+
return crs.is_latlong ? getGeographicGeoKeys(crs, params) :
|
|
33845
|
+
getProjectedGeoKeys(crs, params);
|
|
33846
|
+
}
|
|
33847
|
+
|
|
33848
|
+
function getProj4(crs) {
|
|
33849
|
+
try {
|
|
33850
|
+
return crsToProj4(crs);
|
|
33851
|
+
} catch(e) {
|
|
33852
|
+
return null;
|
|
33853
|
+
}
|
|
33854
|
+
}
|
|
33855
|
+
|
|
33856
|
+
function getGeographicGeoKeys(crs, params) {
|
|
33857
|
+
return Object.assign({
|
|
33858
|
+
GTModelTypeGeoKey: MODEL_TYPE_GEOGRAPHIC$1
|
|
33859
|
+
}, getDatumGeoKeys(crs, params));
|
|
33860
|
+
}
|
|
33861
|
+
|
|
33862
|
+
function getProjectedGeoKeys(crs, params) {
|
|
33863
|
+
var defn = getEncodingDefn(crs, params);
|
|
33864
|
+
var keys;
|
|
33865
|
+
if (!defn) return null;
|
|
33866
|
+
keys = Object.assign({
|
|
33867
|
+
GTModelTypeGeoKey: MODEL_TYPE_PROJECTED$1
|
|
33868
|
+
}, getDatumGeoKeys(crs, params), {
|
|
33869
|
+
// The projection is spelled out in the keys below rather than named by a
|
|
33870
|
+
// code, which is what "user-defined" announces.
|
|
33871
|
+
ProjectedCSTypeGeoKey: USER_DEFINED,
|
|
33872
|
+
ProjectionGeoKey: USER_DEFINED,
|
|
33873
|
+
ProjCoordTransGeoKey: defn.ct
|
|
33874
|
+
}, getLinearUnitGeoKeys(crs));
|
|
33875
|
+
Object.assign(keys, getProjectionParamGeoKeys(crs, params, defn));
|
|
33876
|
+
return keys;
|
|
33877
|
+
}
|
|
33878
|
+
|
|
33879
|
+
// Picks the table entry for a projection, including the cases where one proj4
|
|
33880
|
+
// name maps to two coordinate transformations.
|
|
33881
|
+
function getEncodingDefn(crs, params) {
|
|
33882
|
+
var name = params.proj;
|
|
33883
|
+
if (name == 'lcc' && !hasSecondParallel(params)) {
|
|
33884
|
+
name = 'lcc1sp';
|
|
33885
|
+
} else if (name == 'stere' && isPolarLatitude(crs.phi0 * R2D$1)) {
|
|
33886
|
+
name = 'sterePolar';
|
|
33887
|
+
} else if (name == 'merc' && isFinite(params.lat_ts)) {
|
|
33888
|
+
name = 'mercStdParallel';
|
|
33889
|
+
} else if (name == 'omerc' && params.no_uoff) {
|
|
33890
|
+
name = 'omercNoUoff';
|
|
33891
|
+
}
|
|
33892
|
+
return PROJECTIONS[name] || null;
|
|
33893
|
+
}
|
|
33894
|
+
|
|
33895
|
+
function hasSecondParallel(params) {
|
|
33896
|
+
return isFinite(params.lat_2) && params.lat_2 !== params.lat_1;
|
|
33897
|
+
}
|
|
33898
|
+
|
|
33899
|
+
function isPolarLatitude(deg) {
|
|
33900
|
+
return Math.abs(deg) > 89.999;
|
|
33901
|
+
}
|
|
33902
|
+
|
|
33903
|
+
function getProjectionParamGeoKeys(crs, params, defn) {
|
|
33904
|
+
var origin = ORIGIN_KEYS[defn.origin];
|
|
33905
|
+
var falseXY = FALSE_XY_KEYS[defn.falseXY];
|
|
33906
|
+
var keys = {};
|
|
33907
|
+
keys[origin[0]] = getOriginLongitude(crs, params, defn);
|
|
33908
|
+
if (origin[1]) keys[origin[1]] = getOriginLatitude(crs, params, defn);
|
|
33909
|
+
keys[falseXY[0]] = crs.x0;
|
|
33910
|
+
keys[falseXY[1]] = crs.y0;
|
|
33911
|
+
if (defn.scale) keys[SCALE_KEYS[defn.scale]] = crs.k0;
|
|
33912
|
+
if (defn.parallels == 'lat_ts') {
|
|
33913
|
+
keys.ProjStdParallel1GeoKey = getTrueScaleLatitude(params);
|
|
33914
|
+
} else if (defn.parallels == 'lat_1_2') {
|
|
33915
|
+
keys.ProjStdParallel1GeoKey = isFinite(params.lat_1) ? params.lat_1 : 0;
|
|
33916
|
+
keys.ProjStdParallel2GeoKey = isFinite(params.lat_2) ? params.lat_2 : params.lat_1;
|
|
33917
|
+
}
|
|
33918
|
+
if (defn.azimuth) {
|
|
33919
|
+
keys.ProjAzimuthAngleGeoKey = params.alpha;
|
|
33920
|
+
// The angle from the skew axis to the map's vertical. Absent, it equals the
|
|
33921
|
+
// azimuth, which is how proj4 treats it too.
|
|
33922
|
+
keys.ProjRectifiedGridAngleGeoKey = isFinite(params.gamma) ?
|
|
33923
|
+
params.gamma : params.alpha;
|
|
33924
|
+
}
|
|
33925
|
+
return keys;
|
|
33926
|
+
}
|
|
33927
|
+
|
|
33928
|
+
// An oblique Mercator's center longitude is given by +lonc, which proj4 keeps
|
|
33929
|
+
// separate from the +lon_0 of other projections.
|
|
33930
|
+
function getOriginLongitude(crs, params, defn) {
|
|
33931
|
+
if (defn.azimuth && isFinite(params.lonc)) return params.lonc;
|
|
33932
|
+
return degrees$1(crs.lam0);
|
|
33933
|
+
}
|
|
33934
|
+
|
|
33935
|
+
// A polar stereographic projection is anchored at a pole, so the latitude in
|
|
33936
|
+
// its keys is the parallel of true scale rather than the origin.
|
|
33937
|
+
function getOriginLatitude(crs, params, defn) {
|
|
33938
|
+
if (defn.origin == 'polar' && isFinite(params.lat_ts)) return params.lat_ts;
|
|
33939
|
+
return degrees$1(crs.phi0);
|
|
33940
|
+
}
|
|
33941
|
+
|
|
33942
|
+
// An angle in degrees, cleaned up: the CRS object holds radians, so a whole
|
|
33943
|
+
// number of degrees comes back as 96.00000000000001 without this. Nine decimal
|
|
33944
|
+
// places is a fraction of a millimeter on the ground.
|
|
33945
|
+
function degrees$1(rad) {
|
|
33946
|
+
return round(rad * R2D$1, 9);
|
|
33947
|
+
}
|
|
33948
|
+
|
|
33949
|
+
function round(val, digits) {
|
|
33950
|
+
var k = Math.pow(10, digits);
|
|
33951
|
+
return Math.round(val * k) / k;
|
|
33952
|
+
}
|
|
33953
|
+
|
|
33954
|
+
function getTrueScaleLatitude(params) {
|
|
33955
|
+
if (isFinite(params.lat_ts)) return params.lat_ts;
|
|
33956
|
+
if (isFinite(params.lat_1)) return params.lat_1;
|
|
33957
|
+
return 0;
|
|
33958
|
+
}
|
|
33959
|
+
|
|
33960
|
+
// The ellipsoid and datum, which a projected CRS carries as well as a
|
|
33961
|
+
// geographic one.
|
|
33962
|
+
function getDatumGeoKeys(crs, params) {
|
|
33963
|
+
var code = DATUM_CODES[String(params.datum).toUpperCase()] || 0;
|
|
33964
|
+
var keys = {
|
|
33965
|
+
GeographicTypeGeoKey: code || USER_DEFINED,
|
|
33966
|
+
GeogAngularUnitsGeoKey: ANGULAR_DEGREE,
|
|
33967
|
+
// Written even when the datum is named by a code, so that a reader which
|
|
33968
|
+
// does not recognize the code still has the shape of the earth.
|
|
33969
|
+
GeogSemiMajorAxisGeoKey: crs.a,
|
|
33970
|
+
GeogPrimeMeridianLongGeoKey: degrees$1(crs.from_greenwich || 0)
|
|
33971
|
+
};
|
|
33972
|
+
if (!code) {
|
|
33973
|
+
keys.GeogGeodeticDatumGeoKey = USER_DEFINED;
|
|
33974
|
+
keys.GeogEllipsoidGeoKey = USER_DEFINED;
|
|
33975
|
+
}
|
|
33976
|
+
if (crs.es > 0) {
|
|
33977
|
+
keys.GeogInvFlatteningGeoKey = round(1 / (1 - Math.sqrt(1 - crs.es)), 9);
|
|
33978
|
+
} else {
|
|
33979
|
+
keys.GeogSemiMinorAxisGeoKey = crs.a; // a sphere
|
|
33980
|
+
}
|
|
33981
|
+
return keys;
|
|
33982
|
+
}
|
|
33983
|
+
|
|
33984
|
+
function getLinearUnitGeoKeys(crs) {
|
|
33985
|
+
var size = crs.to_meter || 1;
|
|
33986
|
+
var match = LINEAR_UNIT_CODES.filter(function(pair) {
|
|
33987
|
+
return Math.abs(pair[0] - size) < 1e-12;
|
|
33988
|
+
})[0];
|
|
33989
|
+
if (match) return {ProjLinearUnitsGeoKey: match[1]};
|
|
33990
|
+
return {
|
|
33991
|
+
ProjLinearUnitsGeoKey: USER_DEFINED,
|
|
33992
|
+
ProjLinearUnitSizeGeoKey: size
|
|
33993
|
+
};
|
|
33994
|
+
}
|
|
33995
|
+
|
|
33996
|
+
// Reads back the projection that getCrsGeoKeys() writes: returns the proj4
|
|
33997
|
+
// parameters naming the projection and placing it on the globe, as a string, or
|
|
33998
|
+
// null if the keys describe a projection this module does not know.
|
|
33999
|
+
//
|
|
34000
|
+
// The ellipsoid, datum and units are not included, because a caller reading a
|
|
34001
|
+
// file written by other software has better sources for those.
|
|
34002
|
+
function getGeoKeyProjection(keys) {
|
|
34003
|
+
var defn = getDecodingDefn(keys);
|
|
34004
|
+
var origin, falseXY, params;
|
|
34005
|
+
if (!defn) return null;
|
|
34006
|
+
origin = ORIGIN_KEYS[defn.origin];
|
|
34007
|
+
falseXY = FALSE_XY_KEYS[defn.falseXY];
|
|
34008
|
+
params = {proj: defn.proj || getProjectionName(defn)};
|
|
34009
|
+
addProjParam(params, defn.azimuth ? 'lonc' : 'lon_0', keys[origin[0]]);
|
|
34010
|
+
if (origin[1]) {
|
|
34011
|
+
Object.assign(params, getDecodedLatitude(keys[origin[1]], defn));
|
|
34012
|
+
}
|
|
34013
|
+
addProjParam(params, 'x_0', keys[falseXY[0]]);
|
|
34014
|
+
addProjParam(params, 'y_0', keys[falseXY[1]]);
|
|
34015
|
+
if (defn.scale) addProjParam(params, 'k_0', keys[SCALE_KEYS[defn.scale]]);
|
|
34016
|
+
if (defn.parallels == 'lat_ts') {
|
|
34017
|
+
addProjParam(params, 'lat_ts', keys.ProjStdParallel1GeoKey);
|
|
34018
|
+
} else if (defn.parallels == 'lat_1_2') {
|
|
34019
|
+
addProjParam(params, 'lat_1', keys.ProjStdParallel1GeoKey);
|
|
34020
|
+
addProjParam(params, 'lat_2', keys.ProjStdParallel2GeoKey);
|
|
34021
|
+
}
|
|
34022
|
+
if (defn.azimuth) {
|
|
34023
|
+
addProjParam(params, 'alpha', keys.ProjAzimuthAngleGeoKey);
|
|
34024
|
+
addProjParam(params, 'gamma', keys.ProjRectifiedGridAngleGeoKey);
|
|
34025
|
+
if (defn.noUoff) params.no_uoff = true;
|
|
34026
|
+
}
|
|
34027
|
+
return formatProj4Params(params);
|
|
34028
|
+
}
|
|
34029
|
+
|
|
34030
|
+
function getDecodingDefn(keys) {
|
|
34031
|
+
var ct = keys && keys.ProjCoordTransGeoKey;
|
|
34032
|
+
var names;
|
|
34033
|
+
if (!ct || ct == USER_DEFINED) return null;
|
|
34034
|
+
if (ct == PROJECTIONS.merc.ct) {
|
|
34035
|
+
return isFinite(keys.ProjStdParallel1GeoKey) ?
|
|
34036
|
+
PROJECTIONS.mercStdParallel : PROJECTIONS.merc;
|
|
34037
|
+
}
|
|
34038
|
+
names = Object.keys(PROJECTIONS).filter(function(name) {
|
|
34039
|
+
return PROJECTIONS[name].ct === ct && !PROJECTIONS[name].encodeOnly;
|
|
34040
|
+
});
|
|
34041
|
+
return names.length == 1 ? PROJECTIONS[names[0]] : null;
|
|
34042
|
+
}
|
|
34043
|
+
|
|
34044
|
+
function getProjectionName(defn) {
|
|
34045
|
+
return Object.keys(PROJECTIONS).filter(function(name) {
|
|
34046
|
+
return PROJECTIONS[name] === defn;
|
|
34047
|
+
})[0];
|
|
34048
|
+
}
|
|
34049
|
+
|
|
34050
|
+
// The latitude in a polar stereographic projection's keys is the parallel of
|
|
34051
|
+
// true scale; which pole the map is centered on follows from its sign.
|
|
34052
|
+
function getDecodedLatitude(val, defn) {
|
|
34053
|
+
var params = {};
|
|
34054
|
+
if (!isFinite(val)) return params;
|
|
34055
|
+
if (defn.origin == 'polar') {
|
|
34056
|
+
params.lat_ts = val;
|
|
34057
|
+
params.lat_0 = val < 0 ? -90 : 90;
|
|
34058
|
+
} else {
|
|
34059
|
+
params.lat_0 = val;
|
|
34060
|
+
}
|
|
34061
|
+
if (defn.tangentParallel) {
|
|
34062
|
+
// A one-parallel conic touches the globe at its origin latitude, which
|
|
34063
|
+
// proj4 wants stated as the standard parallel.
|
|
34064
|
+
params.lat_1 = params.lat_0;
|
|
34065
|
+
}
|
|
34066
|
+
return params;
|
|
34067
|
+
}
|
|
34068
|
+
|
|
34069
|
+
function addProjParam(params, name, val) {
|
|
34070
|
+
if (isFinite(val)) params[name] = val;
|
|
34071
|
+
}
|
|
34072
|
+
|
|
34073
|
+
// Replaces the projection parameters of a proj4 string with a different set,
|
|
34074
|
+
// leaving its ellipsoid, datum and unit parameters in place. Used to correct a
|
|
34075
|
+
// projection that another parser has read loosely.
|
|
34076
|
+
function replaceProj4Projection(str, projectionStr) {
|
|
34077
|
+
var params = parseProj4Params(str) || {};
|
|
34078
|
+
var kept = {};
|
|
34079
|
+
Object.keys(params).forEach(function(name) {
|
|
34080
|
+
if (PROJECTION_PARAMS.indexOf(name) == -1) kept[name] = params[name];
|
|
34081
|
+
});
|
|
34082
|
+
return projectionStr + ' ' + formatProj4Params(kept);
|
|
34083
|
+
}
|
|
34084
|
+
|
|
34085
|
+
function formatProj4Params(params) {
|
|
34086
|
+
return Object.keys(params).map(function(name) {
|
|
34087
|
+
return params[name] === true ? '+' + name : '+' + name + '=' + params[name];
|
|
34088
|
+
}).join(' ');
|
|
34089
|
+
}
|
|
34090
|
+
|
|
34091
|
+
// Reads the parameters that mproj keeps inside a projection's own state, and so
|
|
34092
|
+
// does not expose on the CRS object, out of the proj4 string that describes it.
|
|
34093
|
+
// Numeric values come back as numbers, flags as true.
|
|
34094
|
+
function parseProj4Params(str) {
|
|
34095
|
+
var params = {};
|
|
34096
|
+
String(str).split(/\s+/).forEach(function(part) {
|
|
34097
|
+
var match = /^\+([a-z_0-9]+)(?:=(.*))?$/i.exec(part);
|
|
34098
|
+
var val;
|
|
34099
|
+
if (!match) return;
|
|
34100
|
+
val = match[2];
|
|
34101
|
+
params[match[1]] = val === undefined ? true :
|
|
34102
|
+
val !== '' && isFinite(Number(val)) ? Number(val) : val;
|
|
34103
|
+
});
|
|
34104
|
+
return params.proj ? params : null;
|
|
34105
|
+
}
|
|
34106
|
+
|
|
34107
|
+
var GeoTIFFGeoKeys = /*#__PURE__*/Object.freeze({
|
|
34108
|
+
__proto__: null,
|
|
34109
|
+
getCrsGeoKeys: getCrsGeoKeys,
|
|
34110
|
+
getGeoKeyProjection: getGeoKeyProjection,
|
|
34111
|
+
replaceProj4Projection: replaceProj4Projection
|
|
34112
|
+
});
|
|
34113
|
+
|
|
34114
|
+
// GeoTIFF's model type: is the raster referenced to a projected or a
|
|
34115
|
+
// geographic CRS?
|
|
34116
|
+
var MODEL_TYPE_PROJECTED = 1;
|
|
34117
|
+
var MODEL_TYPE_GEOGRAPHIC = 2;
|
|
34118
|
+
// The raster covers areas, i.e. a coordinate refers to a pixel's corner rather
|
|
34119
|
+
// than its center. This matches how mapshaper's grid bbox is defined.
|
|
34120
|
+
var RASTER_TYPE_AREA = 1;
|
|
34121
|
+
|
|
34122
|
+
function exportGeoTIFF(dataset, opts) {
|
|
34123
|
+
var extension = opts.extension ||
|
|
34124
|
+
(opts.file ? getFileExtension(opts.file) : '') || 'tif';
|
|
34125
|
+
var crs = getOutputCrs(dataset);
|
|
34126
|
+
var files = [];
|
|
34127
|
+
dataset.layers.forEach(function(lyr) {
|
|
34128
|
+
var filename = lyr.name + '.' + extension;
|
|
34129
|
+
files.push({
|
|
34130
|
+
filename: filename,
|
|
34131
|
+
content: encodeRasterLayer(lyr, crs, opts)
|
|
34132
|
+
});
|
|
34133
|
+
// A projection that GeoTIFF has no way to describe travels in a sidecar
|
|
34134
|
+
// file instead.
|
|
34135
|
+
if (crs.code || crs.geoKeys) return;
|
|
34136
|
+
if (crs.wkt) {
|
|
34137
|
+
files.push({
|
|
34138
|
+
filename: getAuxFilename(filename),
|
|
34139
|
+
content: formatAuxXml(crs.wkt)
|
|
34140
|
+
});
|
|
34141
|
+
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.');
|
|
34142
|
+
} else {
|
|
34143
|
+
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.');
|
|
34144
|
+
}
|
|
34145
|
+
});
|
|
34146
|
+
return files;
|
|
34147
|
+
}
|
|
34148
|
+
|
|
34149
|
+
function encodeRasterLayer(lyr, crs, opts) {
|
|
34150
|
+
var grid = layerHasRaster(lyr) ? getRasterGrid(lyr.raster) : null;
|
|
34151
|
+
if (!grid || !grid.samples) {
|
|
34152
|
+
stop$1('GeoTIFF output requires a raster layer with pixel data');
|
|
34153
|
+
}
|
|
34154
|
+
if (!grid.bbox) {
|
|
34155
|
+
stop$1('Unable to export a raster layer without georeferencing');
|
|
34156
|
+
}
|
|
34157
|
+
if (!(grid.width > 0 && grid.height > 0) ||
|
|
34158
|
+
grid.samples.length < grid.width * grid.height * grid.bands) {
|
|
34159
|
+
stop$1('Unable to export a raster layer with incomplete pixel data');
|
|
34160
|
+
}
|
|
34161
|
+
if (rasterGridIsRotated(grid)) {
|
|
34162
|
+
// Georeferencing is written as a tiepoint plus a pixel scale, which cannot
|
|
34163
|
+
// express rotation or skew.
|
|
34164
|
+
stop$1('Exporting a rotated or skewed raster is not supported');
|
|
34165
|
+
}
|
|
34166
|
+
return encodeGeoTIFF(grid, {
|
|
34167
|
+
geoKeys: getGeoKeys(crs),
|
|
34168
|
+
compress: getCompressionSetting(opts)
|
|
34169
|
+
});
|
|
34170
|
+
}
|
|
34171
|
+
|
|
34172
|
+
function getCompressionSetting(opts) {
|
|
34173
|
+
var arg = opts.compression;
|
|
34174
|
+
if (!arg || arg == 'deflate') return true;
|
|
34175
|
+
if (arg == 'none') return false;
|
|
34176
|
+
stop$1('Unsupported GeoTIFF compression:', arg);
|
|
34177
|
+
}
|
|
34178
|
+
|
|
34179
|
+
function getGeoKeys(crs) {
|
|
34180
|
+
var keys = {GTRasterTypeGeoKey: RASTER_TYPE_AREA};
|
|
34181
|
+
if (crs.code) {
|
|
34182
|
+
// An EPSG code says everything, including which datum realization the
|
|
34183
|
+
// coordinates are in, so it is preferred to spelling the projection out.
|
|
34184
|
+
keys.GTModelTypeGeoKey = crs.isLatLng ? MODEL_TYPE_GEOGRAPHIC : MODEL_TYPE_PROJECTED;
|
|
34185
|
+
keys[crs.isLatLng ? 'GeographicTypeGeoKey' : 'ProjectedCSTypeGeoKey'] = crs.code;
|
|
34186
|
+
return keys;
|
|
34187
|
+
}
|
|
34188
|
+
// Otherwise the projection is written out parameter by parameter. Failing
|
|
34189
|
+
// that, the raster is georeferenced but says nothing about what its
|
|
34190
|
+
// coordinates refer to, and the CRS goes in a sidecar file.
|
|
34191
|
+
return Object.assign(keys, crs.geoKeys || {});
|
|
34192
|
+
}
|
|
34193
|
+
|
|
34194
|
+
// Describe the dataset's CRS in the forms the writer can use: an EPSG code,
|
|
34195
|
+
// a set of geo keys spelling the projection out, and a WKT string for the
|
|
34196
|
+
// sidecar file. Returns {code, geoKeys, isLatLng, wkt}, any member of which may
|
|
34197
|
+
// be missing.
|
|
34198
|
+
function getOutputCrs(dataset) {
|
|
34199
|
+
var info = (dataset && dataset.info) || {};
|
|
34200
|
+
var crsInfo = tryGetCrsInfo(dataset);
|
|
34201
|
+
var crs = crsInfo && crsInfo.crs;
|
|
34202
|
+
var code = getEpsgCode(info, crs);
|
|
34203
|
+
return {
|
|
34204
|
+
code: code,
|
|
34205
|
+
geoKeys: code || !crs ? null : getCrsGeoKeys(crs),
|
|
34206
|
+
isLatLng: !!crs && isLatLngCRS(crs),
|
|
34207
|
+
wkt: getOutputWkt(info, crs)
|
|
34208
|
+
};
|
|
34209
|
+
}
|
|
34210
|
+
|
|
34211
|
+
function tryGetCrsInfo(dataset) {
|
|
34212
|
+
try {
|
|
34213
|
+
// Besides reading the dataset's CRS metadata, this infers WGS-84 from
|
|
34214
|
+
// lat-long-looking bounds.
|
|
34215
|
+
return getDatasetCrsInfo(dataset);
|
|
34216
|
+
} catch(e) {
|
|
34217
|
+
return null;
|
|
34218
|
+
}
|
|
34219
|
+
}
|
|
34220
|
+
|
|
34221
|
+
function getEpsgCode(info, crs) {
|
|
34222
|
+
var authority = parseAuthorityCodeString(info.crs_string) ||
|
|
34223
|
+
parseAuthorityCodeFromWkt(info.wkt1);
|
|
34224
|
+
if (authority && authority.org == 'EPSG') return authority.code;
|
|
34225
|
+
if (info.geopackage_crs &&
|
|
34226
|
+
String(info.geopackage_crs.organization || '').toUpperCase() == 'EPSG') {
|
|
34227
|
+
return info.geopackage_crs.organization_coordsys_id ||
|
|
34228
|
+
info.geopackage_crs.srs_id || null;
|
|
34229
|
+
}
|
|
34230
|
+
// The two CRSes that mapshaper can recognize from a projection definition
|
|
34231
|
+
// alone; they also cover its wgs84 and webmercator aliases, which arrive here
|
|
34232
|
+
// without a code.
|
|
34233
|
+
if (crs && isWGS84(crs)) return 4326;
|
|
34234
|
+
if (crs && isWebMercator(crs)) return 3857;
|
|
34235
|
+
return null;
|
|
34236
|
+
}
|
|
34237
|
+
|
|
34238
|
+
function getOutputWkt(info, crs) {
|
|
34239
|
+
if (info.wkt1) return info.wkt1; // the CRS as the user supplied it
|
|
34240
|
+
if (!crs) return null;
|
|
34241
|
+
try {
|
|
34242
|
+
// WKT2 carries more of a projection's parameters than WKT1 does.
|
|
34243
|
+
return crsToWkt2(crs) || crsToPrj(crs) || null;
|
|
34244
|
+
} catch(e) {
|
|
34245
|
+
return null;
|
|
34246
|
+
}
|
|
34247
|
+
}
|
|
34248
|
+
|
|
32773
34249
|
function getOutputFormat(dataset, opts) {
|
|
32774
34250
|
var outFile = opts.file || null,
|
|
32775
34251
|
inFmt = dataset.info && dataset.info.input_formats && dataset.info.input_formats[0],
|
|
@@ -32815,6 +34291,8 @@ ${svg}
|
|
|
32815
34291
|
format = 'flatgeobuf';
|
|
32816
34292
|
} else if (ext == 'parquet' || ext == 'geoparquet') {
|
|
32817
34293
|
format = 'geoparquet';
|
|
34294
|
+
} else if (ext == 'tif' || ext == 'tiff') {
|
|
34295
|
+
format = 'geotiff';
|
|
32818
34296
|
} else if (ext == 'svg') {
|
|
32819
34297
|
format = 'svg';
|
|
32820
34298
|
} else if (ext == 'kml' || ext == 'kmz') {
|
|
@@ -32876,8 +34354,11 @@ ${svg}
|
|
|
32876
34354
|
var format = getOutputFormat(datasets[0], opts);
|
|
32877
34355
|
var files;
|
|
32878
34356
|
validateRasterExportFormat(datasets, format);
|
|
32879
|
-
if (format != 'geoparquet' &&
|
|
32880
|
-
error('The
|
|
34357
|
+
if (format != 'geoparquet' && opts.level !== undefined) {
|
|
34358
|
+
error('The level= option only applies to GeoParquet output');
|
|
34359
|
+
}
|
|
34360
|
+
if (format != 'geoparquet' && format != 'geotiff' && opts.compression) {
|
|
34361
|
+
error('The compression= option only applies to GeoParquet and GeoTIFF output');
|
|
32881
34362
|
}
|
|
32882
34363
|
if (format == PACKAGE_EXT) {
|
|
32883
34364
|
opts = utils.defaults({compact: true}, opts);
|
|
@@ -32946,9 +34427,15 @@ ${svg}
|
|
|
32946
34427
|
}
|
|
32947
34428
|
|
|
32948
34429
|
function validateRasterExportFormat(datasets, format) {
|
|
34430
|
+
if (format == 'geotiff') {
|
|
34431
|
+
if (datasetsHaveVectorLayers(datasets)) {
|
|
34432
|
+
stop$1('GeoTIFF output requires raster layers; use -target to select them');
|
|
34433
|
+
}
|
|
34434
|
+
return;
|
|
34435
|
+
}
|
|
32949
34436
|
if (!datasetsHaveRasterLayers(datasets)) return;
|
|
32950
34437
|
if (format == 'svg' || format == PACKAGE_EXT) return;
|
|
32951
|
-
stop$1('Raster layers can only be exported as SVG or ' + PACKAGE_EXT + ' files');
|
|
34438
|
+
stop$1('Raster layers can only be exported as GeoTIFF, SVG or ' + PACKAGE_EXT + ' files');
|
|
32952
34439
|
}
|
|
32953
34440
|
|
|
32954
34441
|
function datasetsHaveRasterLayers(datasets) {
|
|
@@ -32957,6 +34444,14 @@ ${svg}
|
|
|
32957
34444
|
});
|
|
32958
34445
|
}
|
|
32959
34446
|
|
|
34447
|
+
function datasetsHaveVectorLayers(datasets) {
|
|
34448
|
+
return datasets.some(function(dataset) {
|
|
34449
|
+
return dataset.layers && dataset.layers.some(function(lyr) {
|
|
34450
|
+
return !layerHasRaster(lyr);
|
|
34451
|
+
});
|
|
34452
|
+
});
|
|
34453
|
+
}
|
|
34454
|
+
|
|
32960
34455
|
// Return an array of objects with 'filename' and 'content' members.
|
|
32961
34456
|
//
|
|
32962
34457
|
function exportFileContent(dataset, opts) {
|
|
@@ -33017,7 +34512,8 @@ ${svg}
|
|
|
33017
34512
|
json: exportJSON,
|
|
33018
34513
|
flatgeobuf: exportFlatGeobuf,
|
|
33019
34514
|
svg: exportSVG,
|
|
33020
|
-
kml: exportKML
|
|
34515
|
+
kml: exportKML,
|
|
34516
|
+
geotiff: exportGeoTIFF
|
|
33021
34517
|
};
|
|
33022
34518
|
|
|
33023
34519
|
|
|
@@ -34014,7 +35510,7 @@ ${svg}
|
|
|
34014
35510
|
});
|
|
34015
35511
|
|
|
34016
35512
|
function isSupportedOutputFormat(fmt) {
|
|
34017
|
-
var types = ['geojson', 'topojson', 'json', 'dsv', 'dbf', 'shapefile', 'svg', 'kml', PACKAGE_EXT, 'flatgeobuf', 'geopackage', 'geoparquet'];
|
|
35513
|
+
var types = ['geojson', 'topojson', 'json', 'dsv', 'dbf', 'shapefile', 'svg', 'kml', PACKAGE_EXT, 'flatgeobuf', 'geopackage', 'geoparquet', 'geotiff'];
|
|
34018
35514
|
return types.indexOf(fmt) > -1;
|
|
34019
35515
|
}
|
|
34020
35516
|
|
|
@@ -34032,6 +35528,7 @@ ${svg}
|
|
|
34032
35528
|
flatgeobuf: 'Flatgeobuf',
|
|
34033
35529
|
geopackage: 'GeoPackage',
|
|
34034
35530
|
geoparquet: 'GeoParquet',
|
|
35531
|
+
geotiff: 'GeoTIFF',
|
|
34035
35532
|
svg: 'SVG'
|
|
34036
35533
|
}[fmt] || '';
|
|
34037
35534
|
}
|
|
@@ -35447,7 +36944,7 @@ ${svg}
|
|
|
35447
36944
|
}
|
|
35448
36945
|
})
|
|
35449
36946
|
.option('format', {
|
|
35450
|
-
describe: 'options: shapefile,geojson,topojson,flatgeobuf,geopackage,geoparquet,json,dbf,csv,tsv,svg'
|
|
36947
|
+
describe: 'options: shapefile,geojson,topojson,flatgeobuf,geopackage,geoparquet,geotiff,json,dbf,csv,tsv,svg'
|
|
35451
36948
|
})
|
|
35452
36949
|
.option('target', targetOpt)
|
|
35453
36950
|
.option('force', {
|
|
@@ -35641,12 +37138,16 @@ ${svg}
|
|
|
35641
37138
|
describe: '[CSV] export numbers with decimal commas not points'
|
|
35642
37139
|
})
|
|
35643
37140
|
.option('compression', {
|
|
35644
|
-
describe: '[GeoParquet]
|
|
37141
|
+
describe: '[GeoParquet] snappy,zstd,none (default snappy) [GeoTIFF] deflate,none'
|
|
35645
37142
|
})
|
|
35646
37143
|
.option('level', {
|
|
35647
37144
|
describe: '[GeoParquet] zstd compression level',
|
|
35648
37145
|
type: 'integer'
|
|
35649
37146
|
})
|
|
37147
|
+
.option('rowgroup', {
|
|
37148
|
+
describe: '[GeoParquet] rows per row group (default: sized by data)',
|
|
37149
|
+
type: 'integer'
|
|
37150
|
+
})
|
|
35650
37151
|
.option('show-all', {
|
|
35651
37152
|
type: 'flag',
|
|
35652
37153
|
describe: '[Snapshot] show all layers in Web UI'
|
|
@@ -38916,11 +40417,13 @@ ${svg}
|
|
|
38916
40417
|
// Import path data from a non-topological source (Shapefile, GeoJSON, etc)
|
|
38917
40418
|
// in preparation for identifying topology.
|
|
38918
40419
|
// @opts.reserved_points -- estimate of points in dataset, for pre-allocating buffers
|
|
40420
|
+
// (applied when the first path arrives, so that a layer of points -- which
|
|
40421
|
+
// keeps its coords in shapes, not in these buffers -- never allocates it)
|
|
38919
40422
|
//
|
|
38920
40423
|
function PathImporter(opts) {
|
|
38921
|
-
var
|
|
38922
|
-
xx = new Float64Array(
|
|
38923
|
-
yy = new Float64Array(
|
|
40424
|
+
var reservedPoints = opts.reserved_points > 0 ? opts.reserved_points : 0,
|
|
40425
|
+
xx = new Float64Array(20000),
|
|
40426
|
+
yy = new Float64Array(20000),
|
|
38924
40427
|
shapes = [],
|
|
38925
40428
|
properties = [],
|
|
38926
40429
|
nn = [],
|
|
@@ -39064,7 +40567,7 @@ ${svg}
|
|
|
39064
40567
|
|
|
39065
40568
|
function checkBuffers(needed) {
|
|
39066
40569
|
if (needed > xx.length) {
|
|
39067
|
-
var newLen = Math.max(needed, Math.ceil(xx.length * 1.5));
|
|
40570
|
+
var newLen = Math.max(needed, reservedPoints, Math.ceil(xx.length * 1.5));
|
|
39068
40571
|
xx = utils.extendBuffer(xx, newLen, pointId);
|
|
39069
40572
|
yy = utils.extendBuffer(yy, newLen, pointId);
|
|
39070
40573
|
}
|
|
@@ -40216,8 +41719,25 @@ ${svg}
|
|
|
40216
41719
|
return fmt;
|
|
40217
41720
|
}
|
|
40218
41721
|
|
|
41722
|
+
// Guess the number of coordinate pairs in a GeoJSON file from its size, so that
|
|
41723
|
+
// the importer can size its coordinate buffers up front instead of growing them
|
|
41724
|
+
// a dozen times over (each growth copies the coords accumulated so far and
|
|
41725
|
+
// leaves the previous buffer behind, which is what makes peak memory during
|
|
41726
|
+
// import roughly double the size of the coordinate data).
|
|
41727
|
+
//
|
|
41728
|
+
// A pair runs from ~16 bytes ("[-122.42,37.77],") to ~42 at full double
|
|
41729
|
+
// precision, so this sits in the middle and errs low: undershooting just means
|
|
41730
|
+
// the buffers resume growing from a larger starting point, while overshooting
|
|
41731
|
+
// allocates memory that is never used. An attribute-heavy file overshoots, but
|
|
41732
|
+
// only reserves more than the growth it replaces past ~69 bytes per pair.
|
|
41733
|
+
function estimateGeoJSONPointCount(byteLength) {
|
|
41734
|
+
return Math.round(byteLength / 32);
|
|
41735
|
+
}
|
|
41736
|
+
|
|
40219
41737
|
function importGeoJSONFile(fileReader, opts) {
|
|
40220
|
-
var importer = new GeoJSONParser(opts
|
|
41738
|
+
var importer = new GeoJSONParser(utils.defaults({}, opts, {
|
|
41739
|
+
reserved_points: estimateGeoJSONPointCount(fileReader.size())
|
|
41740
|
+
}));
|
|
40221
41741
|
// For collections, parseGeoJSON() returns the top-level object with its
|
|
40222
41742
|
// features/geometries array nulled out but all other members intact.
|
|
40223
41743
|
var obj = parseGeoJSON(fileReader, importer.parseObject);
|
|
@@ -41339,13 +42859,11 @@ ${svg}
|
|
|
41339
42859
|
if (utils.isString(crs)) {
|
|
41340
42860
|
return parseAuthorityString(crs);
|
|
41341
42861
|
}
|
|
41342
|
-
|
|
41343
|
-
|
|
41344
|
-
|
|
41345
|
-
|
|
41346
|
-
|
|
41347
|
-
}
|
|
41348
|
-
return null;
|
|
42862
|
+
// base_crs identifies the geographic CRS that a projected CRS is derived
|
|
42863
|
+
// from, not the projected CRS itself, so it can't stand in for a missing
|
|
42864
|
+
// top-level id -- doing so reinterpreted projected coordinates as lat/long.
|
|
42865
|
+
// Without an id the caller falls back to converting the PROJJSON to Proj4.
|
|
42866
|
+
return parseAuthorityObject(crs.id);
|
|
41349
42867
|
}
|
|
41350
42868
|
|
|
41351
42869
|
function parseAuthorityObject(id) {
|
|
@@ -41371,7 +42889,8 @@ ${svg}
|
|
|
41371
42889
|
var dynamicImportModule = Function('id', 'return import(id)');
|
|
41372
42890
|
var DEFAULT_MAX_IMPORT_PIXELS = 16e6;
|
|
41373
42891
|
|
|
41374
|
-
|
|
42892
|
+
// aux: contents of the file's .aux.xml sidecar, if it has one
|
|
42893
|
+
async function importGeoTIFF(input, optsArg, aux) {
|
|
41375
42894
|
var opts = optsArg || {};
|
|
41376
42895
|
var geotiff = await loadGeoTIFFLib();
|
|
41377
42896
|
var source = getGeoTIFFSource(input);
|
|
@@ -41389,7 +42908,7 @@ ${svg}
|
|
|
41389
42908
|
raster: imported.raster
|
|
41390
42909
|
}]
|
|
41391
42910
|
};
|
|
41392
|
-
await importGeoTIFFCrs(dataset, sourceImage);
|
|
42911
|
+
await importGeoTIFFCrs(dataset, sourceImage, aux);
|
|
41393
42912
|
return dataset;
|
|
41394
42913
|
}
|
|
41395
42914
|
|
|
@@ -41733,26 +43252,122 @@ ${svg}
|
|
|
41733
43252
|
};
|
|
41734
43253
|
}
|
|
41735
43254
|
|
|
41736
|
-
|
|
43255
|
+
// Reads the CRS a GeoTIFF claims, if mapshaper can make sense of it. A file
|
|
43256
|
+
// with no usable CRS metadata still imports: the raster keeps the coordinates
|
|
43257
|
+
// in its own georeferencing, and only the things that need to know what those
|
|
43258
|
+
// coordinates mean (reprojecting, basemaps, measuring) are unavailable. So a
|
|
43259
|
+
// CRS that cannot be read is reported and set aside, never raised as an error.
|
|
43260
|
+
//
|
|
43261
|
+
// A projection GeoTIFF cannot describe travels beside the file in a .aux.xml
|
|
43262
|
+
// sidecar, which is read when the file's own geo keys come up empty. The keys
|
|
43263
|
+
// come first, matching how GDAL treats the two: the sidecar supplements a file
|
|
43264
|
+
// that says nothing, rather than overruling one that does.
|
|
43265
|
+
async function importGeoTIFFCrs(dataset, image, aux) {
|
|
41737
43266
|
var crsInfo = await getGeoTIFFCrsInfo(image);
|
|
41738
|
-
var
|
|
41739
|
-
|
|
41740
|
-
|
|
43267
|
+
var fromFile = await readCrsString(crsInfo.crsString);
|
|
43268
|
+
// The sidecar usually states its CRS in WKT, which has to become a proj4
|
|
43269
|
+
// definition before it can be used like the file's own metadata.
|
|
43270
|
+
var sidecarSrs = fromFile || !aux ? null : parseAuxCrsString(aux.content);
|
|
43271
|
+
var sidecarWkt = auxCrsIsWkt(sidecarSrs) ? sidecarSrs : null;
|
|
43272
|
+
var sidecarString = sidecarWkt ? tryParseWktToProj(sidecarWkt) : sidecarSrs;
|
|
43273
|
+
var fromSidecar = await readCrsString(sidecarString);
|
|
43274
|
+
var found = fromFile || fromSidecar;
|
|
43275
|
+
if (!found) {
|
|
43276
|
+
dataset.info = dataset.info || {};
|
|
43277
|
+
reportMissingCrs(dataset, crsInfo, sidecarString);
|
|
41741
43278
|
return;
|
|
41742
43279
|
}
|
|
43280
|
+
setDatasetCrsInfo(dataset, {crs_string: found.crsString, crs: found.crs});
|
|
43281
|
+
if (found.assumedDatum) {
|
|
43282
|
+
message('This GeoTIFF describes its projection without naming a datum;',
|
|
43283
|
+
'assuming WGS 84.');
|
|
43284
|
+
}
|
|
43285
|
+
if (fromSidecar) {
|
|
43286
|
+
message('Read the CRS from ' + (aux.filename || 'a ' + AUX_EXT + ' file') +
|
|
43287
|
+
', because GeoTIFF has no way to describe this projection.');
|
|
43288
|
+
if (sidecarWkt) {
|
|
43289
|
+
// Keep the sidecar's own wording, so that exporting the raster again
|
|
43290
|
+
// writes back what was read rather than a re-derived approximation.
|
|
43291
|
+
dataset.info.wkt1 = sidecarWkt;
|
|
43292
|
+
}
|
|
43293
|
+
}
|
|
43294
|
+
}
|
|
43295
|
+
|
|
43296
|
+
// Returns {crsString, crs}, or null if there is no string or it cannot be used.
|
|
43297
|
+
async function readCrsString(str) {
|
|
43298
|
+
var crs, assumed;
|
|
43299
|
+
if (!str) return null;
|
|
41743
43300
|
try {
|
|
41744
|
-
await initProjLibrary({crs:
|
|
41745
|
-
setDatasetCrsInfo(dataset, getCrsInfo(crsString));
|
|
43301
|
+
await initProjLibrary({crs: str});
|
|
41746
43302
|
} catch(e) {
|
|
41747
|
-
|
|
41748
|
-
|
|
43303
|
+
// A projection resource that would not load leaves the string to be
|
|
43304
|
+
// parsed with whatever is already known.
|
|
43305
|
+
}
|
|
43306
|
+
crs = tryParseCrsString(str);
|
|
43307
|
+
if (crs) return {crsString: str, crs: crs};
|
|
43308
|
+
// A projection with no earth to sit on is tried again on a WGS-84 one, the
|
|
43309
|
+
// datum mapshaper assumes for any data that does not name one.
|
|
43310
|
+
assumed = addAssumedDatum(str);
|
|
43311
|
+
crs = assumed ? tryParseCrsString(assumed) : null;
|
|
43312
|
+
return crs ? {crsString: assumed, crs: crs, assumedDatum: true} : null;
|
|
43313
|
+
}
|
|
43314
|
+
|
|
43315
|
+
// GeoTIFF metadata can describe a projection without saying what shape of earth
|
|
43316
|
+
// it applies to: the geo keys carry a transformation and its parameters, and
|
|
43317
|
+
// the datum keys are absent or name something the EPSG database does not have.
|
|
43318
|
+
// Since the geokeys-to-proj4 library ends every definition with +no_defs, proj
|
|
43319
|
+
// will not supply its usual default either, and the whole projection is lost
|
|
43320
|
+
// over a missing ellipsoid. Returns the definition with a WGS-84 datum added,
|
|
43321
|
+
// or null if it already has one (or is not a proj4 definition at all).
|
|
43322
|
+
function addAssumedDatum(str) {
|
|
43323
|
+
if (!/\+proj=/.test(str) || /\+(datum|ellps|a|b|R|nadgrids|init)=/.test(str)) {
|
|
43324
|
+
return null;
|
|
41749
43325
|
}
|
|
43326
|
+
return str + ' +datum=WGS84';
|
|
43327
|
+
}
|
|
43328
|
+
|
|
43329
|
+
// What to say about a raster that arrives without a CRS. A file that simply has
|
|
43330
|
+
// no CRS metadata is the ordinary case for an image, and is treated the way a
|
|
43331
|
+
// vector file with no projection metadata is: coordinates in the decimal-degree
|
|
43332
|
+
// range are taken for WGS-84 lat-long when something needs to know, which is
|
|
43333
|
+
// worth saying but not worth warning about. Metadata that mapshaper could not
|
|
43334
|
+
// make sense of is a different matter, and keeps the warning.
|
|
43335
|
+
function reportMissingCrs(dataset, crsInfo, sidecarString) {
|
|
43336
|
+
if (crsInfo.absent && !sidecarString) {
|
|
43337
|
+
if (probablyDecimalDegreeBounds(getDatasetBounds(dataset))) {
|
|
43338
|
+
message('This GeoTIFF has no CRS metadata. Its coordinates are in the',
|
|
43339
|
+
'decimal-degree range, so they are taken for WGS 84 lat-long.');
|
|
43340
|
+
} else {
|
|
43341
|
+
message('This GeoTIFF has no CRS metadata, so what its coordinates refer',
|
|
43342
|
+
'to is unknown, and projecting it or showing it over a basemap are',
|
|
43343
|
+
'unavailable. If you know the CRS, name it with -proj init=<crs>',
|
|
43344
|
+
'crs=<crs>');
|
|
43345
|
+
}
|
|
43346
|
+
return;
|
|
43347
|
+
}
|
|
43348
|
+
warnOnce(getGeoTIFFCrsWarning(crsInfo,
|
|
43349
|
+
getUnusableCrsDetail(crsInfo.crsString, sidecarString)));
|
|
43350
|
+
}
|
|
43351
|
+
|
|
43352
|
+
function getUnusableCrsDetail(fileString, sidecarString) {
|
|
43353
|
+
var tried = [fileString, sidecarString].filter(Boolean);
|
|
43354
|
+
if (!tried.length) return null;
|
|
43355
|
+
return 'Unable to use projection ' + tried.join(' or ');
|
|
41750
43356
|
}
|
|
41751
43357
|
|
|
41752
43358
|
async function getGeoTIFFCrsInfo(image) {
|
|
41753
43359
|
var keys = image.getGeoKeys && image.getGeoKeys() || {};
|
|
41754
43360
|
var code = keys.ProjectedCSTypeGeoKey;
|
|
41755
43361
|
var customProjection;
|
|
43362
|
+
if (!hasCrsGeoKeys(keys)) {
|
|
43363
|
+
// Asked about a file that says nothing, the geokeys-to-proj4 library
|
|
43364
|
+
// answers "+proj=longlat" rather than nothing, having taken the silence for
|
|
43365
|
+
// a geographic CRS. That is a guess about the coordinates, not something
|
|
43366
|
+
// read out of the file, and mapshaper makes it later and better, from the
|
|
43367
|
+
// coordinate range, in the same way it does for a vector file with no
|
|
43368
|
+
// projection metadata.
|
|
43369
|
+
return {crsString: null, absent: true};
|
|
43370
|
+
}
|
|
41756
43371
|
if (isGeoTIFFAuthorityCode(code)) {
|
|
41757
43372
|
return {crsString: 'EPSG:' + code};
|
|
41758
43373
|
}
|
|
@@ -41770,21 +43385,58 @@ ${svg}
|
|
|
41770
43385
|
return 'The GeoTIFF does not contain usable CRS data';
|
|
41771
43386
|
}
|
|
41772
43387
|
|
|
43388
|
+
// Keys that say something about the CRS itself, as opposed to how the pixels
|
|
43389
|
+
// are laid out (GTRasterTypeGeoKey) or what the file calls things (the
|
|
43390
|
+
// citation keys).
|
|
43391
|
+
var CRS_GEO_KEYS = [
|
|
43392
|
+
'GTModelTypeGeoKey',
|
|
43393
|
+
'GeographicTypeGeoKey', 'GeogGeodeticDatumGeoKey', 'GeogEllipsoidGeoKey',
|
|
43394
|
+
'GeogPrimeMeridianGeoKey', 'GeogSemiMajorAxisGeoKey',
|
|
43395
|
+
'GeogSemiMinorAxisGeoKey', 'GeogInvFlatteningGeoKey',
|
|
43396
|
+
'ProjectedCSTypeGeoKey', 'ProjectionGeoKey', 'ProjCoordTransGeoKey'
|
|
43397
|
+
];
|
|
43398
|
+
|
|
43399
|
+
function hasCrsGeoKeys(keys) {
|
|
43400
|
+
return CRS_GEO_KEYS.some(function(name) {
|
|
43401
|
+
return keys[name] !== undefined && keys[name] !== null;
|
|
43402
|
+
});
|
|
43403
|
+
}
|
|
43404
|
+
|
|
41773
43405
|
function isGeoTIFFAuthorityCode(code) {
|
|
41774
43406
|
return code > 0 && code != 32767;
|
|
41775
43407
|
}
|
|
41776
43408
|
|
|
43409
|
+
// Reads a projection that the file spells out in its geo keys, rather than
|
|
43410
|
+
// naming by a code.
|
|
43411
|
+
//
|
|
43412
|
+
// The bulk of the work is done by the geokeys-to-proj4 library, which knows the
|
|
43413
|
+
// EPSG database and so reads datums, ellipsoids and units thoroughly. Its
|
|
43414
|
+
// reading of the projection parameters themselves is loose, though: it drops
|
|
43415
|
+
// the central meridian of a polar stereographic projection, the azimuth of an
|
|
43416
|
+
// oblique Mercator, and the true-scale parallel of a Mercator. So the
|
|
43417
|
+
// projection part of its answer is replaced with mapshaper's own reading of the
|
|
43418
|
+
// same keys, for the projections mapshaper knows how to write.
|
|
41777
43419
|
async function getGeoTIFFCustomProjection(keys) {
|
|
41778
43420
|
var converted = await getGeoTIFFProj4FromGeoKeys(keys);
|
|
41779
|
-
|
|
41780
|
-
if (
|
|
41781
|
-
if (
|
|
41782
|
-
return {
|
|
41783
|
-
crsString: getGeoTIFFAlbersProjection(keys),
|
|
41784
|
-
warning: converted.warning
|
|
41785
|
-
};
|
|
43421
|
+
var projection = getGeoKeyProjection(keys);
|
|
43422
|
+
if (!projection) return converted;
|
|
43423
|
+
if (converted.crsString) {
|
|
43424
|
+
return {crsString: replaceProj4Projection(converted.crsString, projection)};
|
|
41786
43425
|
}
|
|
41787
|
-
return
|
|
43426
|
+
return {
|
|
43427
|
+
crsString: getGeoTIFFEllipsoidProjection(keys, projection),
|
|
43428
|
+
warning: converted.warning
|
|
43429
|
+
};
|
|
43430
|
+
}
|
|
43431
|
+
|
|
43432
|
+
// A last resort for a file whose datum the library could not read: the
|
|
43433
|
+
// projection as mapshaper reads it, on an ellipsoid taken straight from the
|
|
43434
|
+
// geo keys.
|
|
43435
|
+
function getGeoTIFFEllipsoidProjection(keys, projection) {
|
|
43436
|
+
var units = getGeoTIFFLinearUnits(keys);
|
|
43437
|
+
var ellipsoid = getGeoTIFFEllipsoid(keys);
|
|
43438
|
+
if (!units || !ellipsoid) return null;
|
|
43439
|
+
return projection + ' ' + ellipsoid + ' ' + units;
|
|
41788
43440
|
}
|
|
41789
43441
|
|
|
41790
43442
|
async function getGeoTIFFProj4FromGeoKeys(keys) {
|
|
@@ -41813,27 +43465,6 @@ ${svg}
|
|
|
41813
43465
|
});
|
|
41814
43466
|
}
|
|
41815
43467
|
|
|
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
43468
|
function getGeoTIFFLinearUnits(keys) {
|
|
41838
43469
|
var code = keys.ProjLinearUnitsGeoKey;
|
|
41839
43470
|
if (!code || code == 9001) return '+units=m';
|
|
@@ -42979,6 +44610,14 @@ ${svg}
|
|
|
42979
44610
|
data = obj.prj;
|
|
42980
44611
|
dataset = {layers: [], info: {wkt1: data.content}};
|
|
42981
44612
|
|
|
44613
|
+
} else if (obj.aux) {
|
|
44614
|
+
// A sidecar on its own carries nothing but a CRS, which is still worth
|
|
44615
|
+
// reading: it can be the source of a -proj command, and importing the
|
|
44616
|
+
// raster it belongs to picks it up anyway.
|
|
44617
|
+
dataFmt = 'aux';
|
|
44618
|
+
data = obj.aux;
|
|
44619
|
+
dataset = {layers: [], info: getAuxCrsInfo(data.content)};
|
|
44620
|
+
|
|
42982
44621
|
} else if (obj.kml) {
|
|
42983
44622
|
dataFmt = 'kml';
|
|
42984
44623
|
data = obj.kml;
|
|
@@ -43024,7 +44663,7 @@ ${svg}
|
|
|
43024
44663
|
} else if (obj.geotiff) {
|
|
43025
44664
|
dataFmt = 'geotiff';
|
|
43026
44665
|
data = obj.geotiff;
|
|
43027
|
-
dataset = await importGeoTIFF(data, opts);
|
|
44666
|
+
dataset = await importGeoTIFF(data, opts, obj.aux);
|
|
43028
44667
|
} else if (obj.png || obj.jpeg) {
|
|
43029
44668
|
dataFmt = obj.png ? 'png' : 'jpeg';
|
|
43030
44669
|
data = obj[dataFmt];
|
|
@@ -43086,6 +44725,14 @@ ${svg}
|
|
|
43086
44725
|
return dataset;
|
|
43087
44726
|
}
|
|
43088
44727
|
|
|
44728
|
+
// What a raster sidecar amounts to on its own: a projection, stated where the
|
|
44729
|
+
// contents of a .prj file would go if it is WKT, as a CRS string otherwise.
|
|
44730
|
+
function getAuxCrsInfo(content) {
|
|
44731
|
+
var srs = parseAuxCrsString(content);
|
|
44732
|
+
if (!srs) return {};
|
|
44733
|
+
return auxCrsIsWkt(srs) ? {wkt1: srs} : {crs_string: srs};
|
|
44734
|
+
}
|
|
44735
|
+
|
|
43089
44736
|
function importDbf(input, opts) {
|
|
43090
44737
|
var table;
|
|
43091
44738
|
opts = utils.extend({}, opts);
|
|
@@ -43556,6 +45203,8 @@ ${svg}
|
|
|
43556
45203
|
readShapefileAuxFiles(path, input, cache);
|
|
43557
45204
|
} else if (isRasterImageInputType(fileType)) {
|
|
43558
45205
|
readRasterImageAuxFiles(path, input, cache);
|
|
45206
|
+
} else if (fileType == 'geotiff') {
|
|
45207
|
+
readGeoTIFFAuxFiles(path, input, cache);
|
|
43559
45208
|
}
|
|
43560
45209
|
if (fileType == 'shp' && !input.dbf) {
|
|
43561
45210
|
message(utils.format("[%s] .dbf file is missing - shapes imported without attribute data.", path));
|
|
@@ -43680,6 +45329,13 @@ ${svg}
|
|
|
43680
45329
|
}
|
|
43681
45330
|
}
|
|
43682
45331
|
|
|
45332
|
+
function readGeoTIFFAuxFiles(path, obj, cache) {
|
|
45333
|
+
var auxPath = getAuxFilename(path);
|
|
45334
|
+
if (cli.isFile(auxPath, cache)) {
|
|
45335
|
+
obj.aux = {filename: auxPath, content: cli.readFile(auxPath, 'utf-8', cache)};
|
|
45336
|
+
}
|
|
45337
|
+
}
|
|
45338
|
+
|
|
43683
45339
|
function readRasterImageAuxFiles(path, obj, cache) {
|
|
43684
45340
|
var prjPath = replaceFileExtension(path, 'prj');
|
|
43685
45341
|
var worldPath = findWorldFile(path, cache);
|
|
@@ -43717,11 +45373,15 @@ ${svg}
|
|
|
43717
45373
|
|
|
43718
45374
|
function removeRasterImageSidecars(files) {
|
|
43719
45375
|
var imageBases = {};
|
|
45376
|
+
var rasterPaths = {};
|
|
43720
45377
|
files.forEach(function(file) {
|
|
43721
45378
|
var type = guessInputFileType(file);
|
|
43722
45379
|
if (isRasterImageInputType(type)) {
|
|
43723
45380
|
imageBases[getFileBase(file).toLowerCase()] = true;
|
|
43724
45381
|
}
|
|
45382
|
+
if (isRasterImageInputType(type) || type == 'geotiff') {
|
|
45383
|
+
rasterPaths[file.toLowerCase()] = true;
|
|
45384
|
+
}
|
|
43725
45385
|
});
|
|
43726
45386
|
return files.filter(function(file) {
|
|
43727
45387
|
var ext = getFileExtension(file).toLowerCase();
|
|
@@ -43730,6 +45390,11 @@ ${svg}
|
|
|
43730
45390
|
if ((type == 'prj' || isWorldFileExtension(ext)) && imageBases[base]) {
|
|
43731
45391
|
return false;
|
|
43732
45392
|
}
|
|
45393
|
+
// A sidecar is read along with the raster it names, so importing it
|
|
45394
|
+
// separately would only add an empty layer.
|
|
45395
|
+
if (type == 'aux' && rasterPaths[getAuxSourceFilename(file).toLowerCase()]) {
|
|
45396
|
+
return false;
|
|
45397
|
+
}
|
|
43733
45398
|
return true;
|
|
43734
45399
|
});
|
|
43735
45400
|
}
|
|
@@ -44269,7 +45934,7 @@ ${svg}
|
|
|
44269
45934
|
var targetPoints = [];
|
|
44270
45935
|
var targetPointLayers = [];
|
|
44271
45936
|
var targetFlags, otherFlags, transform;
|
|
44272
|
-
dataset.layers.filter(layerHasGeometry).forEach(function(lyr) {
|
|
45937
|
+
dataset.layers.filter(layerHasGeometry$1).forEach(function(lyr) {
|
|
44273
45938
|
var hits = [],
|
|
44274
45939
|
misses = [],
|
|
44275
45940
|
test;
|
|
@@ -47203,7 +48868,7 @@ ${svg}
|
|
|
47203
48868
|
|
|
47204
48869
|
function stopIfBufferReachesPole(shape, dist) {
|
|
47205
48870
|
var maxAbsLat = 0;
|
|
47206
|
-
var angularDist = dist / R$3 * R2D$
|
|
48871
|
+
var angularDist = dist / R$3 * R2D$8;
|
|
47207
48872
|
(shape || []).forEach(function(path) {
|
|
47208
48873
|
latLngPathIter.init(path);
|
|
47209
48874
|
while (latLngPathIter.hasNext()) {
|
|
@@ -53996,7 +55661,7 @@ ${svg}
|
|
|
53996
55661
|
var maxAbsLat = Math.max(Math.abs(bounds.ymin), Math.abs(bounds.ymax));
|
|
53997
55662
|
var maxPositiveDistance = getMaxPositiveBufferDistance(lyr, dataset, opts);
|
|
53998
55663
|
if (!(maxPositiveDistance > 0)) return false;
|
|
53999
|
-
return maxAbsLat + maxPositiveDistance / R$3 * R2D$
|
|
55664
|
+
return maxAbsLat + maxPositiveDistance / R$3 * R2D$8 >= 90 - POLAR_BUFFER_MARGIN_DEGREES;
|
|
54000
55665
|
}
|
|
54001
55666
|
|
|
54002
55667
|
function getMaxPositiveBufferDistance(lyr, dataset, opts) {
|
|
@@ -55248,7 +56913,7 @@ ${svg}
|
|
|
55248
56913
|
}
|
|
55249
56914
|
|
|
55250
56915
|
function getCoordinateDistance(distance, arcs) {
|
|
55251
|
-
return arcs.isPlanar() ? distance : distance / R$3 * R2D$
|
|
56916
|
+
return arcs.isPlanar() ? distance : distance / R$3 * R2D$8;
|
|
55252
56917
|
}
|
|
55253
56918
|
|
|
55254
56919
|
// @shapeIndex: chunk-bounds index of the source shape (see buildShapeSegmentIndex)
|
|
@@ -55709,7 +57374,7 @@ ${svg}
|
|
|
55709
57374
|
geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset);
|
|
55710
57375
|
|
|
55711
57376
|
} else {
|
|
55712
|
-
if (!layerHasGeometry(targetLyr)) {
|
|
57377
|
+
if (!layerHasGeometry$1(targetLyr)) {
|
|
55713
57378
|
stop$1("Layer is missing geometric shapes");
|
|
55714
57379
|
}
|
|
55715
57380
|
geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
|
|
@@ -55977,8 +57642,8 @@ ${svg}
|
|
|
55977
57642
|
p[1] *= D2R$8;
|
|
55978
57643
|
var rotate = inv ? rotatePointInv : rotatePoint;
|
|
55979
57644
|
rotate(p, a, b, c);
|
|
55980
|
-
p[0] *= R2D$
|
|
55981
|
-
p[1] *= R2D$
|
|
57645
|
+
p[0] *= R2D$8;
|
|
57646
|
+
p[1] *= R2D$8;
|
|
55982
57647
|
return p;
|
|
55983
57648
|
};
|
|
55984
57649
|
}
|
|
@@ -56885,7 +58550,7 @@ ${svg}
|
|
|
56885
58550
|
// polygon layer, @clipData). This avoids performing unnecessary intersection
|
|
56886
58551
|
// tests on each line segment.
|
|
56887
58552
|
var layers = dataset.layers.filter(function(lyr) {
|
|
56888
|
-
return layerHasGeometry(lyr) && !layerIsFullyEnclosed(lyr, dataset, clipData);
|
|
58553
|
+
return layerHasGeometry$1(lyr) && !layerIsFullyEnclosed(lyr, dataset, clipData);
|
|
56889
58554
|
});
|
|
56890
58555
|
if (layers.length > 0) {
|
|
56891
58556
|
clipLayersInPlace(layers, clipData, dataset, 'clip', getInternalClipOpts());
|
|
@@ -58726,7 +60391,7 @@ ${svg}
|
|
|
58726
60391
|
|
|
58727
60392
|
cmd.proj = function(dataset, catalog, opts, targetLayers) {
|
|
58728
60393
|
var srcInfo, destInfo, destStr;
|
|
58729
|
-
var implicitlyProjectedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasGeometry);
|
|
60394
|
+
var implicitlyProjectedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasGeometry$1);
|
|
58730
60395
|
// A preserved GeoJSON "crs" member becomes invalid once the CRS changes.
|
|
58731
60396
|
deleteGeoJSONMetadataCRS(dataset);
|
|
58732
60397
|
if (opts.init) {
|
|
@@ -62377,13 +64042,34 @@ ${svg}
|
|
|
62377
64042
|
var MAX_CONTOUR_LEVELS = 2000;
|
|
62378
64043
|
var TARGET_LEVEL_COUNT = 15;
|
|
62379
64044
|
|
|
62380
|
-
// Marching squares traces a staircase with treads one pixel wide,
|
|
62381
|
-
//
|
|
62382
|
-
//
|
|
62383
|
-
//
|
|
62384
|
-
//
|
|
62385
|
-
//
|
|
62386
|
-
|
|
64045
|
+
// Marching squares traces a staircase with treads about one pixel wide, and the
|
|
64046
|
+
// smoother halves the amplitude of detail at roughly five times the distance it
|
|
64047
|
+
// is given, so a quarter of a pixel is already aimed squarely at the stepping.
|
|
64048
|
+
//
|
|
64049
|
+
// The distance also sets how far the line may move, and that is what limits it
|
|
64050
|
+
// from above: neighboring contours are as far apart as the contour interval
|
|
64051
|
+
// divided by the local gradient, which on a coarse grid over steep ground is a
|
|
64052
|
+
// small fraction of a pixel. (In test/data/features/contours/sample2_detail.tif
|
|
64053
|
+
// -- 11x11 pixels -- lines run as close as 0.1 px.) A distance of one pixel
|
|
64054
|
+
// therefore let lines walk over their neighbors: 40 crossings on that file and
|
|
64055
|
+
// 346 on a 1200x1053 DEM, against none at a quarter pixel and one pixel of
|
|
64056
|
+
// visible detail lost for nothing.
|
|
64057
|
+
var SMOOTHING_PIXELS = 0.25;
|
|
64058
|
+
|
|
64059
|
+
// Smallest gap, as a fraction of a cell, that a traced vertex is kept from the
|
|
64060
|
+
// grid point at either end of the edge it crosses.
|
|
64061
|
+
//
|
|
64062
|
+
// A sample whose value is exactly the contour level -- common, since elevations
|
|
64063
|
+
// are usually whole units and levels are round numbers -- puts the crossing
|
|
64064
|
+
// exactly on that grid point. Every cell around the point does the same, so two
|
|
64065
|
+
// branches of the contour meet there instead of passing by. Touching is
|
|
64066
|
+
// harmless in the traced line, but smoothing moves the branches and turns each
|
|
64067
|
+
// touch into a crossing, and no smoothing distance is small enough to avoid it.
|
|
64068
|
+
// Holding vertices a tenth of a cell clear leaves the branches far enough apart
|
|
64069
|
+
// to survive smoothing: on a 1200x1053 DEM this took the crossings that
|
|
64070
|
+
// survived from 67 to 2. Only applied when the lines are going to be smoothed;
|
|
64071
|
+
// with no-smoothing the crossings are placed exactly.
|
|
64072
|
+
var CORNER_CLEARANCE = 0.1;
|
|
62387
64073
|
|
|
62388
64074
|
// A degree of longitude vanishes at the poles; keep the implied pixel width
|
|
62389
64075
|
// from collapsing to zero there.
|
|
@@ -62442,7 +64128,7 @@ ${svg}
|
|
|
62442
64128
|
levels = getContourLevels(grid, band, opts);
|
|
62443
64129
|
return {
|
|
62444
64130
|
levels: levels,
|
|
62445
|
-
lines: traceRasterContours(grid, band, levels)
|
|
64131
|
+
lines: traceRasterContours(grid, band, levels, getCornerClearance(opts))
|
|
62446
64132
|
};
|
|
62447
64133
|
}
|
|
62448
64134
|
|
|
@@ -62576,14 +64262,21 @@ ${svg}
|
|
|
62576
64262
|
return {min: min, max: max};
|
|
62577
64263
|
}
|
|
62578
64264
|
|
|
62579
|
-
function
|
|
64265
|
+
function getCornerClearance(opts) {
|
|
64266
|
+
return opts && opts.no_smoothing ? 0 : CORNER_CLEARANCE;
|
|
64267
|
+
}
|
|
64268
|
+
|
|
64269
|
+
// clearance: see CORNER_CLEARANCE; 0 places crossings exactly.
|
|
64270
|
+
function traceRasterContours(grid, band, levels, clearance) {
|
|
62580
64271
|
var segments = collectContourSegments(grid, band, levels);
|
|
62581
64272
|
var toMapXY = getLatticeToMapTransform(grid);
|
|
62582
64273
|
var hCount = (grid.width - 1) * grid.height;
|
|
62583
64274
|
var lines = [];
|
|
64275
|
+
clearance = clearance || 0;
|
|
62584
64276
|
segments.forEach(function(levelSegments, i) {
|
|
62585
64277
|
stitchContourSegments(levelSegments).forEach(function(path) {
|
|
62586
|
-
var coords = getPathCoords(path, grid, band, levels[i], hCount, toMapXY
|
|
64278
|
+
var coords = getPathCoords(path, grid, band, levels[i], hCount, toMapXY,
|
|
64279
|
+
clearance);
|
|
62587
64280
|
if (coords.length > 1) {
|
|
62588
64281
|
lines.push({value: levels[i], coords: coords});
|
|
62589
64282
|
}
|
|
@@ -62724,12 +64417,12 @@ ${svg}
|
|
|
62724
64417
|
return path;
|
|
62725
64418
|
}
|
|
62726
64419
|
|
|
62727
|
-
function getPathCoords(path, grid, band, level, hCount, toMapXY) {
|
|
64420
|
+
function getPathCoords(path, grid, band, level, hCount, toMapXY, clearance) {
|
|
62728
64421
|
var coords = [];
|
|
62729
64422
|
var prev = null;
|
|
62730
64423
|
var point, xy;
|
|
62731
64424
|
for (var i = 0; i < path.length; i++) {
|
|
62732
|
-
point = getEdgeCrossing(grid, band, level, path[i], hCount);
|
|
64425
|
+
point = getEdgeCrossing(grid, band, level, path[i], hCount, clearance);
|
|
62733
64426
|
xy = toMapXY(point[0], point[1]);
|
|
62734
64427
|
// A corner value exactly equal to the level puts two crossings in the
|
|
62735
64428
|
// same place; keeping both would emit zero-length segments.
|
|
@@ -62743,7 +64436,7 @@ ${svg}
|
|
|
62743
64436
|
// Returns the crossing point in lattice coordinates. The result depends only
|
|
62744
64437
|
// on the edge and the level, so both cells sharing an edge compute the same
|
|
62745
64438
|
// point, down to the last bit.
|
|
62746
|
-
function getEdgeCrossing(grid, band, level, edgeId, hCount) {
|
|
64439
|
+
function getEdgeCrossing(grid, band, level, edgeId, hCount, clearance) {
|
|
62747
64440
|
var W = grid.width;
|
|
62748
64441
|
var bands = grid.bands;
|
|
62749
64442
|
var samples = grid.samples;
|
|
@@ -62753,21 +64446,23 @@ ${svg}
|
|
|
62753
64446
|
x = edgeId - y * (W - 1);
|
|
62754
64447
|
v0 = samples[(y * W + x) * bands + band];
|
|
62755
64448
|
v1 = samples[(y * W + x + 1) * bands + band];
|
|
62756
|
-
return [x + interpolateCrossing(level, v0, v1), y];
|
|
64449
|
+
return [x + interpolateCrossing(level, v0, v1, clearance), y];
|
|
62757
64450
|
}
|
|
62758
64451
|
edgeId -= hCount;
|
|
62759
64452
|
y = Math.floor(edgeId / W);
|
|
62760
64453
|
x = edgeId - y * W;
|
|
62761
64454
|
v0 = samples[(y * W + x) * bands + band];
|
|
62762
64455
|
v1 = samples[((y + 1) * W + x) * bands + band];
|
|
62763
|
-
return [x, y + interpolateCrossing(level, v0, v1)];
|
|
64456
|
+
return [x, y + interpolateCrossing(level, v0, v1, clearance)];
|
|
62764
64457
|
}
|
|
62765
64458
|
|
|
62766
|
-
|
|
64459
|
+
// clearance: fraction of the edge kept free at either end. See CORNER_CLEARANCE.
|
|
64460
|
+
function interpolateCrossing(level, v0, v1, clearance) {
|
|
64461
|
+
var hi = 1 - clearance;
|
|
62767
64462
|
var t;
|
|
62768
64463
|
if (v1 === v0) return 0.5;
|
|
62769
64464
|
t = (level - v0) / (v1 - v0);
|
|
62770
|
-
return t <
|
|
64465
|
+
return t < clearance ? clearance : t > hi ? hi : t;
|
|
62771
64466
|
}
|
|
62772
64467
|
|
|
62773
64468
|
// Lattice coordinates are indexed from the center of the first pixel, not the
|
|
@@ -62792,6 +64487,7 @@ ${svg}
|
|
|
62792
64487
|
getContourBand: getContourBand,
|
|
62793
64488
|
getContourLevels: getContourLevels,
|
|
62794
64489
|
getContourSmoothingDistance: getContourSmoothingDistance,
|
|
64490
|
+
getCornerClearance: getCornerClearance,
|
|
62795
64491
|
getRasterContourLines: getRasterContourLines,
|
|
62796
64492
|
getRasterSampleRange: getRasterSampleRange,
|
|
62797
64493
|
traceRasterContours: traceRasterContours,
|
|
@@ -62829,12 +64525,14 @@ ${svg}
|
|
|
62829
64525
|
function smoothContourDataset(dataset, grid) {
|
|
62830
64526
|
var crs = getDatasetCRS(dataset);
|
|
62831
64527
|
var distance = getContourSmoothingDistance(grid, crs);
|
|
64528
|
+
var unsmoothed, repair;
|
|
62832
64529
|
if (!(distance > 0)) {
|
|
62833
64530
|
message('Skipped contour smoothing: unable to determine the pixel size');
|
|
62834
64531
|
return;
|
|
62835
64532
|
}
|
|
62836
64533
|
message('Smoothing contours with an auto-selected interval of ' +
|
|
62837
|
-
formatSmoothingDistance(distance, crs) + ' (
|
|
64534
|
+
formatSmoothingDistance(distance, crs) + ' (a quarter of a pixel)');
|
|
64535
|
+
unsmoothed = dataset.arcs.getCopy();
|
|
62838
64536
|
cmd.smooth(dataset, {
|
|
62839
64537
|
distance: distance,
|
|
62840
64538
|
// The contour staircase is an artifact, so there are no real corners to
|
|
@@ -62842,6 +64540,19 @@ ${svg}
|
|
|
62842
64540
|
no_corners: true,
|
|
62843
64541
|
no_prefilter: true
|
|
62844
64542
|
}, dataset.layers);
|
|
64543
|
+
// Contour lines never cross, so any crossing is smoothing pulling a line over
|
|
64544
|
+
// its neighbor. The interval is small enough that this is rare, and where it
|
|
64545
|
+
// happens the lines involved are better left as they were traced.
|
|
64546
|
+
repair = repairCrossedArcs(dataset.arcs, unsmoothed);
|
|
64547
|
+
if (repair.reverted > 0) {
|
|
64548
|
+
message('Left ' + repair.reverted + ' contour ' +
|
|
64549
|
+
(repair.reverted == 1 ? 'line' : 'lines') +
|
|
64550
|
+
' unsmoothed, to keep lines from crossing');
|
|
64551
|
+
}
|
|
64552
|
+
if (repair.remaining > 0) {
|
|
64553
|
+
message('Unable to remove ' + repair.remaining + ' crossing' +
|
|
64554
|
+
(repair.remaining == 1 ? '' : 's') + ' between contour lines');
|
|
64555
|
+
}
|
|
62845
64556
|
}
|
|
62846
64557
|
|
|
62847
64558
|
function formatSmoothingDistance(distance, crs) {
|
|
@@ -66068,7 +67779,7 @@ ${svg}
|
|
|
66068
67779
|
}
|
|
66069
67780
|
|
|
66070
67781
|
cmd.filterGeom = function(lyr, arcs, opts) {
|
|
66071
|
-
if (!layerHasGeometry(lyr)) {
|
|
67782
|
+
if (!layerHasGeometry$1(lyr)) {
|
|
66072
67783
|
stop$1("Layer is missing geometry");
|
|
66073
67784
|
}
|
|
66074
67785
|
if (opts.bbox) {
|
|
@@ -74186,7 +75897,7 @@ ${svg}
|
|
|
74186
75897
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
74187
75898
|
}
|
|
74188
75899
|
|
|
74189
|
-
var version = "0.7.
|
|
75900
|
+
var version = "0.7.51";
|
|
74190
75901
|
|
|
74191
75902
|
// Parse command line args into commands and run them
|
|
74192
75903
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -76076,6 +77787,9 @@ ${svg}
|
|
|
76076
77787
|
JoinTables,
|
|
76077
77788
|
JsonImport,
|
|
76078
77789
|
JsonTable,
|
|
77790
|
+
GeoTIFFAux,
|
|
77791
|
+
GeoTIFFEncode,
|
|
77792
|
+
GeoTIFFGeoKeys,
|
|
76079
77793
|
KeepShapes,
|
|
76080
77794
|
LatLon,
|
|
76081
77795
|
LayerUtils,
|
|
@@ -76127,6 +77841,7 @@ ${svg}
|
|
|
76127
77841
|
Scalebar,
|
|
76128
77842
|
SegmentGeom,
|
|
76129
77843
|
SegmentIntersection,
|
|
77844
|
+
SegmentIntersectionRepair,
|
|
76130
77845
|
ShapeIter$1,
|
|
76131
77846
|
ShapeUtils,
|
|
76132
77847
|
ShpCommon,
|