mapshaper 0.6.99 → 0.6.100

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,
@@ -18520,6 +18525,7 @@
18520
18525
  var pixBounds = calcOutputSizeInPixels(bounds, opts);
18521
18526
  return {
18522
18527
  bbox: bounds.toArray(),
18528
+ bbox2: pixBounds.toArray(),
18523
18529
  width: Math.round(pixBounds.width()),
18524
18530
  height: Math.round(pixBounds.height()) || 1,
18525
18531
  type: 'frame'
@@ -18934,7 +18940,7 @@
18934
18940
  parts.shift();
18935
18941
  var colors = [];
18936
18942
  var background = parts.pop();
18937
- var spacing = parseInt(parts.pop());
18943
+ var spacing = parseNum(parts.pop());
18938
18944
  var tmp;
18939
18945
  while (parts.length > 0) {
18940
18946
  tmp = parts.pop();
@@ -18945,11 +18951,11 @@
18945
18951
  colors.push(tmp);
18946
18952
  }
18947
18953
  }
18948
- var width = parseInt(parts.pop());
18949
- var dashes = [parseInt(parts.pop()), parseInt(parts.pop())].reverse();
18954
+ var width = parseNum(parts.pop());
18955
+ var dashes = [parseNum(parts.pop()), parseNum(parts.pop())].reverse();
18950
18956
  var rotation = 45;
18951
18957
  if (parts.length > 0) {
18952
- rotation = parseInt(parts.pop());
18958
+ rotation = parseNum(parts.pop());
18953
18959
  }
18954
18960
  if (parts.length > 0) {
18955
18961
  return null;
@@ -18974,10 +18980,10 @@
18974
18980
  // 1px red 1px white 1px black
18975
18981
  // -45deg 3 #eee 3 rgb(0,0,0)
18976
18982
  parts.shift();
18977
- var rot = parts.length % 2 == 1 ? parseInt(parts.shift()) : 45, // default is 45
18983
+ var rot = parts.length % 2 == 1 ? parseNum(parts.shift()) : 45, // default is 45
18978
18984
  colors = [], widths = [];
18979
18985
  for (var i=0; i<parts.length; i+=2) {
18980
- widths.push(parseInt(parts[i]));
18986
+ widths.push(parseNum(parts[i]));
18981
18987
  colors.push(parts[i+1]);
18982
18988
  }
18983
18989
  if (Math.min.apply(null, widths) > 0 === false) return null;
@@ -18991,7 +18997,7 @@
18991
18997
  }
18992
18998
 
18993
18999
  function isSize(str) {
18994
- return parseInt(str) > 0;
19000
+ return parseNum(str) > 0;
18995
19001
  }
18996
19002
 
18997
19003
  function parseDots(parts) {
@@ -19004,11 +19010,11 @@
19004
19010
  var type = parts.shift();
19005
19011
  var rot = 0;
19006
19012
  if (isSize(parts[1])) { // if rotation is present, there are two numbers
19007
- rot = parseInt(parts.shift());
19013
+ rot = parseNum(parts.shift());
19008
19014
  }
19009
- var size = parseInt(parts.shift());
19015
+ var size = parseNum(parts.shift());
19010
19016
  var bg = parts.pop();
19011
- var spacing = parseInt(parts.pop());
19017
+ var spacing = parseNum(parts.pop());
19012
19018
  while (parts.length > 0) {
19013
19019
  colors.push(parts.shift());
19014
19020
  }
@@ -19026,6 +19032,12 @@
19026
19032
  };
19027
19033
  }
19028
19034
 
