mapshaper 0.6.98 → 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) {
@@ -14931,6 +14932,24 @@
14931
14932
  return opts.geojson_editor.get(_id);
14932
14933
  }
14933
14934
  });
14935
+
14936
+ Object.defineProperty(ctx, 'feature', {
14937
+ set: function(o) {
14938
+ opts.geojson_editor.set(o, _id);
14939
+ },
14940
+ get: function() {
14941
+ return opts.geojson_editor.get(_id);
14942
+ }
14943
+ });
14944
+
14945
+ Object.defineProperty(ctx, 'geometry', {
14946
+ set: function(o) {
14947
+ opts.geojson_editor.setGeometry(o, _id);
14948
+ },
14949
+ get: function() {
14950
+ return opts.geojson_editor.getGeometry(_id);
14951
+ }
14952
+ });
14934
14953
  }
14935
14954
 
14936
14955
  if (_records) {
@@ -18136,6 +18155,9 @@
18136
18155
  if (ids) {
18137
18156
  obj.id = ids[i];
18138
18157
  }
18158
+ if (opts.no_null_props && !obj.properties) {
18159
+ obj.properties = {};
18160
+ }
18139
18161
  } else if (!geom) {
18140
18162
  return memo; // don't add null objects to GeometryCollection
18141
18163
  } else {
@@ -18481,7 +18503,8 @@
18481
18503
  var bounds = getLayerBounds(lyr, arcs);
18482
18504
  var d = lyr.data.getReadOnlyRecordAt(0);
18483
18505
  var w = d.width || 800;
18484
- var h = w * bounds.height() / bounds.width();
18506
+ // prevent rounding errors (like 1000.0000000002)
18507
+ var h = Math.round(w * bounds.height() / bounds.width());
18485
18508
  return {
18486
18509
  type: 'frame',
18487
18510
  width: w,
@@ -18502,6 +18525,7 @@
18502
18525
  var pixBounds = calcOutputSizeInPixels(bounds, opts);
18503
18526
  return {
18504
18527
  bbox: bounds.toArray(),
18528
+ bbox2: pixBounds.toArray(),
18505
18529
  width: Math.round(pixBounds.width()),
18506
18530
  height: Math.round(pixBounds.height()) || 1,
18507
18531
  type: 'frame'
@@ -18916,7 +18940,7 @@
18916
18940
  parts.shift();
18917
18941
  var colors = [];
18918
18942
  var background = parts.pop();
18919
- var spacing = parseInt(parts.pop());
18943
+ var spacing = parseNum(parts.pop());
18920
18944
  var tmp;
18921
18945
  while (parts.length > 0) {
18922
18946
  tmp = parts.pop();
@@ -18927,11 +18951,11 @@
18927
18951
  colors.push(tmp);
18928
18952
  }
18929
18953
  }
18930
- var width = parseInt(parts.pop());
18931
- 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();
18932
18956
  var rotation = 45;
18933
18957
  if (parts.length > 0) {
18934
- rotation = parseInt(parts.pop());
18958
+ rotation = parseNum(parts.pop());
18935
18959
  }
18936
18960
  if (parts.length > 0) {
18937
18961
  return null;
@@ -18956,10 +18980,10 @@
18956
18980
  // 1px red 1px white 1px black
18957
18981
  // -45deg 3 #eee 3 rgb(0,0,0)
18958
18982
  parts.shift();
18959
- 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
18960
18984
  colors = [], widths = [];
18961
18985
  for (var i=0; i<parts.length; i+=2) {
18962
- widths.push(parseInt(parts[i]));
18986
+ widths.push(parseNum(parts[i]));
18963
18987
  colors.push(parts[i+1]);
18964
18988
  }
18965
18989
  if (Math.min.apply(null, widths) > 0 === false) return null;
@@ -18973,7 +18997,7 @@
18973
18997
  }
18974
18998
 
18975
18999
  function isSize(str) {
18976
- return parseInt(str) > 0;
19000
+ return parseNum(str) > 0;
18977
19001
  }
18978
19002
 
18979
19003
  function parseDots(parts) {
@@ -18986,11 +19010,11 @@
18986
19010
  var type = parts.shift();
18987
19011
  var rot = 0;
18988
19012
  if (isSize(parts[1])) { // if rotation is present, there are two numbers
18989
- rot = parseInt(parts.shift());
19013
+ rot = parseNum(parts.shift());
18990
19014
  }
18991
- var size = parseInt(parts.shift());
19015
+ var size = parseNum(parts.shift());
18992
19016
  var bg = parts.pop();
18993
- var spacing = parseInt(parts.pop());
19017
+ var spacing = parseNum(parts.pop());
18994
19018
  while (parts.length > 0) {
18995
19019
  colors.push(parts.shift());
18996
19020
  }
@@ -19008,6 +19032,12 @@
19008
19032
  };
19009
19033
  }
19010
19034
 
19035
+ function parseNum(str) {
19036
+ // return parseNum(str);
19037
+ // support sub-pixel sizes
19038
+ return parseFloat(str) || 0;
19039
+ }
19040
+
19011
19041
  function splitPattern(str) {
19012
19042
  // split apart space and comma-delimited tokens
19013
19043
  // ... but don't split rgb(...) colors
@@ -20098,7 +20128,7 @@
20098
20128
 
20099
20129
  function fitDatasetToFrame(dataset, frame, opts) {
20100
20130
  var bounds = new Bounds(frame.bbox);
20101
- 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);
20102
20132
  var fwd = bounds.getTransform(bounds2, opts.invert_y);
20103
20133
  transformPoints(dataset, function(x, y) {
20104
20134
  return fwd.transform(x, y);
@@ -24484,6 +24514,10 @@ ${svg}
24484
24514
  .option('geojson-type', {
24485
24515
  describe: '[GeoJSON] FeatureCollection, GeometryCollection or Feature'
24486
24516
  })
24517
+ .option('no-null-props', {
24518
+ describe: '[GeoJSON] use "properties":{} when a Feature has no data',
24519
+ type: 'flag'
24520
+ })
24487
24521
  .option('hoist', {
24488
24522
  describe: '[GeoJSON] move properties to the root level of each Feature',
24489
24523
  type: 'strings'
@@ -26046,15 +26080,32 @@ ${svg}
26046
26080
  });
26047
26081
 
26048
26082
  parser.command('frame')
26049
- // .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
+ })
26050
26094
  .option('bbox', {
26051
26095
  describe: 'frame coordinates (xmin,ymin,xmax,ymax)',
26052
26096
  type: 'bbox'
26053
26097
  })
26054
- // .option('offset', offsetOpt)
26055
- .option('width', {
26056
- 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'
26057
26101
  })
26102
+ .option('offsets', {
26103
+ describe: 'separate offsets for each side, in l,b,r,t order',
26104
+ type: 'strings'
26105
+ })
26106
+ .option('name', nameOpt)
26107
+ .option('target', targetOpt);
26108
+
26058
26109
  // .option('height', {
26059
26110
  // describe: 'pixel height of output (may be a range)'
26060
26111
  // })
@@ -26065,7 +26116,6 @@ ${svg}
26065
26116
  // .option('source', {
26066
26117
  // describe: 'name of layer to enclose'
26067
26118
  // })
26068
- .option('name', nameOpt);
26069
26119
 
26070
26120
  parser.command('fuzzy-join')
26071
26121
  .describe('join points to polygons, with data fill and fuzzy match')
@@ -35762,59 +35812,10 @@ ${svg}
35762
35812
  getColorizerFunction: getColorizerFunction
35763
35813
  });
