mapshaper 0.7.50 → 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 +243 -36
- package/package.json +2 -2
- package/www/mapshaper-gui.js +80 -17
- package/www/mapshaper.js +243 -36
package/mapshaper.js
CHANGED
|
@@ -10465,6 +10465,22 @@
|
|
|
10465
10465
|
return crs;
|
|
10466
10466
|
}
|
|
10467
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
|
+
|
|
10468
10484
|
function parseCrsString$1(str) {
|
|
10469
10485
|
var defn = getProjDefn(str); // defn is a string or a Proj object
|
|
10470
10486
|
var P;
|
|
@@ -10720,6 +10736,7 @@
|
|
|
10720
10736
|
setProjectionLoader: setProjectionLoader,
|
|
10721
10737
|
toLngLat: toLngLat,
|
|
10722
10738
|
tryParseCrsString: tryParseCrsString,
|
|
10739
|
+
tryParseWktToProj: tryParseWktToProj,
|
|
10723
10740
|
wkt1ToProj: wkt1ToProj,
|
|
10724
10741
|
wkt2ToProj: wkt2ToProj,
|
|
10725
10742
|
wktToProj: wktToProj
|
|
@@ -13129,6 +13146,67 @@
|
|
|
13129
13146
|
toLowerCaseExtension: toLowerCaseExtension
|
|
13130
13147
|
});
|
|
13131
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
|
+
|
|
13132
13210
|
// Constants shared between the .msx (mapshaper snapshot) writer in
|
|
13133
13211
|
// mapshaper-pack.mjs and the file-type registry in
|
|
13134
13212
|
// io/mapshaper-file-types.mjs. Living here lets file-types reference the
|
|
@@ -13142,7 +13220,10 @@
|
|
|
13142
13220
|
function guessInputFileType(file) {
|
|
13143
13221
|
var ext = getFileExtension(file || '').toLowerCase(),
|
|
13144
13222
|
type = null;
|
|
13145
|
-
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') {
|
|
13146
13227
|
type = ext;
|
|
13147
13228
|
} else if (ext == 'jpg' || ext == 'jpeg') {
|
|
13148
13229
|
type = 'jpeg';
|
|
@@ -13166,7 +13247,8 @@
|
|
|
13166
13247
|
|
|
13167
13248
|
// File types that can be imported but are not convertible to datasets
|
|
13168
13249
|
function isAuxiliaryInputFileType(type) {
|
|
13169
|
-
return type == 'prj' || type == 'shx' || type == 'cpg' || type == 'world'
|
|
13250
|
+
return type == 'prj' || type == 'shx' || type == 'cpg' || type == 'world' ||
|
|
13251
|
+
type == 'aux';
|
|
13170
13252
|
}
|
|
13171
13253
|
|
|
13172
13254
|
function isRasterImageInputType(type) {
|
|
@@ -34036,11 +34118,6 @@ ${svg}
|
|
|
34036
34118
|
// The raster covers areas, i.e. a coordinate refers to a pixel's corner rather
|
|
34037
34119
|
// than its center. This matches how mapshaper's grid bbox is defined.
|
|
34038
34120
|
var RASTER_TYPE_AREA = 1;
|
|
34039
|
-
// GDAL's "persistent auxiliary metadata" sidecar, which it reads for any raster
|
|
34040
|
-
// it opens. Unlike the .prj file that accompanies a shapefile, this is a
|
|
34041
|
-
// sidecar that GDAL-based software (QGIS included) actually consults for a
|
|
34042
|
-
// GeoTIFF's CRS.
|
|
34043
|
-
var AUX_EXT = '.aux.xml';
|
|
34044
34121
|
|
|
34045
34122
|
function exportGeoTIFF(dataset, opts) {
|
|
34046
34123
|
var extension = opts.extension ||
|
|
@@ -34058,8 +34135,8 @@ ${svg}
|
|
|
34058
34135
|
if (crs.code || crs.geoKeys) return;
|
|
34059
34136
|
if (crs.wkt) {
|
|
34060
34137
|
files.push({
|
|
34061
|
-
filename: filename
|
|
34062
|
-
content:
|
|
34138
|
+
filename: getAuxFilename(filename),
|
|
34139
|
+
content: formatAuxXml(crs.wkt)
|
|
34063
34140
|
});
|
|
34064
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.');
|
|
34065
34142
|
} else {
|
|
@@ -34169,14 +34246,6 @@ ${svg}
|
|
|
34169
34246
|
}
|
|
34170
34247
|
}
|
|
34171
34248
|
|
|
34172
|
-
function getAuxXml(wkt) {
|
|
34173
|
-
return '<PAMDataset>\n <SRS>' + escapeXml(wkt) + '</SRS>\n</PAMDataset>\n';
|
|
34174
|
-
}
|
|
34175
|
-
|
|
34176
|
-
function escapeXml(str) {
|
|
34177
|
-
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
34178
|
-
}
|
|
34179
|
-
|
|
34180
34249
|
function getOutputFormat(dataset, opts) {
|
|
34181
34250
|
var outFile = opts.file || null,
|
|
34182
34251
|
inFmt = dataset.info && dataset.info.input_formats && dataset.info.input_formats[0],
|
|
@@ -42820,7 +42889,8 @@ ${svg}
|
|
|
42820
42889
|
var dynamicImportModule = Function('id', 'return import(id)');
|
|
42821
42890
|
var DEFAULT_MAX_IMPORT_PIXELS = 16e6;
|
|
42822
42891
|
|
|
42823
|
-
|
|
42892
|
+
// aux: contents of the file's .aux.xml sidecar, if it has one
|
|
42893
|
+
async function importGeoTIFF(input, optsArg, aux) {
|
|
42824
42894
|
var opts = optsArg || {};
|
|
42825
42895
|
var geotiff = await loadGeoTIFFLib();
|
|
42826
42896
|
var source = getGeoTIFFSource(input);
|
|
@@ -42838,7 +42908,7 @@ ${svg}
|
|
|
42838
42908
|
raster: imported.raster
|
|
42839
42909
|
}]
|
|
42840
42910
|
};
|
|
42841
|
-
await importGeoTIFFCrs(dataset, sourceImage);
|
|
42911
|
+
await importGeoTIFFCrs(dataset, sourceImage, aux);
|
|
42842
42912
|
return dataset;
|
|
42843
42913
|
}
|
|
42844
42914
|
|
|
@@ -43187,32 +43257,117 @@ ${svg}
|
|
|
43187
43257
|
// in its own georeferencing, and only the things that need to know what those
|
|
43188
43258
|
// coordinates mean (reprojecting, basemaps, measuring) are unavailable. So a
|
|
43189
43259
|
// CRS that cannot be read is reported and set aside, never raised as an error.
|
|
43190
|
-
|
|
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) {
|
|
43191
43266
|
var crsInfo = await getGeoTIFFCrsInfo(image);
|
|
43192
|
-
var
|
|
43193
|
-
|
|
43194
|
-
|
|
43195
|
-
|
|
43196
|
-
|
|
43197
|
-
|
|
43198
|
-
|
|
43199
|
-
|
|
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);
|
|
43278
|
+
return;
|
|
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;
|
|
43200
43292
|
}
|
|
43201
|
-
crs = tryParseCrsString(crsString);
|
|
43202
43293
|
}
|
|
43203
|
-
|
|
43204
|
-
|
|
43205
|
-
|
|
43206
|
-
|
|
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;
|
|
43300
|
+
try {
|
|
43301
|
+
await initProjLibrary({crs: str});
|
|
43302
|
+
} catch(e) {
|
|
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;
|
|
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
|
+
}
|
|
43207
43346
|
return;
|
|
43208
43347
|
}
|
|
43209
|
-
|
|
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 ');
|
|
43210
43356
|
}
|
|
43211
43357
|
|
|
43212
43358
|
async function getGeoTIFFCrsInfo(image) {
|
|
43213
43359
|
var keys = image.getGeoKeys && image.getGeoKeys() || {};
|
|
43214
43360
|
var code = keys.ProjectedCSTypeGeoKey;
|
|
43215
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
|
+
}
|
|
43216
43371
|
if (isGeoTIFFAuthorityCode(code)) {
|
|
43217
43372
|
return {crsString: 'EPSG:' + code};
|
|
43218
43373
|
}
|
|
@@ -43230,6 +43385,23 @@ ${svg}
|
|
|
43230
43385
|
return 'The GeoTIFF does not contain usable CRS data';
|
|
43231
43386
|
}
|
|
43232
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
|
+
|
|
43233
43405
|
function isGeoTIFFAuthorityCode(code) {
|
|
43234
43406
|
return code > 0 && code != 32767;
|
|
43235
43407
|
}
|
|
@@ -44438,6 +44610,14 @@ ${svg}
|
|
|
44438
44610
|
data = obj.prj;
|
|
44439
44611
|
dataset = {layers: [], info: {wkt1: data.content}};
|
|
44440
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
|
+
|
|
44441
44621
|
} else if (obj.kml) {
|
|
44442
44622
|
dataFmt = 'kml';
|
|
44443
44623
|
data = obj.kml;
|
|
@@ -44483,7 +44663,7 @@ ${svg}
|
|
|
44483
44663
|
} else if (obj.geotiff) {
|
|
44484
44664
|
dataFmt = 'geotiff';
|
|
44485
44665
|
data = obj.geotiff;
|
|
44486
|
-
dataset = await importGeoTIFF(data, opts);
|
|
44666
|
+
dataset = await importGeoTIFF(data, opts, obj.aux);
|
|
44487
44667
|
} else if (obj.png || obj.jpeg) {
|
|
44488
44668
|
dataFmt = obj.png ? 'png' : 'jpeg';
|
|
44489
44669
|
data = obj[dataFmt];
|
|
@@ -44545,6 +44725,14 @@ ${svg}
|
|
|
44545
44725
|
return dataset;
|
|
44546
44726
|
}
|
|
44547
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
|
+
|
|
44548
44736
|
function importDbf(input, opts) {
|
|
44549
44737
|
var table;
|
|
44550
44738
|
opts = utils.extend({}, opts);
|
|
@@ -45015,6 +45203,8 @@ ${svg}
|
|
|
45015
45203
|
readShapefileAuxFiles(path, input, cache);
|
|
45016
45204
|
} else if (isRasterImageInputType(fileType)) {
|
|
45017
45205
|
readRasterImageAuxFiles(path, input, cache);
|
|
45206
|
+
} else if (fileType == 'geotiff') {
|
|
45207
|
+
readGeoTIFFAuxFiles(path, input, cache);
|
|
45018
45208
|
}
|
|
45019
45209
|
if (fileType == 'shp' && !input.dbf) {
|
|
45020
45210
|
message(utils.format("[%s] .dbf file is missing - shapes imported without attribute data.", path));
|
|
@@ -45139,6 +45329,13 @@ ${svg}
|
|
|
45139
45329
|
}
|
|
45140
45330
|
}
|
|
45141
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
|
+
|
|
45142
45339
|
function readRasterImageAuxFiles(path, obj, cache) {
|
|
45143
45340
|
var prjPath = replaceFileExtension(path, 'prj');
|
|
45144
45341
|
var worldPath = findWorldFile(path, cache);
|
|
@@ -45176,11 +45373,15 @@ ${svg}
|
|
|
45176
45373
|
|
|
45177
45374
|
function removeRasterImageSidecars(files) {
|
|
45178
45375
|
var imageBases = {};
|
|
45376
|
+
var rasterPaths = {};
|
|
45179
45377
|
files.forEach(function(file) {
|
|
45180
45378
|
var type = guessInputFileType(file);
|
|
45181
45379
|
if (isRasterImageInputType(type)) {
|
|
45182
45380
|
imageBases[getFileBase(file).toLowerCase()] = true;
|
|
45183
45381
|
}
|
|
45382
|
+
if (isRasterImageInputType(type) || type == 'geotiff') {
|
|
45383
|
+
rasterPaths[file.toLowerCase()] = true;
|
|
45384
|
+
}
|
|
45184
45385
|
});
|
|
45185
45386
|
return files.filter(function(file) {
|
|
45186
45387
|
var ext = getFileExtension(file).toLowerCase();
|
|
@@ -45189,6 +45390,11 @@ ${svg}
|
|
|
45189
45390
|
if ((type == 'prj' || isWorldFileExtension(ext)) && imageBases[base]) {
|
|
45190
45391
|
return false;
|
|
45191
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
|
+
}
|
|
45192
45398
|
return true;
|
|
45193
45399
|
});
|
|
45194
45400
|
}
|
|
@@ -75691,7 +75897,7 @@ ${svg}
|
|
|
75691
75897
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
75692
75898
|
}
|
|
75693
75899
|
|
|
75694
|
-
var version = "0.7.
|
|
75900
|
+
var version = "0.7.51";
|
|
75695
75901
|
|
|
75696
75902
|
// Parse command line args into commands and run them
|
|
75697
75903
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -77581,6 +77787,7 @@ ${svg}
|
|
|
77581
77787
|
JoinTables,
|
|
77582
77788
|
JsonImport,
|
|
77583
77789
|
JsonTable,
|
|
77790
|
+
GeoTIFFAux,
|
|
77584
77791
|
GeoTIFFEncode,
|
|
77585
77792
|
GeoTIFFGeoKeys,
|
|
77586
77793
|
KeepShapes,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mapshaper",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.51",
|
|
4
4
|
"description": "A tool for editing geospatial data for mapping and GIS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shapefile",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
],
|
|
102
102
|
"check-leaks": true,
|
|
103
103
|
"parallel": true,
|
|
104
|
-
"jobs":
|
|
104
|
+
"jobs": 6
|
|
105
105
|
},
|
|
106
106
|
"bin": {
|
|
107
107
|
"mapshaper": "bin/mapshaper",
|
package/www/mapshaper-gui.js
CHANGED
|
@@ -6876,31 +6876,37 @@
|
|
|
6876
6876
|
// instead.
|
|
6877
6877
|
|
|
6878
6878
|
|
|
6879
|
+
// Undo is on unless it has been turned off. Undoing is what a user expects by
|
|
6880
|
+
// default; the History menu's checkbox exists so that sessions working with
|
|
6881
|
+
// data too large to store restore payloads for can opt out.
|
|
6882
|
+
var APP_UNDO_DEFAULT = true;
|
|
6883
|
+
|
|
6879
6884
|
// Returns true if the manifest, URL query, gui-installed checker, or
|
|
6880
6885
|
// localStorage indicates that app-level undo should be active.
|
|
6881
6886
|
function appUndoIsEnabled(gui) {
|
|
6882
6887
|
var opt = gui && gui.options && (gui.options.undoCommands || gui.options.appUndo);
|
|
6883
6888
|
var query = getUndoQueryValue();
|
|
6889
|
+
if (query == 'off') return false;
|
|
6884
6890
|
if (opt === true || query == 'on' || query == 'commands') return true;
|
|
6885
6891
|
if (gui && gui.appUndoIsEnabled) return gui.appUndoIsEnabled();
|
|
6886
6892
|
return appUndoSettingIsOn();
|
|
6887
6893
|
}
|
|
6888
6894
|
|
|
6889
|
-
// True when the undo URL flag
|
|
6890
|
-
// Mirrors the toggle-disable behavior in HistoryMenu.
|
|
6895
|
+
// True when the undo URL flag pins app undo on or off regardless of UI
|
|
6896
|
+
// settings. Mirrors the toggle-disable behavior in HistoryMenu.
|
|
6891
6897
|
function appUndoForcedByUrl() {
|
|
6892
6898
|
var query = getUndoQueryValue();
|
|
6893
|
-
return query == 'on' || query == 'commands';
|
|
6899
|
+
return query == 'on' || query == 'commands' || query == 'off';
|
|
6894
6900
|
}
|
|
6895
6901
|
|
|
6896
|
-
// Read the persisted
|
|
6897
|
-
//
|
|
6902
|
+
// Read the persisted setting. Only an explicit opt-out turns undo off, so a
|
|
6903
|
+
// first-time session (and one where storage is unreadable) gets the default.
|
|
6898
6904
|
function appUndoSettingIsOn() {
|
|
6899
6905
|
try {
|
|
6900
|
-
|
|
6901
|
-
|
|
6906
|
+
if (typeof window == 'undefined' || !window.localStorage) return APP_UNDO_DEFAULT;
|
|
6907
|
+
return window.localStorage.getItem('mapshaper.undo') != 'off';
|
|
6902
6908
|
} catch(e) {
|
|
6903
|
-
return
|
|
6909
|
+
return APP_UNDO_DEFAULT;
|
|
6904
6910
|
}
|
|
6905
6911
|
}
|
|
6906
6912
|
|
|
@@ -8067,6 +8073,15 @@
|
|
|
8067
8073
|
});
|
|
8068
8074
|
}
|
|
8069
8075
|
|
|
8076
|
+
// A .aux.xml sidecar is named after the whole raster file
|
|
8077
|
+
// (world.tif.aux.xml), so it joins that file's group rather than one of its
|
|
8078
|
+
// own. Returns null if the raster it names was not loaded with it.
|
|
8079
|
+
function auxSourceGroup(d) {
|
|
8080
|
+
var sourceName = internal.getAuxSourceFilename(d.name).toLowerCase();
|
|
8081
|
+
var source = data.find(f => f.name.toLowerCase() == sourceName);
|
|
8082
|
+
return source ? key(fileBase(source), fileType(source)) : null;
|
|
8083
|
+
}
|
|
8084
|
+
|
|
8070
8085
|
data.forEach(d => {
|
|
8071
8086
|
var basename = fileBase(d);
|
|
8072
8087
|
var type = fileType(d);
|
|
@@ -8078,6 +8093,8 @@
|
|
|
8078
8093
|
} else if (type == 'dbf') {
|
|
8079
8094
|
d.filename = d.name;
|
|
8080
8095
|
d.group = key(basename, 'dbf');
|
|
8096
|
+
} else if (type == 'aux' && auxSourceGroup(d)) {
|
|
8097
|
+
d.group = auxSourceGroup(d);
|
|
8081
8098
|
} else if ((type == 'png' || type == 'jpeg') || isRasterImagePart(d.name) && hasRasterImage(basename)) {
|
|
8082
8099
|
d.group = key(basename, type == 'png' || type == 'jpeg' ? type : getRasterImageGroupType(data, basename));
|
|
8083
8100
|
if (type == 'png' || type == 'jpeg') d.filename = d.name;
|
|
@@ -10641,6 +10658,8 @@
|
|
|
10641
10658
|
var unsupportedMsg = "Exporting is not supported in this browser";
|
|
10642
10659
|
var menu = gui.container.findChild('.export-options').on('click', GUI.handleDirectEvent(gui.clearMode));
|
|
10643
10660
|
var layersArr = [];
|
|
10661
|
+
var menuFormats = null; // formats the format menu was built with
|
|
10662
|
+
var formatPickedByUser = false;
|
|
10644
10663
|
var toggleBtn = null; // checkbox <input> for toggling layer selection
|
|
10645
10664
|
var exportBtn = gui.container.findChild('.export-btn').addClass('disabled');
|
|
10646
10665
|
var ofileName = gui.container.findChild('#ofile-name');
|
|
@@ -10709,6 +10728,7 @@
|
|
|
10709
10728
|
|
|
10710
10729
|
function turnOn() {
|
|
10711
10730
|
layersArr = initLayerMenu();
|
|
10731
|
+
formatPickedByUser = false;
|
|
10712
10732
|
// initZipOption();
|
|
10713
10733
|
initFormatMenu();
|
|
10714
10734
|
updateExportCheckboxes();
|
|
@@ -10890,6 +10910,7 @@
|
|
|
10890
10910
|
}
|
|
10891
10911
|
|
|
10892
10912
|
function updateToggleBtn() {
|
|
10913
|
+
refreshFormatMenu(); // the layer selection decides which formats apply
|
|
10893
10914
|
updateExportCheckboxes(); // checkbox visibility is affected by number of export layers
|
|
10894
10915
|
if (!toggleBtn) return;
|
|
10895
10916
|
var state = getSelectionState();
|
|
@@ -10912,13 +10933,17 @@
|
|
|
10912
10933
|
}
|
|
10913
10934
|
|
|
10914
10935
|
function getDefaultExportFormat() {
|
|
10936
|
+
var layers = getExportFormatLayers();
|
|
10915
10937
|
var active = model.getActiveLayer();
|
|
10916
10938
|
var dataset = active.dataset;
|
|
10917
10939
|
var inputFmt = dataset.info && dataset.info.input_formats &&
|
|
10918
10940
|
dataset.info.input_formats[0];
|
|
10919
|
-
//
|
|
10920
|
-
//
|
|
10921
|
-
if (
|
|
10941
|
+
// Rasters and nothing else can only go to GeoTIFF, the one format that
|
|
10942
|
+
// takes their pixels.
|
|
10943
|
+
if (layers.length > 0 && layers.every(hasRaster)) return 'geotiff';
|
|
10944
|
+
// SVG is the default for a selection that mixes rasters with vector layers,
|
|
10945
|
+
// being the only format that accepts both.
|
|
10946
|
+
if (layers.some(hasRaster)) return 'svg';
|
|
10922
10947
|
return getExportFormats().includes(inputFmt) ? inputFmt : 'geojson';
|
|
10923
10948
|
}
|
|
10924
10949
|
|
|
@@ -10926,16 +10951,26 @@
|
|
|
10926
10951
|
var formats = ['shapefile', 'json', 'geojson', 'dsv', 'topojson', 'flatgeobuf', 'geopackage', 'geoparquet', 'kml', 'svg', internal.PACKAGE_EXT];
|
|
10927
10952
|
// GeoTIFF is the one format here that only accepts raster layers, so it is
|
|
10928
10953
|
// offered only when there is a raster to export.
|
|
10929
|
-
if (
|
|
10954
|
+
if (getExportFormatLayers().some(hasRaster)) formats.push('geotiff');
|
|
10930
10955
|
return formats;
|
|
10931
10956
|
}
|
|
10932
10957
|
|
|
10933
|
-
|
|
10958
|
+
// The layers the format menu describes: the ones checked for export, falling
|
|
10959
|
+
// back to the active layer when the menu is not up yet or nothing is checked.
|
|
10960
|
+
function getExportFormatLayers() {
|
|
10961
|
+
var selected = layersArr.length ? getSelectedLayerEntries() : [];
|
|
10934
10962
|
var active = model.getActiveLayer();
|
|
10935
|
-
|
|
10963
|
+
if (selected.length > 0) {
|
|
10964
|
+
return selected.map(function(o) { return o.layer; });
|
|
10965
|
+
}
|
|
10966
|
+
return active && active.layer ? [active.layer] : [];
|
|
10967
|
+
}
|
|
10968
|
+
|
|
10969
|
+
function hasRaster(lyr) {
|
|
10970
|
+
return internal.layerHasRaster(lyr);
|
|
10936
10971
|
}
|
|
10937
10972
|
|
|
10938
|
-
function initFormatMenu() {
|
|
10973
|
+
function initFormatMenu(preferredFmt) {
|
|
10939
10974
|
var formats = getExportFormats();
|
|
10940
10975
|
var items = formats.map(function(fmt) {
|
|
10941
10976
|
return utils$1.format('<td><label><input type="radio" name="format" value="%s"' +
|
|
@@ -10947,13 +10982,39 @@
|
|
|
10947
10982
|
}
|
|
10948
10983
|
table += '</table>';
|
|
10949
10984
|
menu.findChild('.export-formats').html(table);
|
|
10950
|
-
|
|
10985
|
+
menuFormats = formats;
|
|
10986
|
+
setSelectedFormat(formats.includes(preferredFmt) ? preferredFmt :
|
|
10987
|
+
getDefaultExportFormat());
|
|
10951
10988
|
// update save-as settings when value changes
|
|
10952
10989
|
menu.findChildren('input[type="radio"]').forEach(el => {
|
|
10953
|
-
el.on('change',
|
|
10990
|
+
el.on('change', onFormatChange);
|
|
10954
10991
|
});
|
|
10955
10992
|
}
|
|
10956
10993
|
|
|
10994
|
+
function onFormatChange() {
|
|
10995
|
+
// A format the user picked is theirs to keep, however the layer selection
|
|
10996
|
+
// changes afterwards.
|
|
10997
|
+
formatPickedByUser = true;
|
|
10998
|
+
updateExportCheckboxes();
|
|
10999
|
+
}
|
|
11000
|
+
|
|
11001
|
+
function setSelectedFormat(fmt) {
|
|
11002
|
+
var el = menu.findChild('.export-formats input[value="' + fmt + '"]');
|
|
11003
|
+
if (el) el.node().checked = true;
|
|
11004
|
+
}
|
|
11005
|
+
|
|
11006
|
+
// Which formats apply depends on what is checked for export, so unchecking
|
|
11007
|
+
// the vector layers of a mixed session both offers GeoTIFF and makes it the
|
|
11008
|
+
// default -- unless the user has already chosen a format.
|
|
11009
|
+
function refreshFormatMenu() {
|
|
11010
|
+
var formats = getExportFormats();
|
|
11011
|
+
if (formats.join(',') != (menuFormats || []).join(',')) {
|
|
11012
|
+
initFormatMenu(formatPickedByUser ? getSelectedFormat() : null);
|
|
11013
|
+
} else if (!formatPickedByUser) {
|
|
11014
|
+
setSelectedFormat(getDefaultExportFormat());
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
|
|
10957
11018
|
|
|
10958
11019
|
// function getInputFormats() {
|
|
10959
11020
|
// return model.getDatasets().reduce(function(memo, d) {
|
|
@@ -12095,6 +12156,7 @@
|
|
|
12095
12156
|
}
|
|
12096
12157
|
|
|
12097
12158
|
function isAppUndoEnabled() {
|
|
12159
|
+
if (getUndoQueryValue() == 'off') return false;
|
|
12098
12160
|
return appUndoForcedByUrl() || appUndoSettingIsOn();
|
|
12099
12161
|
}
|
|
12100
12162
|
|
|
@@ -25430,6 +25492,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
25430
25492
|
return {
|
|
25431
25493
|
layerCount: dataset.layers ? dataset.layers.length : 0,
|
|
25432
25494
|
arcCount: dataset.arcs ? dataset.arcs.size() : 0,
|
|
25495
|
+
crs_string: dataset.info && dataset.info.crs_string || null,
|
|
25433
25496
|
layers: (dataset.layers || []).map(function(lyr) {
|
|
25434
25497
|
return {
|
|
25435
25498
|
name: getLayerName(lyr),
|
package/www/mapshaper.js
CHANGED
|
@@ -10465,6 +10465,22 @@
|
|
|
10465
10465
|
return crs;
|
|
10466
10466
|
}
|
|
10467
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
|
+
|
|
10468
10484
|
function parseCrsString$1(str) {
|
|
10469
10485
|
var defn = getProjDefn(str); // defn is a string or a Proj object
|
|
10470
10486
|
var P;
|
|
@@ -10720,6 +10736,7 @@
|
|
|
10720
10736
|
setProjectionLoader: setProjectionLoader,
|
|
10721
10737
|
toLngLat: toLngLat,
|
|
10722
10738
|
tryParseCrsString: tryParseCrsString,
|
|
10739
|
+
tryParseWktToProj: tryParseWktToProj,
|
|
10723
10740
|
wkt1ToProj: wkt1ToProj,
|
|
10724
10741
|
wkt2ToProj: wkt2ToProj,
|
|
10725
10742
|
wktToProj: wktToProj
|
|
@@ -13129,6 +13146,67 @@
|
|
|
13129
13146
|
toLowerCaseExtension: toLowerCaseExtension
|
|
13130
13147
|
});
|
|
13131
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
|
+
|
|
13132
13210
|
// Constants shared between the .msx (mapshaper snapshot) writer in
|
|
13133
13211
|
// mapshaper-pack.mjs and the file-type registry in
|
|
13134
13212
|
// io/mapshaper-file-types.mjs. Living here lets file-types reference the
|
|
@@ -13142,7 +13220,10 @@
|
|
|
13142
13220
|
function guessInputFileType(file) {
|
|
13143
13221
|
var ext = getFileExtension(file || '').toLowerCase(),
|
|
13144
13222
|
type = null;
|
|
13145
|
-
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') {
|
|
13146
13227
|
type = ext;
|
|
13147
13228
|
} else if (ext == 'jpg' || ext == 'jpeg') {
|
|
13148
13229
|
type = 'jpeg';
|
|
@@ -13166,7 +13247,8 @@
|
|
|
13166
13247
|
|
|
13167
13248
|
// File types that can be imported but are not convertible to datasets
|
|
13168
13249
|
function isAuxiliaryInputFileType(type) {
|
|
13169
|
-
return type == 'prj' || type == 'shx' || type == 'cpg' || type == 'world'
|
|
13250
|
+
return type == 'prj' || type == 'shx' || type == 'cpg' || type == 'world' ||
|
|
13251
|
+
type == 'aux';
|
|
13170
13252
|
}
|
|
13171
13253
|
|
|
13172
13254
|
function isRasterImageInputType(type) {
|
|
@@ -34036,11 +34118,6 @@ ${svg}
|
|
|
34036
34118
|
// The raster covers areas, i.e. a coordinate refers to a pixel's corner rather
|
|
34037
34119
|
// than its center. This matches how mapshaper's grid bbox is defined.
|
|
34038
34120
|
var RASTER_TYPE_AREA = 1;
|
|
34039
|
-
// GDAL's "persistent auxiliary metadata" sidecar, which it reads for any raster
|
|
34040
|
-
// it opens. Unlike the .prj file that accompanies a shapefile, this is a
|
|
34041
|
-
// sidecar that GDAL-based software (QGIS included) actually consults for a
|
|
34042
|
-
// GeoTIFF's CRS.
|
|
34043
|
-
var AUX_EXT = '.aux.xml';
|
|
34044
34121
|
|
|
34045
34122
|
function exportGeoTIFF(dataset, opts) {
|
|
34046
34123
|
var extension = opts.extension ||
|
|
@@ -34058,8 +34135,8 @@ ${svg}
|
|
|
34058
34135
|
if (crs.code || crs.geoKeys) return;
|
|
34059
34136
|
if (crs.wkt) {
|
|
34060
34137
|
files.push({
|
|
34061
|
-
filename: filename
|
|
34062
|
-
content:
|
|
34138
|
+
filename: getAuxFilename(filename),
|
|
34139
|
+
content: formatAuxXml(crs.wkt)
|
|
34063
34140
|
});
|
|
34064
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.');
|
|
34065
34142
|
} else {
|
|
@@ -34169,14 +34246,6 @@ ${svg}
|
|
|
34169
34246
|
}
|
|
34170
34247
|
}
|
|
34171
34248
|
|
|
34172
|
-
function getAuxXml(wkt) {
|
|
34173
|
-
return '<PAMDataset>\n <SRS>' + escapeXml(wkt) + '</SRS>\n</PAMDataset>\n';
|
|
34174
|
-
}
|
|
34175
|
-
|
|
34176
|
-
function escapeXml(str) {
|
|
34177
|
-
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
34178
|
-
}
|
|
34179
|
-
|
|
34180
34249
|
function getOutputFormat(dataset, opts) {
|
|
34181
34250
|
var outFile = opts.file || null,
|
|
34182
34251
|
inFmt = dataset.info && dataset.info.input_formats && dataset.info.input_formats[0],
|
|
@@ -42820,7 +42889,8 @@ ${svg}
|
|
|
42820
42889
|
var dynamicImportModule = Function('id', 'return import(id)');
|
|
42821
42890
|
var DEFAULT_MAX_IMPORT_PIXELS = 16e6;
|
|
42822
42891
|
|
|
42823
|
-
|
|
42892
|
+
// aux: contents of the file's .aux.xml sidecar, if it has one
|
|
42893
|
+
async function importGeoTIFF(input, optsArg, aux) {
|
|
42824
42894
|
var opts = optsArg || {};
|
|
42825
42895
|
var geotiff = await loadGeoTIFFLib();
|
|
42826
42896
|
var source = getGeoTIFFSource(input);
|
|
@@ -42838,7 +42908,7 @@ ${svg}
|
|
|
42838
42908
|
raster: imported.raster
|
|
42839
42909
|
}]
|
|
42840
42910
|
};
|
|
42841
|
-
await importGeoTIFFCrs(dataset, sourceImage);
|
|
42911
|
+
await importGeoTIFFCrs(dataset, sourceImage, aux);
|
|
42842
42912
|
return dataset;
|
|
42843
42913
|
}
|
|
42844
42914
|
|
|
@@ -43187,32 +43257,117 @@ ${svg}
|
|
|
43187
43257
|
// in its own georeferencing, and only the things that need to know what those
|
|
43188
43258
|
// coordinates mean (reprojecting, basemaps, measuring) are unavailable. So a
|
|
43189
43259
|
// CRS that cannot be read is reported and set aside, never raised as an error.
|
|
43190
|
-
|
|
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) {
|
|
43191
43266
|
var crsInfo = await getGeoTIFFCrsInfo(image);
|
|
43192
|
-
var
|
|
43193
|
-
|
|
43194
|
-
|
|
43195
|
-
|
|
43196
|
-
|
|
43197
|
-
|
|
43198
|
-
|
|
43199
|
-
|
|
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);
|
|
43278
|
+
return;
|
|
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;
|
|
43200
43292
|
}
|
|
43201
|
-
crs = tryParseCrsString(crsString);
|
|
43202
43293
|
}
|
|
43203
|
-
|
|
43204
|
-
|
|
43205
|
-
|
|
43206
|
-
|
|
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;
|
|
43300
|
+
try {
|
|
43301
|
+
await initProjLibrary({crs: str});
|
|
43302
|
+
} catch(e) {
|
|
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;
|
|
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
|
+
}
|
|
43207
43346
|
return;
|
|
43208
43347
|
}
|
|
43209
|
-
|
|
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 ');
|
|
43210
43356
|
}
|
|
43211
43357
|
|
|
43212
43358
|
async function getGeoTIFFCrsInfo(image) {
|
|
43213
43359
|
var keys = image.getGeoKeys && image.getGeoKeys() || {};
|
|
43214
43360
|
var code = keys.ProjectedCSTypeGeoKey;
|
|
43215
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
|
+
}
|
|
43216
43371
|
if (isGeoTIFFAuthorityCode(code)) {
|
|
43217
43372
|
return {crsString: 'EPSG:' + code};
|
|
43218
43373
|
}
|
|
@@ -43230,6 +43385,23 @@ ${svg}
|
|
|
43230
43385
|
return 'The GeoTIFF does not contain usable CRS data';
|
|
43231
43386
|
}
|
|
43232
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
|
+
|
|
43233
43405
|
function isGeoTIFFAuthorityCode(code) {
|
|
43234
43406
|
return code > 0 && code != 32767;
|
|
43235
43407
|
}
|
|
@@ -44438,6 +44610,14 @@ ${svg}
|
|
|
44438
44610
|
data = obj.prj;
|
|
44439
44611
|
dataset = {layers: [], info: {wkt1: data.content}};
|
|
44440
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
|
+
|
|
44441
44621
|
} else if (obj.kml) {
|
|
44442
44622
|
dataFmt = 'kml';
|
|
44443
44623
|
data = obj.kml;
|
|
@@ -44483,7 +44663,7 @@ ${svg}
|
|
|
44483
44663
|
} else if (obj.geotiff) {
|
|
44484
44664
|
dataFmt = 'geotiff';
|
|
44485
44665
|
data = obj.geotiff;
|
|
44486
|
-
dataset = await importGeoTIFF(data, opts);
|
|
44666
|
+
dataset = await importGeoTIFF(data, opts, obj.aux);
|
|
44487
44667
|
} else if (obj.png || obj.jpeg) {
|
|
44488
44668
|
dataFmt = obj.png ? 'png' : 'jpeg';
|
|
44489
44669
|
data = obj[dataFmt];
|
|
@@ -44545,6 +44725,14 @@ ${svg}
|
|
|
44545
44725
|
return dataset;
|
|
44546
44726
|
}
|
|
44547
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
|
+
|
|
44548
44736
|
function importDbf(input, opts) {
|
|
44549
44737
|
var table;
|
|
44550
44738
|
opts = utils.extend({}, opts);
|
|
@@ -45015,6 +45203,8 @@ ${svg}
|
|
|
45015
45203
|
readShapefileAuxFiles(path, input, cache);
|
|
45016
45204
|
} else if (isRasterImageInputType(fileType)) {
|
|
45017
45205
|
readRasterImageAuxFiles(path, input, cache);
|
|
45206
|
+
} else if (fileType == 'geotiff') {
|
|
45207
|
+
readGeoTIFFAuxFiles(path, input, cache);
|
|
45018
45208
|
}
|
|
45019
45209
|
if (fileType == 'shp' && !input.dbf) {
|
|
45020
45210
|
message(utils.format("[%s] .dbf file is missing - shapes imported without attribute data.", path));
|
|
@@ -45139,6 +45329,13 @@ ${svg}
|
|
|
45139
45329
|
}
|
|
45140
45330
|
}
|
|
45141
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
|
+
|
|
45142
45339
|
function readRasterImageAuxFiles(path, obj, cache) {
|
|
45143
45340
|
var prjPath = replaceFileExtension(path, 'prj');
|
|
45144
45341
|
var worldPath = findWorldFile(path, cache);
|
|
@@ -45176,11 +45373,15 @@ ${svg}
|
|
|
45176
45373
|
|
|
45177
45374
|
function removeRasterImageSidecars(files) {
|
|
45178
45375
|
var imageBases = {};
|
|
45376
|
+
var rasterPaths = {};
|
|
45179
45377
|
files.forEach(function(file) {
|
|
45180
45378
|
var type = guessInputFileType(file);
|
|
45181
45379
|
if (isRasterImageInputType(type)) {
|
|
45182
45380
|
imageBases[getFileBase(file).toLowerCase()] = true;
|
|
45183
45381
|
}
|
|
45382
|
+
if (isRasterImageInputType(type) || type == 'geotiff') {
|
|
45383
|
+
rasterPaths[file.toLowerCase()] = true;
|
|
45384
|
+
}
|
|
45184
45385
|
});
|
|
45185
45386
|
return files.filter(function(file) {
|
|
45186
45387
|
var ext = getFileExtension(file).toLowerCase();
|
|
@@ -45189,6 +45390,11 @@ ${svg}
|
|
|
45189
45390
|
if ((type == 'prj' || isWorldFileExtension(ext)) && imageBases[base]) {
|
|
45190
45391
|
return false;
|
|
45191
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
|
+
}
|
|
45192
45398
|
return true;
|
|
45193
45399
|
});
|
|
45194
45400
|
}
|
|
@@ -75691,7 +75897,7 @@ ${svg}
|
|
|
75691
75897
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
75692
75898
|
}
|
|
75693
75899
|
|
|
75694
|
-
var version = "0.7.
|
|
75900
|
+
var version = "0.7.51";
|
|
75695
75901
|
|
|
75696
75902
|
// Parse command line args into commands and run them
|
|
75697
75903
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -77581,6 +77787,7 @@ ${svg}
|
|
|
77581
77787
|
JoinTables,
|
|
77582
77788
|
JsonImport,
|
|
77583
77789
|
JsonTable,
|
|
77790
|
+
GeoTIFFAux,
|
|
77584
77791
|
GeoTIFFEncode,
|
|
77585
77792
|
GeoTIFFGeoKeys,
|
|
77586
77793
|
KeepShapes,
|