19035
+ function parseNum(str) {
19036
+ // return parseNum(str);
19037
+ // support sub-pixel sizes
19038
+ return parseFloat(str) || 0;
19039
+ }
19040
+
19029
19041
  function splitPattern(str) {
19030
19042
  // split apart space and comma-delimited tokens
19031
19043
  // ... but don't split rgb(...) colors
@@ -20116,7 +20128,7 @@
20116
20128
 
20117
20129
  function fitDatasetToFrame(dataset, frame, opts) {
20118
20130
  var bounds = new Bounds(frame.bbox);
20119
- var bounds2 = new Bounds(0, 0, frame.width, frame.height);
20131
+ var bounds2 = frame.bbox2 ? new Bounds(frame.bbox2) : new Bounds(0, 0, frame.width, frame.height);
20120
20132
  var fwd = bounds.getTransform(bounds2, opts.invert_y);
20121
20133
  transformPoints(dataset, function(x, y) {
20122
20134
  return fwd.transform(x, y);
@@ -24502,6 +24514,10 @@ ${svg}
24502
24514
  .option('geojson-type', {
24503
24515
  describe: '[GeoJSON] FeatureCollection, GeometryCollection or Feature'
24504
24516
  })
24517
+ .option('no-null-props', {
24518
+ describe: '[GeoJSON] use "properties":{} when a Feature has no data',
24519
+ type: 'flag'
24520
+ })
24505
24521
  .option('hoist', {
24506
24522
  describe: '[GeoJSON] move properties to the root level of each Feature',
24507
24523
  type: 'strings'
@@ -26064,15 +26080,32 @@ ${svg}
26064
26080
  });
26065
26081
 
26066
26082
  parser.command('frame')
26067
- // .describe('create a map frame at a given size')
26083
+ .describe('create a rectangular map frame layer at a given display width')
26084
+ .option('width', {
26085
+ describe: 'width of frame (e.g. 5in, 10cm, 600px; default is 800px)'
26086
+ })
26087
+ .option('height', {
26088
+ describe: '(optional) height of frame; similar to width= option'
26089
+ })
26090
+ .option('aspect-ratio', {
26091
+ describe: '(optional) aspect ratio of frame, if height= or width= is omitted',
26092
+ type: 'number'
26093
+ })
26068
26094
  .option('bbox', {
26069
26095
  describe: 'frame coordinates (xmin,ymin,xmax,ymax)',
26070
26096
  type: 'bbox'
26071
26097
  })
26072
- // .option('offset', offsetOpt)
26073
- .option('width', {
26074
- describe: 'width of output (default is 800px)'
26098
+ .option('offset', {
26099
+ describe: 'padding in display units or pct of width, e.g. 5cm 20px 5%',
26100
+ type: 'strings'
26101
+ })
26102
+ .option('offsets', {
26103
+ describe: 'separate offsets for each side, in l,b,r,t order',
26104
+ type: 'strings'
26075
26105
  })
26106
+ .option('name', nameOpt)
26107
+ .option('target', targetOpt);
26108
+
26076
26109
  // .option('height', {
26077
26110
  // describe: 'pixel height of output (may be a range)'
26078
26111
  // })
@@ -26083,7 +26116,6 @@ ${svg}
26083
26116
  // .option('source', {
26084
26117
  // describe: 'name of layer to enclose'
26085
26118
  // })
26086
- .option('name', nameOpt);
26087
26119
 
26088
26120
  parser.command('fuzzy-join')
26089
26121
  .describe('join points to polygons, with data fill and fuzzy match')
@@ -35780,7 +35812,10 @@ ${svg}
35780
35812
  getColorizerFunction: getColorizerFunction
35781
35813
  });
35782
35814
 
35783
- cmd.comment = function() {}; // no-op, so -comment doesn't trigger a parsing error
35815
+ cmd.comment = function(opts) {
35816
+ // TODO: print the comment in verbose mode
35817
+ // message('[comment]', opts.message);
35818
+ }; // no-op, so -comment doesn't trigger a parsing error
35784
35819
 
35785
35820
  cmd.dashlines = function(lyr, dataset, opts) {
35786
35821
  var crs = getDatasetCRS(dataset);
@@ -38158,46 +38193,637 @@ ${svg}
38158
38193
  return parsed[0];
38159
38194
  }
38160
38195
 
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);
38196
+ // Returns number of arcs that were removed
38197
+ function editArcs(arcs, onPoint) {
38198
+ var nn2 = [],
38199
+ xx2 = [],
38200
+ yy2 = [],
38201
+ errors = 0,
38202
+ n;
38203
+
38204
+ arcs.forEach(function(arc, i) {
38205
+ editArc(arc, onPoint);
38206
+ });
38207
+ arcs.updateVertexData(nn2, xx2, yy2);
38208
+ return errors;
38209
+
38210
+ function append(p) {
38211
+ if (p) {
38212
+ xx2.push(p[0]);
38213
+ yy2.push(p[1]);
38214
+ n++;
38215
+ }
38216
+ }
38217
+
38218
+ function editArc(arc, cb) {
38219
+ var x, y, xp, yp, retn;
38220
+ var valid = true;
38221
+ var i = 0;
38222
+ n = 0;
38223
+ while (arc.hasNext()) {
38224
+ x = arc.x;
38225
+ y = arc.y;
38226
+ retn = cb(append, x, y, xp, yp, i++);
38227
+ if (retn === false) {
38228
+ valid = false;
38229
+ // assumes that it's ok for the arc iterator to be interrupted.
38230
+ break;
38231
+ }
38232
+ xp = x;
38233
+ yp = y;
38234
+ }
38235
+ if (valid && n == 1) {
38236
+ // only one valid point was added to this arc (invalid)
38237
+ // e.g. this could happen during reprojection.
38238
+ // making this arc empty
38239
+ // error("An invalid arc was created");
38240
+ message("An invalid arc was created");
38241
+ valid = false;
38242
+ }
38243
+ if (valid) {
38244
+ nn2.push(n);
38245
+ } else {
38246
+ // remove any points that were added for an invalid arc
38247
+ while (n-- > 0) {
38248
+ xx2.pop();
38249
+ yy2.pop();
38250
+ }
38251
+ nn2.push(0); // add empty arc (to preserve mapping from paths to arcs)
38252
+ errors++;
38253
+ }
38254
+ }
38255
+ }
38256
+
38257
+ function DatasetEditor(dataset) {
38258
+ var layers = [];
38259
+ var arcs = [];
38260
+
38261
+ this.done = function() {
38262
+ dataset.layers = layers;
38263
+ if (arcs.length) {
38264
+ dataset.arcs = new ArcCollection(arcs);
38265
+ buildTopology(dataset);
38266
+ }
38267
+ };
38268
+
38269
+ this.editLayer = function(lyr, cb) {
38270
+ var type = lyr.geometry_type;
38271
+ if (dataset.layers.indexOf(lyr) != layers.length) {
38272
+ error('Layer was edited out-of-order');
38273
+ }
38274
+ if (!type) {
38275
+ layers.push(lyr);
38276
+ return;
38277
+ }
38278
+ var shapes = lyr.shapes.map(function(shape, shpId) {
38279
+ var shape2 = [], retn, input;
38280
+ for (var i=0, n=shape ? shape.length : 0; i<n; i++) {
38281
+ input = type == 'point' ? shape[i] : idsToCoords(shape[i]);
38282
+ retn = cb(input, i, shape);
38283
+ if (!Array.isArray(retn)) continue;
38284
+ if (type == 'point') {
38285
+ shape2.push(retn);
38286
+ } else if (type == 'polygon' || type == 'polyline') {
38287
+ extendPathShape(shape2, retn || []);
38288
+ }
38289
+ }
38290
+ return shape2.length > 0 ? shape2 : null;
38291
+ });
38292
+ layers.push(Object.assign(lyr, {shapes: shapes}));
38293
+ };
38294
+
38295
+ function extendPathShape(shape, parts) {
38296
+ for (var i=0; i<parts.length; i++) {
38297
+ shape.push([arcs.length]);
38298
+ arcs.push(parts[i]);
38299
+ }
38300
+ }
38301
+
38302
+ function idsToCoords(ids) {
38303
+ var coords = [];
38304
+ var iter = dataset.arcs.getShapeIter(ids);
38305
+ while (iter.hasNext()) {
38306
+ coords.push([iter.x, iter.y]);
38307
+ }
38308
+ return coords;
38309
+ }
38310
+ }
38311
+
38312
+ // Planar densification by an interval
38313
+ function densifyPathByInterval(coords, interval, interpolate) {
38314
+ if (findMaxPathInterval(coords) < interval) return coords;
38315
+ if (!interpolate) {
38316
+ interpolate = getIntervalInterpolator(interval);
38317
+ }
38318
+ var coords2 = [coords[0]], a, b;
38319
+ for (var i=1, n=coords.length; i<n; i++) {
38320
+ a = coords[i-1];
38321
+ b = coords[i];
38322
+ if (geom.distance2D(a[0], a[1], b[0], b[1]) > interval + 1e-4) {
38323
+ appendArr(coords2, interpolate(a, b));
38324
+ }
38325
+ coords2.push(b);
38326
+ }
38327
+ return coords2;
38328
+ }
38329
+
38330
+ function getIntervalInterpolator(interval) {
38331
+ return function(a, b) {
38332
+ var points = [];
38333
+ // var rev = a[0] == b[0] ? a[1] > b[1] : a[0] > b[0];
38334
+ var dist = geom.distance2D(a[0], a[1], b[0], b[1]);
38335
+ var n = Math.round(dist / interval) - 1;
38336
+ var dx = (b[0] - a[0]) / (n + 1),
38337
+ dy = (b[1] - a[1]) / (n + 1);
38338
+ for (var i=1; i<=n; i++) {
38339
+ points.push([a[0] + dx * i, a[1] + dy * i]);
38340
+ }
38341
+ return points;
38342
+ };
38343
+ }
38344
+
38345
+
38346
+ // Interpolate the same points regardless of segment direction
38347
+ function densifyAntimeridianSegment(a, b, interval) {
38348
+ var y1, y2;
38349
+ var coords = [];
38350
+ var ascending = a[1] < b[1];
38351
+ if (a[0] != b[0]) error('Expected an edge segment');
38352
+ if (interval > 0 === false) error('Expected a positive interval');
38353
+ if (ascending) {
38354
+ y1 = a[1];
38355
+ y2 = b[1];
38356
+ } else {
38357
+ y1 = b[1];
38358
+ y2 = a[1];
38359
+ }
38360
+ var y = Math.floor(y1 / interval) * interval + interval;
38361
+ while (y < y2) {
38362
+ coords.push([a[0], y]);
38363
+ y += interval;
38364
+ }
38365
+ if (!ascending) coords.reverse();
38366
+ return coords;
38367
+ }
38368
+
38369
+ function appendArr(dest, src) {
38370
+ for (var i=0; i<src.length; i++) dest.push(src[i]);
38371
+ }
38372
+
38373
+ function findMaxPathInterval(coords) {
38374
+ var maxSq = 0, intSq, a, b;
38375
+ for (var i=1, n=coords.length; i<n; i++) {
38376
+ a = coords[i-1];
38377
+ b = coords[i];
38378
+ intSq = geom.distanceSq(a[0], a[1], b[0], b[1]);
38379
+ if (intSq > maxSq) maxSq = intSq;
38380
+ }
38381
+ return Math.sqrt(maxSq);
38382
+ }
38383
+
38384
+ function projectAndDensifyArcs(arcs, proj) {
38385
+ var interval = getDefaultDensifyInterval(arcs, proj);
38386
+ var minIntervalSq = interval * interval * 25;
38387
+ var p;
38388
+ return editArcs(arcs, onPoint);
38389
+
38390
+ function onPoint(append, lng, lat, prevLng, prevLat, i) {
38391
+ var pp = p;
38392
+ p = proj(lng, lat);
38393
+ if (!p) return false; // signal that current arc contains an error
38394
+
38395
+ // Don't try to densify shorter segments (optimization)
38396
+ if (i > 0 && geom.distanceSq(p[0], p[1], pp[0], pp[1]) > minIntervalSq) {
38397
+ densifySegment(prevLng, prevLat, pp[0], pp[1], lng, lat, p[0], p[1], proj, interval)
38398
+ .forEach(append);
38399
+ }
38400
+ append(p);
38401
+ }
38402
+ }
38403
+
38404
+ // Use the median of intervals computed by projecting segments.
38405
+ // We're probing a number of points, because @proj might only be valid in
38406
+ // a sub-region of the dataset bbox (e.g. +proj=tpers)
38407
+ function findDensifyInterval(bounds, xy, proj) {
38408
+ var steps = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
38409
+ var points = [];
38410
+ for (var i=0; i<steps.length; i++) {
38411
+ for (var j=0; j<steps.length; j++) {
38412
+ points.push([steps[i], steps[j]]);
38413
+ }
38414
+ }
38415
+ var intervals = points.map(function(pos) {
38416
+ var x = bounds.xmin + bounds.width() * pos[0];
38417
+ var y = bounds.ymin + bounds.height() * pos[1];
38418
+ var a = proj(x, y);
38419
+ var b = proj(x + xy[0], y + xy[1]);
38420
+ return a && b ? geom.distance2D(a[0], a[1], b[0], b[1]) : Infinity;
38421
+ }).filter(function(int) {return int < Infinity;});
38422
+ return intervals.length > 0 ? utils.findMedian(intervals) : Infinity;
38423
+ }
38424
+
38425
+ // Kludgy way to get a useful interval for densifying a bounding box.
38426
+ // Uses a fraction of average bbox side length)
38427
+ // TODO: improve
38428
+ function findDensifyInterval2(bb, proj) {
38429
+ var a = proj(bb.centerX(), bb.centerY()),
38430
+ c = proj(bb.centerX(), bb.ymin), // right center
38431
+ d = proj(bb.xmax, bb.centerY()); // bottom center
38432
+ var interval = a && c && d ? (geom.distance2D(a[0], a[1], c[0], c[1]) +
38433
+ geom.distance2D(a[0], a[1], d[0], d[1])) / 5000 : Infinity;
38434
+ return interval;
38435
+ }
38436
+
38437
+ // Returns an interval in projected units
38438
+ function getDefaultDensifyInterval(arcs, proj) {
38439
+ var xy = getAvgSegment2(arcs),
38440
+ bb = arcs.getBounds(),
38441
+ intervalA = findDensifyInterval(bb, xy, proj),
38442
+ intervalB = findDensifyInterval2(bb, proj),
38443
+ interval = Math.min(intervalA, intervalB);
38444
+ if (interval == Infinity) {
38445
+ error('Densification error');
38446
+ }
38447
+ return interval;
38448
+ }
38449
+
38450
+ // Interpolate points into a projected line segment if needed to prevent large
38451
+ // deviations from path of original unprojected segment.
38452
+ // @points (optional) array of accumulated points
38453
+ function densifySegment(lng0, lat0, x0, y0, lng2, lat2, x2, y2, proj, interval, points) {
38454
+ // Find midpoint between two endpoints and project it (assumes longitude does
38455
+ // not wrap). TODO Consider bisecting along great circle path -- although this
38456
+ // would not be good for boundaries that follow line of constant latitude.
38457
+ var lng1 = (lng0 + lng2) / 2,
38458
+ lat1 = (lat0 + lat2) / 2,
38459
+ p = proj(lng1, lat1),
38460
+ distSq;
38461
+ if (!p) return; // TODO: consider if this is adequate for handling proj. errors
38462
+ distSq = geom.pointSegDistSq2(p[0], p[1], x0, y0, x2, y2); // sq displacement
38463
+ points = points || [];
38464
+ // Bisect current segment if the projected midpoint deviates from original
38465
+ // segment by more than the @interval parameter.
38466
+ // ... but don't bisect very small segments to prevent infinite recursion
38467
+ // (e.g. if projection function is discontinuous)
38468
+ if (distSq > interval * interval * 0.25 && geom.distance2D(lng0, lat0, lng2, lat2) > 0.01) {
38469
+ densifySegment(lng0, lat0, x0, y0, lng1, lat1, p[0], p[1], proj, interval, points);
38470
+ points.push(p);
38471
+ densifySegment(lng1, lat1, p[0], p[1], lng2, lat2, x2, y2, proj, interval, points);
38472
+ }
38473
+ return points;
38474
+ }
38475
+
38476
+ // Create rectangles around each feature in a layer
38477
+ cmd.rectangles = function(targetLyr, targetDataset, opts) {
38478
+ var crsInfo = getDatasetCrsInfo(targetDataset);
38479
+ var records = targetLyr.data ? targetLyr.data.getRecords() : null;
38480
+ var geometries;
38481
+
38482
+ if (opts.bbox) {
38483
+ geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset);
38484
+
38485
+ } else {
38486
+ if (!layerHasGeometry(targetLyr)) {
38487
+ stop("Layer is missing geometric shapes");
38488
+ }
38489
+ geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
38490
+ }
38491
+
38492
+ var geojson = {
38493
+ type: 'FeatureCollection',
38494
+ features: geometries.map(function(geom, i) {
38495
+ var rec = records && records[i] || null;
38496
+ if (rec && opts.no_replace) {
38497
+ rec = utils.extend({}, rec); // make a copy
38498
+ }
38499
+ return {
38500
+ type: 'Feature',
38501
+ properties: rec,
38502
+ geometry: geom
38503
+ };
38504
+ })
38505
+ };
38506
+ var dataset = importGeoJSON(geojson, {});
38507
+ setDatasetCrsInfo(dataset, crsInfo);
38508
+ var outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
38509
+ setOutputLayerName(outputLayers[0], targetLyr, null, opts);
38510
+ return outputLayers;
38511
+ };
38512
+
38513
+
38514
+
38515
+
38516
+ function shapesToBoxGeometries(lyr, dataset, opts) {
38517
+ var crsInfo = getDatasetCrsInfo(dataset);
38518
+ return lyr.shapes.map(function(shp) {
38519
+ var bounds = lyr.geometry_type == 'point' ?
38520
+ getPointFeatureBounds(shp) : dataset.arcs.getMultiShapeBounds(shp);
38521
+ bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
38522
+ if (!bounds) return null;
38523
+ return bboxToPolygon(bounds.toArray(), opts);
38524
+ });
38525
+ }
38526
+
38527
+ function bboxExpressionToGeometries(exp, lyr, dataset, opts) {
38528
+ var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, {});
38529
+ var n = getFeatureCount(lyr);
38530
+ var result;
38531
+ var geometries = [];
38532
+ for (var i=0; i<n; i++) {
38533
+ result = compiled(i);
38534
+ if (!looksLikeBbox(result)) {
38535
+ stop('Invalid bbox value (expected a GeoJSON-type bbox):', result);
38536
+ }
38537
+ geometries.push(bboxToPolygon(result));
38538
+ }
38539
+ return geometries;
38540
+ }
38541
+
38542
+ function looksLikeBbox(o) {
38543
+ if (!o || o.length != 4) return false;
38544
+ if (o.some(isNaN)) return false;
38545
+ if (o[0] <= o[2] == false || o[1] <= o[3] == false) return false;
38546
+ return true;
38547
+ }
38548
+
38549
+ // Create rectangles around one or more target layers
38550
+ //
38551
+ cmd.rectangle2 = function(target, opts) {
38552
+ // if target layer is a rectangle and we're applying frame properties,
38553
+ // turn the target into a frame instead of creating a new rectangle
38554
+ if (target.layers.length == 1 && opts.width &&
38555
+ layerIsRectangle(target.layers[0], target.dataset.arcs)) {
38556
+ applyFrameProperties(target.layers[0], opts);
38557
+ return;
38558
+ }
38559
+ var datasets = target.layers.map(function(lyr) {
38560
+ var dataset = cmd.rectangle({layer: lyr, dataset: target.dataset}, opts);
38561
+ setOutputLayerName(dataset.layers[0], lyr, null, opts);
38562
+ if (!opts.no_replace) {
38563
+ dataset.layers[0].name = lyr.name || dataset.layers[0].name;
38564
+ }
38565
+ return dataset;
38566
+ });
38567
+ return mergeDatasetsIntoDataset(target.dataset, datasets);
38568
+ };
38569
+
38570
+ cmd.rectangle = function(target, opts) {
38571
+ var bounds, crsInfo;
38572
+ if (opts.bbox) {
38573
+ bounds = new Bounds(opts.bbox);
38574
+ crsInfo = target && getDatasetCrsInfo(target.dataset) ||
38575
+ probablyDecimalDegreeBounds(bounds) && getCrsInfo('wgs84') || {};
38576
+ } else if (target) {
38577
+ bounds = getLayerBounds(target.layer, target.dataset.arcs);
38578
+ crsInfo = getDatasetCrsInfo(target.dataset);
38579
+ }
38580
+ bounds = bounds && applyRectangleOptions(bounds, crsInfo.crs, opts);
38581
+ if (!bounds || !bounds.hasBounds()) {
38582
+ stop('Missing rectangle extent');
38583
+ }
38584
+ var feature = {
38585
+ type: 'Feature',
38586
+ properties: {},
38587
+ geometry: bboxToPolygon(bounds.toArray(), opts)
38588
+ };
38589
+ var dataset = importGeoJSON(feature, {});
38590
+ applyFrameProperties(dataset.layers[0], opts);
38591
+ dataset.layers[0].name = opts.name || 'rectangle';
38592
+ setDatasetCrsInfo(dataset, crsInfo);
38593
+ return dataset;
38199
38594
  };
38200
38595
 
38596
+ function applyFrameProperties(lyr, opts) {
38597
+ if (!opts.width) return;
38598
+ if (!lyr.data) initDataTable(lyr);
38599
+ var d = lyr.data.getRecords()[0] || {};
38600
+ d.width = parseSizeParam(opts.width);
38601
+ d.type = 'frame';
38602
+ }
38603
+
38604
+ function applyRectangleOptions(bounds, crs, opts) {
38605
+ var isGeoBox = probablyDecimalDegreeBounds(bounds);
38606
+ if (opts.offset) {
38607
+ bounds = applyBoundsOffset(opts.offset, bounds, crs);
38608
+ }
38609
+ if (bounds.area() > 0 === false) return null;
38610
+ if (opts.aspect_ratio) {
38611
+ bounds = applyAspectRatio(opts.aspect_ratio, bounds);
38612
+ }
38613
+ if (isGeoBox) {
38614
+ bounds = clampToWorldBounds(bounds);
38615
+ }
38616
+ return bounds;
38617
+ }
38618
+
38619
+ // opt: aspect ratio as a single number or a range (e.g. "1,2");
38620
+ function applyAspectRatio(opt, bounds) {
38621
+ var range = String(opt).split(',').map(parseFloat),
38622
+ aspectRatio = bounds.width() / bounds.height(),
38623
+ min, max; // min is height limit, max is width limit
38624
+ if (range.length == 1) {
38625
+ range.push(range[0]);
38626
+ } else if (range[0] > range[1]) {
38627
+ range.reverse();
38628
+ }
38629
+ min = range[0];
38630
+ max = range[1];
38631
+ if (!min && !max) return bounds;
38632
+ if (!min) min = -Infinity;
38633
+ if (!max) max = Infinity;
38634
+ if (aspectRatio < min) {
38635
+ bounds.fillOut(min);
38636
+ } else if (aspectRatio > max) {
38637
+ bounds.fillOut(max);
38638
+ }
38639
+ return bounds;
38640
+ }
38641
+
38642
+ function applyBoundsOffset(offsetOpt, bounds, crs) {
38643
+ var offsets = convertFourSides(offsetOpt, crs, bounds);
38644
+ bounds.padBounds(offsets[0], offsets[1], offsets[2], offsets[3]);
38645
+ return bounds;
38646
+ }
38647
+
38648
+ function bboxToPolygon(bbox, optsArg) {
38649
+ var opts = optsArg || {};
38650
+ var coords = bboxToCoords(bbox);
38651
+ if (opts.interval > 0) {
38652
+ coords = densifyPathByInterval(coords, opts.interval);
38653
+ }
38654
+ return {
38655
+ type: 'Polygon',
38656
+ coordinates: [coords]
38657
+ };
38658
+ }
38659
+
38660
+ var Rectangle = /*#__PURE__*/Object.freeze({
38661
+ __proto__: null,
38662
+ applyAspectRatio: applyAspectRatio,
38663
+ bboxToPolygon: bboxToPolygon
38664
+ });
38665
+
38666
+ cmd.frame = function(catalog, targets, opts) {
38667
+ var widthPx, heightPx, aspectRatio, bbox;
38668
+ if (opts.width) {
38669
+ widthPx = parseSizeParam(opts.width);
38670
+ if (widthPx > 0 === false) {
38671
+ stop('Invalid width parameter:', opts.width);
38672
+ }
38673
+ }
38674
+ if (opts.height) {
38675
+ heightPx = parseSizeParam(opts.height);
38676
+ if (heightPx > 0 === false) {
38677
+ stop('Invalid height parameter:', opts.height);
38678
+ }
38679
+ }
38680
+ if (!widthPx && !heightPx) {
38681
+ widthPx = 800;
38682
+ message('Using default 800px frame width');
38683
+ }
38684
+
38685
+ if (opts.aspect_ratio) {
38686
+ if (opts.aspect_ratio > 0 === false) {
38687
+ stop('Invalid aspect-ratio parameter:', opts.aspect_ratio);
38688
+ }
38689
+ if (!heightPx) {
38690
+ heightPx = roundToDigits(widthPx / opts.aspect_ratio, 1);
38691
+ } else if (!widthPx) {
38692
+ widthPx = roundToDigits(heightPx * opts.aspect_ratio, 1);
38693
+ }
38694
+ }
38695
+
38696
+ if (opts.bbox) {
38697
+ bbox = opts.bbox;
38698
+ // TODO: validate
38699
+ } else {
38700
+ var datasets = utils.pluck(targets, 'dataset');
38701
+ requireDatasetsHaveCompatibleCRS(datasets, 'Targets include both projected and unprojected coordinates');
38702
+ bbox = getTargetBbox(targets);
38703
+ if (!bbox) {
38704
+ stop('Command target is missing geographical bounds');
38705
+ }
38706
+ }
38707
+
38708
+ applyPercentageOffsets(bbox, opts.offset || opts.offsets);
38709
+ applyPixelOffsets(bbox, widthPx, heightPx, opts.offset || opts.offsets);
38710
+
38711
+ if (bbox[3] - bbox[1] > 0 === false || bbox[2] - bbox[0] > 0 === false) {
38712
+ stop('Frame has a collapsed bbox');
38713
+ }
38714
+
38715
+ aspectRatio = (bbox[2] - bbox[0]) / (bbox[3] - bbox[1]);
38716
+ if (!widthPx) {
38717
+ widthPx = roundToDigits(heightPx * aspectRatio, 1);
38718
+ } else if (!heightPx) {
38719
+ heightPx = roundToDigits(widthPx / aspectRatio, 1);
38720
+ }
38721
+
38722
+ var feature = {
38723
+ type: 'Feature',
38724
+ properties: {type: 'frame', width: widthPx, height: heightPx},
38725
+ geometry: bboxToPolygon(bbox)
38726
+ };
38727
+ var frameDataset = importGeoJSON(feature);
38728
+ // set CRS from target dataset
38729
+ // TODO: handle case: targets have different projections
38730
+ // TODO: handle case: first target is missing CRS
38731
+ if (targets.length > 0) {
38732
+ var crsInfo = getDatasetCrsInfo(targets[0].dataset);
38733
+ setDatasetCrsInfo(frameDataset, crsInfo);
38734
+ }
38735
+ frameDataset.layers[0].name = opts.name || 'frame';
38736
+ catalog.addDataset(frameDataset);
38737
+ };
38738
+
38739
+ function fillOutBbox(bbox, widthPx, heightPx) {
38740
+ var hpad = 0, vpad = 0;
38741
+ var w = bbox[2] - bbox[0];
38742
+ var h = bbox[3] - bbox[1];
38743
+ if (widthPx / heightPx > w / h) { // need to add horizontal padding
38744
+ hpad = h * widthPx / heightPx - w;
38745
+ } else {
38746
+ vpad = w * heightPx / widthPx - h;
38747
+ }
38748
+ bbox[0] -= hpad / 2;
38749
+ bbox[1] -= vpad / 2;
38750
+ bbox[2] += hpad / 2;
38751
+ bbox[3] += vpad / 2;
38752
+ }
38753
+
38754
+ function applyPercentageOffsets(bbox, arg) {
38755
+ var sides = getPctOffsets(arg);
38756
+ var l = sides[0],
38757
+ b = sides[1],
38758
+ r = sides[2],
38759
+ t = sides[3],
38760
+ w2 = (bbox[2] - bbox[0]) / (1 - l - r),
38761
+ h2 = (bbox[3] - bbox[1]) / (1 - t - b);
38762
+ bbox[0] -= l * w2;
38763
+ bbox[1] -= b * h2;
38764
+ bbox[2] += r * w2;
38765
+ bbox[3] += t * h2;
38766
+ }
38767
+
38768
+ function applyPixelOffsets(bbox, widthPx, heightPx, arg) {
38769
+ var sides = getPixelOffsets(arg);
38770
+ var l = sides[0],
38771
+ b = sides[1],
38772
+ r = sides[2],
38773
+ t = sides[3],
38774
+ scale, w;
38775
+
38776
+ if (widthPx && heightPx) {
38777
+ // add padding to bbox to match pixel dimensions, if needed
38778
+ fillOutBbox(bbox, widthPx, heightPx);
38779
+ }
38780
+
38781
+ w = bbox[2] - bbox[0];
38782
+ bbox[3] - bbox[1];
38783
+
38784
+ if (widthPx) {
38785
+ scale = w / (widthPx - l - r);
38786
+ } else {
38787
+ scale = w / (heightPx - t - b);
38788
+ }
38789
+
38790
+ bbox[0] -= scale * l;
38791
+ bbox[1] -= scale * b;
38792
+ bbox[2] += scale * r;
38793
+ bbox[3] += scale * t;
38794
+ return scale;
38795
+ }
38796
+
38797
+ function getPctOffsets(arg) {
38798
+ return adjustOffsetsArg(arg).map(str => {
38799
+ return str.includes('%') ? utils.parsePercent(str) : 0;
38800
+ });
38801
+ }
38802
+
38803
+ function getPixelOffsets(arg) {
38804
+ return adjustOffsetsArg(arg).map(str => {
38805
+ return str.includes('%') ? 0 : parseSizeParam(str);
38806
+ });
38807
+ }
38808
+
38809
+ function adjustOffsetsArg(arg) {
38810
+ if (!arg) arg = ['0'];
38811
+ if (arg.length == 1) {
38812
+ return [arg[0], arg[0], arg[0], arg[0]];
38813
+ }
38814
+ if (arg.length != 4) {
38815
+ stop('List of offsets should have 4 values');
38816
+ }
38817
+ return arg;
38818
+ }
38819
+
38820
+ function getTargetBbox(targets) {
38821
+ var expanded = expandCommandTargets(targets);
38822
+ var bounds = expanded.reduce(function(memo, o) {
38823
+ return memo.mergeBounds(getLayerBounds(o.layer, o.dataset.arcs));
38824
+ }, new Bounds());
38825
+ return bounds.hasBounds() ? bounds.toArray() : null;
38826
+ }
38201
38827
 
38202
38828
  // Convert width and height args to aspect ratio arg for the rectangle() function
38203
38829
  function getAspectRatioArg(widthArg, heightArg) {
@@ -38211,21 +38837,6 @@ ${svg}
38211
38837
  }).reverse().join(',');