35764
35814
 
35765
- cmd.comment = function() {}; // no-op, so -comment doesn't trigger a parsing error
35766
-
35767
- function expressionUsesGeoJSON(exp) {
35768
- return exp.includes('this.geojson');
35769
- }
35770
-
35771
- function getFeatureEditor(lyr, dataset) {
35772
- var api = {};
35773
- // need to copy attribute to avoid circular references if geojson is assigned
35774
- // to a data property.
35775
- var copy = copyLayer(lyr);
35776
- var features = exportLayerAsGeoJSON(copy, dataset, {rfc7946: true}, true);
35777
- var features2 = [];
35778
-
35779
- api.get = function(i) {
35780
- if (i > 0) features[i-1] = null; // garbage-collect old features
35781
- return features[i];
35782
- };
35783
-
35784
- api.set = function(feat, i) {
35785
- var arr;
35786
-
35787
- if (utils.isString(feat)) {
35788
- feat = JSON.parse(feat);
35789
- }
35790
-
35791
- if (!feat) return;
35792
-
35793
- if (feat.type == 'GeometryCollection') {
35794
- arr = feat.geometries.map(geom => GeoJSON.toFeature(geom));
35795
- } else if (feat.type == 'FeatureCollection') {
35796
- arr = feat.features;
35797
- } else {
35798
- feat = GeoJSON.toFeature(feat);
35799
- }
35800
-
35801
- if (arr) {
35802
- features2 = features2.concat(arr);
35803
- } else {
35804
- features2.push(feat);
35805
- }
35806
- };
35807
-
35808
- api.done = function() {
35809
- if (features2.length === 0) return; // read-only expression
35810
- var geojson = {
35811
- type: 'FeatureCollection',
35812
- features: features2
35813
- };
35814
- return importGeoJSON(geojson);
35815
- };
35816
- return api;
35817
- }
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
35818
35819
 
35819
35820
  cmd.dashlines = function(lyr, dataset, opts) {
35820
35821
  var crs = getDatasetCRS(dataset);
@@ -37780,6 +37781,76 @@ ${svg}
37780
37781
  }
37781
37782
  };
37782
37783
 
