mapshaper 0.5.78 → 0.5.79

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
+ v0.5.79
2
+ * More permissive importing of some non-standard Shapefiles.
3
+
1
4
  v0.5.78
2
5
  * Added support for reading and writing fixed-width text files.
6
+ * Bug fixes.
3
7
 
4
8
  v0.5.77
5
9
  * Added -dashlines command (formerly -split-lines).
package/mapshaper.js CHANGED
@@ -1330,9 +1330,10 @@
1330
1330
  }
1331
1331
 
1332
1332
  function logArgs(args) {
1333
- if (LOGGING && !getStateVar('QUIET') && utils.isArrayLike(args)) {
1334
- (!STDOUT && console.error || console.log).call(console, formatLogArgs(args));
1335
- }
1333
+ if (!LOGGING || getStateVar('QUIET') || !utils.isArrayLike(args)) return;
1334
+ var msg = formatLogArgs(args);
1335
+ if (STDOUT) console.log(msg);
1336
+ else console.error(msg);
1336
1337
  }
1337
1338
 
1338
1339
  var Logging = /*#__PURE__*/Object.freeze({
@@ -20340,6 +20341,16 @@ ${svg}
20340
20341
  })
20341
20342
  .option('name', nameOpt);
20342
20343
 
20344
+ // parser.command('shapes')
20345
+ // .describe('convert points to shapes')
20346
+ // .option('type', {
20347
+ // })
20348
+ // .option('size', {
20349
+ // })
20350
+ // .option('rotation', {
20351
+ // })
20352
+
20353
+
20343
20354
  parser.command('subdivide')
20344
20355
  .describe('recursively split a layer using a JS expression')
20345
20356
  .validate(validateExpressionOpt)
@@ -21208,7 +21219,7 @@ ${svg}
21208
21219
  var header = parseHeader(shpFile.readToBinArray(0, 100));
21209
21220
  var shpSize = shpFile.size();
21210
21221
  var RecordClass = new ShpRecordClass(header.type);
21211
- var shpOffset, recordCount, skippedBytes;
21222
+ var shpOffset, recordCount;
21212
21223
  var shxBin, shxFile;
21213
21224
 