38212
38838
  }
38213
38839
 
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
38840
  var Frame = /*#__PURE__*/Object.freeze({
38230
38841
  __proto__: null,
38231
38842
  getAspectRatioArg: getAspectRatioArg
@@ -38679,473 +39290,6 @@ ${svg}
38679
39290
  return maxValue;
38680
39291
  }
38681
39292
 
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
39293
  function getSemiMinorAxis(P) {
39150
39294
  return P.a * Math.sqrt(1 - (P.es || 0));
39151
39295
  }
@@ -45259,14 +45403,16 @@ ${svg}
45259
45403
  }
45260
45404
 
45261
45405
  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';
45406
+ return name == 'rotate' || name == 'info' || name == 'proj' ||
45407
+ name == 'require' || name == 'drop' || name == 'target' ||
45408
+ name == 'if' || name == 'elif' || name == 'else' || name == 'endif' ||
45409
+ name == 'run' || name == 'i' || name == 'snap' || name == 'frame' ||
45410
+ name == 'comment';
45265
45411
  }
45266
45412
 
45267
45413
  function commandAcceptsEmptyTarget(name) {
45268
45414
  return name == 'graticule' || name == 'i' || name == 'help' ||
45269
- name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
45415
+ name == 'point-grid' || name == 'shape' || name == 'rectangle' || name == 'frame' ||
45270
45416
  name == 'require' || name == 'run' || name == 'define' ||
45271
45417
  name == 'include' || name == 'print' || name == 'comment' || name == 'if' || name == 'elif' ||
45272
45418
  name == 'else' || name == 'endif' || name == 'stop' || name == 'add-shape' ||
@@ -45290,12 +45436,14 @@ ${svg}
45290
45436
  }