37784
+ function expressionUsesGeoJSON(exp) {
37785
+ return exp.includes('this.geojson') || exp.includes('this.geometry') || exp.includes('this.feature');
37786
+ }
37787
+
37788
+ function getFeatureEditor(lyr, dataset) {
37789
+ var api = {};
37790
+ // need to copy attribute to avoid circular references if geojson is assigned
37791
+ // to a data property.
37792
+ var copy = copyLayer(lyr);
37793
+ var features = exportLayerAsGeoJSON(copy, dataset, {rfc7946: true}, true);
37794
+ var features2 = [];
37795
+
37796
+ api.get = function(i) {
37797
+ if (i > 0) features[i-1] = null; // garbage-collect old features
37798
+ return features[i];
37799
+ };
37800
+
37801
+ api.getGeometry = function(i) {
37802
+ if (i > 0) features[i-1] = null; // garbage-collect old features
37803
+ return features[i] ? features[i].geometry : null;
37804
+ };
37805
+
37806
+ api.setGeometry = function(geom, i) {
37807
+ if (utils.isString(geom)) {
37808
+ geom = JSON.parse(geom);
37809
+ }
37810
+ // TODO: validate? validate geometry in feature setter?
37811
+ var feat = {
37812
+ type: 'Feature',
37813
+ properties: features[i] ? features[i].properties : null,
37814
+ geometry: geom || null
37815
+ };
37816
+ api.set(feat, i);
37817
+ };
37818
+
37819
+ api.set = function(feat, i) {
37820
+ var arr;
37821
+
37822
+ if (utils.isString(feat)) {
37823
+ feat = JSON.parse(feat);
37824
+ }
37825
+
37826
+ if (!feat) return;
37827
+
37828
+ if (feat.type == 'GeometryCollection') {
37829
+ arr = feat.geometries.map(geom => GeoJSON.toFeature(geom));
37830
+ } else if (feat.type == 'FeatureCollection') {
37831
+ arr = feat.features;
37832
+ } else {
37833
+ feat = GeoJSON.toFeature(feat);
37834
+ }
37835
+
37836
+ if (arr) {
37837
+ features2 = features2.concat(arr);
37838
+ } else {
37839
+ features2.push(feat);
37840
+ }
37841
+ };
37842
+
37843
+ api.done = function() {
37844
+ if (features2.length === 0) return; // read-only expression
37845
+ var geojson = {
37846
+ type: 'FeatureCollection',
37847
+ features: features2
37848
+ };
37849
+ return importGeoJSON(geojson);
37850
+ };
37851
+ return api;
37852
+ }
37853
+
37783
37854
  cmd.filterGeom = function(lyr, arcs, opts) {
37784
37855
  if (!layerHasGeometry(lyr)) {
37785
37856
  stop("Layer is missing geometry");
@@ -38122,46 +38193,637 @@ ${svg}
38122
38193
  return parsed[0];
38123
38194
  }
38124
38195
 
38125
- cmd.frame = function(catalog, source, opts) {
38126
- var size, bounds, tmp, dataset;
38127
- if (+opts.width > 0 === false && +opts.pixels > 0 === false) {
38128
- stop("Missing a width or area");
38129
- }
38130
- if (opts.width && opts.height) {
38131
- opts = utils.extend({}, opts);
38132
- // Height is a string containing either a number or a
38133
- // comma-sep. pair of numbers (range); here we convert height to
38134
- // an aspect-ratio parameter for the rectangle() function
38135
- opts.aspect_ratio = getAspectRatioArg(opts.width, opts.height);
38136
- // TODO: currently returns max,min aspect ratio, should return in min,max order
38137
- // (rectangle() function should handle max,min argument correctly now anyway)
38138
- }
38139
- tmp = cmd.rectangle(source, opts);
38140
- bounds = getDatasetBounds(tmp);
38141
- if (probablyDecimalDegreeBounds(bounds)) {
38142
- stop('Frames require projected, not geographical coordinates');
38143
- } else if (!getDatasetCRS(tmp)) {
38144
- message('Warning: missing projection data. Assuming coordinates are meters and k (scale factor) is 1');
38145
- }
38146
- size = getFrameSize(bounds, opts);
38147
- if (size[0] > 0 === false) {
38148
- stop('Missing a valid frame width');
38149
- }
38150
- if (size[1] > 0 === false) {
38151
- stop('Missing a valid frame height');
38152
- }
38153
- dataset = {info: {}, layers:[{
38154
- name: opts.name || 'frame',
38155
- data: new DataTable([{
38156
- width: size[0],
38157
- height: size[1],
38158
- bbox: bounds.toArray(),
38159
- type: 'frame'
38160
- }])
38161
- }]};
38162
- 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;
38594
+ };
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);
38163
38737
  };
38164
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
+ }
38165
38827
 
38166
38828
  // Convert width and height args to aspect ratio arg for the rectangle() function
38167
38829
  function getAspectRatioArg(widthArg, heightArg) {
@@ -38175,21 +38837,6 @@ ${svg}
38175
38837
  }).reverse().join(',');
38176
38838
  }
38177
38839
 
