mapshaper 0.6.99 → 0.6.101

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
@@ -5165,12 +5165,12 @@
5165
5165
  return getDatasetCrsInfo(dataset).crs;
5166
5166
  }
5167
5167
 
5168
- function requireDatasetsHaveCompatibleCRS(arr) {
5168
+ function requireDatasetsHaveCompatibleCRS(arr, msg) {
5169
5169
  arr.reduce(function(memo, dataset) {
5170
5170
  var P = getDatasetCRS(dataset);
5171
5171
  if (memo && P) {
5172
5172
  if (isLatLngCRS(memo) != isLatLngCRS(P)) {
5173
- stop("Unable to combine projected and unprojected datasets");
5173
+ stop(msg || "Unable to combine projected and unprojected datasets");
5174
5174
  }
5175
5175
  }
5176
5176
  return P || memo;
@@ -13640,7 +13640,7 @@
13640
13640
  var px = units == 'in' && num * 72 ||
13641
13641
  units == 'cm' && Math.round(num * 28.3465) ||
13642
13642
  num;
13643
- if (px > 0 === false || !units) {
13643
+ if (px >= 0 === false || !units) {
13644
13644
  stop('Invalid size:', str);
13645
13645
  }
13646
13646
  return px;
@@ -14715,6 +14715,7 @@
14715
14715
  }
14716
14716
 
14717
14717
 
14718
+
14718
14719
  function findCommandTargets(layers, pattern, type) {
14719
14720
  var matches = findMatchingLayers(layers, pattern, true);
14720
14721
  if (type) {
@@ -18154,6 +18155,9 @@
18154
18155
  if (ids) {
18155
18156
  obj.id = ids[i];
18156
18157
  }
18158
+ if (opts.no_null_props && !obj.properties) {
18159
+ obj.properties = {};
18160
+ }
18157
18161
  } else if (!geom) {
18158
18162
  return memo; // don't add null objects to GeometryCollection
18159
18163
  } else {
@@ -18499,7 +18503,8 @@
18499
18503
  var bounds = getLayerBounds(lyr, arcs);
18500
18504
  var d = lyr.data.getReadOnlyRecordAt(0);
18501
18505
  var w = d.width || 800;
18502
- var h = w * bounds.height() / bounds.width();
18506
+ // prevent rounding errors (like 1000.0000000002)
18507
+ var h = Math.round(w * bounds.height() / bounds.width());
18503
18508
  return {
18504
18509
  type: 'frame',
18505
18510
  width: w,
@@ -18511,17 +18516,19 @@
18511
18516
 
18512
18517
  function calcFrameData(dataset, opts) {
18513
18518
  var bounds;
18514
- if (opts.svg_bbox) {
18515
- bounds = new Bounds(opts.svg_bbox);
18519
+ var outputBbox = opts.svg_bbox || opts.output_bbox || null;
18520
+ if (outputBbox) {
18521
+ bounds = new Bounds(outputBbox);
18516
18522
  opts = Object.assign({margin: 0}, opts); // prevent default pixel margin around content
18517
18523
  } else {
18518
18524
  bounds = getDatasetBounds(dataset);
18519
18525
  }
18520
- var pixBounds = calcOutputSizeInPixels(bounds, opts);
18526
+ var outputBounds = calcOutputSizeInPixels(bounds, opts);
18521
18527
  return {
18522
18528
  bbox: bounds.toArray(),
18523
- width: Math.round(pixBounds.width()),
18524
- height: Math.round(pixBounds.height()) || 1,
18529
+ bbox2: outputBounds.toArray(),
18530
+ width: Math.round(outputBounds.width()),
18531
+ height: Math.round(outputBounds.height()) || 1,
18525
18532
  type: 'frame'
18526
18533
  };
18527
18534
  }
@@ -18703,6 +18710,7 @@
18703
18710
  __proto__: null,
18704
18711
  getFrameData: getFrameData,
18705
18712
  getFrameLayerData: getFrameLayerData,
18713
+ calcFrameData: calcFrameData,
18706
18714
  getFrameSize: getFrameSize,
18707
18715
  isFrameLayer: isFrameLayer,
18708
18716
  findFrameLayerInDataset: findFrameLayerInDataset,
@@ -18766,6 +18774,9 @@
18766
18774
  geometry_type: 'point', shapes: targetPoints}, {geometry_type: 'polyline',
18767
18775
  shapes: targetShapes}]}, opts);
18768
18776
  transform = getAffineTransform(rotateArg, scaleArg, shiftArg, anchorArg);
18777
+ if (opts.fit_bbox) {
18778
+ transform = getFitBoxTransform(opts.fit_bbox, targetPoints, targetShapes, arcs);
18779
+ }
18769
18780
  if (targetShapes.length > 0) {
18770
18781
  targetFlags = new Uint8Array(arcs.size());
18771
18782
  otherFlags = new Uint8Array(arcs.size());
@@ -18815,6 +18826,33 @@
18815
18826
  };
18816
18827
  }
18817
18828
 
18829
+ function getFitBoxTransform(bbox, points, shapes, arcs) {
18830
+ var dataset = {
18831
+ arcs: arcs,
18832
+ info: {},
18833
+ layers: []
18834
+ };
18835
+ if (points && points.length) {
18836
+ dataset.layers.push({
18837
+ geometry_type: 'point',
18838
+ shapes: points
18839
+ });
18840
+ }
18841
+ if (shapes && shapes.length) {
18842
+ dataset.layers.push({
18843
+ geometry_type: 'polyline',
18844
+ shapes: shapes
18845
+ });
18846
+ }
18847
+ var frame = calcFrameData(dataset, {fit_bbox: bbox});
18848
+ var fromBounds = new Bounds(frame.bbox);
18849
+ var toBounds = new Bounds(frame.bbox2);
18850
+ var fwd = fromBounds.getTransform(toBounds, false);
18851
+ return function(x, y) {
18852
+ return fwd.transform(x, y);
18853
+ };
18854
+ }
18855
+
18818
18856
  function applyArrayMask(destArr, maskArr) {
18819
18857
  for (var i=0, n=destArr.length; i<n; i++) {
18820
18858
  if (maskArr[i] === 0) destArr[i] = 0;
@@ -18934,7 +18972,7 @@
18934
18972
  parts.shift();
18935
18973
  var colors = [];
18936
18974
  var background = parts.pop();
18937
- var spacing = parseInt(parts.pop());
18975
+ var spacing = parseNum(parts.pop());
18938
18976
  var tmp;
18939
18977
  while (parts.length > 0) {
18940
18978
  tmp = parts.pop();
@@ -18945,11 +18983,11 @@
18945
18983
  colors.push(tmp);
18946
18984
  }
18947
18985
  }
18948
- var width = parseInt(parts.pop());
18949
- var dashes = [parseInt(parts.pop()), parseInt(parts.pop())].reverse();
18986
+ var width = parseNum(parts.pop());
18987
+ var dashes = [parseNum(parts.pop()), parseNum(parts.pop())].reverse();
18950
18988
  var rotation = 45;
18951
18989
  if (parts.length > 0) {
18952
- rotation = parseInt(parts.pop());
18990
+ rotation = parseNum(parts.pop());
18953
18991
  }
18954
18992
  if (parts.length > 0) {
18955
18993
  return null;
@@ -18974,10 +19012,10 @@
18974
19012
  // 1px red 1px white 1px black
18975
19013
  // -45deg 3 #eee 3 rgb(0,0,0)
18976
19014
  parts.shift();
18977
- var rot = parts.length % 2 == 1 ? parseInt(parts.shift()) : 45, // default is 45
19015
+ var rot = parts.length % 2 == 1 ? parseNum(parts.shift()) : 45, // default is 45
18978
19016
  colors = [], widths = [];
18979
19017
  for (var i=0; i<parts.length; i+=2) {
18980
- widths.push(parseInt(parts[i]));
19018
+ widths.push(parseNum(parts[i]));
18981
19019
  colors.push(parts[i+1]);
18982
19020
  }
18983
19021
  if (Math.min.apply(null, widths) > 0 === false) return null;
@@ -18991,7 +19029,7 @@
18991
19029
  }
18992
19030
 
18993
19031
  function isSize(str) {
18994
- return parseInt(str) > 0;
19032
+ return parseNum(str) > 0;
18995
19033
  }
18996
19034
 
18997
19035
  function parseDots(parts) {
@@ -19004,11 +19042,11 @@
19004
19042
  var type = parts.shift();
19005
19043
  var rot = 0;
19006
19044
  if (isSize(parts[1])) { // if rotation is present, there are two numbers
19007
- rot = parseInt(parts.shift());
19045
+ rot = parseNum(parts.shift());
19008
19046
  }
19009
- var size = parseInt(parts.shift());
19047
+ var size = parseNum(parts.shift());
19010
19048
  var bg = parts.pop();
19011
- var spacing = parseInt(parts.pop());
19049
+ var spacing = parseNum(parts.pop());
19012
19050
  while (parts.length > 0) {
19013
19051
  colors.push(parts.shift());
19014
19052
  }
@@ -19026,6 +19064,12 @@
19026
19064
  };
19027
19065
  }
19028
19066
 
19067
+ function parseNum(str) {
19068
+ // return parseNum(str);
19069
+ // support sub-pixel sizes
19070
+ return parseFloat(str) || 0;
19071
+ }
19072
+
19029
19073
  function splitPattern(str) {
19030
19074
  // split apart space and comma-delimited tokens
19031
19075
  // ... but don't split rgb(...) colors
@@ -20116,7 +20160,7 @@
20116
20160
 
20117
20161
  function fitDatasetToFrame(dataset, frame, opts) {
20118
20162
  var bounds = new Bounds(frame.bbox);
20119
- var bounds2 = new Bounds(0, 0, frame.width, frame.height);
20163
+ var bounds2 = frame.bbox2 ? new Bounds(frame.bbox2) : new Bounds(0, 0, frame.width, frame.height);
20120
20164
  var fwd = bounds.getTransform(bounds2, opts.invert_y);
20121
20165
  transformPoints(dataset, function(x, y) {
20122
20166
  return fwd.transform(x, y);
@@ -24502,6 +24546,10 @@ ${svg}
24502
24546
  .option('geojson-type', {
24503
24547
  describe: '[GeoJSON] FeatureCollection, GeometryCollection or Feature'
24504
24548
  })
24549
+ .option('no-null-props', {
24550
+ describe: '[GeoJSON] use "properties":{} when a Feature has no data',
24551
+ type: 'flag'
24552
+ })
24505
24553
  .option('hoist', {
24506
24554
  describe: '[GeoJSON] move properties to the root level of each Feature',
24507
24555
  type: 'strings'
@@ -24593,6 +24641,10 @@ ${svg}
24593
24641
  type: 'numbers',
24594
24642
  describe: 'center of rotation/scaling (default is center of selected shapes)'
24595
24643
  })
24644
+ .option('fit-bbox', {
24645
+ type: 'bbox',
24646
+ describe: 'scale and shift coordinates to fit a bbox'
24647
+ })
24596
24648
  .option('where', whereOpt)
24597
24649
  .option('target', targetOpt);
24598
24650
 
@@ -26064,15 +26116,32 @@ ${svg}
26064
26116
  });
26065
26117
 
26066
26118
  parser.command('frame')
26067
- // .describe('create a map frame at a given size')
26119
+ .describe('create a rectangular map frame layer at a given display width')
26120
+ .option('width', {
26121
+ describe: 'width of frame (e.g. 5in, 10cm, 600px; default is 800px)'
26122
+ })
26123
+ .option('height', {
26124
+ describe: '(optional) height of frame; similar to width= option'
26125
+ })
26126
+ .option('aspect-ratio', {
26127
+ describe: '(optional) aspect ratio of frame, if height= or width= is omitted',
26128
+ type: 'number'
26129
+ })
26068
26130
  .option('bbox', {
26069
26131
  describe: 'frame coordinates (xmin,ymin,xmax,ymax)',
26070
26132
  type: 'bbox'
26071
26133
  })
26072
- // .option('offset', offsetOpt)
26073
- .option('width', {
26074
- describe: 'width of output (default is 800px)'
26134
+ .option('offset', {
26135
+ describe: 'padding in display units or pct of width, e.g. 5cm 20px 5%',
26136
+ type: 'strings'
26075
26137
  })
26138
+ .option('offsets', {
26139
+ describe: 'separate offsets for each side, in l,b,r,t order',
26140
+ type: 'strings'
26141
+ })
26142
+ .option('name', nameOpt)
26143
+ .option('target', targetOpt);
26144
+
26076
26145
  // .option('height', {
26077
26146
  // describe: 'pixel height of output (may be a range)'
26078
26147
  // })
@@ -26083,7 +26152,6 @@ ${svg}
26083
26152
  // .option('source', {
26084
26153
  // describe: 'name of layer to enclose'
26085
26154
  // })
26086
- .option('name', nameOpt);
26087
26155
 
26088
26156
  parser.command('fuzzy-join')
26089
26157
  .describe('join points to polygons, with data fill and fuzzy match')
@@ -35780,7 +35848,10 @@ ${svg}
35780
35848
  getColorizerFunction: getColorizerFunction
35781
35849
  });
35782
35850
 
35783
- cmd.comment = function() {}; // no-op, so -comment doesn't trigger a parsing error
35851
+ cmd.comment = function(opts) {
35852
+ // TODO: print the comment in verbose mode
35853
+ // message('[comment]', opts.message);
35854
+ }; // no-op, so -comment doesn't trigger a parsing error
35784
35855
 
35785
35856
  cmd.dashlines = function(lyr, dataset, opts) {
35786
35857
  var crs = getDatasetCRS(dataset);
@@ -38158,46 +38229,637 @@ ${svg}
38158
38229
  return parsed[0];
38159
38230
  }
38160
38231
 
38161
- cmd.frame = function(catalog, source, opts) {
38162
- var size, bounds, tmp, dataset;
38163
- if (+opts.width > 0 === false && +opts.pixels > 0 === false) {
38164
- stop("Missing a width or area");
38165
- }
38166
- if (opts.width && opts.height) {
38167
- opts = utils.extend({}, opts);
38168
- // Height is a string containing either a number or a
38169
- // comma-sep. pair of numbers (range); here we convert height to
38170
- // an aspect-ratio parameter for the rectangle() function
38171
- opts.aspect_ratio = getAspectRatioArg(opts.width, opts.height);
38172
- // TODO: currently returns max,min aspect ratio, should return in min,max order
38173
- // (rectangle() function should handle max,min argument correctly now anyway)
38174
- }
38175
- tmp = cmd.rectangle(source, opts);
38176
- bounds = getDatasetBounds(tmp);
38177
- if (probablyDecimalDegreeBounds(bounds)) {
38178
- stop('Frames require projected, not geographical coordinates');
38179
- } else if (!getDatasetCRS(tmp)) {
38180
- message('Warning: missing projection data. Assuming coordinates are meters and k (scale factor) is 1');
38181
- }
38182
- size = getFrameSize(bounds, opts);
38183
- if (size[0] > 0 === false) {
38184
- stop('Missing a valid frame width');
38185
- }
38186
- if (size[1] > 0 === false) {
38187
- stop('Missing a valid frame height');
38188
- }
38189
- dataset = {info: {}, layers:[{
38190
- name: opts.name || 'frame',
38191
- data: new DataTable([{
38192
- width: size[0],
38193
- height: size[1],
38194
- bbox: bounds.toArray(),
38195
- type: 'frame'
38196
- }])
38197
- }]};
38198
- catalog.addDataset(dataset);
38232
+ // Returns number of arcs that were removed
38233
+ function editArcs(arcs, onPoint) {
38234
+ var nn2 = [],
38235
+ xx2 = [],
38236
+ yy2 = [],
38237
+ errors = 0,
38238
+ n;
38239
+
38240
+ arcs.forEach(function(arc, i) {
38241
+ editArc(arc, onPoint);
38242
+ });
38243
+ arcs.updateVertexData(nn2, xx2, yy2);
38244
+ return errors;
38245
+
38246
+ function append(p) {
38247
+ if (p) {
38248
+ xx2.push(p[0]);
38249
+ yy2.push(p[1]);
38250
+ n++;
38251
+ }
38252
+ }
38253
+
38254
+ function editArc(arc, cb) {
38255
+ var x, y, xp, yp, retn;
38256
+ var valid = true;
38257
+ var i = 0;
38258
+ n = 0;
38259
+ while (arc.hasNext()) {
38260
+ x = arc.x;
38261
+ y = arc.y;
38262
+ retn = cb(append, x, y, xp, yp, i++);
38263
+ if (retn === false) {
38264
+ valid = false;
38265
+ // assumes that it's ok for the arc iterator to be interrupted.
38266
+ break;
38267
+ }
38268
+ xp = x;
38269
+ yp = y;
38270
+ }
38271
+ if (valid && n == 1) {
38272
+ // only one valid point was added to this arc (invalid)
38273
+ // e.g. this could happen during reprojection.
38274
+ // making this arc empty
38275
+ // error("An invalid arc was created");
38276
+ message("An invalid arc was created");
38277
+ valid = false;
38278
+ }
38279
+ if (valid) {
38280
+ nn2.push(n);
38281
+ } else {
38282
+ // remove any points that were added for an invalid arc
38283
+ while (n-- > 0) {
38284
+ xx2.pop();
38285
+ yy2.pop();
38286
+ }
38287
+ nn2.push(0); // add empty arc (to preserve mapping from paths to arcs)
38288
+ errors++;
38289
+ }
38290
+ }
38291
+ }
38292
+
38293
+ function DatasetEditor(dataset) {
38294
+ var layers = [];
38295
+ var arcs = [];
38296
+
38297
+ this.done = function() {
38298
+ dataset.layers = layers;
38299
+ if (arcs.length) {
38300
+ dataset.arcs = new ArcCollection(arcs);
38301
+ buildTopology(dataset);
38302
+ }
38303
+ };
38304
+
38305
+ this.editLayer = function(lyr, cb) {
38306
+ var type = lyr.geometry_type;
38307
+ if (dataset.layers.indexOf(lyr) != layers.length) {
38308
+ error('Layer was edited out-of-order');
38309
+ }
38310
+ if (!type) {
38311
+ layers.push(lyr);
38312
+ return;
38313
+ }
38314
+ var shapes = lyr.shapes.map(function(shape, shpId) {
38315
+ var shape2 = [], retn, input;
38316
+ for (var i=0, n=shape ? shape.length : 0; i<n; i++) {
38317
+ input = type == 'point' ? shape[i] : idsToCoords(shape[i]);
38318
+ retn = cb(input, i, shape);
38319
+ if (!Array.isArray(retn)) continue;
38320
+ if (type == 'point') {
38321
+ shape2.push(retn);
38322
+ } else if (type == 'polygon' || type == 'polyline') {
38323
+ extendPathShape(shape2, retn || []);
38324
+ }
38325
+ }
38326
+ return shape2.length > 0 ? shape2 : null;
38327
+ });
38328
+ layers.push(Object.assign(lyr, {shapes: shapes}));
38329
+ };
38330
+
38331
+ function extendPathShape(shape, parts) {
38332
+ for (var i=0; i<parts.length; i++) {
38333
+ shape.push([arcs.length]);
38334
+ arcs.push(parts[i]);
38335
+ }
38336
+ }
38337
+
38338
+ function idsToCoords(ids) {
38339
+ var coords = [];
38340
+ var iter = dataset.arcs.getShapeIter(ids);
38341
+ while (iter.hasNext()) {
38342
+ coords.push([iter.x, iter.y]);
38343
+ }
38344
+ return coords;
38345
+ }
38346
+ }
38347
+
38348
+ // Planar densification by an interval
38349
+ function densifyPathByInterval(coords, interval, interpolate) {
38350
+ if (findMaxPathInterval(coords) < interval) return coords;
38351
+ if (!interpolate) {
38352
+ interpolate = getIntervalInterpolator(interval);
38353
+ }
38354
+ var coords2 = [coords[0]], a, b;
38355
+ for (var i=1, n=coords.length; i<n; i++) {
38356
+ a = coords[i-1];
38357
+ b = coords[i];
38358
+ if (geom.distance2D(a[0], a[1], b[0], b[1]) > interval + 1e-4) {
38359
+ appendArr(coords2, interpolate(a, b));
38360
+ }
38361
+ coords2.push(b);
38362
+ }
38363
+ return coords2;
38364
+ }
38365
+
38366
+ function getIntervalInterpolator(interval) {
38367
+ return function(a, b) {
38368
+ var points = [];
38369
+ // var rev = a[0] == b[0] ? a[1] > b[1] : a[0] > b[0];
38370
+ var dist = geom.distance2D(a[0], a[1], b[0], b[1]);
38371
+ var n = Math.round(dist / interval) - 1;
38372
+ var dx = (b[0] - a[0]) / (n + 1),
38373
+ dy = (b[1] - a[1]) / (n + 1);
38374
+ for (var i=1; i<=n; i++) {
38375
+ points.push([a[0] + dx * i, a[1] + dy * i]);
38376
+ }
38377
+ return points;
38378
+ };
38379
+ }
38380
+
38381
+
38382
+ // Interpolate the same points regardless of segment direction
38383
+ function densifyAntimeridianSegment(a, b, interval) {
38384
+ var y1, y2;
38385
+ var coords = [];
38386
+ var ascending = a[1] < b[1];
38387
+ if (a[0] != b[0]) error('Expected an edge segment');
38388
+ if (interval > 0 === false) error('Expected a positive interval');
38389
+ if (ascending) {
38390
+ y1 = a[1];
38391
+ y2 = b[1];
38392
+ } else {
38393
+ y1 = b[1];
38394
+ y2 = a[1];
38395
+ }
38396
+ var y = Math.floor(y1 / interval) * interval + interval;
38397
+ while (y < y2) {
38398
+ coords.push([a[0], y]);
38399
+ y += interval;
38400
+ }
38401
+ if (!ascending) coords.reverse();
38402
+ return coords;
38403
+ }
38404
+
38405
+ function appendArr(dest, src) {
38406
+ for (var i=0; i<src.length; i++) dest.push(src[i]);
38407
+ }
38408
+
38409
+ function findMaxPathInterval(coords) {
38410
+ var maxSq = 0, intSq, a, b;
38411
+ for (var i=1, n=coords.length; i<n; i++) {
38412
+ a = coords[i-1];
38413
+ b = coords[i];
38414
+ intSq = geom.distanceSq(a[0], a[1], b[0], b[1]);
38415
+ if (intSq > maxSq) maxSq = intSq;
38416
+ }
38417
+ return Math.sqrt(maxSq);
38418
+ }
38419
+
38420
+ function projectAndDensifyArcs(arcs, proj) {
38421
+ var interval = getDefaultDensifyInterval(arcs, proj);
38422
+ var minIntervalSq = interval * interval * 25;
38423
+ var p;
38424
+ return editArcs(arcs, onPoint);
38425
+
38426
+ function onPoint(append, lng, lat, prevLng, prevLat, i) {
38427
+ var pp = p;
38428
+ p = proj(lng, lat);
38429
+ if (!p) return false; // signal that current arc contains an error
38430
+
38431
+ // Don't try to densify shorter segments (optimization)
38432
+ if (i > 0 && geom.distanceSq(p[0], p[1], pp[0], pp[1]) > minIntervalSq) {
38433
+ densifySegment(prevLng, prevLat, pp[0], pp[1], lng, lat, p[0], p[1], proj, interval)
38434
+ .forEach(append);
38435
+ }
38436
+ append(p);
38437
+ }
38438
+ }
38439
+
38440
+ // Use the median of intervals computed by projecting segments.
38441
+ // We're probing a number of points, because @proj might only be valid in
38442
+ // a sub-region of the dataset bbox (e.g. +proj=tpers)
38443
+ function findDensifyInterval(bounds, xy, proj) {
38444
+ var steps = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
38445
+ var points = [];
38446
+ for (var i=0; i<steps.length; i++) {
38447
+ for (var j=0; j<steps.length; j++) {
38448
+ points.push([steps[i], steps[j]]);
38449
+ }
38450
+ }
38451
+ var intervals = points.map(function(pos) {
38452
+ var x = bounds.xmin + bounds.width() * pos[0];
38453
+ var y = bounds.ymin + bounds.height() * pos[1];
38454
+ var a = proj(x, y);
38455
+ var b = proj(x + xy[0], y + xy[1]);
38456
+ return a && b ? geom.distance2D(a[0], a[1], b[0], b[1]) : Infinity;
38457
+ }).filter(function(int) {return int < Infinity;});
38458
+ return intervals.length > 0 ? utils.findMedian(intervals) : Infinity;
38459
+ }
38460
+
38461
+ // Kludgy way to get a useful interval for densifying a bounding box.
38462
+ // Uses a fraction of average bbox side length)
38463
+ // TODO: improve
38464
+ function findDensifyInterval2(bb, proj) {
38465
+ var a = proj(bb.centerX(), bb.centerY()),
38466
+ c = proj(bb.centerX(), bb.ymin), // right center
38467
+ d = proj(bb.xmax, bb.centerY()); // bottom center
38468
+ var interval = a && c && d ? (geom.distance2D(a[0], a[1], c[0], c[1]) +
38469
+ geom.distance2D(a[0], a[1], d[0], d[1])) / 5000 : Infinity;
38470
+ return interval;
38471
+ }
38472
+
38473
+ // Returns an interval in projected units
38474
+ function getDefaultDensifyInterval(arcs, proj) {
38475
+ var xy = getAvgSegment2(arcs),
38476
+ bb = arcs.getBounds(),
38477
+ intervalA = findDensifyInterval(bb, xy, proj),
38478
+ intervalB = findDensifyInterval2(bb, proj),
38479
+ interval = Math.min(intervalA, intervalB);
38480
+ if (interval == Infinity) {
38481
+ error('Densification error');
38482
+ }
38483
+ return interval;
38484
+ }
38485
+
38486
+ // Interpolate points into a projected line segment if needed to prevent large
38487
+ // deviations from path of original unprojected segment.
38488
+ // @points (optional) array of accumulated points
38489
+ function densifySegment(lng0, lat0, x0, y0, lng2, lat2, x2, y2, proj, interval, points) {
38490
+ // Find midpoint between two endpoints and project it (assumes longitude does
38491
+ // not wrap). TODO Consider bisecting along great circle path -- although this
38492
+ // would not be good for boundaries that follow line of constant latitude.
38493
+ var lng1 = (lng0 + lng2) / 2,
38494
+ lat1 = (lat0 + lat2) / 2,
38495
+ p = proj(lng1, lat1),
38496
+ distSq;
38497
+ if (!p) return; // TODO: consider if this is adequate for handling proj. errors
38498
+ distSq = geom.pointSegDistSq2(p[0], p[1], x0, y0, x2, y2); // sq displacement
38499
+ points = points || [];
38500
+ // Bisect current segment if the projected midpoint deviates from original
38501
+ // segment by more than the @interval parameter.
38502
+ // ... but don't bisect very small segments to prevent infinite recursion
38503
+ // (e.g. if projection function is discontinuous)
38504
+ if (distSq > interval * interval * 0.25 && geom.distance2D(lng0, lat0, lng2, lat2) > 0.01) {
38505
+ densifySegment(lng0, lat0, x0, y0, lng1, lat1, p[0], p[1], proj, interval, points);
38506
+ points.push(p);
38507
+ densifySegment(lng1, lat1, p[0], p[1], lng2, lat2, x2, y2, proj, interval, points);
38508
+ }
38509
+ return points;
38510
+ }
38511
+
38512
+ // Create rectangles around each feature in a layer
38513
+ cmd.rectangles = function(targetLyr, targetDataset, opts) {
38514
+ var crsInfo = getDatasetCrsInfo(targetDataset);
38515
+ var records = targetLyr.data ? targetLyr.data.getRecords() : null;
38516
+ var geometries;
38517
+
38518
+ if (opts.bbox) {
38519
+ geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset);
38520
+
38521
+ } else {
38522
+ if (!layerHasGeometry(targetLyr)) {
38523
+ stop("Layer is missing geometric shapes");
38524
+ }
38525
+ geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
38526
+ }
38527
+
38528
+ var geojson = {
38529
+ type: 'FeatureCollection',
38530
+ features: geometries.map(function(geom, i) {
38531
+ var rec = records && records[i] || null;
38532
+ if (rec && opts.no_replace) {
38533
+ rec = utils.extend({}, rec); // make a copy
38534
+ }
38535
+ return {
38536
+ type: 'Feature',
38537
+ properties: rec,
38538
+ geometry: geom
38539
+ };
38540
+ })
38541
+ };
38542
+ var dataset = importGeoJSON(geojson, {});
38543
+ setDatasetCrsInfo(dataset, crsInfo);
38544
+ var outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
38545
+ setOutputLayerName(outputLayers[0], targetLyr, null, opts);
38546
+ return outputLayers;
38547
+ };
38548
+
38549
+
38550
+
38551
+
38552
+ function shapesToBoxGeometries(lyr, dataset, opts) {
38553
+ var crsInfo = getDatasetCrsInfo(dataset);
38554
+ return lyr.shapes.map(function(shp) {
38555
+ var bounds = lyr.geometry_type == 'point' ?
38556
+ getPointFeatureBounds(shp) : dataset.arcs.getMultiShapeBounds(shp);
38557
+ bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
38558
+ if (!bounds) return null;
38559
+ return bboxToPolygon(bounds.toArray(), opts);
38560
+ });
38561
+ }
38562
+
38563
+ function bboxExpressionToGeometries(exp, lyr, dataset, opts) {
38564
+ var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, {});
38565
+ var n = getFeatureCount(lyr);
38566
+ var result;
38567
+ var geometries = [];
38568
+ for (var i=0; i<n; i++) {
38569
+ result = compiled(i);
38570
+ if (!looksLikeBbox(result)) {
38571
+ stop('Invalid bbox value (expected a GeoJSON-type bbox):', result);
38572
+ }
38573
+ geometries.push(bboxToPolygon(result));
38574
+ }
38575
+ return geometries;
38576
+ }
38577
+
38578
+ function looksLikeBbox(o) {
38579
+ if (!o || o.length != 4) return false;
38580
+ if (o.some(isNaN)) return false;
38581
+ if (o[0] <= o[2] == false || o[1] <= o[3] == false) return false;
38582
+ return true;
38583
+ }
38584
+
38585
+ // Create rectangles around one or more target layers
38586
+ //
38587
+ cmd.rectangle2 = function(target, opts) {
38588
+ // if target layer is a rectangle and we're applying frame properties,
38589
+ // turn the target into a frame instead of creating a new rectangle
38590
+ if (target.layers.length == 1 && opts.width &&
38591
+ layerIsRectangle(target.layers[0], target.dataset.arcs)) {
38592
+ applyFrameProperties(target.layers[0], opts);
38593
+ return;
38594
+ }
38595
+ var datasets = target.layers.map(function(lyr) {
38596
+ var dataset = cmd.rectangle({layer: lyr, dataset: target.dataset}, opts);
38597
+ setOutputLayerName(dataset.layers[0], lyr, null, opts);
38598
+ if (!opts.no_replace) {
38599
+ dataset.layers[0].name = lyr.name || dataset.layers[0].name;
38600
+ }
38601
+ return dataset;
38602
+ });
38603
+ return mergeDatasetsIntoDataset(target.dataset, datasets);
38604
+ };
38605
+
38606
+ cmd.rectangle = function(target, opts) {
38607
+ var bounds, crsInfo;
38608
+ if (opts.bbox) {
38609
+ bounds = new Bounds(opts.bbox);
38610
+ crsInfo = target && getDatasetCrsInfo(target.dataset) ||
38611
+ probablyDecimalDegreeBounds(bounds) && getCrsInfo('wgs84') || {};
38612
+ } else if (target) {
38613
+ bounds = getLayerBounds(target.layer, target.dataset.arcs);
38614
+ crsInfo = getDatasetCrsInfo(target.dataset);
38615
+ }
38616
+ bounds = bounds && applyRectangleOptions(bounds, crsInfo.crs, opts);
38617
+ if (!bounds || !bounds.hasBounds()) {
38618
+ stop('Missing rectangle extent');
38619
+ }
38620
+ var feature = {
38621
+ type: 'Feature',
38622
+ properties: {},
38623
+ geometry: bboxToPolygon(bounds.toArray(), opts)
38624
+ };
38625
+ var dataset = importGeoJSON(feature, {});
38626
+ applyFrameProperties(dataset.layers[0], opts);
38627
+ dataset.layers[0].name = opts.name || 'rectangle';
38628
+ setDatasetCrsInfo(dataset, crsInfo);
38629
+ return dataset;
38199
38630
  };
38200
38631
 
38632
+ function applyFrameProperties(lyr, opts) {
38633
+ if (!opts.width) return;
38634
+ if (!lyr.data) initDataTable(lyr);
38635
+ var d = lyr.data.getRecords()[0] || {};
38636
+ d.width = parseSizeParam(opts.width);
38637
+ d.type = 'frame';
38638
+ }
38639
+
38640
+ function applyRectangleOptions(bounds, crs, opts) {
38641
+ var isGeoBox = probablyDecimalDegreeBounds(bounds);
38642
+ if (opts.offset) {
38643
+ bounds = applyBoundsOffset(opts.offset, bounds, crs);
38644
+ }
38645
+ if (bounds.area() > 0 === false) return null;
38646
+ if (opts.aspect_ratio) {
38647
+ bounds = applyAspectRatio(opts.aspect_ratio, bounds);
38648
+ }
38649
+ if (isGeoBox) {
38650
+ bounds = clampToWorldBounds(bounds);
38651
+ }
38652
+ return bounds;
38653
+ }
38654
+
38655
+ // opt: aspect ratio as a single number or a range (e.g. "1,2");
38656
+ function applyAspectRatio(opt, bounds) {
38657
+ var range = String(opt).split(',').map(parseFloat),
38658
+ aspectRatio = bounds.width() / bounds.height(),
38659
+ min, max; // min is height limit, max is width limit
38660
+ if (range.length == 1) {
38661
+ range.push(range[0]);
38662
+ } else if (range[0] > range[1]) {
38663
+ range.reverse();
38664
+ }
38665
+ min = range[0];
38666
+ max = range[1];
38667
+ if (!min && !max) return bounds;
38668
+ if (!min) min = -Infinity;
38669
+ if (!max) max = Infinity;
38670
+ if (aspectRatio < min) {
38671
+ bounds.fillOut(min);
38672
+ } else if (aspectRatio > max) {
38673
+ bounds.fillOut(max);
38674
+ }
38675
+ return bounds;
38676
+ }
38677
+
38678
+ function applyBoundsOffset(offsetOpt, bounds, crs) {
38679
+ var offsets = convertFourSides(offsetOpt, crs, bounds);
38680
+ bounds.padBounds(offsets[0], offsets[1], offsets[2], offsets[3]);
38681
+ return bounds;
38682
+ }
38683
+
38684
+ function bboxToPolygon(bbox, optsArg) {
38685
+ var opts = optsArg || {};
38686
+ var coords = bboxToCoords(bbox);
38687
+ if (opts.interval > 0) {
38688
+ coords = densifyPathByInterval(coords, opts.interval);
38689
+ }
38690
+ return {
38691
+ type: 'Polygon',
38692
+ coordinates: [coords]
38693
+ };
38694
+ }
38695
+
38696
+ var Rectangle = /*#__PURE__*/Object.freeze({
38697
+ __proto__: null,
38698
+ applyAspectRatio: applyAspectRatio,
38699
+ bboxToPolygon: bboxToPolygon
38700
+ });
38701
+
38702
+ cmd.frame = function(catalog, targets, opts) {
38703
+ var widthPx, heightPx, aspectRatio, bbox;
38704
+ if (opts.width) {
38705
+ widthPx = parseSizeParam(opts.width);
38706
+ if (widthPx > 0 === false) {
38707
+ stop('Invalid width parameter:', opts.width);
38708
+ }
38709
+ }
38710
+ if (opts.height) {
38711
+ heightPx = parseSizeParam(opts.height);
38712
+ if (heightPx > 0 === false) {
38713
+ stop('Invalid height parameter:', opts.height);
38714
+ }
38715
+ }
38716
+ if (!widthPx && !heightPx) {
38717
+ widthPx = 800;
38718
+ message('Using default 800px frame width');
38719
+ }
38720
+
38721
+ if (opts.aspect_ratio) {
38722
+ if (opts.aspect_ratio > 0 === false) {
38723
+ stop('Invalid aspect-ratio parameter:', opts.aspect_ratio);
38724
+ }
38725
+ if (!heightPx) {
38726
+ heightPx = roundToDigits(widthPx / opts.aspect_ratio, 1);
38727
+ } else if (!widthPx) {
38728
+ widthPx = roundToDigits(heightPx * opts.aspect_ratio, 1);
38729
+ }
38730
+ }
38731
+
38732
+ if (opts.bbox) {
38733
+ bbox = opts.bbox;
38734
+ // TODO: validate
38735
+ } else {
38736
+ var datasets = utils.pluck(targets, 'dataset');
38737
+ requireDatasetsHaveCompatibleCRS(datasets, 'Targets include both projected and unprojected coordinates');
38738
+ bbox = getTargetBbox(targets);
38739
+ if (!bbox) {
38740
+ stop('Command target is missing geographical bounds');
38741
+ }
38742
+ }
38743
+
38744
+ applyPercentageOffsets(bbox, opts.offset || opts.offsets);
38745
+ applyPixelOffsets(bbox, widthPx, heightPx, opts.offset || opts.offsets);
38746
+
38747
+ if (bbox[3] - bbox[1] > 0 === false || bbox[2] - bbox[0] > 0 === false) {
38748
+ stop('Frame has a collapsed bbox');
38749
+ }
38750
+
38751
+ aspectRatio = (bbox[2] - bbox[0]) / (bbox[3] - bbox[1]);
38752
+ if (!widthPx) {
38753
+ widthPx = roundToDigits(heightPx * aspectRatio, 1);
38754
+ } else if (!heightPx) {
38755
+ heightPx = roundToDigits(widthPx / aspectRatio, 1);
38756
+ }
38757
+
38758
+ var feature = {
38759
+ type: 'Feature',
38760
+ properties: {type: 'frame', width: widthPx, height: heightPx},
38761
+ geometry: bboxToPolygon(bbox)
38762
+ };
38763
+ var frameDataset = importGeoJSON(feature);
38764
+ // set CRS from target dataset
38765
+ // TODO: handle case: targets have different projections
38766
+ // TODO: handle case: first target is missing CRS
38767
+ if (targets.length > 0) {
38768
+ var crsInfo = getDatasetCrsInfo(targets[0].dataset);
38769
+ setDatasetCrsInfo(frameDataset, crsInfo);
38770
+ }
38771
+ frameDataset.layers[0].name = opts.name || 'frame';
38772
+ catalog.addDataset(frameDataset);
38773
+ };
38774
+
38775
+ function fillOutBbox(bbox, widthPx, heightPx) {
38776
+ var hpad = 0, vpad = 0;
38777
+ var w = bbox[2] - bbox[0];
38778
+ var h = bbox[3] - bbox[1];
38779
+ if (widthPx / heightPx > w / h) { // need to add horizontal padding
38780
+ hpad = h * widthPx / heightPx - w;
38781
+ } else {
38782
+ vpad = w * heightPx / widthPx - h;
38783
+ }
38784
+ bbox[0] -= hpad / 2;
38785
+ bbox[1] -= vpad / 2;
38786
+ bbox[2] += hpad / 2;
38787
+ bbox[3] += vpad / 2;
38788
+ }
38789
+
38790
+ function applyPercentageOffsets(bbox, arg) {
38791
+ var sides = getPctOffsets(arg);
38792
+ var l = sides[0],
38793
+ b = sides[1],
38794
+ r = sides[2],
38795
+ t = sides[3],
38796
+ w2 = (bbox[2] - bbox[0]) / (1 - l - r),
38797
+ h2 = (bbox[3] - bbox[1]) / (1 - t - b);
38798
+ bbox[0] -= l * w2;
38799
+ bbox[1] -= b * h2;
38800
+ bbox[2] += r * w2;
38801
+ bbox[3] += t * h2;
38802
+ }
38803
+
38804
+ function applyPixelOffsets(bbox, widthPx, heightPx, arg) {
38805
+ var sides = getPixelOffsets(arg);
38806
+ var l = sides[0],
38807
+ b = sides[1],
38808
+ r = sides[2],
38809
+ t = sides[3],
38810
+ scale, w;
38811
+
38812
+ if (widthPx && heightPx) {
38813
+ // add padding to bbox to match pixel dimensions, if needed
38814
+ fillOutBbox(bbox, widthPx, heightPx);
38815
+ }
38816
+
38817
+ w = bbox[2] - bbox[0];
38818
+ bbox[3] - bbox[1];
38819
+
38820
+ if (widthPx) {
38821
+ scale = w / (widthPx - l - r);
38822
+ } else {
38823
+ scale = w / (heightPx - t - b);
38824
+ }
38825
+
38826
+ bbox[0] -= scale * l;
38827
+ bbox[1] -= scale * b;
38828
+ bbox[2] += scale * r;
38829
+ bbox[3] += scale * t;
38830
+ return scale;
38831
+ }
38832
+
38833
+ function getPctOffsets(arg) {
38834
+ return adjustOffsetsArg(arg).map(str => {
38835
+ return str.includes('%') ? utils.parsePercent(str) : 0;
38836
+ });
38837
+ }
38838
+
38839
+ function getPixelOffsets(arg) {
38840
+ return adjustOffsetsArg(arg).map(str => {
38841
+ return str.includes('%') ? 0 : parseSizeParam(str);
38842
+ });
38843
+ }
38844
+
38845
+ function adjustOffsetsArg(arg) {
38846
+ if (!arg) arg = ['0'];
38847
+ if (arg.length == 1) {
38848
+ return [arg[0], arg[0], arg[0], arg[0]];
38849
+ }
38850
+ if (arg.length != 4) {
38851
+ stop('List of offsets should have 4 values');
38852
+ }
38853
+ return arg;
38854
+ }
38855
+
38856
+ function getTargetBbox(targets) {
38857
+ var expanded = expandCommandTargets(targets);
38858
+ var bounds = expanded.reduce(function(memo, o) {
38859
+ return memo.mergeBounds(getLayerBounds(o.layer, o.dataset.arcs));
38860
+ }, new Bounds());
38861
+ return bounds.hasBounds() ? bounds.toArray() : null;
38862
+ }
38201
38863
 
38202
38864
  // Convert width and height args to aspect ratio arg for the rectangle() function
38203
38865
  function getAspectRatioArg(widthArg, heightArg) {
@@ -38211,21 +38873,6 @@ ${svg}
38211
38873
  }).reverse().join(',');
38212
38874
  }
38213
38875
 
38214
- // export function renderFrame(d) {
38215
- // var lineWidth = 1,
38216
- // // inset stroke by half of line width
38217
- // off = lineWidth / 2,
38218
- // obj = importPolygon([[[off, off], [off, d.height - off],
38219
- // [d.width - off, d.height - off],
38220
- // [d.width - off, off], [off, off]]]);
38221
- // utils.extend(obj.properties, {
38222
- // fill: 'none',
38223
- // stroke: d.stroke || 'black',
38224
- // 'stroke-width': d['stroke-width'] || lineWidth
38225
- // });
38226
- // return [obj];
38227
- // }
38228
-
38229
38876
  var Frame = /*#__PURE__*/Object.freeze({
38230
38877
  __proto__: null,
38231
38878
  getAspectRatioArg: getAspectRatioArg
@@ -38679,473 +39326,6 @@ ${svg}
38679
39326
  return maxValue;
38680
39327
  }
38681
39328
 
38682
- // Returns number of arcs that were removed
38683
- function editArcs(arcs, onPoint) {
38684
- var nn2 = [],
38685
- xx2 = [],
38686
- yy2 = [],
38687
- errors = 0,
38688
- n;
38689
-
38690
- arcs.forEach(function(arc, i) {
38691
- editArc(arc, onPoint);
38692
- });
38693
- arcs.updateVertexData(nn2, xx2, yy2);
38694
- return errors;
38695
-
38696
- function append(p) {
38697
- if (p) {
38698
- xx2.push(p[0]);
38699
- yy2.push(p[1]);
38700
- n++;
38701
- }
38702
- }
38703
-
38704
- function editArc(arc, cb) {
38705
- var x, y, xp, yp, retn;
38706
- var valid = true;
38707
- var i = 0;
38708
- n = 0;
38709
- while (arc.hasNext()) {
38710
- x = arc.x;
38711
- y = arc.y;
38712
- retn = cb(append, x, y, xp, yp, i++);
38713
- if (retn === false) {
38714
- valid = false;
38715
- // assumes that it's ok for the arc iterator to be interrupted.
38716
- break;
38717
- }
38718
- xp = x;
38719
- yp = y;
38720
- }
38721
- if (valid && n == 1) {
38722
- // only one valid point was added to this arc (invalid)
38723
- // e.g. this could happen during reprojection.
38724
- // making this arc empty
38725
- // error("An invalid arc was created");
38726
- message("An invalid arc was created");
38727
- valid = false;
38728
- }
38729
- if (valid) {
38730
- nn2.push(n);
38731
- } else {
38732
- // remove any points that were added for an invalid arc
38733
- while (n-- > 0) {
38734
- xx2.pop();
38735
- yy2.pop();
38736
- }
38737
- nn2.push(0); // add empty arc (to preserve mapping from paths to arcs)
38738
- errors++;
38739
- }
38740
- }
38741
- }
38742
-
38743
- function DatasetEditor(dataset) {
38744
- var layers = [];
38745
- var arcs = [];
38746
-
38747
- this.done = function() {
38748
- dataset.layers = layers;
38749
- if (arcs.length) {
38750
- dataset.arcs = new ArcCollection(arcs);
38751
- buildTopology(dataset);
38752
- }
38753
- };
38754
-
38755
- this.editLayer = function(lyr, cb) {
38756
- var type = lyr.geometry_type;
38757
- if (dataset.layers.indexOf(lyr) != layers.length) {
38758
- error('Layer was edited out-of-order');
38759
- }
38760
- if (!type) {
38761
- layers.push(lyr);
38762
- return;
38763
- }
38764
- var shapes = lyr.shapes.map(function(shape, shpId) {
38765
- var shape2 = [], retn, input;
38766
- for (var i=0, n=shape ? shape.length : 0; i<n; i++) {
38767
- input = type == 'point' ? shape[i] : idsToCoords(shape[i]);
38768
- retn = cb(input, i, shape);
38769
- if (!Array.isArray(retn)) continue;
38770
- if (type == 'point') {
38771
- shape2.push(retn);
38772
- } else if (type == 'polygon' || type == 'polyline') {
38773
- extendPathShape(shape2, retn || []);
38774
- }
38775
- }
38776
- return shape2.length > 0 ? shape2 : null;
38777
- });
38778
- layers.push(Object.assign(lyr, {shapes: shapes}));
38779
- };
38780
-
38781
- function extendPathShape(shape, parts) {
38782
- for (var i=0; i<parts.length; i++) {
38783
- shape.push([arcs.length]);
38784
- arcs.push(parts[i]);
38785
- }
38786
- }
38787
-
38788
- function idsToCoords(ids) {
38789
- var coords = [];
38790
- var iter = dataset.arcs.getShapeIter(ids);
38791
- while (iter.hasNext()) {
38792
- coords.push([iter.x, iter.y]);
38793
- }
38794
- return coords;
38795
- }
38796
- }
38797
-
38798
- // Planar densification by an interval
38799
- function densifyPathByInterval(coords, interval, interpolate) {
38800
- if (findMaxPathInterval(coords) < interval) return coords;
38801
- if (!interpolate) {
38802
- interpolate = getIntervalInterpolator(interval);
38803
- }
38804
- var coords2 = [coords[0]], a, b;
38805
- for (var i=1, n=coords.length; i<n; i++) {
38806
- a = coords[i-1];
38807
- b = coords[i];
38808
- if (geom.distance2D(a[0], a[1], b[0], b[1]) > interval + 1e-4) {
38809
- appendArr(coords2, interpolate(a, b));
38810
- }
38811
- coords2.push(b);
38812
- }
38813
- return coords2;
38814
- }
38815
-
38816
- function getIntervalInterpolator(interval) {
38817
- return function(a, b) {
38818
- var points = [];
38819
- // var rev = a[0] == b[0] ? a[1] > b[1] : a[0] > b[0];
38820
- var dist = geom.distance2D(a[0], a[1], b[0], b[1]);
38821
- var n = Math.round(dist / interval) - 1;
38822
- var dx = (b[0] - a[0]) / (n + 1),
38823
- dy = (b[1] - a[1]) / (n + 1);
38824
- for (var i=1; i<=n; i++) {
38825
- points.push([a[0] + dx * i, a[1] + dy * i]);
38826
- }
38827
- return points;
38828
- };
38829
- }
38830
-
38831
-
38832
- // Interpolate the same points regardless of segment direction
38833
- function densifyAntimeridianSegment(a, b, interval) {
38834
- var y1, y2;
38835
- var coords = [];
38836
- var ascending = a[1] < b[1];
38837
- if (a[0] != b[0]) error('Expected an edge segment');
38838
- if (interval > 0 === false) error('Expected a positive interval');
38839
- if (ascending) {
38840
- y1 = a[1];
38841
- y2 = b[1];
38842
- } else {
38843
- y1 = b[1];
38844
- y2 = a[1];
38845
- }
38846
- var y = Math.floor(y1 / interval) * interval + interval;
38847
- while (y < y2) {
38848
- coords.push([a[0], y]);
38849
- y += interval;
38850
- }
38851
- if (!ascending) coords.reverse();
38852
- return coords;
38853
- }
38854
-
38855
- function appendArr(dest, src) {
38856
- for (var i=0; i<src.length; i++) dest.push(src[i]);
38857
- }
38858
-
38859
- function findMaxPathInterval(coords) {
38860
- var maxSq = 0, intSq, a, b;
38861
- for (var i=1, n=coords.length; i<n; i++) {
38862
- a = coords[i-1];
38863
- b = coords[i];
38864
- intSq = geom.distanceSq(a[0], a[1], b[0], b[1]);
38865
- if (intSq > maxSq) maxSq = intSq;
38866
- }
38867
- return Math.sqrt(maxSq);
38868
- }
38869
-
38870
- function projectAndDensifyArcs(arcs, proj) {
38871
- var interval = getDefaultDensifyInterval(arcs, proj);
38872
- var minIntervalSq = interval * interval * 25;
38873
- var p;
38874
- return editArcs(arcs, onPoint);
38875
-
38876
- function onPoint(append, lng, lat, prevLng, prevLat, i) {
38877
- var pp = p;
38878
- p = proj(lng, lat);
38879
- if (!p) return false; // signal that current arc contains an error
38880
-
38881
- // Don't try to densify shorter segments (optimization)
38882
- if (i > 0 && geom.distanceSq(p[0], p[1], pp[0], pp[1]) > minIntervalSq) {
38883
- densifySegment(prevLng, prevLat, pp[0], pp[1], lng, lat, p[0], p[1], proj, interval)
38884
- .forEach(append);
38885
- }
38886
- append(p);
38887
- }
38888
- }
38889
-
38890
- // Use the median of intervals computed by projecting segments.
38891
- // We're probing a number of points, because @proj might only be valid in
38892
- // a sub-region of the dataset bbox (e.g. +proj=tpers)
38893
- function findDensifyInterval(bounds, xy, proj) {
38894
- var steps = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
38895
- var points = [];
38896
- for (var i=0; i<steps.length; i++) {
38897
- for (var j=0; j<steps.length; j++) {
38898
- points.push([steps[i], steps[j]]);
38899
- }
38900
- }
38901
- var intervals = points.map(function(pos) {
38902
- var x = bounds.xmin + bounds.width() * pos[0];
38903
- var y = bounds.ymin + bounds.height() * pos[1];
38904
- var a = proj(x, y);
38905
- var b = proj(x + xy[0], y + xy[1]);
38906
- return a && b ? geom.distance2D(a[0], a[1], b[0], b[1]) : Infinity;
38907
- }).filter(function(int) {return int < Infinity;});
38908
- return intervals.length > 0 ? utils.findMedian(intervals) : Infinity;
38909
- }
38910
-
38911
- // Kludgy way to get a useful interval for densifying a bounding box.
38912
- // Uses a fraction of average bbox side length)
38913
- // TODO: improve
38914
- function findDensifyInterval2(bb, proj) {
38915
- var a = proj(bb.centerX(), bb.centerY()),
38916
- c = proj(bb.centerX(), bb.ymin), // right center
38917
- d = proj(bb.xmax, bb.centerY()); // bottom center
38918
- var interval = a && c && d ? (geom.distance2D(a[0], a[1], c[0], c[1]) +
38919
- geom.distance2D(a[0], a[1], d[0], d[1])) / 5000 : Infinity;
38920
- return interval;
38921
- }
38922
-
38923
- // Returns an interval in projected units
38924
- function getDefaultDensifyInterval(arcs, proj) {
38925
- var xy = getAvgSegment2(arcs),
38926
- bb = arcs.getBounds(),
38927
- intervalA = findDensifyInterval(bb, xy, proj),
38928
- intervalB = findDensifyInterval2(bb, proj),
38929
- interval = Math.min(intervalA, intervalB);
38930
- if (interval == Infinity) {
38931
- error('Densification error');
38932
- }
38933
- return interval;
38934
- }
38935
-
38936
- // Interpolate points into a projected line segment if needed to prevent large
38937
- // deviations from path of original unprojected segment.
38938
- // @points (optional) array of accumulated points
38939
- function densifySegment(lng0, lat0, x0, y0, lng2, lat2, x2, y2, proj, interval, points) {
38940
- // Find midpoint between two endpoints and project it (assumes longitude does
38941
- // not wrap). TODO Consider bisecting along great circle path -- although this
38942
- // would not be good for boundaries that follow line of constant latitude.
38943
- var lng1 = (lng0 + lng2) / 2,
38944
- lat1 = (lat0 + lat2) / 2,
38945
- p = proj(lng1, lat1),
38946
- distSq;
38947
- if (!p) return; // TODO: consider if this is adequate for handling proj. errors
38948
- distSq = geom.pointSegDistSq2(p[0], p[1], x0, y0, x2, y2); // sq displacement
38949
- points = points || [];
38950
- // Bisect current segment if the projected midpoint deviates from original
38951
- // segment by more than the @interval parameter.
38952
- // ... but don't bisect very small segments to prevent infinite recursion
38953
- // (e.g. if projection function is discontinuous)
38954
- if (distSq > interval * interval * 0.25 && geom.distance2D(lng0, lat0, lng2, lat2) > 0.01) {
38955
- densifySegment(lng0, lat0, x0, y0, lng1, lat1, p[0], p[1], proj, interval, points);
38956
- points.push(p);
38957
- densifySegment(lng1, lat1, p[0], p[1], lng2, lat2, x2, y2, proj, interval, points);
38958
- }
38959
- return points;
38960
- }
38961
-
38962
- // Create rectangles around each feature in a layer
38963
- cmd.rectangles = function(targetLyr, targetDataset, opts) {
38964
- var crsInfo = getDatasetCrsInfo(targetDataset);
38965
- var records = targetLyr.data ? targetLyr.data.getRecords() : null;
38966
- var geometries;
38967
-
38968
- if (opts.bbox) {
38969
- geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset);
38970
-
38971
- } else {
38972
- if (!layerHasGeometry(targetLyr)) {
38973
- stop("Layer is missing geometric shapes");
38974
- }
38975
- geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
38976
- }
38977
-
38978
- var geojson = {
38979
- type: 'FeatureCollection',
38980
- features: geometries.map(function(geom, i) {
38981
- var rec = records && records[i] || null;
38982
- if (rec && opts.no_replace) {
38983
- rec = utils.extend({}, rec); // make a copy
38984
- }
38985
- return {
38986
- type: 'Feature',
38987
- properties: rec,
38988
- geometry: geom
38989
- };
38990
- })
38991
- };
38992
- var dataset = importGeoJSON(geojson, {});
38993
- setDatasetCrsInfo(dataset, crsInfo);
38994
- var outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
38995
- setOutputLayerName(outputLayers[0], targetLyr, null, opts);
38996
- return outputLayers;
38997
- };
38998
-
38999
- function shapesToBoxGeometries(lyr, dataset, opts) {
39000
- var crsInfo = getDatasetCrsInfo(dataset);
39001
- return lyr.shapes.map(function(shp) {
39002
- var bounds = lyr.geometry_type == 'point' ?
39003
- getPointFeatureBounds(shp) : dataset.arcs.getMultiShapeBounds(shp);
39004
- bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
39005
- if (!bounds) return null;
39006
- return bboxToPolygon(bounds.toArray(), opts);
39007
- });
39008
- }
39009
-
39010
- function bboxExpressionToGeometries(exp, lyr, dataset, opts) {
39011
- var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, {});
39012
- var n = getFeatureCount(lyr);
39013
- var result;
39014
- var geometries = [];
39015
- for (var i=0; i<n; i++) {
39016
- result = compiled(i);
39017
- if (!looksLikeBbox(result)) {
39018
- stop('Invalid bbox value (expected a GeoJSON-type bbox):', result);
39019
- }
39020
- geometries.push(bboxToPolygon(result));
39021
- }
39022
- return geometries;
39023
- }
39024
-
39025
- function looksLikeBbox(o) {
39026
- if (!o || o.length != 4) return false;
39027
- if (o.some(isNaN)) return false;
39028
- if (o[0] <= o[2] == false || o[1] <= o[3] == false) return false;
39029
- return true;
39030
- }
39031
-
39032
- // Create rectangles around one or more target layers
39033
- //
39034
- cmd.rectangle2 = function(target, opts) {
39035
- // if target layer is a rectangle and we're applying frame properties,
39036
- // turn the target into a frame instead of creating a new rectangle
39037
- if (target.layers.length == 1 && opts.width &&
39038
- layerIsRectangle(target.layers[0], target.dataset.arcs)) {
39039
- applyFrameProperties(target.layers[0], opts);
39040
- return;
39041
- }
39042
- var datasets = target.layers.map(function(lyr) {
39043
- var dataset = cmd.rectangle({layer: lyr, dataset: target.dataset}, opts);
39044
- setOutputLayerName(dataset.layers[0], lyr, null, opts);
39045
- if (!opts.no_replace) {
39046
- dataset.layers[0].name = lyr.name || dataset.layers[0].name;
39047
- }
39048
- return dataset;
39049
- });
39050
- return mergeDatasetsIntoDataset(target.dataset, datasets);
39051
- };
39052
-
39053
- cmd.rectangle = function(target, opts) {
39054
- var bounds, crsInfo;
39055
- if (opts.bbox) {
39056
- bounds = new Bounds(opts.bbox);
39057
- crsInfo = target && getDatasetCrsInfo(target.dataset) ||
39058
- probablyDecimalDegreeBounds(bounds) && getCrsInfo('wgs84') || {};
39059
- } else if (target) {
39060
- bounds = getLayerBounds(target.layer, target.dataset.arcs);
39061
- crsInfo = getDatasetCrsInfo(target.dataset);
39062
- }
39063
- bounds = bounds && applyRectangleOptions(bounds, crsInfo.crs, opts);
39064
- if (!bounds || !bounds.hasBounds()) {
39065
- stop('Missing rectangle extent');
39066
- }
39067
- var feature = {
39068
- type: 'Feature',
39069
- properties: {},
39070
- geometry: bboxToPolygon(bounds.toArray(), opts)
39071
- };
39072
- var dataset = importGeoJSON(feature, {});
39073
- applyFrameProperties(dataset.layers[0], opts);
39074
- dataset.layers[0].name = opts.name || 'rectangle';
39075
- setDatasetCrsInfo(dataset, crsInfo);
39076
- return dataset;
39077
- };
39078
-
39079
- function applyFrameProperties(lyr, opts) {
39080
- if (!opts.width) return;
39081
- if (!lyr.data) initDataTable(lyr);
39082
- var d = lyr.data.getRecords()[0] || {};
39083
- d.width = parseSizeParam(opts.width);
39084
- d.type = 'frame';
39085
- }
39086
-
39087
- function applyRectangleOptions(bounds, crs, opts) {
39088
- var isGeoBox = probablyDecimalDegreeBounds(bounds);
39089
- if (opts.offset) {
39090
- bounds = applyBoundsOffset(opts.offset, bounds, crs);
39091
- }
39092
- if (bounds.area() > 0 === false) return null;
39093
- if (opts.aspect_ratio) {
39094
- bounds = applyAspectRatio(opts.aspect_ratio, bounds);
39095
- }
39096
- if (isGeoBox) {
39097
- bounds = clampToWorldBounds(bounds);
39098
- }
39099
- return bounds;
39100
- }
39101
-
39102
- // opt: aspect ratio as a single number or a range (e.g. "1,2");
39103
- function applyAspectRatio(opt, bounds) {
39104
- var range = String(opt).split(',').map(parseFloat),
39105
- aspectRatio = bounds.width() / bounds.height(),
39106
- min, max; // min is height limit, max is width limit
39107
- if (range.length == 1) {
39108
- range.push(range[0]);
39109
- } else if (range[0] > range[1]) {
39110
- range.reverse();
39111
- }
39112
- min = range[0];
39113
- max = range[1];
39114
- if (!min && !max) return bounds;
39115
- if (!min) min = -Infinity;
39116
- if (!max) max = Infinity;
39117
- if (aspectRatio < min) {
39118
- bounds.fillOut(min);
39119
- } else if (aspectRatio > max) {
39120
- bounds.fillOut(max);
39121
- }
39122
- return bounds;
39123
- }
39124
-
39125
- function applyBoundsOffset(offsetOpt, bounds, crs) {
39126
- var offsets = convertFourSides(offsetOpt, crs, bounds);
39127
- bounds.padBounds(offsets[0], offsets[1], offsets[2], offsets[3]);
39128
- return bounds;
39129
- }
39130
-
39131
- function bboxToPolygon(bbox, optsArg) {
39132
- var opts = optsArg || {};
39133
- var coords = bboxToCoords(bbox);
39134
- if (opts.interval > 0) {
39135
- coords = densifyPathByInterval(coords, opts.interval);
39136
- }
39137
- return {
39138
- type: 'Polygon',
39139
- coordinates: [coords]
39140
- };
39141
- }
39142
-
39143
- var Rectangle = /*#__PURE__*/Object.freeze({
39144
- __proto__: null,
39145
- applyAspectRatio: applyAspectRatio,
39146
- bboxToPolygon: bboxToPolygon
39147
- });
39148
-
39149
39329
  function getSemiMinorAxis(P) {
39150
39330
  return P.a * Math.sqrt(1 - (P.es || 0));
39151
39331
  }
@@ -44377,6 +44557,7 @@ ${svg}
44377
44557
  if (!size) return null;
44378
44558
  var stemLen = size.stemLen,
44379
44559
  headLen = size.headLen,
44560
+ totalLen = stickArrow ? Math.max(stemLen, headLen) : stemLen + headLen,
44380
44561
  headDx = size.headWidth / 2,
44381
44562
  stemDx = size.stemWidth / 2,
44382
44563
  baseDx = stemDx * (1 - stemTaper),
@@ -44429,11 +44610,10 @@ ${svg}
44429
44610
  }
