mapshaper 0.6.101 → 0.6.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mapshaper.js CHANGED
@@ -267,6 +267,7 @@
267
267
  });
268
268
  }
269
269
 
270
+
270
271
  function defaults(dest) {
271
272
  for (var i=1, n=arguments.length; i<n; i++) {
272
273
  var src = arguments[i] || {};
@@ -13762,7 +13763,7 @@
13762
13763
  convertIntervalParam(opt[1], crs)];
13763
13764
  }
13764
13765
 
13765
- // Accepts a single value or a list of four values. List order is l,b,t,r
13766
+ // Accepts a single value or a list of four values. List order is l,b,r,t
13766
13767
  function convertFourSides(opt, crs, bounds) {
13767
13768
  var arr = opt.includes(',') ? opt.split(',') : opt.split(' ');
13768
13769
  if (arr.length == 1) {
@@ -13776,7 +13777,7 @@
13776
13777
  tmp = parseFloat(param) / 100 || 0;
13777
13778
  return tmp * (i == 1 || i == 3 ? bounds.height() : bounds.width());
13778
13779
  }
13779
- return convertIntervalParam(opt, crs);
13780
+ return convertIntervalParam(param, crs);
13780
13781
  });
13781
13782
  }
13782
13783
 
@@ -18486,18 +18487,30 @@
18486
18487
  type: 'frame'
18487
18488
  }
18488
18489
  */
18490
+
18491
+
18489
18492
  function getFrameData(dataset, exportOpts) {
18490
18493
  var frameLyr = findFrameLayerInDataset(dataset);
18491
- var frameData;
18494
+ var data;
18492
18495
  if (frameLyr) {
18493
- frameData = getFrameLayerData(frameLyr, dataset.arcs);
18496
+ data = getFrameLayerData(frameLyr, dataset.arcs);
18494
18497
  } else {
18495
- frameData = calcFrameData(dataset, exportOpts);
18498
+ data = calcFrameData(dataset, exportOpts);
18496
18499
  }
18497
- frameData.crs = getDatasetCRS(dataset);
18498
- return frameData;
18500
+ data.invert_y = !!exportOpts.invert_y;
18501
+ data.crs = getDatasetCRS(dataset);
18502
+ return data;
18499
18503
  }
18500
18504
 
18505
+ function fitDatasetToFrame(dataset, frame) {
18506
+ var bounds = new Bounds(frame.bbox);
18507
+ var bounds2 = frame.bbox2 ? new Bounds(frame.bbox2) : new Bounds(0, 0, frame.width, frame.height);
18508
+ bounds.fillOut(bounds2.width() / bounds2.height());
18509
+ var fwd = bounds.getTransform(bounds2, frame.invert_y);
18510
+ transformPoints(dataset, function(x, y) {
18511
+ return fwd.transform(x, y);
18512
+ });
18513
+ }
18501
18514
 