38178
- // export function renderFrame(d) {
38179
- // var lineWidth = 1,
38180
- // // inset stroke by half of line width
38181
- // off = lineWidth / 2,
38182
- // obj = importPolygon([[[off, off], [off, d.height - off],
38183
- // [d.width - off, d.height - off],
38184
- // [d.width - off, off], [off, off]]]);
38185
- // utils.extend(obj.properties, {
38186
- // fill: 'none',
38187
- // stroke: d.stroke || 'black',
38188
- // 'stroke-width': d['stroke-width'] || lineWidth
38189
- // });
38190
- // return [obj];
38191
- // }
38192
-
38193
38840
  var Frame = /*#__PURE__*/Object.freeze({
38194
38841
  __proto__: null,
38195
38842
  getAspectRatioArg: getAspectRatioArg
@@ -38643,473 +39290,6 @@ ${svg}
38643
39290
  return maxValue;
38644
39291
  }
38645
39292
 
38646
- // Returns number of arcs that were removed
38647
- function editArcs(arcs, onPoint) {
38648
- var nn2 = [],
38649
- xx2 = [],
38650
- yy2 = [],
38651
- errors = 0,
38652
- n;
38653
-
38654
- arcs.forEach(function(arc, i) {
38655
- editArc(arc, onPoint);
38656
- });
38657
- arcs.updateVertexData(nn2, xx2, yy2);
38658
- return errors;
38659
-
38660
- function append(p) {
38661
- if (p) {
38662
- xx2.push(p[0]);
38663
- yy2.push(p[1]);
38664
- n++;
38665
- }
38666
- }
38667
-
38668
- function editArc(arc, cb) {
38669
- var x, y, xp, yp, retn;
38670
- var valid = true;
38671
- var i = 0;
38672
- n = 0;
38673
- while (arc.hasNext()) {
38674
- x = arc.x;
38675
- y = arc.y;
38676
- retn = cb(append, x, y, xp, yp, i++);
38677
- if (retn === false) {
38678
- valid = false;
38679
- // assumes that it's ok for the arc iterator to be interrupted.
38680
- break;
38681
- }
38682
- xp = x;
38683
- yp = y;
38684
- }
38685
- if (valid && n == 1) {
38686
- // only one valid point was added to this arc (invalid)
38687
- // e.g. this could happen during reprojection.
38688
- // making this arc empty
38689
- // error("An invalid arc was created");
38690
- message("An invalid arc was created");
38691
- valid = false;
38692
- }
38693
- if (valid) {
38694
- nn2.push(n);
38695
- } else {
38696
- // remove any points that were added for an invalid arc
38697
- while (n-- > 0) {
38698
- xx2.pop();
38699
- yy2.pop();
38700
- }
38701
- nn2.push(0); // add empty arc (to preserve mapping from paths to arcs)
38702
- errors++;
38703
- }
38704
- }
38705
- }
38706
-
38707
- function DatasetEditor(dataset) {
38708
- var layers = [];
38709
- var arcs = [];
38710
-
38711
- this.done = function() {
38712
- dataset.layers = layers;
38713
- if (arcs.length) {
38714
- dataset.arcs = new ArcCollection(arcs);
38715
- buildTopology(dataset);
38716
- }
38717
- };
38718
-
38719
- this.editLayer = function(lyr, cb) {
38720
- var type = lyr.geometry_type;
38721
- if (dataset.layers.indexOf(lyr) != layers.length) {
38722
- error('Layer was edited out-of-order');
38723
- }
38724
- if (!type) {
38725
- layers.push(lyr);
38726
- return;
38727
- }
38728
- var shapes = lyr.shapes.map(function(shape, shpId) {
38729
- var shape2 = [], retn, input;
38730
- for (var i=0, n=shape ? shape.length : 0; i<n; i++) {
38731
- input = type == 'point' ? shape[i] : idsToCoords(shape[i]);
38732
- retn = cb(input, i, shape);
38733
- if (!Array.isArray(retn)) continue;
38734
- if (type == 'point') {
38735
- shape2.push(retn);
38736
- } else if (type == 'polygon' || type == 'polyline') {
38737
- extendPathShape(shape2, retn || []);
38738
- }
38739
- }
38740
- return shape2.length > 0 ? shape2 : null;
38741
- });
38742
- layers.push(Object.assign(lyr, {shapes: shapes}));
38743
- };
38744
-
38745
- function extendPathShape(shape, parts) {
38746
- for (var i=0; i<parts.length; i++) {
38747
- shape.push([arcs.length]);
38748
- arcs.push(parts[i]);
38749
- }
38750
- }
38751
-
38752
- function idsToCoords(ids) {
38753
- var coords = [];
38754
- var iter = dataset.arcs.getShapeIter(ids);
38755
- while (iter.hasNext()) {
38756
- coords.push([iter.x, iter.y]);
38757
- }
38758
- return coords;
38759
- }
38760
- }
38761
-
38762
- // Planar densification by an interval
38763
- function densifyPathByInterval(coords, interval, interpolate) {
38764
- if (findMaxPathInterval(coords) < interval) return coords;
38765
- if (!interpolate) {
38766
- interpolate = getIntervalInterpolator(interval);
38767
- }
38768
- var coords2 = [coords[0]], a, b;
38769
- for (var i=1, n=coords.length; i<n; i++) {
38770
- a = coords[i-1];
38771
- b = coords[i];
38772
- if (geom.distance2D(a[0], a[1], b[0], b[1]) > interval + 1e-4) {
38773
- appendArr(coords2, interpolate(a, b));
38774
- }
38775
- coords2.push(b);
38776
- }
38777
- return coords2;
38778
- }
38779
-
38780
- function getIntervalInterpolator(interval) {
38781
- return function(a, b) {
38782
- var points = [];
38783
- // var rev = a[0] == b[0] ? a[1] > b[1] : a[0] > b[0];
38784
- var dist = geom.distance2D(a[0], a[1], b[0], b[1]);
38785
- var n = Math.round(dist / interval) - 1;
38786
- var dx = (b[0] - a[0]) / (n + 1),
38787
- dy = (b[1] - a[1]) / (n + 1);
38788
- for (var i=1; i<=n; i++) {
38789
- points.push([a[0] + dx * i, a[1] + dy * i]);
38790
- }
38791
- return points;
38792
- };
38793
- }
38794
-
38795
-
38796
- // Interpolate the same points regardless of segment direction
38797
- function densifyAntimeridianSegment(a, b, interval) {
38798
- var y1, y2;
38799
- var coords = [];
38800
- var ascending = a[1] < b[1];
38801
- if (a[0] != b[0]) error('Expected an edge segment');
38802
- if (interval > 0 === false) error('Expected a positive interval');
38803
- if (ascending) {
38804
- y1 = a[1];
38805
- y2 = b[1];
38806
- } else {
38807
- y1 = b[1];
38808
- y2 = a[1];
38809
- }
38810
- var y = Math.floor(y1 / interval) * interval + interval;
38811
- while (y < y2) {
38812
- coords.push([a[0], y]);
38813
- y += interval;
38814
- }
38815
- if (!ascending) coords.reverse();
38816
- return coords;
38817
- }
38818
-
38819
- function appendArr(dest, src) {
38820
- for (var i=0; i<src.length; i++) dest.push(src[i]);
38821
- }
38822
-
38823
- function findMaxPathInterval(coords) {
38824
- var maxSq = 0, intSq, a, b;
38825
- for (var i=1, n=coords.length; i<n; i++) {
38826
- a = coords[i-1];
38827
- b = coords[i];
38828
- intSq = geom.distanceSq(a[0], a[1], b[0], b[1]);
38829
- if (intSq > maxSq) maxSq = intSq;
38830
- }
38831
- return Math.sqrt(maxSq);
38832
- }
38833
-
38834
- function projectAndDensifyArcs(arcs, proj) {
38835
- var interval = getDefaultDensifyInterval(arcs, proj);
38836
- var minIntervalSq = interval * interval * 25;
38837
- var p;
38838
- return editArcs(arcs, onPoint);
38839
-
38840
- function onPoint(append, lng, lat, prevLng, prevLat, i) {
38841
- var pp = p;
38842
- p = proj(lng, lat);
38843
- if (!p) return false; // signal that current arc contains an error
38844
-
38845
- // Don't try to densify shorter segments (optimization)
38846
- if (i > 0 && geom.distanceSq(p[0], p[1], pp[0], pp[1]) > minIntervalSq) {
38847
- densifySegment(prevLng, prevLat, pp[0], pp[1], lng, lat, p[0], p[1], proj, interval)
38848
- .forEach(append);
38849
- }
38850
- append(p);
38851
- }
38852
- }
38853
-
38854
- // Use the median of intervals computed by projecting segments.
38855
- // We're probing a number of points, because @proj might only be valid in
38856
- // a sub-region of the dataset bbox (e.g. +proj=tpers)
38857
- function findDensifyInterval(bounds, xy, proj) {
38858
- var steps = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
38859
- var points = [];
38860
- for (var i=0; i<steps.length; i++) {
38861
- for (var j=0; j<steps.length; j++) {
38862
- points.push([steps[i], steps[j]]);
38863
- }
38864
- }
38865
- var intervals = points.map(function(pos) {
38866
- var x = bounds.xmin + bounds.width() * pos[0];
38867
- var y = bounds.ymin + bounds.height() * pos[1];
38868
- var a = proj(x, y);
38869
- var b = proj(x + xy[0], y + xy[1]);
38870
- return a && b ? geom.distance2D(a[0], a[1], b[0], b[1]) : Infinity;
38871
- }).filter(function(int) {return int < Infinity;});
38872
- return intervals.length > 0 ? utils.findMedian(intervals) : Infinity;
38873
- }
38874
-
38875
- // Kludgy way to get a useful interval for densifying a bounding box.
38876
- // Uses a fraction of average bbox side length)
38877
- // TODO: improve
38878
- function findDensifyInterval2(bb, proj) {
38879
- var a = proj(bb.centerX(), bb.centerY()),
38880
- c = proj(bb.centerX(), bb.ymin), // right center
38881
- d = proj(bb.xmax, bb.centerY()); // bottom center
38882
- var interval = a && c && d ? (geom.distance2D(a[0], a[1], c[0], c[1]) +
38883
- geom.distance2D(a[0], a[1], d[0], d[1])) / 5000 : Infinity;
38884
- return interval;
38885
- }
38886
-
38887
- // Returns an interval in projected units
38888
- function getDefaultDensifyInterval(arcs, proj) {
38889
- var xy = getAvgSegment2(arcs),
38890
- bb = arcs.getBounds(),
38891
- intervalA = findDensifyInterval(bb, xy, proj),
38892
- intervalB = findDensifyInterval2(bb, proj),
38893
- interval = Math.min(intervalA, intervalB);
38894
- if (interval == Infinity) {
38895
- error('Densification error');
38896
- }
38897
- return interval;
38898
- }
38899
-
38900
- // Interpolate points into a projected line segment if needed to prevent large
38901
- // deviations from path of original unprojected segment.
38902
- // @points (optional) array of accumulated points
38903
- function densifySegment(lng0, lat0, x0, y0, lng2, lat2, x2, y2, proj, interval, points) {
38904
- // Find midpoint between two endpoints and project it (assumes longitude does
38905
- // not wrap). TODO Consider bisecting along great circle path -- although this
38906
- // would not be good for boundaries that follow line of constant latitude.
38907
- var lng1 = (lng0 + lng2) / 2,
38908
- lat1 = (lat0 + lat2) / 2,
38909
- p = proj(lng1, lat1),
38910
- distSq;
38911
- if (!p) return; // TODO: consider if this is adequate for handling proj. errors
38912
- distSq = geom.pointSegDistSq2(p[0], p[1], x0, y0, x2, y2); // sq displacement
38913
- points = points || [];
38914
- // Bisect current segment if the projected midpoint deviates from original
38915
- // segment by more than the @interval parameter.
38916
- // ... but don't bisect very small segments to prevent infinite recursion
38917
- // (e.g. if projection function is discontinuous)
38918
- if (distSq > interval * interval * 0.25 && geom.distance2D(lng0, lat0, lng2, lat2) > 0.01) {
38919
- densifySegment(lng0, lat0, x0, y0, lng1, lat1, p[0], p[1], proj, interval, points);
38920
- points.push(p);
38921
- densifySegment(lng1, lat1, p[0], p[1], lng2, lat2, x2, y2, proj, interval, points);
38922
- }
38923
- return points;
38924
- }
38925
-
38926
- // Create rectangles around each feature in a layer
38927
- cmd.rectangles = function(targetLyr, targetDataset, opts) {
38928
- var crsInfo = getDatasetCrsInfo(targetDataset);
38929
- var records = targetLyr.data ? targetLyr.data.getRecords() : null;
38930
- var geometries;
38931
-
38932
- if (opts.bbox) {
38933
- geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset);
38934
-
38935
- } else {
38936
- if (!layerHasGeometry(targetLyr)) {
38937
- stop("Layer is missing geometric shapes");
38938
- }
38939
- geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
38940
- }
38941
-
38942
- var geojson = {
38943
- type: 'FeatureCollection',
38944
- features: geometries.map(function(geom, i) {
38945
- var rec = records && records[i] || null;
38946
- if (rec && opts.no_replace) {
38947
- rec = utils.extend({}, rec); // make a copy
38948
- }
38949
- return {
38950
- type: 'Feature',
38951
- properties: rec,
38952
- geometry: geom
38953
- };
38954
- })
38955
- };
38956
- var dataset = importGeoJSON(geojson, {});
38957
- setDatasetCrsInfo(dataset, crsInfo);
38958
- var outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
38959
- setOutputLayerName(outputLayers[0], targetLyr, null, opts);
38960
- return outputLayers;
38961
- };
38962
-
38963
- function shapesToBoxGeometries(lyr, dataset, opts) {
38964
- var crsInfo = getDatasetCrsInfo(dataset);
38965
- return lyr.shapes.map(function(shp) {
38966
- var bounds = lyr.geometry_type == 'point' ?
38967
- getPointFeatureBounds(shp) : dataset.arcs.getMultiShapeBounds(shp);
38968
- bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
38969
- if (!bounds) return null;
38970
- return bboxToPolygon(bounds.toArray(), opts);
38971
- });
38972
- }
38973
-
38974
- function bboxExpressionToGeometries(exp, lyr, dataset, opts) {
38975
- var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, {});
38976
- var n = getFeatureCount(lyr);
38977
- var result;
38978
- var geometries = [];
38979
- for (var i=0; i<n; i++) {
38980
- result = compiled(i);
38981
- if (!looksLikeBbox(result)) {
38982
- stop('Invalid bbox value (expected a GeoJSON-type bbox):', result);
38983
- }
38984
- geometries.push(bboxToPolygon(result));
38985
- }
38986
- return geometries;
38987
- }
38988
-
38989
- function looksLikeBbox(o) {
38990
- if (!o || o.length != 4) return false;
38991
- if (o.some(isNaN)) return false;
38992
- if (o[0] <= o[2] == false || o[1] <= o[3] == false) return false;
38993
- return true;
38994
- }
38995
-
38996
- // Create rectangles around one or more target layers
38997
- //
38998
- cmd.rectangle2 = function(target, opts) {
38999
- // if target layer is a rectangle and we're applying frame properties,
39000
- // turn the target into a frame instead of creating a new rectangle
39001
- if (target.layers.length == 1 && opts.width &&
39002
- layerIsRectangle(target.layers[0], target.dataset.arcs)) {
39003
- applyFrameProperties(target.layers[0], opts);
39004
- return;
39005
- }
39006
- var datasets = target.layers.map(function(lyr) {
39007
- var dataset = cmd.rectangle({layer: lyr, dataset: target.dataset}, opts);
39008
- setOutputLayerName(dataset.layers[0], lyr, null, opts);
39009
- if (!opts.no_replace) {
39010
- dataset.layers[0].name = lyr.name || dataset.layers[0].name;
39011
- }
39012
- return dataset;
39013
- });
39014
- return mergeDatasetsIntoDataset(target.dataset, datasets);
39015
- };
39016
-
39017
- cmd.rectangle = function(target, opts) {
39018
- var bounds, crsInfo;
39019
- if (opts.bbox) {
39020
- bounds = new Bounds(opts.bbox);
39021
- crsInfo = target && getDatasetCrsInfo(target.dataset) ||
39022
- probablyDecimalDegreeBounds(bounds) && getCrsInfo('wgs84') || {};
39023
- } else if (target) {
39024
- bounds = getLayerBounds(target.layer, target.dataset.arcs);
39025
- crsInfo = getDatasetCrsInfo(target.dataset);
39026
- }
39027
- bounds = bounds && applyRectangleOptions(bounds, crsInfo.crs, opts);
39028
- if (!bounds || !bounds.hasBounds()) {
39029
- stop('Missing rectangle extent');
39030
- }
39031
- var feature = {
39032
- type: 'Feature',
39033
- properties: {},
39034
- geometry: bboxToPolygon(bounds.toArray(), opts)
39035
- };
39036
- var dataset = importGeoJSON(feature, {});
39037
- applyFrameProperties(dataset.layers[0], opts);
39038
- dataset.layers[0].name = opts.name || 'rectangle';
39039
- setDatasetCrsInfo(dataset, crsInfo);
39040
- return dataset;
39041
- };
39042
-
39043
- function applyFrameProperties(lyr, opts) {
39044
- if (!opts.width) return;
39045
- if (!lyr.data) initDataTable(lyr);
39046
- var d = lyr.data.getRecords()[0] || {};
39047
- d.width = parseSizeParam(opts.width);
39048
- d.type = 'frame';
39049
- }
39050
-
39051
- function applyRectangleOptions(bounds, crs, opts) {
39052
- var isGeoBox = probablyDecimalDegreeBounds(bounds);
39053
- if (opts.offset) {
39054
- bounds = applyBoundsOffset(opts.offset, bounds, crs);
39055
- }
39056
- if (bounds.area() > 0 === false) return null;
39057
- if (opts.aspect_ratio) {
39058
- bounds = applyAspectRatio(opts.aspect_ratio, bounds);
39059
- }
39060
- if (isGeoBox) {
39061
- bounds = clampToWorldBounds(bounds);
39062
- }
39063
- return bounds;
39064
- }
39065
-
39066
- // opt: aspect ratio as a single number or a range (e.g. "1,2");
39067
- function applyAspectRatio(opt, bounds) {
39068
- var range = String(opt).split(',').map(parseFloat),
39069
- aspectRatio = bounds.width() / bounds.height(),
39070
- min, max; // min is height limit, max is width limit
39071
- if (range.length == 1) {
39072
- range.push(range[0]);
39073
- } else if (range[0] > range[1]) {
39074
- range.reverse();
39075
- }
39076
- min = range[0];
39077
- max = range[1];
39078
- if (!min && !max) return bounds;
39079
- if (!min) min = -Infinity;
39080
- if (!max) max = Infinity;
39081
- if (aspectRatio < min) {
39082
- bounds.fillOut(min);
39083
- } else if (aspectRatio > max) {
39084
- bounds.fillOut(max);
39085
- }
39086
- return bounds;
39087
- }
39088
-
39089
- function applyBoundsOffset(offsetOpt, bounds, crs) {
39090
- var offsets = convertFourSides(offsetOpt, crs, bounds);
39091
- bounds.padBounds(offsets[0], offsets[1], offsets[2], offsets[3]);
39092
- return bounds;
39093
- }
39094
-
39095
- function bboxToPolygon(bbox, optsArg) {
39096
- var opts = optsArg || {};
39097
- var coords = bboxToCoords(bbox);
39098
- if (opts.interval > 0) {
39099
- coords = densifyPathByInterval(coords, opts.interval);
39100
- }
39101
- return {
39102
- type: 'Polygon',
39103
- coordinates: [coords]
39104
- };
39105
- }
39106
-
39107
- var Rectangle = /*#__PURE__*/Object.freeze({
39108
- __proto__: null,
39109
- applyAspectRatio: applyAspectRatio,
39110
- bboxToPolygon: bboxToPolygon
39111
- });
39112
-
39113
39293
  function getSemiMinorAxis(P) {
39114
39294
  return P.a * Math.sqrt(1 - (P.es || 0));
39115
39295
  }
