mapshaper 0.6.67 → 0.6.69

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/www/mapshaper.js CHANGED
@@ -7415,6 +7415,8 @@
7415
7415
  return this ? this.unpack(source, options) : Unpackr.prototype.unpack.call(defaultOptions, source, options)
7416
7416
  })
7417
7417
  }
7418
+ if (!source.buffer && source.constructor === ArrayBuffer)
7419
+ source = typeof Buffer !== 'undefined' ? Buffer.from(source) : new Uint8Array(source);
7418
7420
  if (typeof options === 'object') {
7419
7421
  srcEnd = options.end || source.length;
7420
7422
  position$1 = options.start || 0;
@@ -7460,10 +7462,10 @@
7460
7462
  let size = source.length;
7461
7463
  let value = this ? this.unpack(source, size) : defaultUnpackr.unpack(source, size);
7462
7464
  if (forEach) {
7463
- if (forEach(value) === false) return;
7465
+ if (forEach(value, lastPosition, position$1) === false) return;
7464
7466
  while(position$1 < size) {
7465
7467
  lastPosition = position$1;
7466
- if (forEach(checkedRead()) === false) {
7468
+ if (forEach(checkedRead(), lastPosition, position$1) === false) {
7467
7469
  return
7468
7470
  }
7469
7471
  }
@@ -7511,8 +7513,8 @@
7511
7513
  }
7512
7514
  return this.structures = loadedStructures
7513
7515
  }
7514
- decode(source, end) {
7515
- return this.unpack(source, end)
7516
+ decode(source, options) {
7517
+ return this.unpack(source, options)
7516
7518
  }
7517
7519
  }
7518
7520
  function checkedRead(options) {
@@ -7535,6 +7537,10 @@
7535
7537
  position$1 = bundledStrings$1.postBundlePosition;
7536
7538
  bundledStrings$1 = null;
7537
7539
  }
7540
+ if (sequentialMode)
7541
+ // we only need to restore the structures if there was an error, but if we completed a read,
7542
+ // we can clear this out and keep the structures we read
7543
+ currentStructures.restoreStructures = null;
7538
7544
 
7539
7545
  if (position$1 == srcEnd) {
7540
7546
  // finished reading this source, cleanup references
@@ -7548,7 +7554,13 @@
7548
7554
  // over read
7549
7555
  throw new Error('Unexpected end of MessagePack data')
7550
7556
  } else if (!sequentialMode) {
7551
- throw new Error('Data read, but end of buffer not reached ' + JSON.stringify(result).slice(0, 100))
7557
+ let jsonView;
7558
+ try {
7559
+ jsonView = JSON.stringify(result, (_, value) => typeof value === "bigint" ? `${value}n` : value).slice(0, 100);
7560
+ } catch(error) {
7561
+ jsonView = '(JSON view not available ' + error + ')';
7562
+ }
7563
+ throw new Error('Data read, but end of buffer not reached ' + jsonView)
7552
7564
  }
7553
7565
  // else more to read, but we are reading sequentially, so don't clear source yet
7554
7566
  return result
@@ -7704,6 +7716,9 @@
7704
7716
  value += dataView.getUint32(position$1 + 4);
7705
7717
  } else if (currentUnpackr.int64AsType === 'string') {
7706
7718
  value = dataView.getBigUint64(position$1).toString();
7719
+ } else if (currentUnpackr.int64AsType === 'auto') {
7720
+ value = dataView.getBigUint64(position$1);
7721
+ if (value<=BigInt(2)<<BigInt(52)) value=Number(value);
7707
7722
  } else
7708
7723
  value = dataView.getBigUint64(position$1);
7709
7724
  position$1 += 8;
@@ -7726,6 +7741,9 @@
7726
7741
  value += dataView.getUint32(position$1 + 4);
7727
7742
  } else if (currentUnpackr.int64AsType === 'string') {
7728
7743
  value = dataView.getBigInt64(position$1).toString();
7744
+ } else if (currentUnpackr.int64AsType === 'auto') {
7745
+ value = dataView.getBigInt64(position$1);
7746
+ if (value>=BigInt(-2)<<BigInt(52)&&value<=BigInt(2)<<BigInt(52)) value=Number(value);
7729
7747
  } else
7730
7748
  value = dataView.getBigInt64(position$1);
7731
7749
  position$1 += 8;
@@ -8183,7 +8201,7 @@
8183
8201
  return readFixedString(length)
8184
8202
  } else { // not cacheable, go back and do a standard read
8185
8203
  position$1--;
8186
- return read().toString()
8204
+ return asSafeString(read())
8187
8205
  }
8188
8206
  let key = ((length << 5) ^ (length > 1 ? dataView.getUint16(position$1) : length > 0 ? src[position$1] : 0)) & 0xfff;
8189
8207
  let entry = keyCache[key];
@@ -8235,16 +8253,25 @@
8235
8253
  return entry.string = readFixedString(length)
8236
8254
  }
8237
8255
 