18502
18515
  function getFrameLayerData(lyr, arcs) {
18503
18516
  var bounds = getLayerBounds(lyr, arcs);
@@ -18515,17 +18528,17 @@
18515
18528
 
18516
18529
 
18517
18530
  function calcFrameData(dataset, opts) {
18518
- var bounds;
18519
- var outputBbox = opts.svg_bbox || opts.output_bbox || null;
18520
- if (outputBbox) {
18521
- bounds = new Bounds(outputBbox);
18531
+ var inputBounds, outputBounds;
18532
+ if (opts.svg_bbox) {
18533
+ inputBounds = new Bounds(opts.svg_bbox);
18522
18534
  opts = Object.assign({margin: 0}, opts); // prevent default pixel margin around content
18523
18535
  } else {
18524
- bounds = getDatasetBounds(dataset);
18536
+ inputBounds = getDatasetBounds(dataset);
18525
18537
  }
18526
- var outputBounds = calcOutputSizeInPixels(bounds, opts);
18538
+ // side effect: inputBounds may be expanded to add margins
18539
+ outputBounds = calcOutputBounds(inputBounds, opts);
18527
18540
  return {
18528
- bbox: bounds.toArray(),
18541
+ bbox: inputBounds.toArray(),
18529
18542
  bbox2: outputBounds.toArray(),
18530
18543
  width: Math.round(outputBounds.width()),
18531
18544
  height: Math.round(outputBounds.height()) || 1,
@@ -18533,7 +18546,7 @@
18533
18546
  };
18534
18547
  }
18535
18548
 
18536
- // Used by mapshaper-frame and mapshaper-pixel-transform. TODO: refactor
18549
+ // Used by mapshaper-frame TODO: refactor
18537
18550
  function getFrameSize(bounds, opts) {
18538
18551
  var aspectRatio = bounds.width() / bounds.height();
18539
18552
  var height, width;
@@ -18602,9 +18615,9 @@
18602
18615
 
18603
18616
 
18604
18617
  // bounds: Bounds object containing bounds of content in geographic coordinates
18605
- // returns Bounds object containing bounds of pixel output
18618
+ // returns Bounds object containing output bounds
18606
18619
  // side effect: bounds param is modified to match the output frame
18607
- function calcOutputSizeInPixels(bounds, opts) {
18620
+ function calcOutputBounds(bounds, opts) {
18608
18621
  var padX = 0,
18609
18622
  padY = 0,
18610
18623
  offX = 0,
@@ -18617,65 +18630,59 @@
18617
18630
  // TODO: add option to tweak alignment of content when both width and height are given
18618
18631
  wx = 0.5, // how padding is distributed horizontally (0: left aligned, 0.5: centered, 1: right aligned)
18619
18632
  wy = 0.5, // vertical padding distribution
18620
- widthPx, heightPx, size, kx, ky;
18633
+ width2, height2, size2, kx, ky;
18621
18634
 
18622
18635
  if (opts.fit_bbox) {
18623
18636
  // scale + shift content to fit within a bbox
18624
18637
  offX = opts.fit_bbox[0];
18625
18638
  offY = opts.fit_bbox[1];
18626
- widthPx = opts.fit_bbox[2] - offX;
18627
- heightPx = opts.fit_bbox[3] - offY;
18628
- if (width / height > widthPx / heightPx) {
18629
- // data is wider than fit box...
18630
- // scale the data to fit widthwise
18631
- heightPx = 0;
18632
- } else {
18633
- widthPx = 0; // fit the data to the height
18634
- }
18639
+ width2 = opts.fit_bbox[2] - offX;
18640
+ height2 = opts.fit_bbox[3] - offY;
18635
18641
  marginX = marginY = 0; // TODO: support margins
18636
18642
 
18637
18643
  } else if (opts.svg_scale > 0) {
18638
18644
  // alternative to using a fixed width (e.g. when generating multiple files
18639
18645
  // at a consistent geographic scale)
18640
- widthPx = width / opts.svg_scale + marginX;
18641
- heightPx = 0;
18646
+ width2 = width / opts.svg_scale + marginX;
18647
+ height2 = 0;
18642
18648
  } else if (+opts.pixels) {
18643
- size = getFrameSize(bounds, opts);
18644
- widthPx = size[0];
18645
- heightPx = size[1];
18649
+ size2 = getFrameSize(bounds, opts);
18650
+ width2 = size2[0];
18651
+ height2 = size2[1];
18646
18652
  } else {
18647
- heightPx = opts.height || 0;
18648
- widthPx = opts.width || (heightPx > 0 ? 0 : 800); // 800 is default width
18653
+ height2 = opts.height || 0;
18654
+ width2 = opts.width || (height2 > 0 ? 0 : 800); // 800 is default width
18649
18655
  }
18650
18656
 
18651
- if (heightPx > 0) {
18657
+ if (height2 > 0) {
18652
18658
  // vertical meters per pixel to fit height param
18653
- ky = (height || width || 1) / (heightPx - marginY);
18659
+ ky = (height || width || 1) / (height2 - marginY);
18654
18660
  }
18655
- if (widthPx > 0) {
18661
+ if (width2 > 0) {
18656
18662
  // horizontal meters per pixel to fit width param
18657
- kx = (width || height || 1) / (widthPx - marginX);
18663
+ kx = (width || height || 1) / (width2 - marginX);
18658
18664
  }
18659
18665
 
18660
- if (!widthPx) { // heightPx and ky are defined, set width to match
18666
+ if (!width2) { // height2 and ky are defined, set width to match
18661
18667
  kx = ky;
18662
- widthPx = width > 0 ? marginX + width / kx : heightPx; // export square graphic if content has 0 width (reconsider this?)
18663
- } else if (!heightPx) { // widthPx and kx are set, set height to match
18668
+ width2 = width > 0 ? marginX + width / kx : height2; // export square graphic if content has 0 width (reconsider this?)
18669
+ } else if (!height2) { // width2 and kx are set, set height to match
18664
18670
  ky = kx;
18665
- heightPx = height > 0 ? marginY + height / ky : widthPx;
18671
+ height2 = height > 0 ? marginY + height / ky : width2;
18666
18672
  // limit height if max_height is defined
18667
- if (opts.max_height > 0 && heightPx > opts.max_height) {
18668
- ky = kx * heightPx / opts.max_height;
18669
- heightPx = opts.max_height;
18673
+ if (opts.max_height > 0 && height2 > opts.max_height) {
18674
+ ky = kx * height2 / opts.max_height;
18675
+ height2 = opts.max_height;
18670
18676
  }
18671
18677
  }
18672
18678
 
18679
+ // add padding, if needed
18673
18680
  if (kx > ky) { // content is wide -- need to pad vertically
18674
18681
  ky = kx;
18675
- padY = ky * (heightPx - marginY) - height;
18682
+ padY = ky * (height2 - marginY) - height;
18676
18683
  } else if (ky > kx) { // content is tall -- need to pad horizontally
18677
18684
  kx = ky;
18678
- padX = kx * (widthPx - marginX) - width;
18685
+ padX = kx * (width2 - marginX) - width;
18679
18686
  }
18680
18687
 
18681
18688
  bounds.padBounds(
@@ -18684,14 +18691,14 @@
18684
18691
  margins[2] * kx + padX * (1 - wx),
18685
18692
  margins[3] * ky + padY * (1 - wy));
18686
18693
 
18687
- if (!(widthPx > 0 && heightPx > 0)) {
18694
+ if (!(width2 > 0 && height2 > 0)) {
18688
18695
  error("Missing valid height and width parameters");
18689
18696
  }
18690
18697
  if (!(kx === ky && kx > 0)) {
18691
18698
  error("Missing valid margin parameters");
18692
18699
  }
18693
18700
 
18694
- return new Bounds(offX, offY, widthPx + offX, heightPx + offY);
18701
+ return new Bounds(offX, offY, width2 + offX, height2 + offY);
18695
18702
  }
18696
18703
 
18697
18704
  function parseMarginOption(opt) {
@@ -18702,13 +18709,14 @@
18702
18709
  if (margins.length == 3) margins.push(margins[2]);
18703
18710
  return margins.map(function(str) {
18704
18711
  var px = parseFloat(str);
18705
- return isNaN(px) ? 1 : px; // 1 is default
18712
+ return isNaN(px) ? 0 : px; // 0 is default
18706
18713
  });
18707
18714
  }
18708
18715
 
18709
- var FrameData = /*#__PURE__*/Object.freeze({
18716
+ var FrameUtils = /*#__PURE__*/Object.freeze({
18710
18717
  __proto__: null,
18711
18718
  getFrameData: getFrameData,
18719
+ fitDatasetToFrame: fitDatasetToFrame,
18712
18720
  getFrameLayerData: getFrameLayerData,
18713
18721
  calcFrameData: calcFrameData,
18714
18722
  getFrameSize: getFrameSize,
@@ -18719,7 +18727,7 @@
18719
18727
  findFrame: findFrame,
18720
18728
  getFrameLayerBounds: getFrameLayerBounds,
18721
18729
  getMapFrameMetersPerPixel: getMapFrameMetersPerPixel,
18722
- calcOutputSizeInPixels: calcOutputSizeInPixels,
18730
+ calcOutputBounds: calcOutputBounds,
18723
18731
  parseMarginOption: parseMarginOption
18724
18732
  });
18725
18733
 
@@ -20152,27 +20160,6 @@
20152
20160
  renderFurnitureLayer: renderFurnitureLayer
20153
20161
  });
20154
20162
 
20155
- function transformDatasetToPixels(dataset, opts) {
20156
- var frame = getFrameData(dataset, opts);
20157
- fitDatasetToFrame(dataset, frame, opts);
20158
- return [frame.width, frame.height];
20159
- }
20160
-
20161
- function fitDatasetToFrame(dataset, frame, opts) {
20162
- var bounds = new Bounds(frame.bbox);
20163
- var bounds2 = frame.bbox2 ? new Bounds(frame.bbox2) : new Bounds(0, 0, frame.width, frame.height);
20164
- var fwd = bounds.getTransform(bounds2, opts.invert_y);
20165
- transformPoints(dataset, function(x, y) {
20166
- return fwd.transform(x, y);
20167
- });
20168
- }
20169
-
20170
- var PixelTransform = /*#__PURE__*/Object.freeze({
20171
- __proto__: null,
20172
- transformDatasetToPixels: transformDatasetToPixels,
20173
- fitDatasetToFrame: fitDatasetToFrame
20174
- });
20175
-
20176
20163
  function stringify(obj) {
20177
20164
  var svg, joinStr;
20178
20165
  if (!obj || !obj.tag) return '';
@@ -20460,10 +20447,11 @@
20460
20447
  dataset = copyDataset(dataset); // Modify a copy of the dataset
20461
20448
  }
20462
20449
 
20463
- // invert_y setting for screen coordinates and geojson polygon generation
20464
- utils.extend(opts, {invert_y: true});
20450
+ // use invert_y: 0 setting for screen coordinates and geojson polygon generation
20451
+ // use 1px default margin so typical strokes don't get cut off on the sides
20452
+ opts = Object.assign({invert_y: true, margin: "1"}, opts);
20465
20453
  frame = getFrameData(dataset, opts);
20466
- fitDatasetToFrame(dataset, frame, opts);
20454
+ fitDatasetToFrame(dataset, frame);
20467
20455
  setCoordinatePrecision(dataset, opts.precision || 0.0001);
20468
20456
 
20469
20457
  // error if one or more svg_data fields are not present in any layers
@@ -22099,10 +22087,14 @@ ${svg}
22099
22087
  }
22100
22088
 
22101
22089
  if (opts.width > 0 || opts.height > 0) {
22090
+ // these options create a TopoJSON with pixel coordinates, including
22091
+ // origin (0,0) in the top left corner of the viewport, generally for
22092
+ // direct conversion to SVG (many online examples using d3 are like this)
22093
+ //
22102
22094
  opts = utils.defaults({invert_y: true}, opts);
22103
- transformDatasetToPixels(dataset, opts);
22095
+ fitDatasetToFrame(dataset, getFrameData(dataset, opts));
22104
22096
  } else if (opts.fit_bbox) {
22105
- transformDatasetToPixels(dataset, {fit_bbox: opts.fit_bbox});
22097
+ fitDatasetToFrame(dataset, getFrameData(dataset, {fit_bbox: opts.fit_bbox}));
22106
22098
  }
22107
22099
 
22108
22100
  if (opts.precision && opts.no_quantization) {
@@ -24311,7 +24303,7 @@ ${svg}
24311
24303
  describe: 'aspect ratio as a number or range (e.g. 2 0.8,1.6 ,2)'
24312
24304
  },
24313
24305
  offsetOpt = {
24314
- describe: 'padding as distance or pct of h/w (single value or list)',
24306
+ describe: 'offset distance or pct of h/w (single value or l,b,r,t list)',
24315
24307
  type: 'distance'
24316
24308
  };
24317
24309
 
@@ -39353,7 +39345,7 @@ ${svg}
39353
39345
  // TODO: consider projections that may or may not be aligned,
39354
39346
  // depending on parameters
39355
39347
  if (inList(P, 'cassini,gnom,bertin1953,chamb,ob_tran,tpeqd,healpix,rhealpix,' +
39356
- 'ocea,omerc,tmerc,etmerc')) {
39348
+ 'ocea,omerc,tmerc,etmerc,nicol')) {
39357
39349
  return false;
39358
39350
  }
39359
39351
  if (isAzimuthal(P)) {
@@ -39928,7 +39920,7 @@ ${svg}
39928
39920
  }
39929
39921
 
39930
39922
  function isCircleClippedProjection(P) {
39931
- return inList(P, 'stere,sterea,ups,ortho,gnom,laea,nsper,tpers,geos');
39923
+ return inList(P, 'stere,sterea,ups,ortho,gnom,laea,nsper,tpers,geos,nicol');
39932
39924
  }
39933
39925
 
39934
39926
  function getPerspectiveClipAngle(P) {
@@ -39953,6 +39945,7 @@ ${svg}
39953
39945
  laea: 179,
39954
39946
  //ortho: 89.9, // projection errors betwen lat +/-35 to 55
39955
39947
  ortho: 89.85, // TODO: investigate
39948
+ nicol: 89.85,
39956
39949
  stere: 142,
39957
39950
  sterea: 142,
39958
39951
  ups: 10.5 // TODO: should be 6.5 deg at north pole
@@ -45905,7 +45898,7 @@ ${svg}
45905
45898
  });
45906
45899
  }
45907
45900
 
45908
- var version = "0.6.101";
45901
+ var version = "0.6.102";
45909
45902
 
45910
45903
  // Parse command line args into commands and run them
45911
45904
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -46598,7 +46591,7 @@ ${svg}
46598
46591
  FileTypes,
46599
46592
  FilterGeom,
46600
46593
  Frame,
46601
- FrameData,
46594
+ FrameUtils,
46602
46595
  Furniture,
46603
46596
  Geodesic,
46604
46597
  GeojsonExport,
@@ -46633,7 +46626,7 @@ ${svg}
46633
46626
  PathImport,
46634
46627
  PathRepair,
46635
46628
  PathUtils,
46636
- PixelTransform,
46629
+ // PixelTransform,
46637
46630
  PointPolygonJoin,
46638
46631
  Points,
46639
46632
  PointToGrid,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.6.101",
3
+ "version": "0.6.102",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
@@ -56,7 +56,7 @@
56
56
  "iconv-lite": "^0.6.3",
57
57
  "idb-keyval": "^6.2.0",
58
58
  "kdbush": "^3.0.0",
59
- "mproj": "0.0.39",
59
+ "mproj": "0.0.40",
60
60
  "msgpackr": "^1.10.1",
61
61
  "opn": "^5.3.0",
62
62
  "rw": "~1.3.3",
@@ -6843,6 +6843,7 @@
6843
6843
  });
6844
6844
  }
6845
6845
 
6846
+
6846
6847
  function defaults(dest) {
6847
6848
  for (var i=1, n=arguments.length; i<n; i++) {
6848
6849
  var src = arguments[i] || {};
@@ -12974,7 +12975,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12974
12975
  function calcFullBounds() {
12975
12976
  var b;
12976
12977
  if (isPreviewView()) {
12977
- b = new Bounds(getFrameData().bbox);
12978
+ b = new Bounds(getFrameLayerData().bbox);
12978
12979
  } else {
12979
12980
  b = getContentLayerBounds();
12980
12981
  }
@@ -13014,10 +13015,10 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13014
13015
 
13015
13016
  // Preview view: symbols are scaled based on display size of frame layer
13016
13017
  function isPreviewView() {
13017
- return !isTableView() && !!getFrameData();
13018
+ return !isTableView() && !!getFrameLayerData();
13018
13019
  }
13019
13020
 
13020
- function getFrameData() {
13021
+ function getFrameLayerData() {
13021
13022
  var lyr = findFrameLayer();
13022
13023
  return lyr && internal.getFrameLayerData(lyr, lyr.gui.displayArcs) || null;
13023
13024
  }
@@ -13138,7 +13139,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13138
13139
  }
13139
13140
  if (layersMayHaveChanged) {
13140
13141
  // kludge to handle layer visibility toggling
13141
- _ext.setFrameData(isPreviewView() ? getFrameData() : null);
13142
+ _ext.setFrameData(isPreviewView() ? getFrameLayerData() : null);
13142
13143
  updateFullBounds();
13143
13144
  updateLayerStyles(contentLayers);
13144
13145
  updateLayerStackOrder(model.getLayers());// update menu_order property of all layers
package/www/mapshaper.js CHANGED
@@ -267,6 +267,7 @@
267
267
  });
268
268
  }
269
269
 
270
+
270
271
  function defaults(dest) {
271
272
  for (var i=1, n=arguments.length; i<n; i++) {
272
273
  var src = arguments[i] || {};
@@ -13762,7 +13763,7 @@
13762
13763
  convertIntervalParam(opt[1], crs)];
13763
13764
  }
13764
13765
 
13765
- // Accepts a single value or a list of four values. List order is l,b,t,r
13766
+ // Accepts a single value or a list of four values. List order is l,b,r,t
13766
13767
  function convertFourSides(opt, crs, bounds) {
13767
13768
  var arr = opt.includes(',') ? opt.split(',') : opt.split(' ');
13768
13769
  if (arr.length == 1) {
@@ -13776,7 +13777,7 @@
13776
13777
  tmp = parseFloat(param) / 100 || 0;
13777
13778
  return tmp * (i == 1 || i == 3 ? bounds.height() : bounds.width());
13778
13779
  }
13779
- return convertIntervalParam(opt, crs);
13780
+ return convertIntervalParam(param, crs);
13780
13781
  });
13781
13782
  }
13782
13783
 
@@ -18486,18 +18487,30 @@
18486
18487
  type: 'frame'
18487
18488
  }