45291
45437
 
45292
45438
  if (name == 'comment') {
45439
+ cmd.comment(opts);
45293
45440
  return done(null);
45294
45441
  }
45295
45442
 
45296
45443
  if (!job) job = new Job();
45297
45444
  job.startCommand(command);
45298
45445
 
45446
+
45299
45447
  try { // catch errors from synchronous functions
45300
45448
  T$1.start();
45301
45449
 
@@ -45394,8 +45542,8 @@ ${svg}
45394
45542
  } else if (name == 'colorizer') {
45395
45543
  outputLayers = cmd.colorizer(opts);
45396
45544
 
45397
- } else if (name == 'comment') {
45398
- // no-op
45545
+ // } else if (name == 'comment') {
45546
+ // // no-op
45399
45547
 
45400
45548
  } else if (name == 'dashlines') {
45401
45549
  applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
@@ -45453,9 +45601,8 @@ ${svg}
45453
45601
  } else if (name == 'filter-slivers') {
45454
45602
  applyCommandToEachLayer(cmd.filterSlivers, targetLayers, targetDataset, opts);
45455
45603
 
45456
- // // 'frame' and 'rectangle' have merged
45457
- // } else if (name == 'frame') {
45458
- // cmd.frame(job.catalog, source, opts);
45604
+ } else if (name == 'frame') {
45605
+ cmd.frame(job.catalog, targets, opts);
45459
45606
 
45460
45607
  } else if (name == 'fuzzy-join') {
45461
45608
  applyCommandToEachLayer(cmd.fuzzyJoin, targetLayers, arcs, source, opts);
@@ -45557,10 +45704,7 @@ ${svg}
45557
45704
  cmd.proj(targ.dataset, job.catalog, opts);
45558
45705
  });