44430
44611
 
44431
44612
  if (d.anchor == 'end') {
44432
- scaleAndShiftCoords(coords, 1, [-dx, -dy - headLen]);
44613
+ scaleAndShiftCoords(coords, 1, [-dx, -totalLen]);
44433
44614
  } else if (d.anchor == 'middle') {
44434
44615
  // shift midpoint away from the head a bit for a more balanced placement
44435
- // scaleAndShiftCoords(coords, 1, [-dx/2, (-dy - headLen)/2]);
44436
- scaleAndShiftCoords(coords, 1, [-dx * 0.5, -dy * 0.5 - headLen * 0.25]);
44616
+ scaleAndShiftCoords(coords, 1, [-dx * 0.5, -dy * 0.5 - totalLen * 0.25]);
44437
44617
  }
44438
44618
 
44439
44619
  rotateCoords(coords, direction);
@@ -44450,15 +44630,16 @@ ${svg}
44450
44630
 
44451
44631
  function calcArrowSize(d, stickArrow) {
44452
44632
  // don't display arrows with negative length
44453
- var totalLen = Math.max(d.radius || d.length || d.r || 0, 0),
44454
- scale = 1,
44455
- o = initArrowSize(d); // calc several parameters
44633
+ var o = initArrowSize(d), // calc several parameters
44634
+ dataLen = Math.max(d.radius || d.length || d.r || 0),
44635
+ totalLen = Math.max(dataLen, o.headLen, 0),
44636
+ scale = 1;
44456
44637
  if (totalLen >= 0) {
44457
44638
  scale = calcScale(totalLen, o.headLen, d);
44458
44639
  o.stemWidth *= scale;
44459
44640
  o.headWidth *= scale;
44460
44641
  o.headLen *= scale;
44461
- o.stemLen = stickArrow ? totalLen : totalLen - o.headLen;
44642
+ o.stemLen = stickArrow ? dataLen : totalLen - o.headLen;
44462
44643
  }
44463
44644
 
44464
44645
  if (o.headWidth < o.stemWidth && o.headWidth > 0) {
@@ -44507,13 +44688,11 @@ ${svg}
44507
44688
  return o;
44508
44689
  }
44509
44690
 
44510
-
44511
44691
  // Returns ratio of head length to head width
44512
44692
  function getHeadSizeRatio(headAngle) {
44513
44693
  return 1 / Math.tan(Math.PI * headAngle / 180 / 2) / 2;
44514
44694
  }
44515
44695
 
44516
-
44517
44696
  // ax, ay: point on the base
44518
44697
  // bx, by: point on the stem
44519
44698
  function getCurvedStemCoords(ax, ay, bx, by) {
@@ -45259,14 +45438,16 @@ ${svg}
45259
45438
  }
45260
45439
 
45261
45440
  function commandAcceptsMultipleTargetDatasets(name) {
45262
- return name == 'rotate' || name == 'info' || name == 'proj' || name == 'require' ||
45263
- name == 'drop' || name == 'target' || name == 'if' || name == 'elif' ||
45264
- name == 'else' || name == 'endif' || name == 'run' || name == 'i' || name == 'snap';
45441
+ return name == 'rotate' || name == 'info' || name == 'proj' ||
45442
+ name == 'require' || name == 'drop' || name == 'target' ||
45443
+ name == 'if' || name == 'elif' || name == 'else' || name == 'endif' ||
45444
+ name == 'run' || name == 'i' || name == 'snap' || name == 'frame' ||
45445
+ name == 'comment';
45265
45446
  }
45266
45447
 
45267
45448
  function commandAcceptsEmptyTarget(name) {
45268
45449
  return name == 'graticule' || name == 'i' || name == 'help' ||
45269
- name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
45450
+ name == 'point-grid' || name == 'shape' || name == 'rectangle' || name == 'frame' ||
45270
45451
  name == 'require' || name == 'run' || name == 'define' ||
45271
45452
  name == 'include' || name == 'print' || name == 'comment' || name == 'if' || name == 'elif' ||
45272
45453
  name == 'else' || name == 'endif' || name == 'stop' || name == 'add-shape' ||
@@ -45290,12 +45471,14 @@ ${svg}
45290
45471
  }
45291
45472
 
45292
45473
  if (name == 'comment') {
45474
+ cmd.comment(opts);
45293
45475
  return done(null);
45294
45476
  }
45295
45477
 
45296
45478
  if (!job) job = new Job();
45297
45479
  job.startCommand(command);
45298
45480
 
45481
+
45299
45482
  try { // catch errors from synchronous functions
45300
45483
  T$1.start();
45301
45484
 
@@ -45394,8 +45577,8 @@ ${svg}
45394
45577
  } else if (name == 'colorizer') {
45395
45578
  outputLayers = cmd.colorizer(opts);
45396
45579
 
45397
- } else if (name == 'comment') {
45398
- // no-op
45580
+ // } else if (name == 'comment') {
45581
+ // // no-op
45399
45582
 
45400
45583
  } else if (name == 'dashlines') {
45401
45584
  applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
@@ -45453,9 +45636,8 @@ ${svg}
45453
45636
  } else if (name == 'filter-slivers') {
45454
45637
  applyCommandToEachLayer(cmd.filterSlivers, targetLayers, targetDataset, opts);
45455
45638
 
45456
- // // 'frame' and 'rectangle' have merged
45457
- // } else if (name == 'frame') {
45458
- // cmd.frame(job.catalog, source, opts);
45639
+ } else if (name == 'frame') {
45640
+ cmd.frame(job.catalog, targets, opts);
45459
45641
 
45460
45642
  } else if (name == 'fuzzy-join') {
45461
45643
  applyCommandToEachLayer(cmd.fuzzyJoin, targetLayers, arcs, source, opts);
@@ -45557,10 +45739,7 @@ ${svg}
45557
45739
  cmd.proj(targ.dataset, job.catalog, opts);
45558
45740
  });