18488
18489
  */
18490
+
18491
+
18489
18492
  function getFrameData(dataset, exportOpts) {
18490
18493
  var frameLyr = findFrameLayerInDataset(dataset);
18491
- var frameData;
18494
+ var data;
18492
18495
  if (frameLyr) {
18493
- frameData = getFrameLayerData(frameLyr, dataset.arcs);
18496
+ data = getFrameLayerData(frameLyr, dataset.arcs);
18494
18497
  } else {
18495
- frameData = calcFrameData(dataset, exportOpts);
18498
+ data = calcFrameData(dataset, exportOpts);
18496
18499
  }
18497
- frameData.crs = getDatasetCRS(dataset);
18498
- return frameData;
18500
+ data.invert_y = !!exportOpts.invert_y;
18501
+ data.crs = getDatasetCRS(dataset);
18502
+ return data;
18499
18503
  }
18500
18504
 
18505
+ function fitDatasetToFrame(dataset, frame) {
18506
+ var bounds = new Bounds(frame.bbox);
18507
+ var bounds2 = frame.bbox2 ? new Bounds(frame.bbox2) : new Bounds(0, 0, frame.width, frame.height);
18508
+ bounds.fillOut(bounds2.width() / bounds2.height());
18509
+ var fwd = bounds.getTransform(bounds2, frame.invert_y);
18510
+ transformPoints(dataset, function(x, y) {
18511
+ return fwd.transform(x, y);
18512
+ });
18513
+ }
18501
18514
 