45559
45706
 
45560
- } else if (name == 'rectangle' || name == 'frame') {
45561
- if (name == 'frame' && !opts.width) {
45562
- stop('Command requires a width= argument');
45563
- }
45707
+ } else if (name == 'rectangle') {
45564
45708
  if (source || opts.bbox || targets.length === 0) {
45565
45709
  job.catalog.addDataset(cmd.rectangle(source || targets?.[0], opts));
45566
45710
  } else {
@@ -45726,7 +45870,7 @@ ${svg}
45726
45870
  });
45727
45871
  }
45728
45872
 
45729
- var version = "0.6.99";
45873
+ var version = "0.6.100";
45730
45874
 
45731
45875
  // Parse command line args into commands and run them
45732
45876
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -45877,13 +46021,16 @@ ${svg}
45877
46021
 
45878
46022
  function nextGroup(prevJob, commands, next) {
45879
46023
  runParsedCommands(commands, new Job(), function(err, job) {
45880
- err = filterError(err);
46024
+ err = handleNonFatalError(err);
45881
46025
  next(err, job);
45882
46026
  });
45883
46027
  }
45884
46028
 
45885
46029
  function done(err, job) {
45886
- err = filterError(err);
46030
+ if (job && inControlBlock(job)) {
46031
+ message('Warning: -if command is missing a matching -endif');
46032
+ }
46033
+ err = handleNonFatalError(err);
45887
46034
  if (err) printError(err);
45888
46035
  callback(err, job);
45889
46036
  }
@@ -45960,7 +46107,7 @@ ${svg}
45960
46107
  }
45961
46108
  }
45962
46109
 
45963
- function filterError(err) {
46110
+ function handleNonFatalError(err) {
45964
46111
  if (err && err.name == 'NonFatalError') {
45965
46112
  printError(err);
45966
46113
  return null;