@@ -45223,14 +45403,16 @@ ${svg}
45223
45403
  }
45224
45404
 
45225
45405
  function commandAcceptsMultipleTargetDatasets(name) {
45226
- return name == 'rotate' || name == 'info' || name == 'proj' || name == 'require' ||
45227
- name == 'drop' || name == 'target' || name == 'if' || name == 'elif' ||
45228
- 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';
45229
45411
  }
45230
45412
 
45231
45413
  function commandAcceptsEmptyTarget(name) {
45232
45414
  return name == 'graticule' || name == 'i' || name == 'help' ||
45233
- name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
45415
+ name == 'point-grid' || name == 'shape' || name == 'rectangle' || name == 'frame' ||
45234
45416
  name == 'require' || name == 'run' || name == 'define' ||
45235
45417
  name == 'include' || name == 'print' || name == 'comment' || name == 'if' || name == 'elif' ||
45236
45418
  name == 'else' || name == 'endif' || name == 'stop' || name == 'add-shape' ||
@@ -45254,12 +45436,14 @@ ${svg}
45254
45436
  }
45255
45437
 
45256
45438
  if (name == 'comment') {
45439
+ cmd.comment(opts);
45257
45440
  return done(null);
45258
45441
  }
45259
45442
 
45260
45443
  if (!job) job = new Job();