8256
+ function asSafeString(property) {
8257
+ if (typeof property === 'string') return property;
8258
+ if (typeof property === 'number') return property.toString();
8259
+ throw new Error('Invalid property type for record', typeof property);
8260
+ }
8238
8261
  // the registration of the record definition extension (as "r")
8239
8262
  const recordDefinition = (id, highByte) => {
8240
- let structure = read().map(property => property.toString()); // ensure that all keys are strings and that the array is mutable
8263
+ let structure = read().map(asSafeString); // ensure that all keys are strings and
8264
+ // that the array is mutable
8241
8265
  let firstByte = id;
8242
8266
  if (highByte !== undefined) {
8243
8267
  id = id < 32 ? -((highByte << 5) + id) : ((highByte << 5) + id);
8244
8268
  structure.highByte = highByte;
8245
8269
  }
8246
8270
  let existingStructure = currentStructures[id];
8247
- if (existingStructure && existingStructure.isShared) {
8271
+ // If it is a shared structure, we need to restore any changes after reading.
8272
+ // Also in sequential mode, we may get incomplete reads and thus errors, and we need to restore
8273
+ // to the state prior to an incomplete read in order to properly resume.
8274
+ if (existingStructure && (existingStructure.isShared || sequentialMode)) {
8248
8275
  (currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure;
8249
8276
  }
8250
8277
  currentStructures[id] = structure;
@@ -8254,13 +8281,26 @@
8254
8281
  currentExtensions[0] = () => {}; // notepack defines extension 0 to mean undefined, so use that as the default here
8255
8282
  currentExtensions[0].noBuffer = true;
8256
8283
 
8284
+ currentExtensions[0x42] = (data) => {
8285
+ // decode bigint
8286
+ let length = data.length;
8287
+ let value = BigInt(data[0] & 0x80 ? data[0] - 0x100 : data[0]);
8288
+ for (let i = 1; i < length; i++) {
8289
+ value <<= 8n;
8290
+ value += BigInt(data[i]);
8291
+ }
8292
+ return value;
8293
+ };
8294
+
8295
+ let errors = { Error, TypeError, ReferenceError };
8257
8296
  currentExtensions[0x65] = () => {
8258
8297
  let data = read();
8259
- return (globalThis[data[0]] || Error)(data[1])
8298
+ return (errors[data[0]] || Error)(data[1])
8260
8299
  };
8261
8300
 
8262
8301
  currentExtensions[0x69] = (data) => {
8263
8302
  // id extension (for structured clones)
8303
+ if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
8264
8304
  let id = dataView.getUint32(position$1 - 4);
8265
8305
  if (!referenceMap)
8266
8306
  referenceMap = new Map();
@@ -8284,6 +8324,7 @@
8284
8324
 
8285
8325
  currentExtensions[0x70] = (data) => {
8286
8326
  // pointer extension (for structured clones)
8327
+ if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
8287
8328
  let id = dataView.getUint32(position$1 - 4);
8288
8329
  let refEntry = referenceMap.get(id);
8289
8330
  refEntry.used = true;
@@ -8294,13 +8335,14 @@
8294
8335
 
8295
8336
  const typedArrays = ['Int8','Uint8','Uint8Clamped','Int16','Uint16','Int32','Uint32','Float32','Float64','BigInt64','BigUint64'].map(type => type + 'Array');
8296
8337
 
8338
+ let glbl = typeof globalThis === 'object' ? globalThis : window;
8297
8339
  currentExtensions[0x74] = (data) => {
8298
8340
  let typeCode = data[0];
8299
8341
  let typedArrayName = typedArrays[typeCode];
8300
8342
  if (!typedArrayName)
8301
8343
  throw new Error('Could not find typed array for code ' + typeCode)
8302
8344
  // we have to always slice/copy here to get a new ArrayBuffer that is word/byte aligned
8303
- return new globalThis[typedArrayName](Uint8Array.prototype.slice.call(data, 1).buffer)
8345
+ return new glbl[typedArrayName](Uint8Array.prototype.slice.call(data, 1).buffer)
8304
8346
  };
8305
8347
  currentExtensions[0x78] = () => {
8306
8348
  let data = read();
@@ -8465,6 +8507,7 @@
8465
8507
  } else
8466
8508
  position = (position + 7) & 0x7ffffff8; // Word align to make any future copying of this buffer faster
8467
8509
  start = position;
8510
+ if (encodeOptions & RESERVE_START_SPACE) position += (encodeOptions & 0xff);
8468
8511
  referenceMap = packr.structuredClone ? new Map() : null;
8469
8512
  if (packr.bundleStrings && typeof value !== 'string') {
8470
8513
  bundledStrings = [];
@@ -8506,8 +8549,9 @@
8506
8549
  }
8507
8550
  if (hasSharedUpdate)
8508
8551
  hasSharedUpdate = false;
8552
+ let encodingError;
8509
8553
  try {
8510
- if (packr.randomAccessStructure && value.constructor && value.constructor === Object)
8554
+ if (packr.randomAccessStructure && value && value.constructor && value.constructor === Object)
8511
8555
  writeStruct(value);
8512
8556
  else
8513
8557
  pack(value);
@@ -8556,42 +8600,51 @@
8556
8600
  return target
8557
8601
  }
8558
8602
  return target.subarray(start, position) // position can change if we call pack again in saveStructures, so we get the buffer now
8603
+ } catch(error) {
8604
+ encodingError = error;
8605
+ throw error;
8559
8606
  } finally {
8560
8607
  if (structures) {
8561
- if (serializationsSinceTransitionRebuild < 10)
8562
- serializationsSinceTransitionRebuild++;
8563
- let sharedLength = structures.sharedLength || 0;
8564
- if (structures.length > sharedLength)
8565
- structures.length = sharedLength;
8566
- if (transitionsCount > 10000) {
8567
- // force a rebuild occasionally after a lot of transitions so it can get cleaned up
8568
- structures.transitions = null;
8569
- serializationsSinceTransitionRebuild = 0;
8570
- transitionsCount = 0;
8571
- if (recordIdsToRemove.length > 0)
8572
- recordIdsToRemove = [];
8573
- } else if (recordIdsToRemove.length > 0 && !isSequential) {
8574
- for (let i = 0, l = recordIdsToRemove.length; i < l; i++) {
8575
- recordIdsToRemove[i][RECORD_SYMBOL] = 0;
8576
- }
8577
- recordIdsToRemove = [];
8578
- }
8608
+ resetStructures();
8579
8609
  if (hasSharedUpdate && packr.saveStructures) {
8610
+ let sharedLength = structures.sharedLength || 0;
8580
8611
  // we can't rely on start/end with REUSE_BUFFER_MODE since they will (probably) change when we save
8581
8612
  let returnBuffer = target.subarray(start, position);
8582
8613
  let newSharedData = prepareStructures(structures, packr);
8583
- if (packr.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
8584
- // get updated structures and try again if the update failed
8585
- return packr.pack(value)
8614
+ if (!encodingError) { // TODO: If there is an encoding error, should make the structures as uninitialized so they get rebuilt next time
8615
+ if (packr.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
8616
+ // get updated structures and try again if the update failed
8617
+ return packr.pack(value, encodeOptions)
8618
+ }
8619
+ packr.lastNamedStructuresLength = sharedLength;
8620
+ return returnBuffer
8586
8621
  }
8587
- packr.lastNamedStructuresLength = sharedLength;
8588
- return returnBuffer
8589
8622
  }
8590
8623
  }
8591
8624
  if (encodeOptions & RESET_BUFFER_MODE)
8592
8625
  position = start;
8593
8626
  }
8594
8627
  };
8628
+ const resetStructures = () => {
8629
+ if (serializationsSinceTransitionRebuild < 10)
8630
+ serializationsSinceTransitionRebuild++;
8631
+ let sharedLength = structures.sharedLength || 0;
8632
+ if (structures.length > sharedLength && !isSequential)
8633
+ structures.length = sharedLength;
8634
+ if (transitionsCount > 10000) {
8635
+ // force a rebuild occasionally after a lot of transitions so it can get cleaned up
8636
+ structures.transitions = null;
8637
+ serializationsSinceTransitionRebuild = 0;
8638
+ transitionsCount = 0;
8639
+ if (recordIdsToRemove.length > 0)
8640
+ recordIdsToRemove = [];
8641
+ } else if (recordIdsToRemove.length > 0 && !isSequential) {
8642
+ for (let i = 0, l = recordIdsToRemove.length; i < l; i++) {
8643
+ recordIdsToRemove[i][RECORD_SYMBOL] = 0;
8644
+ }
8645
+ recordIdsToRemove = [];
8646
+ }
8647
+ };
8595
8648
  const packArray = (value) => {
8596
8649
  var length = value.length;
8597
8650
  if (length < 0x10) {
@@ -8769,7 +8822,7 @@
8769
8822
  targetView.setFloat64(position, value);
8770
8823
  position += 8;
8771
8824
  }
8772
- } else if (type === 'object') {
8825
+ } else if (type === 'object' || type === 'function') {
8773
8826
  if (!value)
8774
8827
  target[position++] = 0xc0;
8775
8828
  else {
@@ -8794,21 +8847,24 @@
8794
8847
  } else if (constructor === Array) {
8795
8848
  packArray(value);
8796
8849
  } else if (constructor === Map) {
8797
- length = value.size;
8798
- if (length < 0x10) {
8799
- target[position++] = 0x80 | length;
8800
- } else if (length < 0x10000) {
8801
- target[position++] = 0xde;
8802
- target[position++] = length >> 8;
8803
- target[position++] = length & 0xff;
8804
- } else {
8805
- target[position++] = 0xdf;
8806
- targetView.setUint32(position, length);
8807
- position += 4;
8808
- }
8809
- for (let [ key, entryValue ] of value) {
8810
- pack(key);
8811
- pack(entryValue);
8850
+ if (this.mapAsEmptyObject) target[position++] = 0x80;
8851
+ else {
8852
+ length = value.size;
8853
+ if (length < 0x10) {
8854
+ target[position++] = 0x80 | length;
8855
+ } else if (length < 0x10000) {
8856
+ target[position++] = 0xde;
8857
+ target[position++] = length >> 8;
8858
+ target[position++] = length & 0xff;
8859
+ } else {
8860
+ target[position++] = 0xdf;
8861
+ targetView.setUint32(position, length);
8862
+ position += 4;
8863
+ }
8864
+ for (let [key, entryValue] of value) {
8865
+ pack(key);
8866
+ pack(entryValue);
8867
+ }
8812
8868
  }
8813
8869
  } else {
8814
8870
  for (let i = 0, l = extensions.length; i < l; i++) {
@@ -8871,6 +8927,18 @@
8871
8927
  if (Array.isArray(value)) {
8872
8928
  packArray(value);
8873
8929
  } else {
8930
+ // use this as an alternate mechanism for expressing how to serialize
8931
+ if (value.toJSON) {
8932
+ const json = value.toJSON();
8933
+ // if for some reason value.toJSON returns itself it'll loop forever
8934
+ if (json !== value)
8935
+ return pack(json)
8936
+ }
8937
+
8938
+ // if there is a writeFunction, use it, otherwise just encode as undefined
8939
+ if (type === 'function')
8940
+ return pack(this.writeFunction && this.writeFunction(value));
8941
+
8874
8942
  // no extension found, write as object
8875
8943
  writeObject(value, !value.hasOwnProperty); // if it doesn't have hasOwnProperty, don't do hasOwnProperty checks
8876
8944
  }
@@ -8892,8 +8960,26 @@
8892
8960
  if (this.largeBigIntToFloat) {
8893
8961
  target[position++] = 0xcb;
8894
8962
  targetView.setFloat64(position, Number(value));
8963
+ } else if (this.useBigIntExtension && value < 2n**(1023n) && value > -(2n**(1023n))) {
8964
+ target[position++] = 0xc7;
8965
+ position++;
8966
+ target[position++] = 0x42; // "B" for BigInt
8967
+ let bytes = [];
8968
+ let alignedSign;
8969
+ do {
8970
+ let byte = value & 0xffn;
8971
+ alignedSign = (byte & 0x80n) === (value < 0n ? 0x80n : 0n);
8972
+ bytes.push(byte);
8973
+ value >>= 8n;
8974
+ } while (!((value === 0n || value === -1n) && alignedSign));
8975
+ target[position-2] = bytes.length;
8976
+ for (let i = bytes.length; i > 0;) {
8977
+ target[position++] = Number(bytes[--i]);
8978
+ }
8979
+ return
8895
8980
  } else {
8896
- throw new RangeError(value + ' was too large to fit in MessagePack 64-bit integer format, set largeBigIntToFloat to convert to float-64')
8981
+ throw new RangeError(value + ' was too large to fit in MessagePack 64-bit integer format, use' +
8982
+ ' useBigIntExtension or set largeBigIntToFloat to convert to float-64')
8897
8983
  }
8898
8984
  }
8899
8985
  position += 8;
@@ -8905,14 +8991,12 @@
8905
8991
  target[position++] = 0;
8906
8992
  target[position++] = 0;
8907
8993
  }
8908
- } else if (type === 'function') {
8909
- pack(this.writeFunction && this.writeFunction()); // if there is a writeFunction, use it, otherwise just encode as undefined
8910
8994
  } else {
8911
8995
  throw new Error('Unknown type: ' + type)
8912
8996
  }
8913
8997
  };
8914
8998
 
8915
- const writeObject = this.useRecords === false ? this.variableMapSize ? (object) => {
8999
+ const writePlainObject = (this.variableMapSize || this.coercibleKeyAsNumber) ? (object) => {
8916
9000
  // this method is slightly slower, but generates "preferred serialization" (optimally small for smaller objects)
8917
9001
  let keys = Object.keys(object);
8918
9002
  let length = keys.length;
@@ -8928,9 +9012,19 @@
8928
9012
  position += 4;
8929
9013
  }
8930
9014
  let key;
8931
- for (let i = 0; i < length; i++) {
8932
- pack(key = keys[i]);
8933
- pack(object[key]);
9015
+ if (this.coercibleKeyAsNumber) {
9016
+ for (let i = 0; i < length; i++) {
9017
+ key = keys[i];
9018
+ let num = Number(key);
9019
+ pack(isNaN(num) ? key : num);
9020
+ pack(object[key]);
9021
+ }
9022
+
9023
+ } else {
9024
+ for (let i = 0; i < length; i++) {
9025
+ pack(key = keys[i]);
9026
+ pack(object[key]);
9027
+ }
8934
9028
  }
8935
9029
  } :
8936
9030
  (object, safePrototype) => {
@@ -8947,7 +9041,9 @@
8947
9041
  }
8948
9042
  target[objectOffset++ + start] = size >> 8;
8949
9043
  target[objectOffset + start] = size & 0xff;
8950
- } :
9044
+ };
9045
+
9046
+ const writeRecord = this.useRecords === false ? writePlainObject :
8951
9047
  (options.progressiveRecords && !useTwoByteRecords) ? // this is about 2% faster for highly stable structures, since it only requires one for-in loop (but much more expensive when new structure needs to be written)
8952
9048
  (object, safePrototype) => {
8953
9049
  let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
@@ -9016,9 +9112,18 @@
9016
9112
  }
9017
9113
  // now write the values
9018
9114
  for (let key in object)
9019
- if (safePrototype || object.hasOwnProperty(key))
9115
+ if (safePrototype || object.hasOwnProperty(key)) {
9020
9116
  pack(object[key]);
9117
+ }
9021
9118
  };
9119
+
9120
+ // craete reference to useRecords if useRecords is a function
9121
+ const checkUseRecords = typeof this.useRecords == 'function' && this.useRecords;
9122
+
9123
+ const writeObject = checkUseRecords ? (object, safePrototype) => {
9124
+ checkUseRecords(object) ? writeRecord(object,safePrototype) : writePlainObject(object,safePrototype);
9125
+ } : writeRecord;
9126
+
9022
9127
  const makeRoom = (end) => {
9023
9128
  let newSize;
9024
9129
  if (end > 0x1000000) {
@@ -9122,12 +9227,13 @@
9122
9227
  }
9123
9228
  };
9124
9229
  const writeStruct = (object, safePrototype) => {
9125
- let newPosition = writeStructSlots(object, target, position, structures, makeRoom, (value, newPosition, notifySharedUpdate) => {
9230
+ let newPosition = writeStructSlots(object, target, start, position, structures, makeRoom, (value, newPosition, notifySharedUpdate) => {
9126
9231
  if (notifySharedUpdate)
9127
9232
  return hasSharedUpdate = true;
9128
9233
  position = newPosition;
9129
9234
  let startTarget = target;
9130
9235
  pack(value);
9236
+ resetStructures();
9131
9237
  if (startTarget !== target) {
9132
9238
  return { position, targetView, target }; // indicate the buffer was re-allocated
9133
9239
  }
@@ -9191,6 +9297,10 @@
9191
9297
  }
9192
9298
  }, {
9193
9299
  pack(set, allocateForWrite, pack) {
9300
+ if (this.setAsEmptyObject) {
9301
+ allocateForWrite(0);
9302
+ return pack({})
9303
+ }
9194
9304
  let array = Array.from(set);
9195
9305
  let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
9196
9306
  if (this.moreTypes) {
@@ -9371,6 +9481,7 @@
9371
9481
  defaultPackr.pack;
9372
9482
  const REUSE_BUFFER_MODE = 512;
9373
9483
  const RESET_BUFFER_MODE = 1024;
9484
+ const RESERVE_START_SPACE = 2048;
9374
9485
 
9375
9486
  // DEFLATE is a complex format; to read this code, you should probably check the RFC first:
9376
9487
  // https://tools.ietf.org/html/rfc1951
@@ -14726,6 +14837,9 @@
14726
14837
  bounds: function() {
14727
14838
  return shapeBounds().toArray();
14728
14839
  },
14840
+ bbox: function() {
14841
+ return shapeBounds().toArray();
14842
+ },
14729
14843
  height: function() {
14730
14844
  return shapeBounds().height();
14731
14845
  },
@@ -17323,9 +17437,10 @@
17323
17437
  }
17324
17438
 
17325
17439
  function getGapRemovalMessage(removed, retained, areaLabel) {
17440
+ var tot = removed + retained;
17326
17441
  if (removed > 0 === false) return '';
17327
- return utils.format('Removed %,d / %,d sliver%s using %s',
17328
- removed, removed + retained, utils.pluralSuffix(removed), areaLabel);
17442
+ return utils.format('Removed %,d of %,d sliver%s using %s',
17443
+ removed, tot, utils.pluralSuffix(tot), areaLabel);
17329
17444
  }
17330
17445
 
17331
17446
  function dissolvePolygonGroups2(groups, lyr, dataset, opts) {
@@ -19274,12 +19389,12 @@
19274
19389
  // Kludge for applying fill and other styles to a <text> element
19275
19390
  // (for rendering labels in the GUI with the dot in Canvas, not SVG)
19276
19391
  function renderStyledLabel(rec) {
19277
- var o = renderLabel(rec);
19392
+ var o = renderLabel$1(rec);
19278
19393
  applyStyleAttributes(o, 'label', rec);
19279
19394
  return o;
19280
19395
  }
19281
19396
 
19282
- function renderLabel(rec) {
19397
+ function renderLabel$1(rec) {
19283
19398
  var line = toLabelString(rec['label-text']);
19284
19399
  var morelines, obj;
19285
19400
  // Accepting \n (two chars) as an alternative to the newline character
@@ -19324,7 +19439,7 @@
19324
19439
  var SvgLabels = /*#__PURE__*/Object.freeze({
19325
19440
  __proto__: null,
19326
19441
  renderStyledLabel: renderStyledLabel,
19327
- renderLabel: renderLabel
19442
+ renderLabel: renderLabel$1
19328
19443
  });
19329
19444
 
19330
19445
  // convert data records (properties like svg-symbol, label-text, fill, r) to svg symbols
@@ -19536,13 +19651,64 @@
19536
19651
  };
19537
19652
  }
19538
19653
 
19539
- // TODO: generalize to other kinds of furniture as they are developed
19540
- function getScalebarPosition(opts) {
19541
- var pos = opts.position || 'top-left';
19542
- return {
19543
- valign: pos.includes('top') ? 'top' : 'bottom',
19544
- halign: pos.includes('left') ? 'left' : 'right'
19654
+ function renderScalebar(d, frame) {
19655
+ if (!frame.crs) {
19656
+ message('Unable to render scalebar: unknown CRS.');
19657
+ return [];
19658
+ }
19659
+ if (frame.width > 0 === false) {
19660
+ return [];
19661
+ }
19662
+
19663
+ var opts = getScalebarOpts(d);
19664
+ var metersPerPx = getMapFrameMetersPerPixel(frame);
19665
+ var frameWidthPx = frame.width;
19666
+ var labels = d.label ? d.label.split(',') : null;
19667
+ var label1 = labels && labels[0] || null;
19668
+ var props1 = parseScalebarLabel(label1 || getAutoScalebarLabel(frameWidthPx, metersPerPx, 'mile'));
19669
+ var label2 = opts.style == 'b' && labels && labels[1] || null;
19670
+ var props2, length2;
19671
+
19672
+ if (props1.km > 0 === false) {
19673
+ message('Unusable scalebar label:', label1);
19674
+ return [];
19675
+ }
19676
+
19677
+ var length1 = Math.round(props1.km / metersPerPx * 1000);
19678
+ if (length1 > 0 === false) {
19679
+ stop("Null scalebar length");
19680
+ }
19681
+
19682
+ if (label2) {
19683
+ props2 = parseScalebarLabel(label2);
19684
+ length2 = Math.round(props2.km / metersPerPx * 1000);
19685
+ if (length2 > length1) {
19686
+ stop("First part of a dual-unit scalebar must be longer than the second part.");
19687
+ }
19688
+ }
19689
+
19690
+ var barPos = getScalebarPosition(opts);
19691
+ var labelPos = getLabelPosition(opts);
19692
+ var dx = barPos.xpos == 'right' ? frameWidthPx - length1 - opts.margin : opts.margin;
19693
+ var dy = barPos.ypos == 'bottom' ? frame.height - opts.margin : opts.margin;
19694
+
19695
+ // vshift to adjust for height above or below the baseline
19696
+ var labelHeight = Math.round(opts.label_offset + opts.tic_length + opts.font_size * 0.8 + opts.bar_width / 2);
19697
+ var bareHeight = Math.round(opts.bar_width / 2);
19698
+ var topHeight = labelPos.ypos == 'top' || label2 ? labelHeight : bareHeight;
19699
+ var bottomHeight = labelPos.ypos == 'bottom' || label2 ? labelHeight : bareHeight;
19700
+ if (barPos.ypos == 'top') {
19701
+ dy += topHeight;
19702
+ } else {
19703
+ dy -= bottomHeight;
19704
+ }
19705
+
19706
+ var g = renderAsSvg(length1, label1, length2, label2, opts);
19707
+ g.properties = {
19708
+ transform: 'translate(' + dx + ' ' + dy + ')'
19545
19709
  };
19710
+
19711
+ return [g];
19546
19712
  }
19547
19713
 
19548
19714
  var styleOpts = {
@@ -19569,105 +19735,112 @@
19569
19735
  return Object.assign({}, defaultOpts, styleOpts[style], d, {style: style});
19570
19736
  }
19571
19737
 
19572
- // approximate pixel height of the scalebar
19573
- function getScalebarHeight(opts) {
19574
- return Math.round(opts.bar_width + opts.label_offset +
19575
- opts.tic_length + opts.font_size * 0.8);
19738
+ function renderAsSvg(length, text, length2, text2, opts) {
19739
+ var labelPart = renderLabel(text, length, opts);
19740
+ var zeroLabelPart = renderLabel('0', length, Object.assign({flipx: true}, opts));
19741
+ var barPart = renderBar(length, length2, opts);
19742
+ var parts = opts.style == 'b' ? [zeroLabelPart, labelPart, barPart] : [labelPart, barPart];
19743
+ if (text2) {
19744
+ parts.push(renderLabel(text2, length2, Object.assign({flipy: true}, opts)));
19745
+ parts.push(renderLabel('0', length2, Object.assign({flipx: true, flipy: true}, opts)));
19746
+ }
19747
+ return {
19748
+ tag: 'g',
19749
+ children: parts
19750
+ };
19576
19751
  }
19577
19752
 
19578
- function renderAsSvg(length, text, opts) {
19579
- // label part
19580
- var xOff = opts.style == 'b' ? Math.round(opts.font_size / 4) : 0;
19581
- var alignLeft = opts.style == 'a' && opts.position.includes('left');
19582
- var anchorX = alignLeft ? -xOff : length + xOff;
19583
- var anchorY = opts.bar_width + + opts.tic_length + opts.label_offset;
19584
- if (opts.label_position == 'top') {
19585
- anchorY = -opts.label_offset - opts.tic_length;
19586
- }
19753
+ // TODO: generalize to other kinds of furniture as they are developed
19754
+ function getScalebarPosition(opts) {
19755
+ var pos = opts.position || 'top-left';
19756
+ return {
19757
+ ypos: pos.includes('top') ? 'top' : 'bottom',
19758
+ xpos: pos.includes('left') ? 'left' : 'right'
19759
+ };
19760
+ }
19761
+
19762
+ function renderLabel(text, length, opts) {
19763
+ var labelPos = getLabelPosition(opts);
19764
+ var anchorX = length * labelPos.kx + labelPos.dx;
19765
+ var bottomLabelY = opts.bar_width + opts.tic_length + opts.label_offset;
19766
+ var topLabelY = -opts.label_offset - opts.tic_length;
19767
+ var anchorY = labelPos.ypos == 'top' ? topLabelY : bottomLabelY;
19587
19768
  var labelOpts = {
19588
19769
  'label-text': text,
19589
19770
  'font-size': opts.font_size,
19590
- 'text-anchor': alignLeft ? 'start': 'end',
19591
- 'dominant-baseline': opts.label_position == 'top' ? 'auto' : 'hanging'
19771
+ 'text-anchor': labelPos.anchor,
19772
+ 'dominant-baseline': labelPos.ypos == 'top' ? 'auto' : 'hanging'
19592
19773
  //// 'dominant-baseline': labelPos == 'top' ? 'text-after-edge' : 'text-before-edge'
19593
19774
  // 'text-after-edge' is buggy in Safari and unsupported by Illustrator,
19594
19775
  // so I'm using 'hanging' and 'auto', which seem to be well supported.
19595
19776
  // downside: requires a kludgy multiplier to calculate scalebar height (see above)
19596
19777
  };
19597
- var labelPart = symbolRenderers.label(labelOpts, anchorX, anchorY);
19598
- var zeroOpts = Object.assign({}, labelOpts, {'label-text': '0', 'text-anchor': 'start'});
19599
- var zeroLabel = symbolRenderers.label(zeroOpts, -xOff, anchorY);
19778
+ return symbolRenderers.label(labelOpts, anchorX, anchorY);
19779
+ }
19600
19780
 
19601
- // bar part
19602
- var y = 0;
19603
- var y2 = opts.tic_length + opts.bar_width / 2;
19604
- var coords;
19605
- if (opts.label_position == "top") {
19606
- y2 = -y2;
19607
- }
19608
- if (opts.tic_length > 0) {
19609
- coords = [[0, y2], [0, y], [length, y], [length, y2]];
19781
+ function getLabelPosition(opts) {
19782
+ var pos = opts.label_position;
19783
+ var ypos = pos.includes('bottom') && 'bottom' || 'top';
19784
+ var dx = 0;
19785
+ var xpos;
19786
+ if (opts.style == 'a') {
19787
+ xpos = pos.includes('center') && 'center' || pos.includes('right') && 'right' || 'left';
19610
19788
  } else {
19611
- coords = [[0, y], [length, y]];
19789
+ xpos = 'right'; // style b
19790
+ }
19791
+ if (opts.flipx) {
19792
+ xpos = xpos == 'left' && 'right' || xpos == 'right' && 'left' || xpos;
19793
+ }
19794
+ if (opts.flipy) {
19795
+ ypos = ypos == 'top' && 'bottom' || ypos == 'bottom' && 'top' || ypos;
19796
+ }
19797
+ if (opts.style == 'b') {
19798
+ dx = xpos == 'left' && -opts.font_size / 4 || xpos == 'right' && opts.font_size / 4 || 0;
19612
19799
  }
19613
- var barPart = importLineString(coords);
19614
- Object.assign(barPart.properties, {
19615
- stroke: 'black',
19616
- fill: 'none',
19617
- 'stroke-width': opts.bar_width,
19618
- 'stroke-linecap': 'butt',
19619
- 'stroke-linejoin': 'miter'
19620
- });
19621
- var parts = opts.style == 'b' ? [zeroLabel, labelPart, barPart] : [labelPart, barPart];
19622
19800
  return {
19623
- tag: 'g',
19624
- children: parts
19801
+ xpos,
19802
+ ypos,
19803
+ dx,
19804
+ kx: xpos == 'right' && 1 || xpos == 'center' && 0.5 || 0,
19805
+ anchor: xpos == 'center' && 'middle' || xpos == 'left' && 'start' || 'end'
19625
19806
  };
19626
19807
  }
19627
19808
 
19628
- function renderScalebar(d, frame) {
19629
- if (!frame.crs) {
19630
- message('Unable to render scalebar: unknown CRS.');
19631
- return [];
19632
- }
19633
- if (frame.width > 0 === false) {
19634
- return [];
19635
- }
19636
-
19637
- var opts = getScalebarOpts(d);
19638
- var metersPerPx = getMapFrameMetersPerPixel(frame);
19639
- var frameWidthPx = frame.width;
19640
- var unit = d.label ? parseScalebarUnits(d.label) : 'mile';
19641
- var number = d.label ? parseScalebarNumber(d.label) : null;
19642
- var label = number && unit ? d.label : getAutoScalebarLabel(frameWidthPx, metersPerPx, unit);
19643
-
19644
- var scalebarKm = parseScalebarLabelToKm(label);
19645
- if (scalebarKm > 0 === false) {
19646
- message('Unusable scalebar label:', label);
19647
- return [];
19809
+ // length1: length of main bar
19810
+ // length2: length of optional second distance (assumes that length2 <= length1)
19811
+ function getStyleBCoords(length1, length2, opts) {
19812
+ var coords = [];
19813
+ var labelPos = getLabelPosition(opts);
19814
+ var y = opts.tic_length + opts.bar_width / 2;
19815
+ if (labelPos.ypos == "top") {
19816
+ y = -y;
19648
19817
  }
19649
-
19650
- var width = Math.round(scalebarKm / metersPerPx * 1000);
19651
- if (width > 0 === false) {
19652
- stop("Null scalebar length");
19818
+ coords.push([[0, y], [0, 0], [length1, 0], [length1, y]]);
19819
+ if (length2 > 0) {
19820
+ coords.push([[0, 0], [0, -y]]);
19821
+ coords.push([[length2, 0], [length2, -y]]);
19653
19822
  }
19823
+ return coords;
19824
+ }
19654
19825
 
19655
- var pos = getScalebarPosition(opts);
19656
- var height = getScalebarHeight(opts);
19657
- var dx = pos.halign == 'right' ? frameWidthPx - width - opts.margin : opts.margin;
19658
- var dy = pos.valign == 'bottom' ? frame.height - height - opts.margin : opts.margin;
19659
- if (opts.label_position == 'top') {
19660
- dy += Math.round(opts.label_offset + opts.tic_length + opts.font_size * 0.8 + opts.bar_width / 2);
19826
+ // length: length of scale bar in px
19827
+ // length2: length of optional dual-units portion of the scalebar
19828
+ function renderBar(length, length2, opts) {
19829
+ var coords;
19830
+ if (opts.style == 'b') {
19831
+ coords = getStyleBCoords(length, length2, opts);
19661
19832
  } else {
19662
- dy += Math.round(opts.bar_width / 2);
19833
+ coords = [[[0, 0], [length, 0]]];
19663
19834
  }
19664
-
19665
- var g = renderAsSvg(width, label, opts);
19666
- g.properties = {
19667
- transform: 'translate(' + dx + ' ' + dy + ')'
19668
- };
19669
-
19670
- return [g];
19835
+ var bar = importMultiLineString(coords);
19836
+ Object.assign(bar.properties, {
19837
+ stroke: 'black',
19838
+ fill: 'none',
19839
+ 'stroke-width': opts.bar_width,
19840
+ 'stroke-linecap': 'butt',
19841
+ 'stroke-linejoin': 'miter'
19842
+ });
19843
+ return bar;
19671
19844
  }
19672
19845
 
19673
19846
  // unit: 'km' || 'mile'
@@ -19701,6 +19874,20 @@
19701
19874
  return units == 'mile' ? value * 1.60934 : value;
19702
19875
  }
19703
19876
 
19877
+ function parseScalebarLabel(label) {
19878
+ var num = label ? parseScalebarNumber(label) : null;
19879
+ var units = label ? parseScalebarUnits(label) : 'mile';
19880
+ var km = NaN;
19881
+ if (units && num) {
19882
+ km = units == 'mile' ? num * 1.60934 : num;
19883
+ }
19884
+ return {
19885
+ number: num,
19886
+ units: units,
19887
+ km: km
19888
+ };
19889
+ }
19890
+
19704
19891
  function parseScalebarUnits(str) {
19705
19892
  var isMiles = /miles?$/.test(str.toLowerCase());
19706
19893
  var isKm = /(k\.m\.|km|kilometers?|kilometres?)$/.test(str.toLowerCase());
@@ -25821,7 +26008,11 @@ ${svg}
25821
26008
  describe: 'e.g. bottom-right (default is top-left)'
25822
26009
  })
25823
26010
  .option('label-position', {
25824
- describe: 'top or bottom'
26011
+ describe: 'top, bottom, top-center (style a), etc'
26012
+ })
26013
+ .option('dual-units', {
26014
+ // describe: 'display both metric and imperial units',
26015
+ type: 'flag'
25825
26016
  })
25826
26017
  .option('margin', {
25827
26018
  describe: 'offset in pixels from edge of map',
@@ -45224,7 +45415,7 @@ ${svg}
45224
45415
  });
45225
45416
  }
45226
45417
 
45227
- var version = "0.6.67";
45418
+ var version = "0.6.69";
45228
45419
 
45229
45420
  // Parse command line args into commands and run them
45230
45421
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.