21214
21225
  if (shxSrc) {
@@ -21235,61 +21246,36 @@ ${svg}
21235
21246
 
21236
21247
  // Iterator interface for reading shape records
21237
21248
  this.nextShape = function() {
21238
- var shape = readNextShape();
21239
- if (!shape) {
21240
- if (skippedBytes > 0) {
21241
- // Encountered in files from natural earth v2.0.0:
21242
- // ne_10m_admin_0_boundary_lines_land.shp
21243
- // ne_110m_admin_0_scale_rank.shp
21244
- verbose("Skipped over " + skippedBytes + " non-data bytes in the .shp file.");
21245
- }
21249
+ var shape = readNextShape(recordCount);
21250
+ if (shape) {
21251
+ recordCount++;
21252
+ } else {
21246
21253
  shpFile.close();
21247
21254
  reset();
21248
21255
  }
21249
21256
  return shape;
21250
21257
  };
21251
21258
 
21252
- function readNextShape() {
21253
- var expectedId = recordCount + 1; // Shapefile ids are 1-based
21259
+ // Returns a shape record or null if no more shapes can be read
21260
+ //
21261
+ function readNextShape(i) {
21262
+ var expectedId = i + 1; // Shapefile ids are 1-based
21254
21263
  var shape, offset;
21255
- if (done()) return null;
21256
21264
  if (shxBin) {
21257
- shxBin.position(100 + recordCount * 8);
21265
+ if (shxFile.size() <= 100 + i * 8) return null; // done
21266
+ shxBin.position(100 + i * 8);
21258
21267
  offset = shxBin.readUint32() * 2;
21259
- if (offset > shpOffset) {
21260
- skippedBytes += offset - shpOffset;
21261
- }
21268
+ shape = readIndexedShape(shpFile, offset, expectedId);
21262
21269
  } else {
21270
+ // Reading without a .shx file (returns null at end-of-file)
21263
21271
  offset = shpOffset;
21264
- }
21265
- shape = readShapeAtOffset(offset);
21266
- if (!shape) {
21267
- // Some in-the-wild .shp files contain junk bytes between records. This
21268
- // is a problem if the .shx index file is not present.
21269
- // Here, we try to scan past the junk to find the next record.
21270
- shape = huntForNextShape(offset, expectedId);
21271
- }
21272
- if (shape) {
21273
- if (shape.id < expectedId) {
21274
- message("Found a Shapefile record with the same id as a previous record (" + shape.id + ") -- skipping.");
21275
- return readNextShape();
21276
- } else if (shape.id > expectedId) {
21277
- stop("Shapefile contains an out-of-sequence record. Possible data corruption -- bailing.");
21278
- }
21279
- recordCount++;
21272
+ shape = readNonIndexedShape(shpFile, offset, expectedId);
21280
21273
  }
21281
21274
  return shape || null;
21282
21275
  }
21283
21276
 
21284
- function done() {
21285
- if (shxFile && shxFile.size() <= 100 + recordCount * 8) return true;
21286
- if (shpOffset + 12 > shpSize) return true;
21287
- return false;
21288
- }
21289
-
21290
21277
  function reset() {
21291
21278
  shpOffset = 100;
21292
- skippedBytes = 0;
21293
21279
  recordCount = 0;
21294
21280
  }
21295
21281
 
@@ -21319,7 +21305,8 @@ ${svg}
21319
21305
  return header;
21320
21306
  }
21321
21307
 
21322
- function readShapeAtOffset(offset) {
21308
+
21309
+ function readShapeAtOffset(shpFile, offset) {
21323
21310
  var shape = null,
21324
21311
  recordSize, recordType, recordId, goodSize, goodType, bin;
21325
21312
 
@@ -21334,32 +21321,62 @@ ${svg}
21334
21321
  if (goodSize && goodType) {
21335
21322
  bin = shpFile.readToBinArray(offset, recordSize);
21336
21323
  shape = new RecordClass(bin, recordSize);
21337
- shpOffset = offset + shape.byteLength; // advance read position
21338
21324
  }
21339
21325
  }
21340
21326
  return shape;
21341
21327
  }
21342
21328
 
21343
- // TODO: add tests
21344
- // Try to scan past unreadable content to find next record
21345
- function huntForNextShape(start, id) {
21346
- var offset = start + 4,
21329
+ function readIndexedShape(shpFile, offset, expectedId) {
21330
+ var shape = readShapeAtOffset(shpFile, offset);
21331
+ if (!shape) {
21332
+ stop('Index of Shapefile record', expectedId, 'in the .shx file is invalid.');
21333
+ }
21334
+ if (shape.id != expectedId) {
21335
+ // stop("Found a Shapefile record with an out-of-sequence id (" + shape.id + ") -- bailing.");
21336
+ message(`Warning: A feature has a different record number in .shx (${expectedId}) and .shp (${shape.id}).`);
21337
+ }
21338
+ // TODO: consider printing verbose message if a .shp file contains garbage bytes
21339
+ // example files:
21340
+ // ne_10m_admin_0_boundary_lines_land.shp
21341
+ // ne_110m_admin_0_scale_rank.shp
21342
+ return shape;
21343
+ }
21344
+
21345
+ // The Shapefile specification does not require records to be densely packed or
21346
+ // in consecutive sequence in the .shp file. This is a problem when the .shx
21347
+ // index file is not present.
21348
+ //
21349
+ // Here, we try to scan past invalid content to find the next record.
21350
+ // Records are required to be in sequential order.
21351
+ //
21352
+ function readNonIndexedShape(shpFile, start, expectedId) {
21353
+ var offset = start,
21354
+ fileSize = shpFile.size(),
21347
21355
  shape = null,
21348
- bin, recordId, recordType, count;
21349
- while (offset + 12 <= shpSize) {
21356
+ bin, recordId, recordType, isValidType;
21357
+ while (offset + 12 <= fileSize) {
21350
21358
  bin = shpFile.readToBinArray(offset, 12);
21351
21359
  recordId = bin.bigEndian().readUint32();
21352
21360
  recordType = bin.littleEndian().skipBytes(4).readUint32();
21353
- if (recordId == id && (recordType == header.type || recordType === 0)) {
21354
- // we have a likely position, but may still be unparsable
21355
- shape = readShapeAtOffset(offset);
21356
- break;
21361
+ isValidType = recordType == header.type || recordType === 0;
21362
+ if (!isValidType || recordId != expectedId && recordType === 0) {
21363
+ offset += 4; // keep scanning -- try next integer position
21364
+ continue;
21357
21365
  }
21358
- offset += 4; // try next integer position
21366
+ shape = readShapeAtOffset(shpFile, offset);
21367
+ if (!shape) break; // probably ran into end of file
21368
+ shpOffset = offset + shape.byteLength; // update
21369
+ if (recordId == expectedId) break; // found an apparently valid shape
21370
+ if (recordId < expectedId) {
21371
+ message("Found a Shapefile record with the same id as a previous record (" + shape.id + ") -- skipping.");
21372
+ offset += shape.byteLength;
21373
+ } else {
21374
+ stop("Shapefile contains an out-of-sequence record. Possible data corruption -- bailing.");
21375
+ }
21376
+ }
21377
+ if (shape && offset > start) {
21378
+ verbose("Skipped over " + (offset - start) + " non-data bytes in the .shp file.");
21359
21379
  }
21360
- count = shape ? offset - start : shpSize - start;
21361
- // debug('Skipped', count, 'bytes', shape ? 'before record ' + id : 'at the end of the file');
21362
- skippedBytes += count;
21363
21380
  return shape;
21364
21381
  }
21365
21382
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.5.78",
3
+ "version": "0.5.79",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
package/www/mapshaper.js CHANGED
@@ -1330,9 +1330,10 @@
1330
1330
  }
1331
1331
 
1332
1332
  function logArgs(args) {
1333
- if (LOGGING && !getStateVar('QUIET') && utils.isArrayLike(args)) {
1334
- (!STDOUT && console.error || console.log).call(console, formatLogArgs(args));
1335
- }
1333
+ if (!LOGGING || getStateVar('QUIET') || !utils.isArrayLike(args)) return;
1334
+ var msg = formatLogArgs(args);
1335
+ if (STDOUT) console.log(msg);
1336
+ else console.error(msg);
1336
1337
  }
1337
1338
 
1338
1339
  var Logging = /*#__PURE__*/Object.freeze({
@@ -20340,6 +20341,16 @@ ${svg}
20340
20341
  })
20341
20342
  .option('name', nameOpt);
20342
20343
 
20344
+ // parser.command('shapes')
20345
+ // .describe('convert points to shapes')
20346
+ // .option('type', {
20347
+ // })
20348
+ // .option('size', {
20349
+ // })
20350
+ // .option('rotation', {
20351
+ // })
20352
+
20353
+
20343
20354
  parser.command('subdivide')
20344
20355
  .describe('recursively split a layer using a JS expression')
20345
20356
  .validate(validateExpressionOpt)
@@ -21208,7 +21219,7 @@ ${svg}
21208
21219
  var header = parseHeader(shpFile.readToBinArray(0, 100));
21209
21220
  var shpSize = shpFile.size();
21210
21221
  var RecordClass = new ShpRecordClass(header.type);
21211
- var shpOffset, recordCount, skippedBytes;
21222
+ var shpOffset, recordCount;
21212
21223
  var shxBin, shxFile;
21213
21224
 
21214
21225
  if (shxSrc) {
@@ -21235,61 +21246,36 @@ ${svg}
21235
21246
 
21236
21247
  // Iterator interface for reading shape records
21237
21248
  this.nextShape = function() {
21238
- var shape = readNextShape();
21239
- if (!shape) {
21240
- if (skippedBytes > 0) {
21241
- // Encountered in files from natural earth v2.0.0:
21242
- // ne_10m_admin_0_boundary_lines_land.shp
21243
- // ne_110m_admin_0_scale_rank.shp
21244
- verbose("Skipped over " + skippedBytes + " non-data bytes in the .shp file.");
21245
- }
21249
+ var shape = readNextShape(recordCount);
21250
+ if (shape) {
21251
+ recordCount++;
21252
+ } else {
21246
21253
  shpFile.close();
21247
21254
  reset();
21248
21255
  }
21249
21256
  return shape;
21250
21257
  };
21251
21258
 
21252
- function readNextShape() {
21253
- var expectedId = recordCount + 1; // Shapefile ids are 1-based
21259
+ // Returns a shape record or null if no more shapes can be read
21260
+ //
21261
+ function readNextShape(i) {
21262
+ var expectedId = i + 1; // Shapefile ids are 1-based
21254
21263
  var shape, offset;
21255
- if (done()) return null;
21256
21264
  if (shxBin) {
21257
- shxBin.position(100 + recordCount * 8);
21265
+ if (shxFile.size() <= 100 + i * 8) return null; // done
21266
+ shxBin.position(100 + i * 8);
21258
21267
  offset = shxBin.readUint32() * 2;
21259
- if (offset > shpOffset) {
21260
- skippedBytes += offset - shpOffset;
21261
- }
21268
+ shape = readIndexedShape(shpFile, offset, expectedId);
21262
21269
  } else {
21270
+ // Reading without a .shx file (returns null at end-of-file)
21263
21271
  offset = shpOffset;
21264
- }
21265
- shape = readShapeAtOffset(offset);
21266
- if (!shape) {
21267
- // Some in-the-wild .shp files contain junk bytes between records. This
21268
- // is a problem if the .shx index file is not present.
21269
- // Here, we try to scan past the junk to find the next record.
21270
- shape = huntForNextShape(offset, expectedId);
21271
- }
21272
- if (shape) {
21273
- if (shape.id < expectedId) {
21274
- message("Found a Shapefile record with the same id as a previous record (" + shape.id + ") -- skipping.");
21275
- return readNextShape();
21276
- } else if (shape.id > expectedId) {
21277
- stop("Shapefile contains an out-of-sequence record. Possible data corruption -- bailing.");
21278
- }
21279
- recordCount++;
21272
+ shape = readNonIndexedShape(shpFile, offset, expectedId);
21280
21273
  }
21281
21274
  return shape || null;
21282
21275
  }
21283
21276
 
21284
- function done() {
21285
- if (shxFile && shxFile.size() <= 100 + recordCount * 8) return true;
21286
- if (shpOffset + 12 > shpSize) return true;
21287
- return false;
21288
- }
21289
-
21290
21277
  function reset() {
21291
21278
  shpOffset = 100;
21292
- skippedBytes = 0;
21293
21279
  recordCount = 0;
21294
21280
  }
21295
21281
 
@@ -21319,7 +21305,8 @@ ${svg}
21319
21305
  return header;
21320
21306
  }
21321
21307
 
21322
- function readShapeAtOffset(offset) {
21308
+
21309
+ function readShapeAtOffset(shpFile, offset) {
21323
21310
  var shape = null,
21324
21311
  recordSize, recordType, recordId, goodSize, goodType, bin;
21325
21312
 
@@ -21334,32 +21321,62 @@ ${svg}
21334
21321
  if (goodSize && goodType) {
21335
21322
  bin = shpFile.readToBinArray(offset, recordSize);
21336
21323
  shape = new RecordClass(bin, recordSize);
21337
- shpOffset = offset + shape.byteLength; // advance read position
21338
21324
  }
21339
21325
  }
21340
21326
  return shape;
21341
21327
  }
21342
21328
 
21343
- // TODO: add tests
21344
- // Try to scan past unreadable content to find next record
21345
- function huntForNextShape(start, id) {
21346
- var offset = start + 4,
21329
+ function readIndexedShape(shpFile, offset, expectedId) {
21330
+ var shape = readShapeAtOffset(shpFile, offset);
21331
+ if (!shape) {
21332
+ stop('Index of Shapefile record', expectedId, 'in the .shx file is invalid.');
21333
+ }
21334
+ if (shape.id != expectedId) {
21335
+ // stop("Found a Shapefile record with an out-of-sequence id (" + shape.id + ") -- bailing.");
21336
+ message(`Warning: A feature has a different record number in .shx (${expectedId}) and .shp (${shape.id}).`);
21337
+ }
21338
+ // TODO: consider printing verbose message if a .shp file contains garbage bytes
21339
+ // example files:
21340
+ // ne_10m_admin_0_boundary_lines_land.shp
21341
+ // ne_110m_admin_0_scale_rank.shp
21342
+ return shape;
21343
+ }
21344
+
21345
+ // The Shapefile specification does not require records to be densely packed or
21346
+ // in consecutive sequence in the .shp file. This is a problem when the .shx
21347
+ // index file is not present.
21348
+ //
21349
+ // Here, we try to scan past invalid content to find the next record.
21350
+ // Records are required to be in sequential order.
21351
+ //
21352
+ function readNonIndexedShape(shpFile, start, expectedId) {
21353
+ var offset = start,
21354
+ fileSize = shpFile.size(),
21347
21355
  shape = null,
21348
- bin, recordId, recordType, count;
21349
- while (offset + 12 <= shpSize) {
21356
+ bin, recordId, recordType, isValidType;
21357
+ while (offset + 12 <= fileSize) {
21350
21358
  bin = shpFile.readToBinArray(offset, 12);
21351
21359
  recordId = bin.bigEndian().readUint32();
21352
21360
  recordType = bin.littleEndian().skipBytes(4).readUint32();
21353
- if (recordId == id && (recordType == header.type || recordType === 0)) {
21354
- // we have a likely position, but may still be unparsable
21355
- shape = readShapeAtOffset(offset);
21356
- break;
21361
+ isValidType = recordType == header.type || recordType === 0;
21362
+ if (!isValidType || recordId != expectedId && recordType === 0) {
21363
+ offset += 4; // keep scanning -- try next integer position
21364
+ continue;
21357
21365
  }
21358
- offset += 4; // try next integer position
21366
+ shape = readShapeAtOffset(shpFile, offset);
21367
+ if (!shape) break; // probably ran into end of file
21368
+ shpOffset = offset + shape.byteLength; // update
21369
+ if (recordId == expectedId) break; // found an apparently valid shape
21370
+ if (recordId < expectedId) {
21371
+ message("Found a Shapefile record with the same id as a previous record (" + shape.id + ") -- skipping.");
21372
+ offset += shape.byteLength;
21373
+ } else {
21374
+ stop("Shapefile contains an out-of-sequence record. Possible data corruption -- bailing.");
21375
+ }
21376
+ }
21377
+ if (shape && offset > start) {
21378
+ verbose("Skipped over " + (offset - start) + " non-data bytes in the .shp file.");
21359
21379
  }
21360
- count = shape ? offset - start : shpSize - start;
21361
- // debug('Skipped', count, 'bytes', shape ? 'before record ' + id : 'at the end of the file');
21362
- skippedBytes += count;
21363
21380
  return shape;
21364
21381
  }
21365
21382
  }