45261
45444
  job.startCommand(command);
45262
45445
 
45446
+
45263
45447
  try { // catch errors from synchronous functions
45264
45448
  T$1.start();
45265
45449
 
@@ -45358,8 +45542,8 @@ ${svg}
45358
45542
  } else if (name == 'colorizer') {
45359
45543
  outputLayers = cmd.colorizer(opts);
45360
45544
 
45361
- } else if (name == 'comment') {
45362
- // no-op
45545
+ // } else if (name == 'comment') {
45546
+ // // no-op
45363
45547
 
45364
45548
  } else if (name == 'dashlines') {
45365
45549
  applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
@@ -45417,9 +45601,8 @@ ${svg}
45417
45601
  } else if (name == 'filter-slivers') {
45418
45602
  applyCommandToEachLayer(cmd.filterSlivers, targetLayers, targetDataset, opts);
45419
45603
 
45420
- // // 'frame' and 'rectangle' have merged
45421
- // } else if (name == 'frame') {
45422
- // cmd.frame(job.catalog, source, opts);
45604
+ } else if (name == 'frame') {
45605
+ cmd.frame(job.catalog, targets, opts);
45423
45606
 
45424
45607
  } else if (name == 'fuzzy-join') {
45425
45608
  applyCommandToEachLayer(cmd.fuzzyJoin, targetLayers, arcs, source, opts);
@@ -45521,10 +45704,7 @@ ${svg}
45521
45704
  cmd.proj(targ.dataset, job.catalog, opts);
45522
45705
  });