45559
45741
 
45560
- } else if (name == 'rectangle' || name == 'frame') {
45561
- if (name == 'frame' && !opts.width) {
45562
- stop('Command requires a width= argument');
45563
- }
45742
+ } else if (name == 'rectangle') {
45564
45743
  if (source || opts.bbox || targets.length === 0) {
45565
45744
  job.catalog.addDataset(cmd.rectangle(source || targets?.[0], opts));
45566
45745
  } else {
@@ -45726,7 +45905,7 @@ ${svg}
45726
45905
  });
45727
45906
  }
45728
45907
 
45729
- var version = "0.6.99";
45908
+ var version = "0.6.101";
45730
45909
 
45731
45910
  // Parse command line args into commands and run them
45732
45911
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -45877,13 +46056,16 @@ ${svg}
45877
46056
 
45878
46057
  function nextGroup(prevJob, commands, next) {
45879
46058
  runParsedCommands(commands, new Job(), function(err, job) {
45880
- err = filterError(err);
46059
+ err = handleNonFatalError(err);
45881
46060
  next(err, job);
45882
46061
  });
45883
46062
  }
45884
46063
 
45885
46064
  function done(err, job) {
45886
- err = filterError(err);
46065
+ if (job && inControlBlock(job)) {
46066
+ message('Warning: -if command is missing a matching -endif');
46067
+ }
46068
+ err = handleNonFatalError(err);
45887
46069
  if (err) printError(err);
45888
46070
  callback(err, job);
45889
46071
  }
@@ -45960,7 +46142,7 @@ ${svg}
45960
46142
  }
45961
46143
  }
45962
46144
 
45963
- function filterError(err) {
46145
+ function handleNonFatalError(err) {
45964
46146
  if (err && err.name == 'NonFatalError') {
45965
46147
  printError(err);
45966
46148
  return null;