18502
18515
  function getFrameLayerData(lyr, arcs) {
18503
18516
  var bounds = getLayerBounds(lyr, arcs);
@@ -18515,17 +18528,17 @@
18515
18528
 
18516
18529
 
18517
18530
  function calcFrameData(dataset, opts) {
18518
- var bounds;
18519
- var outputBbox = opts.svg_bbox || opts.output_bbox || null;
18520
- if (outputBbox) {
18521
- bounds = new Bounds(outputBbox);
18531
+ var inputBounds, outputBounds;
18532
+ if (opts.svg_bbox) {
18533
+ inputBounds = new Bounds(opts.svg_bbox);
18522
18534
  opts = Object.assign({margin: 0}, opts); // prevent default pixel margin around content
18523
18535
  } else {
18524
- bounds = getDatasetBounds(dataset);
18536
+ inputBounds = getDatasetBounds(dataset);
18525
18537
  }
18526
- var outputBounds = calcOutputSizeInPixels(bounds, opts);
18538
+ // side effect: inputBounds may be expanded to add margins
18539
+ outputBounds = calcOutputBounds(inputBounds, opts);
18527
18540
  return {
18528
- bbox: bounds.toArray(),
18541
+ bbox: inputBounds.toArray(),
18529
18542
  bbox2: outputBounds.toArray(),
18530
18543
  width: Math.round(outputBounds.width()),
18531
18544
  height: Math.round(outputBounds.height()) || 1,
@@ -18533,7 +18546,7 @@
18533
18546
  };
18534
18547
  }
18535
18548
 
18536
- // Used by mapshaper-frame and mapshaper-pixel-transform. TODO: refactor
18549
+ // Used by mapshaper-frame TODO: refactor
18537
18550
  function getFrameSize(bounds, opts) {
18538
18551
  var aspectRatio = bounds.width() / bounds.height();
18539
18552
  var height, width;
@@ -18602,9 +18615,9 @@
18602
18615
 
18603
18616
 
18604
18617
  // bounds: Bounds object containing bounds of content in geographic coordinates
18605
- // returns Bounds object containing bounds of pixel output
18618
+ // returns Bounds object containing output bounds
18606
18619
  // side effect: bounds param is modified to match the output frame
18607
- function calcOutputSizeInPixels(bounds, opts) {
18620
+ function calcOutputBounds(bounds, opts) {
18608
18621
  var padX = 0,
18609
18622
  padY = 0,
18610
18623
  offX = 0,
@@ -18617,65 +18630,59 @@
18617
18630
  // TODO: add option to tweak alignment of content when both width and height are given
18618
18631
  wx = 0.5, // how padding is distributed horizontally (0: left aligned, 0.5: centered, 1: right aligned)
18619
18632
  wy = 0.5, // vertical padding distribution
18620
- widthPx, heightPx, size, kx, ky;
18633
+ width2, height2, size2, kx, ky;
18621
18634
 
18622
18635
  if (opts.fit_bbox) {
18623
18636
  // scale + shift content to fit within a bbox
18624
18637
  offX = opts.fit_bbox[0];
18625
18638
  offY = opts.fit_bbox[1];
18626
- widthPx = opts.fit_bbox[2] - offX;
18627
- heightPx = opts.fit_bbox[3] - offY;
18628
- if (width / height > widthPx / heightPx) {
18629
- // data is wider than fit box...
18630
- // scale the data to fit widthwise
18631
- heightPx = 0;
18632
- } else {
18633
- widthPx = 0; // fit the data to the height
18634
- }
18639
+ width2 = opts.fit_bbox[2] - offX;
18640
+ height2 = opts.fit_bbox[3] - offY;
18635
18641
  marginX = marginY = 0; // TODO: support margins
18636
18642
 
18637
18643
  } else if (opts.svg_scale > 0) {
18638
18644
  // alternative to using a fixed width (e.g. when generating multiple files
18639
18645
  // at a consistent geographic scale)
18640
- widthPx = width / opts.svg_scale + marginX;
18641
- heightPx = 0;
18646
+ width2 = width / opts.svg_scale + marginX;
18647
+ height2 = 0;
18642
18648
  } else if (+opts.pixels) {
18643
- size = getFrameSize(bounds, opts);
18644
- widthPx = size[0];
18645
- heightPx = size[1];
18649
+ size2 = getFrameSize(bounds, opts);
18650
+ width2 = size2[0];
18651
+ height2 = size2[1];
18646
18652
  } else {
18647
- heightPx = opts.height || 0;
18648
- widthPx = opts.width || (heightPx > 0 ? 0 : 800); // 800 is default width
18653
+ height2 = opts.height || 0;
18654
+ width2 = opts.width || (height2 > 0 ? 0 : 800); // 800 is default width
18649
18655
  }
18650
18656
 
18651
- if (heightPx > 0) {
18657
+ if (height2 > 0) {
18652
18658
  // vertical meters per pixel to fit height param
18653
- ky = (height || width || 1) / (heightPx - marginY);
18659
+ ky = (height || width || 1) / (height2 - marginY);
18654
18660
  }
18655
- if (widthPx > 0) {
18661
+ if (width2 > 0) {
18656
18662
  // horizontal meters per pixel to fit width param
18657
- kx = (width || height || 1) / (widthPx - marginX);
18663
+ kx = (width || height || 1) / (width2 - marginX);
18658
18664
  }
18659
18665
 
18660
- if (!widthPx) { // heightPx and ky are defined, set width to match
18666
+ if (!width2) { // height2 and ky are defined, set width to match
18661
18667
  kx = ky;
18662
- widthPx = width > 0 ? marginX + width / kx : heightPx; // export square graphic if content has 0 width (reconsider this?)
18663
- } else if (!heightPx) { // widthPx and kx are set, set height to match
18668
+ width2 = width > 0 ? marginX + width / kx : height2; // export square graphic if content has 0 width (reconsider this?)
18669
+ } else if (!height2) { // width2 and kx are set, set height to match
18664
18670
  ky = kx;
18665
- heightPx = height > 0 ? marginY + height / ky : widthPx;
18671
+ height2 = height > 0 ? marginY + height / ky : width2;
18666
18672
  // limit height if max_height is defined
18667
- if (opts.max_height > 0 && heightPx > opts.max_height) {
18668
- ky = kx * heightPx / opts.max_height;
18669
- heightPx = opts.max_height;
18673
+ if (opts.max_height > 0 && height2 > opts.max_height) {
18674
+ ky = kx * height2 / opts.max_height;
18675
+ height2 = opts.max_height;
18670
18676
  }
18671
18677
  }
18672
18678
 
18679
+ // add padding, if needed
18673
18680
  if (kx > ky) { // content is wide -- need to pad vertically
18674
18681
  ky = kx;
18675
- padY = ky * (heightPx - marginY) - height;
18682
+ padY = ky * (height2 - marginY) - height;
18676
18683
  } else if (ky > kx) { // content is tall -- need to pad horizontally
18677
18684
  kx = ky;
18678
- padX = kx * (widthPx - marginX) - width;
18685
+ padX = kx * (width2 - marginX) - width;
18679
18686
  }
18680
18687
 
18681
18688
  bounds.padBounds(
@@ -18684,14 +18691,14 @@
18684
18691
  margins[2] * kx + padX * (1 - wx),
18685
18692
  margins[3] * ky + padY * (1 - wy));
18686
18693
 
18687
- if (!(widthPx > 0 && heightPx > 0)) {
18694
+ if (!(width2 > 0 && height2 > 0)) {
18688
18695
  error("Missing valid height and width parameters");
18689
18696
  }
18690
18697
  if (!(kx === ky && kx > 0)) {
18691
18698
  error("Missing valid margin parameters");
18692
18699
  }
18693
18700
 
18694
- return new Bounds(offX, offY, widthPx + offX, heightPx + offY);
18701
+ return new Bounds(offX, offY, width2 + offX, height2 + offY);
18695
18702
  }
18696
18703
 
18697
18704
  function parseMarginOption(opt) {
@@ -18702,13 +18709,14 @@
18702
18709
  if (margins.length == 3) margins.push(margins[2]);
18703
18710
  return margins.map(function(str) {
18704
18711
  var px = parseFloat(str);
18705
- return isNaN(px) ? 1 : px; // 1 is default
18712
+ return isNaN(px) ? 0 : px; // 0 is default
18706
18713
  });
18707
18714
  }
18708
18715
 
18709
- var FrameData = /*#__PURE__*/Object.freeze({
18716
+ var FrameUtils = /*#__PURE__*/Object.freeze({
18710
18717
  __proto__: null,
18711
18718
  getFrameData: getFrameData,
18719
+ fitDatasetToFrame: fitDatasetToFrame,
18712
18720
  getFrameLayerData: getFrameLayerData,
18713
18721
  calcFrameData: calcFrameData,
18714
18722
  getFrameSize: getFrameSize,
@@ -18719,7 +18727,7 @@
18719
18727
  findFrame: findFrame,
18720
18728
  getFrameLayerBounds: getFrameLayerBounds,
18721
18729
  getMapFrameMetersPerPixel: getMapFrameMetersPerPixel,
18722
- calcOutputSizeInPixels: calcOutputSizeInPixels,
18730
+ calcOutputBounds: calcOutputBounds,
18723
18731
  parseMarginOption: parseMarginOption
18724
18732
  });
18725
18733
 
@@ -20152,27 +20160,6 @@
20152
20160
  renderFurnitureLayer: renderFurnitureLayer
20153
20161
  });
20154
20162
 
20155
- function transformDatasetToPixels(dataset, opts) {
20156
- var frame = getFrameData(dataset, opts);
20157
- fitDatasetToFrame(dataset, frame, opts);
20158
- return [frame.width, frame.height];
20159
- }
20160
-
20161
- function fitDatasetToFrame(dataset, frame, opts) {
20162
- var bounds = new Bounds(frame.bbox);
20163
- var bounds2 = frame.bbox2 ? new Bounds(frame.bbox2) : new Bounds(0, 0, frame.width, frame.height);
20164
- var fwd = bounds.getTransform(bounds2, opts.invert_y);
20165
- transformPoints(dataset, function(x, y) {
20166
- return fwd.transform(x, y);
20167
- });
20168
- }
20169
-
20170
- var PixelTransform = /*#__PURE__*/Object.freeze({
20171
- __proto__: null,
20172
- transformDatasetToPixels: transformDatasetToPixels,
20173
- fitDatasetToFrame: fitDatasetToFrame
20174
- });
20175
-
20176
20163
  function stringify(obj) {
20177
20164
  var svg, joinStr;
20178
20165
  if (!obj || !obj.tag) return '';
@@ -20460,10 +20447,11 @@
20460
20447
  dataset = copyDataset(dataset); // Modify a copy of the dataset
20461
20448
  }
20462
20449
 
20463
- // invert_y setting for screen coordinates and geojson polygon generation
20464
- utils.extend(opts, {invert_y: true});
20450
+ // use invert_y: 0 setting for screen coordinates and geojson polygon generation
20451
+ // use 1px default margin so typical strokes don't get cut off on the sides
20452
+ opts = Object.assign({invert_y: true, margin: "1"}, opts);
20465
20453
  frame = getFrameData(dataset, opts);
20466
- fitDatasetToFrame(dataset, frame, opts);
20454
+ fitDatasetToFrame(dataset, frame);
20467
20455
  setCoordinatePrecision(dataset, opts.precision || 0.0001);
20468
20456
 
20469
20457
  // error if one or more svg_data fields are not present in any layers
@@ -22099,10 +22087,14 @@ ${svg}
22099
22087
  }
22100
22088
 
22101
22089
  if (opts.width > 0 || opts.height > 0) {
22090
+ // these options create a TopoJSON with pixel coordinates, including
22091
+ // origin (0,0) in the top left corner of the viewport, generally for
22092
+ // direct conversion to SVG (many online examples using d3 are like this)
22093
+ //
22102
22094
  opts = utils.defaults({invert_y: true}, opts);
22103
- transformDatasetToPixels(dataset, opts);
22095
+ fitDatasetToFrame(dataset, getFrameData(dataset, opts));
22104
22096
  } else if (opts.fit_bbox) {
22105
- transformDatasetToPixels(dataset, {fit_bbox: opts.fit_bbox});
22097
+ fitDatasetToFrame(dataset, getFrameData(dataset, {fit_bbox: opts.fit_bbox}));
22106
22098
  }
22107
22099
 
22108
22100
  if (opts.precision && opts.no_quantization) {
@@ -24311,7 +24303,7 @@ ${svg}
24311
24303
  describe: 'aspect ratio as a number or range (e.g. 2 0.8,1.6 ,2)'
24312
24304
  },
24313
24305
  offsetOpt = {
24314
- describe: 'padding as distance or pct of h/w (single value or list)',
24306
+ describe: 'offset distance or pct of h/w (single value or l,b,r,t list)',
24315
24307
  type: 'distance'
24316
24308
  };
24317
24309
 
@@ -39353,7 +39345,7 @@ ${svg}
39353
39345
  // TODO: consider projections that may or may not be aligned,
39354
39346
  // depending on parameters
39355
39347
  if (inList(P, 'cassini,gnom,bertin1953,chamb,ob_tran,tpeqd,healpix,rhealpix,' +
39356
- 'ocea,omerc,tmerc,etmerc')) {
39348
+ 'ocea,omerc,tmerc,etmerc,nicol')) {
39357
39349
  return false;
39358
39350
  }
39359
39351
  if (isAzimuthal(P)) {
@@ -39928,7 +39920,7 @@ ${svg}
39928
39920
  }
39929
39921
 
39930
39922
  function isCircleClippedProjection(P) {
39931
- return inList(P, 'stere,sterea,ups,ortho,gnom,laea,nsper,tpers,geos');
39923
+ return inList(P, 'stere,sterea,ups,ortho,gnom,laea,nsper,tpers,geos,nicol');
39932
39924
  }
39933
39925
 
39934
39926
  function getPerspectiveClipAngle(P) {
@@ -39953,6 +39945,7 @@ ${svg}
39953
39945
  laea: 179,
39954
39946
  //ortho: 89.9, // projection errors betwen lat +/-35 to 55
39955
39947
  ortho: 89.85, // TODO: investigate
39948
+ nicol: 89.85,
39956
39949
  stere: 142,
39957
39950
  sterea: 142,
39958
39951
  ups: 10.5 // TODO: should be 6.5 deg at north pole
@@ -45905,7 +45898,7 @@ ${svg}
45905
45898
  });
45906
45899
  }
45907
45900
 
45908
- var version = "0.6.101";
45901
+ var version = "0.6.102";
45909
45902
 
45910
45903
  // Parse command line args into commands and run them
45911
45904
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -46598,7 +46591,7 @@ ${svg}
46598
46591
  FileTypes,
46599
46592
  FilterGeom,
46600
46593
  Frame,
46601
- FrameData,
46594
+ FrameUtils,
46602
46595
  Furniture,
46603
46596
  Geodesic,
46604
46597
  GeojsonExport,
@@ -46633,7 +46626,7 @@ ${svg}
46633
46626
  PathImport,
46634
46627
  PathRepair,
46635
46628
  PathUtils,
46636
- PixelTransform,
46629
+ // PixelTransform,
46637
46630
  PointPolygonJoin,
46638
46631
  Points,
46639
46632
  PointToGrid,
package/www/modules.js CHANGED
@@ -20279,6 +20279,7 @@ function pj_phi2(ts, e) {
20279
20279
 
20280
20280
 
20281
20281
  pj_add(pj_merc, 'merc', 'Mercator', 'Cyl, Sph&Ell\nlat_ts=');
20282
+ pj_add(pj_webmerc, 'webmerc', 'Web Mercator / Pseudo Mercator', 'Cyl, Ell');
20282
20283
 
20283
20284
  function pj_merc(P) {
20284
20285
  var EPS10 = 1e-10;
@@ -20333,6 +20334,26 @@ function pj_merc(P) {
20333
20334
  }
20334
20335
  }
20335
20336
 
20337
+ function pj_webmerc(P) {
20338
+ P.k0 = 1;
20339
+ P.inv = s_inv;
20340
+ P.fwd = s_fwd;
20341
+
20342
+ function s_fwd(lp, xy) {
20343
+ if (fabs(fabs(lp.phi) - M_HALFPI) <= EPS10) {
20344
+ f_error();
20345
+ }
20346
+ xy.x = P.k0 * lp.lam;
20347
+ xy.y = P.k0 * log(tan(M_FORTPI + 0.5 * lp.phi));
20348
+ }
20349
+
20350
+ function s_inv(xy, lp) {
20351
+ lp.phi = M_HALFPI - 2 * atan(exp(-xy.y / P.k0));
20352
+ lp.lam = xy.x / P.k0;
20353
+ }
20354
+ }
20355
+
20356
+
20336
20357
 
20337
20358
  pj_add(pj_mill, 'mill', 'Miller Cylindrical', 'Cyl, Sph');
20338
20359
 
@@ -20790,6 +20811,45 @@ var NITER = 9,
20790
20811
  }
20791
20812
 
20792
20813
 
20814
+ pj_add(pj_nicol, 'nicol', 'Nicolosi Globular', 'Misc Sph, no inv');
20815
+
20816
+ function pj_nicol(P) {
20817
+ P.es = 0;
20818
+ P.fwd = s_fwd;
20819
+
20820
+ function s_fwd(lp, xy) {
20821
+ var EPS = 1e-10;
20822
+ if (fabs(lp.lam) < EPS) {
20823
+ xy.x = 0;
20824
+ xy.y = lp.phi;
20825
+ } else if (fabs(lp.phi) < EPS) {
20826
+ xy.x = lp.lam;
20827
+ xy.y = 0;
20828
+ } else if (fabs(fabs(lp.lam) - M_HALFPI) < EPS) {
20829
+ xy.x = lp.lam * cos(lp.phi);
20830
+ xy.y = M_HALFPI * sin(lp.phi);
20831
+ } else if (fabs(fabs(lp.phi) - M_HALFPI) < EPS) {
20832
+ xy.x = 0;
20833
+ xy.y = lp.phi;
20834
+ } else {
20835
+ var tb = M_HALFPI / lp.lam - lp.lam / M_HALFPI;
20836
+ var c = lp.phi / M_HALFPI;
20837
+ var sp = sin(lp.phi);
20838
+ var d = (1 - c * c) / (sp - c);
20839
+ var r2 = tb / d;
20840
+ r2 *= r2;
20841
+ var m = (tb * sp / d - 0.5 * tb) / (1 + r2);
20842
+ var n = (sp / r2 + 0.5 * d) / (1 + 1 / r2);
20843
+ xy.x = cos(lp.phi);
20844
+ xy.x = sqrt(m * m + xy.x * xy.x / (1 + r2));
20845
+ xy.x = M_HALFPI * (m + (lp.lam < 0. ? -xy.x : xy.x));
20846
+ xy.y = sqrt(n * n - (sp * sp / r2 + d * sp - 1) / (1 + 1 / r2));
20847
+ xy.y = M_HALFPI * (n + (lp.phi < 0. ? xy.y : -xy.y));
20848
+ }
20849
+ }
20850
+ }
20851
+
20852
+
20793
20853
  pj_add(pj_nsper, 'nsper', 'Near-sided perspective', 'Azi, Sph\nh=');
20794
20854
  pj_add(pj_tpers, 'tpers', 'Tilted perspective', 'Azi, Sph\ntilt= azi= h=');
20795
20855