45523
45706
 
45524
- } else if (name == 'rectangle' || name == 'frame') {
45525
- if (name == 'frame' && !opts.width) {
45526
- stop('Command requires a width= argument');
45527
- }
45707
+ } else if (name == 'rectangle') {
45528
45708
  if (source || opts.bbox || targets.length === 0) {
45529
45709
  job.catalog.addDataset(cmd.rectangle(source || targets?.[0], opts));
45530
45710
  } else {
@@ -45690,7 +45870,7 @@ ${svg}
45690
45870
  });
45691
45871
  }
45692
45872
 
45693
- var version = "0.6.98";
45873
+ var version = "0.6.100";
45694
45874
 
45695
45875
  // Parse command line args into commands and run them
45696
45876
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -45841,13 +46021,16 @@ ${svg}
45841
46021
 
45842
46022
  function nextGroup(prevJob, commands, next) {
45843
46023
  runParsedCommands(commands, new Job(), function(err, job) {
45844
- err = filterError(err);
46024
+ err = handleNonFatalError(err);
45845
46025
  next(err, job);
45846
46026
  });
45847
46027
  }
45848
46028
 
45849
46029
  function done(err, job) {
45850
- err = filterError(err);
46030
+ if (job && inControlBlock(job)) {
46031
+ message('Warning: -if command is missing a matching -endif');
46032
+ }
46033
+ err = handleNonFatalError(err);
45851
46034
  if (err) printError(err);
45852
46035
  callback(err, job);
45853
46036
  }
@@ -45924,7 +46107,7 @@ ${svg}
45924
46107
  }
45925
46108
  }
45926
46109
 
45927
- function filterError(err) {
46110
+ function handleNonFatalError(err) {
45928
46111
  if (err && err.name == 'NonFatalError') {
45929
46112
  printError(err);
45930
46113
  return null;