mapshaper 0.5.115 → 0.5.118

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ v0.5.118
2
+ * Added -comment command, as a way to add inline comments in long sequences of commands.
3
+ * Added support for importing .cpg files (Shapefile text encoding) in the web UI.
4
+
5
+ v0.5.117
6
+ * Support reading DBF files larger than 2GB (command line program only).
7
+
8
+ v0.5.116
9
+ * Prevent 0-length DBF string fields, for interoperability with PostGIS.
10
+
1
11
  v0.5.115
2
12
  * Scalebar tweaks.
3
13
 
package/bin/mapshaper-gui CHANGED
@@ -247,6 +247,7 @@ function expandShapefiles(files) {
247
247
  if (isUrl(f)) return; // TODO: try to load aux files from URLs
248
248
  addAuxFile(files, f, '.dbf');
249
249
  addAuxFile(files, f, '.prj');
250
+ addAuxFile(files, f, '.shx');
250
251
  addAuxFile(files, f, '.cpg');
251
252
  });
252
253
  return files;
package/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.115";
3
+ var VERSION = "0.5.118";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -16006,7 +16006,9 @@ ${svg}
16006
16006
 
16007
16007
  function initStringField(info, arr, name, encoding) {
16008
16008
  var formatter = encoding == 'ascii' ? encodeValueAsAscii : getStringWriterEncoded(encoding);
16009
- var size = 0;
16009
+ // Set minimum field size to 1 byte, for interoperability with PostGIS
16010
+ // (see https://github.com/mbloch/mapshaper/issues/541)
16011
+ var size = 1;
16010
16012
  var truncated = 0;
16011
16013
  var buffers = arr.map(function(rec) {
16012
16014
  var strval = convertValueToString(rec[name]);
@@ -17213,7 +17215,7 @@ ${svg}
17213
17215
  function guessInputFileType(file) {
17214
17216
  var ext = getFileExtension(file || '').toLowerCase(),
17215
17217
  type = null;
17216
- if (ext == 'dbf' || ext == 'shp' || ext == 'prj' || ext == 'shx' || ext == 'kml') {
17218
+ if (ext == 'dbf' || ext == 'shp' || ext == 'prj' || ext == 'shx' || ext == 'kml' || ext == 'cpg') {
17217
17219
  type = ext;
17218
17220
  } else if (/json$/.test(ext)) {
17219
17221
  type = 'json';
@@ -17626,7 +17628,9 @@ ${svg}
17626
17628
  binArr, buf;
17627
17629
 
17628
17630
  this.readToBinArray = function(start, length) {
17629
- if (bufSize < start + length) error("Out-of-range error");
17631
+ if (bufSize < start + length) {
17632
+ error("Out-of-range error");
17633
+ }
17630
17634
  if (!binArr) binArr = new BinArray(src);
17631
17635
  binArr.position(start);
17632
17636
  return binArr;
@@ -21084,6 +21088,10 @@ ${svg}
21084
21088
  parser.command('colors')
21085
21089
  .describe('print list of color scheme names');
21086
21090
 
21091
+ parser.command('comment')
21092
+ .describe('add a comment to the sequence of commands')
21093
+ .flag('multi_arg');
21094
+
21087
21095
  parser.command('encodings')
21088
21096
  .describe('print list of supported text encodings (for .dbf import)');
21089
21097
 
@@ -21135,6 +21143,9 @@ ${svg}
21135
21143
  // http://www.digitalpreservation.gov/formats/fdd/fdd000325.shtml
21136
21144
  // http://www.clicketyclick.dk/databases/xbase/format/index.html
21137
21145
  // http://www.clicketyclick.dk/databases/xbase/format/data_types.html
21146
+ // esri docs:
21147
+ // https://support.esri.com/en/technical-article/000007920
21148
+ // https://desktop.arcgis.com/en/arcmap/latest/manage-data/shapefiles/geoprocessing-considerations-for-shapefile-output.htm
21138
21149
 
21139
21150
  // source: http://webhelp.esri.com/arcpad/8.0/referenceguide/index.htm#locales/task_code.htm
21140
21151
  var languageIds = [0x01,'437',0x02,'850',0x03,'1252',0x08,'865',0x09,'437',0x0A,'850',0x0B,'437',0x0D,'437',0x0E,'850',0x0F,'437',0x10,'850',0x11,'437',0x12,'850',0x13,'932',0x14,'850',0x15,'437',0x16,'850',0x17,'865',0x18,'437',0x19,'437',0x1A,'850',0x1B,'437',0x1C,'863',0x1D,'850',0x1F,'852',0x22,'852',0x23,'852',0x24,'860',0x25,'850',0x26,'866',0x37,'850',0x40,'852',0x4D,'936',0x4E,'949',0x4F,'950',0x50,'874',0x57,'1252',0x58,'1252',0x59,'1252',0x64,'852',0x65,'866',0x66,'865',0x67,'861',0x6A,'737',0x6B,'857',0x6C,'863',0x78,'950',0x79,'949',0x7A,'936',0x7B,'932',0x7C,'874',0x86,'737',0x87,'852',0x88,'857',0xC8,'1250',0xC9,'1251',0xCA,'1254',0xCB,'1253',0xCC,'1257'];
@@ -21271,11 +21282,12 @@ ${svg}
21271
21282
  // @src is a Buffer or ArrayBuffer or filename
21272
21283
  //
21273
21284
  function DbfReader(src, encodingArg) {
21274
- if (utils.isString(src)) {
21275
- error("[DbfReader] Expected a buffer, not a string");
21276
- }
21277
- var bin = new BinArray(src);
21278
- var header = readHeader(bin);
21285
+ var opts = {
21286
+ cacheSize: 0x2000000, // 32MB
21287
+ bufferSize: 0x400000 // 4MB
21288
+ };
21289
+ var dbfFile = utils.isString(src) ? new FileReader(src, opts) : new BufferReader(src);
21290
+ var header = readHeader(dbfFile);
21279
21291
 
21280
21292
  // encoding and fields are set on first access
21281
21293
  var fields;
@@ -21291,7 +21303,11 @@ ${svg}
21291
21303
 
21292
21304
  this.getFields = getFieldNames;
21293
21305
 
21294
- this.getBuffer = function() {return bin.buffer();};
21306
+ // TODO: switch to streaming output under Node.js
21307
+ this.getBuffer = function() {
21308
+ return dbfFile.readSync(0, dbfFile.size());
21309
+ // return bin.buffer();
21310
+ };
21295
21311
 
21296
21312
  this.deleteField = function(f) {
21297
21313
  prepareToRead();
@@ -21332,7 +21348,10 @@ ${svg}
21332
21348
  });
21333
21349
  }
21334
21350
 
21335
- function readHeader(bin) {
21351
+ function readHeader(reader) {
21352
+ // fetch enough bytes to accomodate any header
21353
+ var maxHeaderLen = 32 * 256 + 1; // 255 fields * fieldRecSize + headerRecSize + terminator
21354
+ var bin = reader.readToBinArray(0, Math.min(maxHeaderLen, reader.size()));
21336
21355
  bin.position(0).littleEndian();
21337
21356
  var header = {
21338
21357
  version: bin.readInt8(),
@@ -21418,30 +21437,32 @@ ${svg}
21418
21437
  return new Function('return {' + args.join(',') + '};');
21419
21438
  }
21420
21439
 
21421
- function findEofPos(bin) {
21422
- var pos = bin.size() - 1;
21423
- if (bin.peek(pos) != 0x1A) { // last byte may or may not be EOF
21424
- pos++;
21425
- }
21426
- return pos;
21427
- }
21440
+ // function findEofPos(bin) {
21441
+ // var pos = bin.size() - 1;
21442
+ // if (bin.peek(pos) != 0x1A) { // last byte may or may not be EOF
21443
+ // pos++;
21444
+ // }
21445
+ // return pos;
21446
+ // }
21428
21447
 
21429
21448
  function getRecordReader() {
21430
21449
  prepareToRead();
21431
21450
  var readers = fields.map(getFieldReader),
21432
- eofOffs = findEofPos(bin),
21433
21451
  create = getRecordConstructor(),
21434
21452
  values = [];
21435
21453
 
21436
21454
  return function readRow(r) {
21437
- var offs = getRowOffset(r),
21455
+ var bin = dbfFile.readToBinArray(getRowOffset(r), header.recordSize),
21456
+ rowOffs = bin.position(),
21438
21457
  fieldOffs, field;
21458
+ if (bin.bytesLeft() < header.recordSize ||
21459
+ bin.bytesLeft() == header.recordSize && bin.peek(bin.size() - 1) == 0x1A) {
21460
+ // check for observed data error: last data byte contains EOF
21461
+ stop('Invalid DBF file: encountered end-of-file while reading data');
21462
+ }
21439
21463
  for (var c=0, cols=fields.length; c<cols; c++) {
21440
21464
  field = fields[c];
21441
- fieldOffs = offs + field.columnOffset;
21442
- if (fieldOffs + field.size > eofOffs) {
21443
- stop('Invalid DBF file: encountered end-of-file while reading data');
21444
- }
21465
+ fieldOffs = rowOffs + field.columnOffset;
21445
21466
  bin.position(fieldOffs);
21446
21467
  values[c] = readers[c](bin, field.size);
21447
21468
  }
@@ -21499,11 +21520,12 @@ ${svg}
21499
21520
 
21500
21521
  // Show a sample of decoded text if non-ascii-range text has been found
21501
21522
  if (encoding && samples.length > 0) {
21523
+ msg = "Detected DBF text encoding: " + encoding + (encoding in encodingNames ? " (" + encodingNames[encoding] + ")" : "");
21524
+ message(msg);
21502
21525
  msg = decodeSamples(encoding, samples);
21503
21526
  msg = formatStringsAsGrid(msg.split('\n'));
21504
21527
  msg = "\nSample text containing non-ascii characters:" + (msg.length > 60 ? '\n' : '') + msg;
21505
- msg = "Detected DBF text encoding: " + encoding + (encoding in encodingNames ? " (" + encodingNames[encoding] + ")" : "") + msg;
21506
- message(msg);
21528
+ verbose(msg);
21507
21529
  }
21508
21530
  return encoding;
21509
21531
  }
@@ -21531,14 +21553,17 @@ ${svg}
21531
21553
  var maxSamples = 50;
21532
21554
  var buf = utils.createBuffer(256);
21533
21555
  var index = {};
21534
- var f, chars, sample, hash;
21556
+ var f, chars, sample, hash, bin, rowOffs;
21535
21557
  // include non-ascii field names, if any
21536
21558
  samples = getNonAsciiHeaders();
21537
21559
  for (var r=0; r<rows; r++) {
21560
+ bin = dbfFile.readToBinArray(getRowOffset(r), header.recordSize);
21561
+ rowOffs = bin.position();
21538
21562
  for (var c=0; c<cols; c++) {
21539
21563
  if (samples.length >= maxSamples) break;
21540
21564
  f = stringFields[c];
21541
- bin.position(getRowOffset(r) + f.columnOffset);
21565
+ // bin.position(getRowOffset(r) + f.columnOffset);
21566
+ bin.position(rowOffs + f.columnOffset);
21542
21567
  chars = readStringBytes(bin, f.size, buf);
21543
21568
  if (chars > 0 && bufferContainsHighBit(buf, chars)) {
21544
21569
  sample = utils.createBuffer(buf.slice(0, chars)); //
@@ -21554,9 +21579,9 @@ ${svg}
21554
21579
  }
21555
21580
  }
21556
21581
 
21557
- function importDbfTable(buf, o) {
21582
+ function importDbfTable(src, o) {
21558
21583
  var opts = o || {};
21559
- return new ShapefileTable(buf, opts.encoding);
21584
+ return new ShapefileTable(src, opts.encoding);
21560
21585
  }
21561
21586
 
21562
21587
  // Implements the DataTable api for DBF file data.
@@ -21565,8 +21590,8 @@ ${svg}
21565
21590
  // just the shapes and exporting in Shapefile format.
21566
21591
  // TODO: consider accepting just the filename, so buffer doesn't consume memory needlessly.
21567
21592
  //
21568
- function ShapefileTable(buf, encoding) {
21569
- var reader = new DbfReader(buf, encoding),
21593
+ function ShapefileTable(src, encoding) {
21594
+ var reader = new DbfReader(src, encoding),
21570
21595
  altered = false,
21571
21596
  table;
21572
21597
 
@@ -21575,15 +21600,23 @@ ${svg}
21575
21600
  // export DBF records on first table access
21576
21601
  table = new DataTable(reader.readRows());
21577
21602
  reader = null;
21578
- buf = null; // null out references to DBF data for g.c.
21603
+ src = null; // null out references to DBF data for g.c.
21579
21604
  }
21580
21605
  return table;
21581
21606
  }
21582
21607
 
21583
21608
  this.exportAsDbf = function(opts) {
21584
- // export original dbf bytes if possible, for performance
21609
+ // export original dbf bytes if possible
21610
+ // (e.g. if the data attributes haven't changed)
21585
21611
  var useOriginal = !!reader && !altered && !opts.field_order && !opts.encoding;
21586
- if (useOriginal) return reader.getBuffer();
21612
+ if (useOriginal) {
21613
+ try {
21614
+ // Maximum Buffer in current Node.js is 2GB
21615
+ // We fall back to import-export if getBuffer() fails.
21616
+ // This may produce a buffer that does not exceed the maximum size.
21617
+ return reader.getBuffer();
21618
+ } catch(e) {}
21619
+ }
21587
21620
  return Dbf.exportRecords(getTable().getRecords(), opts.encoding, opts.field_order);
21588
21621
  };
21589
21622
 
@@ -23584,6 +23617,10 @@ ${svg}
23584
23617
  if (obj.prj) {
23585
23618
  dataset.info.prj = obj.prj.content;
23586
23619
  }
23620
+ if (obj.cpg) {
23621
+ // TODO: consider using the input encoding as the default output encoding
23622
+ dataset.info.cpg = obj.cpg.content;
23623
+ }
23587
23624
  return dataset;
23588
23625
  }
23589
23626
 
@@ -23593,7 +23630,7 @@ ${svg}
23593
23630
  if (input.cpg && !opts.encoding) {
23594
23631
  opts.encoding = input.cpg.content;
23595
23632
  }
23596
- table = importDbfTable(input.dbf.content, opts);
23633
+ table = importDbfTable(input.dbf.content || input.dbf.filename, opts);
23597
23634
  return {
23598
23635
  info: {},
23599
23636
  layers: [{data: table}]
@@ -23691,18 +23728,10 @@ ${svg}
23691
23728
  content;
23692
23729
 
23693
23730
  cli.checkFileExists(path, cache);
23694
- if (fileType == 'shp' && !cached) {
23695
- // let ShpReader read the file (supports larger files)
23731
+ if ((fileType == 'shp' || fileType == 'json' || fileType == 'text' || fileType == 'dbf') && !cached) {
23732
+ // these file types are read incrementally
23696
23733
  content = null;
23697
23734
 
23698
- } else if (fileType == 'json' && !cached) {
23699
- // postpone reading of JSON files, to support incremental parsing
23700
- content = null;
23701
-
23702
- } else if (fileType == 'text' && !cached) {
23703
- // content = cli.readFile(path); // read from buffer
23704
- content = null; // read from file, to support largest files (see mapshaper-delim-import.js)
23705
-
23706
23735
  } else if (fileType && isSupportedBinaryInputType(path)) {
23707
23736
  content = cli.readFile(path, null, cache);
23708
23737
  if (utils.isString(content)) {
@@ -23759,7 +23788,11 @@ ${svg}
23759
23788
  obj.shx = {filename: shxPath, content: cli.readFile(shxPath, null, cache)};
23760
23789
  }
23761
23790
  if (!obj.dbf && cli.isFile(dbfPath, cache)) {
23762
- obj.dbf = {filename: dbfPath, content: cli.readFile(dbfPath, null, cache)};
23791
+ // obj.dbf = {filename: dbfPath, content: cli.readFile(dbfPath, null, cache)};
23792
+ obj.dbf = {
23793
+ filename: dbfPath,
23794
+ content: (cache && (dbfPath in cache)) ? cli.readFile(dbfPath, null, cache) : null
23795
+ };
23763
23796
  }
23764
23797
  if (obj.dbf && cli.isFile(cpgPath, cache)) {
23765
23798
  obj.cpg = {filename: cpgPath, content: cli.readFile(cpgPath, 'utf-8', cache).trim()};
@@ -24037,6 +24070,7 @@ ${svg}
24037
24070
  clearStash(); // prevent errors from overwriting stash
24038
24071
  stashVar('current_command', cmd.name);
24039
24072
  stashVar('DEBUG', job.settings.DEBUG || cmd.debug);
24073
+ stashVar('VERBOSE', job.settings.VERBOSE || cmd.verbose);
24040
24074
  stashVar('QUIET', job.settings.QUIET || cmd.quiet);
24041
24075
  stashVar('defs', job.defs);
24042
24076
  stashVar('input_files', job.input_files);
@@ -30463,6 +30497,9 @@ ${svg}
30463
30497
  targetDataset.arcs = mergedDataset.arcs;
30464
30498
  // dissolve clip layer shapes (to remove overlaps and other topological issues
30465
30499
  // that might confuse the clipping function)
30500
+ // use a data-free copy of the clip lyr, so data records are not dissolved
30501
+ // (this avoids triggering an unnecessary and expensive DBF read operation in some cases).
30502
+ clipLyr = utils.defaults({data: null}, clipLyr);
30466
30503
  clipLyr = dissolvePolygonLayer2(clipLyr, mergedDataset, {quiet: true, silent: true});
30467
30504
 
30468
30505
  } else {
@@ -30864,6 +30901,8 @@ ${svg}
30864
30901
  getColorizerFunction: getColorizerFunction
30865
30902
  });
30866
30903
 
30904
+ cmd.comment = function() {}; // no-op, so -comment doesn't trigger a parsing error
30905
+
30867
30906
  function expressionUsesGeoJSON(exp) {
30868
30907
  return exp.includes('this.geojson');
30869
30908
  }
@@ -39778,7 +39817,7 @@ ${svg}
39778
39817
  function commandAcceptsEmptyTarget(name) {
39779
39818
  return name == 'graticule' || name == 'i' || name == 'help' ||
39780
39819
  name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
39781
- name == 'include' || name == 'print' || name == 'if' || name == 'elif' ||
39820
+ name == 'include' || name == 'print' || name == 'comment' || name == 'if' || name == 'elif' ||
39782
39821
  name == 'else' || name == 'endif';
39783
39822
  }
39784
39823
 
@@ -39886,6 +39925,9 @@ ${svg}
39886
39925
  } else if (name == 'colorizer') {
39887
39926
  outputLayers = cmd.colorizer(opts);
39888
39927
 
39928
+ } else if (name == 'comment') {
39929
+ // no-op
39930
+
39889
39931
  } else if (name == 'dashlines') {
39890
39932
  applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
39891
39933
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.5.115",
3
+ "version": "0.5.118",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
package/www/elements.css CHANGED
@@ -67,10 +67,10 @@ div.tip {
67
67
  z-index: 500;
68
68
  cursor: pointer;
69
69
  text-align: center;
70
- font-size: 13px;
70
+ font-size: 14px;
71
71
  line-height: 1;
72
72
  font-weight: normal;
73
- color: #799FCB;
73
+ color: #1385B7;
74
74
  }
75
75
 
76
76
  .tip-button .tip-anchor {
Binary file
package/www/index.html CHANGED
@@ -130,7 +130,8 @@
130
130
 
131
131
  <div class="export-options main-area popup-dialog">
132
132
  <div class="info-box">
133
- <h3>Export menu</h3>
133
+ <div class="close2-btn"></div>
134
+ <h3>Export options</h3>
134
135
  <div style="height:3px"></div>
135
136
  <div class=export-layers>
136
137
  <h4 class="menu-title">Layers</h4>
@@ -140,22 +141,23 @@
140
141
  <div class="export-formats option-menu">
141
142
  </div>
142
143
 
143
- <div class="option-menu"><input type="text" class="advanced-options" placeholder="export options" /><div class="tip-button">?<div class="tip-anchor">
144
+ <div class="option-menu"><input type="text" class="advanced-options" placeholder="command line options" /><div class="tip-button">?<div class="tip-anchor">
144
145
  <div class="tip">Enter options from the command line interface for the -o command. Examples: "bbox" "no-quantization"
145
146
  "precision=0.001"</div></div></div></div>
146
- <div class="cancel-btn btn dialog-btn">Cancel</div>
147
+ <!-- <div class="cancel-btn btn dialog-btn">Cancel</div> -->
147
148
  <div class="save-btn btn dialog-btn">Export</div>
148
149
  </div>
149
150
  </div>
150
151
 
151
152
  <div class="basemap-options main-area popup-dialog">
152
153
  <div class="info-box">
154
+ <div class="close2-btn"></div>
153
155
  <h3>Basemap options</h3>
154
156
  <p class="basemap-note">Your data will be displayed using the Mercator projection.</p>
155
157
  <p class="basemap-warning"></p>
156
158
  <div class="basemap-styles"></div>
157
159
  <div>
158
- <div class="close-btn btn dialog-btn">Close</div>
160
+ <!-- <div class="close-btn btn dialog-btn">Close</div> -->
159
161
  <div class="clear-btn btn dialog-btn disabled">Clear</div>
160
162
  <div class="fade-btn btn dialog-btn disabled">Fade</div>
161
163
  <!-- <div class="hide-btn btn dialog-btn disabled">Hide</div> -->
@@ -165,7 +167,8 @@
165
167
 
166
168
  <div class="simplify-options main-area popup-dialog">
167
169
  <div class="info-box">
168
- <h3>Simplification menu</h3>
170
+ <div class="close2-btn"></div>
171
+ <h3>Simplification</h3>
169
172
  <div class="option-menu">
170
173
  <div><label for="import-retain-opt"><input type="checkbox" class="checkbox import-retain-opt"/>prevent shape removal</label>
171
174
  <div class="tip-button">?<div class="tip-anchor">
@@ -208,7 +211,7 @@ a smoother appearance.</div></div></div></div>
208
211
  </div> <!-- option menu -->
209
212
 
210
213
  <div>
211
- <div class="cancel-btn btn dialog-btn">Cancel</div>
214
+ <!-- <div class="cancel-btn btn dialog-btn">Cancel</div> -->
212
215
  <div class="submit-btn btn dialog-btn">Apply</div>
213
216
  </div>
214
217
  </div> <!-- .info-box -->
@@ -310,7 +310,6 @@
310
310
 
311
311
  // tests if filename is a type that can be used
312
312
  GUI.isReadableFileType = function(filename) {
313
- var ext = internal.getFileExtension(filename).toLowerCase();
314
313
  return !!internal.guessInputFileType(filename) || internal.couldBeDsvFile(filename) ||
315
314
  internal.isZipFile(filename);
316
315
  };
@@ -1354,7 +1353,6 @@
1354
1353
  }
1355
1354
  }
1356
1355
  model.updated({select: true});
1357
-
1358
1356
  }
1359
1357
 
1360
1358
  function clearQueuedFiles() {
@@ -1451,9 +1449,8 @@
1451
1449
  }
1452
1450
 
1453
1451
 
1454
- // TODO: support .cpg
1455
1452
  function isShapefilePart(name) {
1456
- return /\.(shp|shx|dbf|prj)$/i.test(name);
1453
+ return /\.(shp|shx|dbf|prj|cpg)$/i.test(name);
1457
1454
  }
1458
1455
 
1459
1456
  function readImportOpts() {
@@ -2131,7 +2128,7 @@
2131
2128
 
2132
2129
  // init settings menu
2133
2130
  new SimpleButton(menu.findChild('.submit-btn').addClass('default-btn')).on('click', onSubmit);
2134
- new SimpleButton(menu.findChild('.cancel-btn')).on('click', function() {
2131
+ new SimpleButton(menu.findChild('.close2-btn')).on('click', function() {
2135
2132
  if (el.visible()) {
2136
2133
  // cancel just hides menu if slider is visible
2137
2134
  menu.hide();
@@ -3159,7 +3156,7 @@
3159
3156
  var layersArr = [];
3160
3157
  var toggleBtn = null; // checkbox <input> for toggling layer selection
3161
3158
  var exportBtn = gui.container.findChild('.export-btn');
3162
- new SimpleButton(menu.findChild('.cancel-btn')).on('click', gui.clearMode);
3159
+ new SimpleButton(menu.findChild('.close2-btn')).on('click', gui.clearMode);
3163
3160
 
3164
3161
  if (!GUI.exportIsSupported()) {
3165
3162
  exportBtn.on('click', function() {
@@ -9764,7 +9761,7 @@
9764
9761
  var menu = gui.container.findChild('.basemap-options');
9765
9762
  // var hideBtn = new SimpleButton(menu.findChild('.hide-btn'));
9766
9763
  var fadeBtn = new SimpleButton(menu.findChild('.fade-btn'));
9767
- var closeBtn = new SimpleButton(menu.findChild('.close-btn'));
9764
+ var closeBtn = new SimpleButton(menu.findChild('.close2-btn'));
9768
9765
  var clearBtn = new SimpleButton(menu.findChild('.clear-btn'));
9769
9766
  var list = menu.findChild('.basemap-styles');
9770
9767
  var container = gui.container.findChild('.basemap-container');
package/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.115";
3
+ var VERSION = "0.5.118";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -16006,7 +16006,9 @@ ${svg}
16006
16006
 
16007
16007
  function initStringField(info, arr, name, encoding) {
16008
16008
  var formatter = encoding == 'ascii' ? encodeValueAsAscii : getStringWriterEncoded(encoding);
16009
- var size = 0;
16009
+ // Set minimum field size to 1 byte, for interoperability with PostGIS
16010
+ // (see https://github.com/mbloch/mapshaper/issues/541)
16011
+ var size = 1;
16010
16012
  var truncated = 0;
16011
16013
  var buffers = arr.map(function(rec) {
16012
16014
  var strval = convertValueToString(rec[name]);
@@ -17213,7 +17215,7 @@ ${svg}
17213
17215
  function guessInputFileType(file) {
17214
17216
  var ext = getFileExtension(file || '').toLowerCase(),
17215
17217
  type = null;
17216
- if (ext == 'dbf' || ext == 'shp' || ext == 'prj' || ext == 'shx' || ext == 'kml') {
17218
+ if (ext == 'dbf' || ext == 'shp' || ext == 'prj' || ext == 'shx' || ext == 'kml' || ext == 'cpg') {
17217
17219
  type = ext;
17218
17220
  } else if (/json$/.test(ext)) {
17219
17221
  type = 'json';
@@ -17626,7 +17628,9 @@ ${svg}
17626
17628
  binArr, buf;
17627
17629
 
17628
17630
  this.readToBinArray = function(start, length) {
17629
- if (bufSize < start + length) error("Out-of-range error");
17631
+ if (bufSize < start + length) {
17632
+ error("Out-of-range error");
17633
+ }
17630
17634
  if (!binArr) binArr = new BinArray(src);
17631
17635
  binArr.position(start);
17632
17636
  return binArr;
@@ -21084,6 +21088,10 @@ ${svg}
21084
21088
  parser.command('colors')
21085
21089
  .describe('print list of color scheme names');
21086
21090
 
21091
+ parser.command('comment')
21092
+ .describe('add a comment to the sequence of commands')
21093
+ .flag('multi_arg');
21094
+
21087
21095
  parser.command('encodings')
21088
21096
  .describe('print list of supported text encodings (for .dbf import)');
21089
21097
 
@@ -21135,6 +21143,9 @@ ${svg}
21135
21143
  // http://www.digitalpreservation.gov/formats/fdd/fdd000325.shtml
21136
21144
  // http://www.clicketyclick.dk/databases/xbase/format/index.html
21137
21145
  // http://www.clicketyclick.dk/databases/xbase/format/data_types.html
21146
+ // esri docs:
21147
+ // https://support.esri.com/en/technical-article/000007920
21148
+ // https://desktop.arcgis.com/en/arcmap/latest/manage-data/shapefiles/geoprocessing-considerations-for-shapefile-output.htm
21138
21149
 
21139
21150
  // source: http://webhelp.esri.com/arcpad/8.0/referenceguide/index.htm#locales/task_code.htm
21140
21151
  var languageIds = [0x01,'437',0x02,'850',0x03,'1252',0x08,'865',0x09,'437',0x0A,'850',0x0B,'437',0x0D,'437',0x0E,'850',0x0F,'437',0x10,'850',0x11,'437',0x12,'850',0x13,'932',0x14,'850',0x15,'437',0x16,'850',0x17,'865',0x18,'437',0x19,'437',0x1A,'850',0x1B,'437',0x1C,'863',0x1D,'850',0x1F,'852',0x22,'852',0x23,'852',0x24,'860',0x25,'850',0x26,'866',0x37,'850',0x40,'852',0x4D,'936',0x4E,'949',0x4F,'950',0x50,'874',0x57,'1252',0x58,'1252',0x59,'1252',0x64,'852',0x65,'866',0x66,'865',0x67,'861',0x6A,'737',0x6B,'857',0x6C,'863',0x78,'950',0x79,'949',0x7A,'936',0x7B,'932',0x7C,'874',0x86,'737',0x87,'852',0x88,'857',0xC8,'1250',0xC9,'1251',0xCA,'1254',0xCB,'1253',0xCC,'1257'];
@@ -21271,11 +21282,12 @@ ${svg}
21271
21282
  // @src is a Buffer or ArrayBuffer or filename
21272
21283
  //
21273
21284
  function DbfReader(src, encodingArg) {
21274
- if (utils.isString(src)) {
21275
- error("[DbfReader] Expected a buffer, not a string");
21276
- }
21277
- var bin = new BinArray(src);
21278
- var header = readHeader(bin);
21285
+ var opts = {
21286
+ cacheSize: 0x2000000, // 32MB
21287
+ bufferSize: 0x400000 // 4MB
21288
+ };
21289
+ var dbfFile = utils.isString(src) ? new FileReader(src, opts) : new BufferReader(src);
21290
+ var header = readHeader(dbfFile);
21279
21291
 
21280
21292
  // encoding and fields are set on first access
21281
21293
  var fields;
@@ -21291,7 +21303,11 @@ ${svg}
21291
21303
 
21292
21304
  this.getFields = getFieldNames;
21293
21305
 
21294
- this.getBuffer = function() {return bin.buffer();};
21306
+ // TODO: switch to streaming output under Node.js
21307
+ this.getBuffer = function() {
21308
+ return dbfFile.readSync(0, dbfFile.size());
21309
+ // return bin.buffer();
21310
+ };
21295
21311
 
21296
21312
  this.deleteField = function(f) {
21297
21313
  prepareToRead();
@@ -21332,7 +21348,10 @@ ${svg}
21332
21348
  });
21333
21349
  }
21334
21350
 
21335
- function readHeader(bin) {
21351
+ function readHeader(reader) {
21352
+ // fetch enough bytes to accomodate any header
21353
+ var maxHeaderLen = 32 * 256 + 1; // 255 fields * fieldRecSize + headerRecSize + terminator
21354
+ var bin = reader.readToBinArray(0, Math.min(maxHeaderLen, reader.size()));
21336
21355
  bin.position(0).littleEndian();
21337
21356
  var header = {
21338
21357
  version: bin.readInt8(),
@@ -21418,30 +21437,32 @@ ${svg}
21418
21437
  return new Function('return {' + args.join(',') + '};');
21419
21438
  }
21420
21439
 
21421
- function findEofPos(bin) {
21422
- var pos = bin.size() - 1;
21423
- if (bin.peek(pos) != 0x1A) { // last byte may or may not be EOF
21424
- pos++;
21425
- }
21426
- return pos;
21427
- }
21440
+ // function findEofPos(bin) {
21441
+ // var pos = bin.size() - 1;
21442
+ // if (bin.peek(pos) != 0x1A) { // last byte may or may not be EOF
21443
+ // pos++;
21444
+ // }
21445
+ // return pos;
21446
+ // }
21428
21447
 
21429
21448
  function getRecordReader() {
21430
21449
  prepareToRead();
21431
21450
  var readers = fields.map(getFieldReader),
21432
- eofOffs = findEofPos(bin),
21433
21451
  create = getRecordConstructor(),
21434
21452
  values = [];
21435
21453
 
21436
21454
  return function readRow(r) {
21437
- var offs = getRowOffset(r),
21455
+ var bin = dbfFile.readToBinArray(getRowOffset(r), header.recordSize),
21456
+ rowOffs = bin.position(),
21438
21457
  fieldOffs, field;
21458
+ if (bin.bytesLeft() < header.recordSize ||
21459
+ bin.bytesLeft() == header.recordSize && bin.peek(bin.size() - 1) == 0x1A) {
21460
+ // check for observed data error: last data byte contains EOF
21461
+ stop('Invalid DBF file: encountered end-of-file while reading data');
21462
+ }
21439
21463
  for (var c=0, cols=fields.length; c<cols; c++) {
21440
21464
  field = fields[c];
21441
- fieldOffs = offs + field.columnOffset;
21442
- if (fieldOffs + field.size > eofOffs) {
21443
- stop('Invalid DBF file: encountered end-of-file while reading data');
21444
- }
21465
+ fieldOffs = rowOffs + field.columnOffset;
21445
21466
  bin.position(fieldOffs);
21446
21467
  values[c] = readers[c](bin, field.size);
21447
21468
  }
@@ -21499,11 +21520,12 @@ ${svg}
21499
21520
 
21500
21521
  // Show a sample of decoded text if non-ascii-range text has been found
21501
21522
  if (encoding && samples.length > 0) {
21523
+ msg = "Detected DBF text encoding: " + encoding + (encoding in encodingNames ? " (" + encodingNames[encoding] + ")" : "");
21524
+ message(msg);
21502
21525
  msg = decodeSamples(encoding, samples);
21503
21526
  msg = formatStringsAsGrid(msg.split('\n'));
21504
21527
  msg = "\nSample text containing non-ascii characters:" + (msg.length > 60 ? '\n' : '') + msg;
21505
- msg = "Detected DBF text encoding: " + encoding + (encoding in encodingNames ? " (" + encodingNames[encoding] + ")" : "") + msg;
21506
- message(msg);
21528
+ verbose(msg);
21507
21529
  }
21508
21530
  return encoding;
21509
21531
  }
@@ -21531,14 +21553,17 @@ ${svg}
21531
21553
  var maxSamples = 50;
21532
21554
  var buf = utils.createBuffer(256);
21533
21555
  var index = {};
21534
- var f, chars, sample, hash;
21556
+ var f, chars, sample, hash, bin, rowOffs;
21535
21557
  // include non-ascii field names, if any
21536
21558
  samples = getNonAsciiHeaders();
21537
21559
  for (var r=0; r<rows; r++) {
21560
+ bin = dbfFile.readToBinArray(getRowOffset(r), header.recordSize);
21561
+ rowOffs = bin.position();
21538
21562
  for (var c=0; c<cols; c++) {
21539
21563
  if (samples.length >= maxSamples) break;
21540
21564
  f = stringFields[c];
21541
- bin.position(getRowOffset(r) + f.columnOffset);
21565
+ // bin.position(getRowOffset(r) + f.columnOffset);
21566
+ bin.position(rowOffs + f.columnOffset);
21542
21567
  chars = readStringBytes(bin, f.size, buf);
21543
21568
  if (chars > 0 && bufferContainsHighBit(buf, chars)) {
21544
21569
  sample = utils.createBuffer(buf.slice(0, chars)); //
@@ -21554,9 +21579,9 @@ ${svg}
21554
21579
  }
21555
21580
  }
21556
21581
 
21557
- function importDbfTable(buf, o) {
21582
+ function importDbfTable(src, o) {
21558
21583
  var opts = o || {};
21559
- return new ShapefileTable(buf, opts.encoding);
21584
+ return new ShapefileTable(src, opts.encoding);
21560
21585
  }
21561
21586
 
21562
21587
  // Implements the DataTable api for DBF file data.
@@ -21565,8 +21590,8 @@ ${svg}
21565
21590
  // just the shapes and exporting in Shapefile format.
21566
21591
  // TODO: consider accepting just the filename, so buffer doesn't consume memory needlessly.
21567
21592
  //
21568
- function ShapefileTable(buf, encoding) {
21569
- var reader = new DbfReader(buf, encoding),
21593
+ function ShapefileTable(src, encoding) {
21594
+ var reader = new DbfReader(src, encoding),
21570
21595
  altered = false,
21571
21596
  table;
21572
21597
 
@@ -21575,15 +21600,23 @@ ${svg}
21575
21600
  // export DBF records on first table access
21576
21601
  table = new DataTable(reader.readRows());
21577
21602
  reader = null;
21578
- buf = null; // null out references to DBF data for g.c.
21603
+ src = null; // null out references to DBF data for g.c.
21579
21604
  }
21580
21605
  return table;
21581
21606
  }
21582
21607
 
21583
21608
  this.exportAsDbf = function(opts) {
21584
- // export original dbf bytes if possible, for performance
21609
+ // export original dbf bytes if possible
21610
+ // (e.g. if the data attributes haven't changed)
21585
21611
  var useOriginal = !!reader && !altered && !opts.field_order && !opts.encoding;
21586
- if (useOriginal) return reader.getBuffer();
21612
+ if (useOriginal) {
21613
+ try {
21614
+ // Maximum Buffer in current Node.js is 2GB
21615
+ // We fall back to import-export if getBuffer() fails.
21616
+ // This may produce a buffer that does not exceed the maximum size.
21617
+ return reader.getBuffer();
21618
+ } catch(e) {}
21619
+ }
21587
21620
  return Dbf.exportRecords(getTable().getRecords(), opts.encoding, opts.field_order);
21588
21621
  };
21589
21622
 
@@ -23584,6 +23617,10 @@ ${svg}
23584
23617
  if (obj.prj) {
23585
23618
  dataset.info.prj = obj.prj.content;
23586
23619
  }
23620
+ if (obj.cpg) {
23621
+ // TODO: consider using the input encoding as the default output encoding
23622
+ dataset.info.cpg = obj.cpg.content;
23623
+ }
23587
23624
  return dataset;
23588
23625
  }
23589
23626
 
@@ -23593,7 +23630,7 @@ ${svg}
23593
23630
  if (input.cpg && !opts.encoding) {
23594
23631
  opts.encoding = input.cpg.content;
23595
23632
  }
23596
- table = importDbfTable(input.dbf.content, opts);
23633
+ table = importDbfTable(input.dbf.content || input.dbf.filename, opts);
23597
23634
  return {
23598
23635
  info: {},
23599
23636
  layers: [{data: table}]
@@ -23691,18 +23728,10 @@ ${svg}
23691
23728
  content;
23692
23729
 
23693
23730
  cli.checkFileExists(path, cache);
23694
- if (fileType == 'shp' && !cached) {
23695
- // let ShpReader read the file (supports larger files)
23731
+ if ((fileType == 'shp' || fileType == 'json' || fileType == 'text' || fileType == 'dbf') && !cached) {
23732
+ // these file types are read incrementally
23696
23733
  content = null;
23697
23734
 
23698
- } else if (fileType == 'json' && !cached) {
23699
- // postpone reading of JSON files, to support incremental parsing
23700
- content = null;
23701
-
23702
- } else if (fileType == 'text' && !cached) {
23703
- // content = cli.readFile(path); // read from buffer
23704
- content = null; // read from file, to support largest files (see mapshaper-delim-import.js)
23705
-
23706
23735
  } else if (fileType && isSupportedBinaryInputType(path)) {
23707
23736
  content = cli.readFile(path, null, cache);
23708
23737
  if (utils.isString(content)) {
@@ -23759,7 +23788,11 @@ ${svg}
23759
23788
  obj.shx = {filename: shxPath, content: cli.readFile(shxPath, null, cache)};
23760
23789
  }
23761
23790
  if (!obj.dbf && cli.isFile(dbfPath, cache)) {
23762
- obj.dbf = {filename: dbfPath, content: cli.readFile(dbfPath, null, cache)};
23791
+ // obj.dbf = {filename: dbfPath, content: cli.readFile(dbfPath, null, cache)};
23792
+ obj.dbf = {
23793
+ filename: dbfPath,
23794
+ content: (cache && (dbfPath in cache)) ? cli.readFile(dbfPath, null, cache) : null
23795
+ };
23763
23796
  }
23764
23797
  if (obj.dbf && cli.isFile(cpgPath, cache)) {
23765
23798
  obj.cpg = {filename: cpgPath, content: cli.readFile(cpgPath, 'utf-8', cache).trim()};
@@ -24037,6 +24070,7 @@ ${svg}
24037
24070
  clearStash(); // prevent errors from overwriting stash
24038
24071
  stashVar('current_command', cmd.name);
24039
24072
  stashVar('DEBUG', job.settings.DEBUG || cmd.debug);
24073
+ stashVar('VERBOSE', job.settings.VERBOSE || cmd.verbose);
24040
24074
  stashVar('QUIET', job.settings.QUIET || cmd.quiet);
24041
24075
  stashVar('defs', job.defs);
24042
24076
  stashVar('input_files', job.input_files);
@@ -30463,6 +30497,9 @@ ${svg}
30463
30497
  targetDataset.arcs = mergedDataset.arcs;
30464
30498
  // dissolve clip layer shapes (to remove overlaps and other topological issues
30465
30499
  // that might confuse the clipping function)
30500
+ // use a data-free copy of the clip lyr, so data records are not dissolved
30501
+ // (this avoids triggering an unnecessary and expensive DBF read operation in some cases).
30502
+ clipLyr = utils.defaults({data: null}, clipLyr);
30466
30503
  clipLyr = dissolvePolygonLayer2(clipLyr, mergedDataset, {quiet: true, silent: true});
30467
30504
 
30468
30505
  } else {
@@ -30864,6 +30901,8 @@ ${svg}
30864
30901
  getColorizerFunction: getColorizerFunction
30865
30902
  });
30866
30903
 
30904
+ cmd.comment = function() {}; // no-op, so -comment doesn't trigger a parsing error
30905
+
30867
30906
  function expressionUsesGeoJSON(exp) {
30868
30907
  return exp.includes('this.geojson');
30869
30908
  }
@@ -39778,7 +39817,7 @@ ${svg}
39778
39817
  function commandAcceptsEmptyTarget(name) {
39779
39818
  return name == 'graticule' || name == 'i' || name == 'help' ||
39780
39819
  name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
39781
- name == 'include' || name == 'print' || name == 'if' || name == 'elif' ||
39820
+ name == 'include' || name == 'print' || name == 'comment' || name == 'if' || name == 'elif' ||
39782
39821
  name == 'else' || name == 'endif';
39783
39822
  }
39784
39823
 
@@ -39886,6 +39925,9 @@ ${svg}
39886
39925
  } else if (name == 'colorizer') {
39887
39926
  outputLayers = cmd.colorizer(opts);
39888
39927
 
39928
+ } else if (name == 'comment') {
39929
+ // no-op
39930
+
39889
39931
  } else if (name == 'dashlines') {
39890
39932
  applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
39891
39933
 
package/www/page.css CHANGED
@@ -222,7 +222,7 @@ body {
222
222
  }
223
223
 
224
224
  .export-options .option-menu .advanced-options {
225
- width: 175px;
225
+ width: 195px;
226
226
  }
227
227
 
228
228
  /* --- Main area ------------- */
@@ -456,11 +456,12 @@ body.dragover #import-options-drop-area .drop-area {
456
456
  .info-box,.popup {
457
457
  border-radius: 9px;
458
458
  /* border: 1px solid #ddd; */
459
- box-shadow: 0 4px 6px rgba(0,0,0,0.35);
459
+ box-shadow: 0 3px 6px rgba(0,0,0,0.35);
460
460
  background-color: #fff;
461
461
  }
462
462
 
463
463
  .info-box {
464
+ min-width: 230px;
464
465
  word-wrap: break-word;
465
466
  text-align: left;
466
467
  margin-top: 12px;
@@ -472,13 +473,16 @@ body.dragover #import-options-drop-area .drop-area {
472
473
  }
473
474
 
474
475
  .info-box h3 {
475
- font-size: 1.2em;
476
+ font-size: 19px;
477
+ line-height: 1.1;
476
478
  padding: 0;
477
- margin: 0 0 0.2em 0;
479
+ margin: 0 0 0.3em 0;
480
+ font-weight: normal;
478
481
  }
479
482
 
480
483
  .info-box h4 {
481
484
  font-size: 1em;
485
+ font-weight: normal;
482
486
  margin: 0 0 1px 0;
483
487
  }
484
488
 
@@ -491,9 +495,11 @@ body.dragover #import-options-drop-area .drop-area {
491
495
  padding: 0 0 9px 0;
492
496
  }
493
497
 
494
- .option-menu input {
498
+ .option-menu input[type="radio"],
499
+ .option-menu input[type="checkbox"]
500
+ {
495
501
  position: relative;
496
- top: 2px;
502
+ top: 1px;
497
503
  width: 12px;
498
504
  height: 12px;
499
505
  }
@@ -720,6 +726,28 @@ body.simplify .layer-control-btn {
720
726
 
721
727
  img.close-btn {
722
728
  opacity: 0.2;
729
+ position: absolute;
730
+ top: 8px;
731
+ right: 8px;
732
+ z-index: 2;
733
+ }
734
+
735
+ .close2-btn {
736
+ display: inline-block;
737
+ background-color: #f1f1f1;
738
+ background-image: url("images/close2.png");
739
+ background-size: cover;
740
+ float: right;
741
+ height: 18px;
742
+ width: 18px;
743
+ margin-top: 2px;
744
+ margin-right: -5px;
745
+ cursor: pointer;
746
+ border-radius: 4px;
747
+ }
748
+
749
+ .close2-btn:hover {
750
+ background-color: #ddd;
723
751
  }
724
752
 
725
753
  .pin-all img {
@@ -1016,6 +1044,7 @@ img.close-btn:hover,
1016
1044
  }
1017
1045
 
1018
1046
  .basemap-styles {
1047
+ margin: 0 -2px;
1019
1048
  white-space: nowrap;
1020
1049
  }
1021
1050
 
@@ -1040,19 +1069,14 @@ img.close-btn:hover,
1040
1069
 
1041
1070
  .basemap-styles > div:nth-child(even) {
1042
1071
  position: relative;
1043
- left: 6px;
1044
- margin-left: 2px;
1072
+ margin-left: 10px;
1045
1073
  }
1046
1074
 
1047
1075
  .basemap-style-btn img {
1048
1076
  display: block;
1049
- left: -2px;
1050
1077
  position: relative;
1051
1078
  border: 2px solid transparent;
1052
- /*width: 120px;
1053
- height: 90px;*/
1054
- width: 96px;
1055
- height: 72px;
1079
+ width: 108px;
1056
1080
  }
1057
1081
 
1058
1082
  div.basemap-style-btn.active img {
@@ -1064,6 +1088,10 @@ div.basemap-style-btn.active img {
1064
1088
  border: 2px solid #ead683;
1065
1089
  }
1066
1090
 
1091
+ .basemap-style-label {
1092
+ margin-left: 2px;
1093
+ }
1094
+
1067
1095
  .mapbox-improve-map {
1068
1096
  display: none;
1069
1097
  }