diff --git a/mapshaper-gui.js b/mapshaper-gui.js
index 9b505327..7dc0139b 100644
--- a/mapshaper-gui.js
+++ b/mapshaper-gui.js
@@ -876,16 +876,36 @@
};
// Filter out delayed click events, e.g. so users can highlight and copy text
+ // Filter out context menu clicks
GUI.onClick = function(el, cb) {
var time;
el.on('mousedown', function() {
time = +new Date();
});
el.on('mouseup', function(e) {
- if (+new Date() - time < 300) cb(e);
+ if (looksLikeContextClick(e)) {
+ return;
+ }
+ if (+new Date() - time < 300) {
+ cb(e);
+ }
+ });
+ };
+
+ GUI.onContextClick = function(el, cb) {
+ el.on('mouseup', function(e) {
+ if (looksLikeContextClick(e)) {
+ e.stopPropagation();
+ e.preventDefault();
+ cb(e);
+ }
});
};
+ function looksLikeContextClick(e) {
+ return e.button > 1 || e.ctrlKey;
+ }
+
// tests if filename is a type that can be used
// GUI.isReadableFileType = function(filename) {
// return !!internal.guessInputFileType(filename) || internal.couldBeDsvFile(filename) ||
@@ -5003,6 +5023,201 @@
};
}
+ var openMenu;
+
+ document.addEventListener('mousedown', function(e) {
+ if (e.target.classList.contains('contextmenu-item')) {
+ return; // don't close menu if clicking on a menu link
+ }
+ closeOpenMenu();
+ });
+
+ function closeOpenMenu() {
+ if (openMenu) {
+ openMenu.close();
+ openMenu = null;
+ }
+ }
+
+ function openContextMenu(e, lyr, parent) {
+ var menu = new ContextMenu(parent);
+ closeOpenMenu();
+ menu.open(e, lyr);
+ }
+
+ function ContextMenu(parentArg) {
+ var body = document.querySelector('body');
+ var parent = parentArg || body;
+ // var menu = El('div').addClass('contextmenu rollover').appendTo(body);
+ var menu = El('div').addClass('contextmenu rollover').appendTo(parent);
+ var _open = false;
+ var _openCount = 0;
+
+ this.isOpen = function() {
+ return _open;
+ };
+
+ this.close = close;
+
+ function close() {
+ var count = _openCount;
+ if (!_open) return;
+ setTimeout(function() {
+ if (count == _openCount) {
+ menu.hide();
+ _open = false;
+ }
+ }, 200);
+ }
+
+ function addMenuItem(label, func, prefixArg) {
+ var prefix = prefixArg === undefined ? '• ' : prefixArg;
+ var item = El('div')
+ .appendTo(menu)
+ .addClass('contextmenu-item')
+ .html(prefix + label)
+ .show();
+
+ GUI.onClick(item, function(e) {
+ func();
+ closeOpenMenu();
+ });
+ }
+
+ function addMenuLabel(label) {
+ El('div')
+ .appendTo(menu)
+ .addClass('contextmenu-label')
+ .html(label);
+ }
+
+ this.open = function(e, lyr) {
+ var copyable = e.ids?.length;
+ if (_open) close();
+ menu.empty();
+
+ if (openMenu && openMenu != this) {
+ openMenu.close();
+ }
+ openMenu = this;
+
+ if (e.deleteLayer) {
+ addMenuItem('delete layer', e.deleteLayer, '');
+ }
+ if (e.selectLayer) {
+ addMenuItem('select layer', e.selectLayer, '');
+ }
+
+ if (lyr && lyr.gui.geographic) {
+ if (e.deleteVertex || e.deletePoint || copyable || e.deleteFeature) {
+
+ addMenuLabel('selection');
+ if (e.deleteVertex) {
+ addMenuItem('delete vertex', e.deleteVertex);
+ }
+ if (e.deletePoint) {
+ addMenuItem('delete point', e.deletePoint);
+ }
+ if (e.ids?.length) {
+ addMenuItem('copy as GeoJSON', copyGeoJSON);
+ }
+ if (e.deleteFeature) {
+ addMenuItem(getDeleteLabel(), e.deleteFeature);
+ }
+ }
+
+ if (e.lonlat_coordinates) {
+ addMenuLabel('longitude, latitude');
+ addCoords(e.lonlat_coordinates);
+ }
+ if (e.projected_coordinates) {
+ addMenuLabel('x, y');
+ addCoords(e.projected_coordinates);
+ }
+ }
+
+ if (menu.node().childNodes.length === 0) {
+ return;
+ }
+
+ var rspace = body.clientWidth - e.pageX;
+ var offs = getParentOffset();
+ var xoffs = 10;
+ if (rspace > 150) {
+ menu.css('left', e.pageX - offs.left + xoffs + 'px');
+ menu.css('right', null);
+ } else {
+ menu.css('right', (body.clientWidth - e.pageX - offs.left + xoffs) + 'px');
+ menu.css('left', null);
+ }
+ menu.css('top', (e.pageY - offs.top - 15) + 'px');
+ menu.show();
+
+ _open = true;
+ _openCount++;
+
+ function getParentOffset() { // crossbrowser version
+ if (parent == body) {
+ return {top: 0, left: 0};
+ }
+
+ var box = parent.getBoundingClientRect();
+ var docEl = document.documentElement;
+
+ var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
+ var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
+
+ var clientTop = docEl.clientTop || body.clientTop || 0;
+ var clientLeft = docEl.clientLeft || body.clientLeft || 0;
+
+ var top = box.top + scrollTop - clientTop;
+ var left = box.left + scrollLeft - clientLeft;
+
+ return { top: Math.round(top), left: Math.round(left) };
+ }
+
+ function getDeleteLabel() {
+ return 'delete ' + (lyr.geometry_type == 'point' ? 'point' : 'shape');
+ }
+
+ function addCoords(p) {
+ var coordStr = p[0] + ',' + p[1];
+ // var displayStr = '• ' + coordStr.replace(/-/g, '–').replace(',', ', ');
+ var displayStr = coordStr.replace(/-/g, '–').replace(',', ', ');
+ addMenuItem(displayStr, function() {
+ saveFileContentToClipboard(coordStr);
+ });
+ }
+
+ function copyGeoJSON() {
+ var opts = {
+ no_replace: true,
+ ids: e.ids,
+ quiet: true
+ };
+ var dataset = lyr.gui.source.dataset;
+ var layer = mapshaper.cmd.filterFeatures(lyr, dataset.arcs, opts);
+ // the drawing tool can send open paths with 'polygon' geometry type,
+ // should be changed to 'polyline'
+ if (layer.geometry_type == 'polygon' && layerHasOpenPaths(layer, dataset.arcs)) {
+ layer.geometry_type = 'polyline';
+ }
+ var features = internal.exportLayerAsGeoJSON(layer, dataset, {rfc7946: true, prettify: true}, true, 'string');
+ var str = internal.geojson.formatCollection({"type": "FeatureCollection"}, features);
+ saveFileContentToClipboard(str);
+ }
+ };
+ }
+
+
+ function layerHasOpenPaths(layer, arcs) {
+ var retn = false;
+ internal.editShapes(layer.shapes, function(part) {
+ if (!geom.pathIsClosed(part, arcs)) retn = true;
+ });
+ return retn;
+ }
+
function LayerControl(gui) {
var model = gui.model;
var map = gui.map;
@@ -5210,7 +5425,7 @@
html = '
';
html += rowHTML('name', '
' + formatLayerNameForDisplay(lyr.name) + '', 'row1');
html += rowHTML('contents', describeLyr(lyr, dataset));
- html += '
';
+ // html += '
';
if (opts.pinnable) {
html += '
';
html += '
';
@@ -5268,16 +5483,35 @@
function initMouseEvents2(entry, id, pinnable) {
initLayerDragging(entry, id);
- // init delete button
- GUI.onClick(entry.findChild('img.close-btn'), function(e) {
+ function deleteLayer() {
var target = findLayerById(id);
- e.stopPropagation();
if (map.isVisibleLayer(target.layer)) {
// TODO: check for double map refresh after model.deleteLayer() below
setLayerPinning(target.layer, false);
}
model.deleteLayer(target.layer, target.dataset);
- });
+ }
+
+ function selectLayer(closeMenu) {
+ var target = findLayerById(id);
+ // don't select if user is typing or dragging
+ if (GUI.textIsSelected() || dragging) return;
+ // undo any temporary hiding when layer is selected
+ target.layer.hidden = false;
+ if (!map.isActiveLayer(target.layer)) {
+ model.selectLayer(target.layer, target.dataset);
+ }
+ // close menu after a delay
+ if (closeMenu === true) setTimeout(function() {
+ gui.clearMode();
+ }, 230);
+ }
+
+ // init delete button
+ // GUI.onClick(entry.findChild('img.close-btn'), function(e) {
+ // e.stopPropagation();
+ // deleteLayer();
+ // });
if (pinnable) {
// init pin button
@@ -5324,18 +5558,15 @@
// init click-to-select
GUI.onClick(entry, function() {
- var target = findLayerById(id);
- // don't select if user is typing or dragging
- if (GUI.textIsSelected() || dragging) return;
- // undo any temporary hiding when layer is selected
- target.layer.hidden = false;
- if (!map.isActiveLayer(target.layer)) {
- model.selectLayer(target.layer, target.dataset);
- }
- // close menu after a delay
- setTimeout(function() {
- gui.clearMode();
- }, 230);
+ selectLayer(true);
+ });
+
+ GUI.onContextClick(entry, function(e) {
+ e.deleteLayer = deleteLayer;
+ e.selectLayer = selectLayer;
+ // contextMenu.open(e);
+ // openContextMenu(e, null, entry.node())
+ openContextMenu(e, null, null);
});
}
@@ -12964,139 +13195,6 @@ GUI and setting the size and crop of SVG output.
150) {
- menu.css('left', e.pageX + xoffs + 'px');
- menu.css('right', null);
- } else {
- menu.css('right', (body.clientWidth - e.pageX + xoffs) + 'px');
- menu.css('left', null);
- }
- menu.css('top', (e.pageY - 15) + 'px');
- menu.show();
-
- function getDeleteLabel() {
- return 'delete ' + (lyr.geometry_type == 'point' ? 'point' : 'shape');
- }
-
- function addCoords(p) {
- var coordStr = p[0] + ',' + p[1];
- // var displayStr = '• ' + coordStr.replace(/-/g, '–').replace(',', ', ');
- var displayStr = coordStr.replace(/-/g, '–').replace(',', ', ');
- addMenuItem(displayStr, function() {
- saveFileContentToClipboard(coordStr);
- });
- }
-
- function copyGeoJSON() {
- var opts = {
- no_replace: true,
- ids: e.ids,
- quiet: true
- };
- var dataset = lyr.gui.source.dataset;
- var layer = mapshaper.cmd.filterFeatures(lyr, dataset.arcs, opts);
- // the drawing tool can send open paths with 'polygon' geometry type,
- // should be changed to 'polyline'
- if (layer.geometry_type == 'polygon' && layerHasOpenPaths(layer, dataset.arcs)) {
- layer.geometry_type = 'polyline';
- }
- var features = internal.exportLayerAsGeoJSON(layer, dataset, {rfc7946: true, prettify: true}, true, 'string');
- var str = internal.geojson.formatCollection({"type": "FeatureCollection"}, features);
- saveFileContentToClipboard(str);
- }
- };
- }
-
-
- function layerHasOpenPaths(layer, arcs) {
- var retn = false;
- internal.editShapes(layer.shapes, function(part) {
- if (!geom.pathIsClosed(part, arcs)) retn = true;
- });
- return retn;
- }
-
function loadScript(url, cb) {
var script = document.createElement('script');
script.onload = cb;
diff --git a/mapshaper.js b/mapshaper.js
index ed1bf335..67af9ced 100644
--- a/mapshaper.js
+++ b/mapshaper.js
@@ -5165,12 +5165,12 @@
return getDatasetCrsInfo(dataset).crs;
}
- function requireDatasetsHaveCompatibleCRS(arr) {
+ function requireDatasetsHaveCompatibleCRS(arr, msg) {
arr.reduce(function(memo, dataset) {
var P = getDatasetCRS(dataset);
if (memo && P) {
if (isLatLngCRS(memo) != isLatLngCRS(P)) {
- stop("Unable to combine projected and unprojected datasets");
+ stop(msg || "Unable to combine projected and unprojected datasets");
}
}
return P || memo;
@@ -13640,7 +13640,7 @@
var px = units == 'in' && num * 72 ||
units == 'cm' && Math.round(num * 28.3465) ||
num;
- if (px > 0 === false || !units) {
+ if (px >= 0 === false || !units) {
stop('Invalid size:', str);
}
return px;
@@ -14715,6 +14715,7 @@
}
+
function findCommandTargets(layers, pattern, type) {
var matches = findMatchingLayers(layers, pattern, true);
if (type) {
@@ -18154,6 +18155,9 @@
if (ids) {
obj.id = ids[i];
}
+ if (opts.no_null_props && !obj.properties) {
+ obj.properties = {};
+ }
} else if (!geom) {
return memo; // don't add null objects to GeometryCollection
} else {
@@ -18499,7 +18503,8 @@
var bounds = getLayerBounds(lyr, arcs);
var d = lyr.data.getReadOnlyRecordAt(0);
var w = d.width || 800;
- var h = w * bounds.height() / bounds.width();
+ // prevent rounding errors (like 1000.0000000002)
+ var h = Math.round(w * bounds.height() / bounds.width());
return {
type: 'frame',
width: w,
@@ -18520,6 +18525,7 @@
var pixBounds = calcOutputSizeInPixels(bounds, opts);
return {
bbox: bounds.toArray(),
+ bbox2: pixBounds.toArray(),
width: Math.round(pixBounds.width()),
height: Math.round(pixBounds.height()) || 1,
type: 'frame'
@@ -18934,7 +18940,7 @@
parts.shift();
var colors = [];
var background = parts.pop();
- var spacing = parseInt(parts.pop());
+ var spacing = parseNum(parts.pop());
var tmp;
while (parts.length > 0) {
tmp = parts.pop();
@@ -18945,11 +18951,11 @@
colors.push(tmp);
}
}
- var width = parseInt(parts.pop());
- var dashes = [parseInt(parts.pop()), parseInt(parts.pop())].reverse();
+ var width = parseNum(parts.pop());
+ var dashes = [parseNum(parts.pop()), parseNum(parts.pop())].reverse();
var rotation = 45;
if (parts.length > 0) {
- rotation = parseInt(parts.pop());
+ rotation = parseNum(parts.pop());
}
if (parts.length > 0) {
return null;
@@ -18974,10 +18980,10 @@
// 1px red 1px white 1px black
// -45deg 3 #eee 3 rgb(0,0,0)
parts.shift();
- var rot = parts.length % 2 == 1 ? parseInt(parts.shift()) : 45, // default is 45
+ var rot = parts.length % 2 == 1 ? parseNum(parts.shift()) : 45, // default is 45
colors = [], widths = [];
for (var i=0; i
0 === false) return null;
@@ -18991,7 +18997,7 @@
}
function isSize(str) {
- return parseInt(str) > 0;
+ return parseNum(str) > 0;
}
function parseDots(parts) {
@@ -19004,11 +19010,11 @@
var type = parts.shift();
var rot = 0;
if (isSize(parts[1])) { // if rotation is present, there are two numbers
- rot = parseInt(parts.shift());
+ rot = parseNum(parts.shift());
}
- var size = parseInt(parts.shift());
+ var size = parseNum(parts.shift());
var bg = parts.pop();
- var spacing = parseInt(parts.pop());
+ var spacing = parseNum(parts.pop());
while (parts.length > 0) {
colors.push(parts.shift());
}
@@ -19026,6 +19032,12 @@
};
}
+ function parseNum(str) {
+ // return parseNum(str);
+ // support sub-pixel sizes
+ return parseFloat(str) || 0;
+ }
+
function splitPattern(str) {
// split apart space and comma-delimited tokens
// ... but don't split rgb(...) colors
@@ -20116,7 +20128,7 @@
function fitDatasetToFrame(dataset, frame, opts) {
var bounds = new Bounds(frame.bbox);
- var bounds2 = new Bounds(0, 0, frame.width, frame.height);
+ var bounds2 = frame.bbox2 ? new Bounds(frame.bbox2) : new Bounds(0, 0, frame.width, frame.height);
var fwd = bounds.getTransform(bounds2, opts.invert_y);
transformPoints(dataset, function(x, y) {
return fwd.transform(x, y);
@@ -24502,6 +24514,10 @@ ${svg}
.option('geojson-type', {
describe: '[GeoJSON] FeatureCollection, GeometryCollection or Feature'
})
+ .option('no-null-props', {
+ describe: '[GeoJSON] use "properties":{} when a Feature has no data',
+ type: 'flag'
+ })
.option('hoist', {
describe: '[GeoJSON] move properties to the root level of each Feature',
type: 'strings'
@@ -26064,15 +26080,32 @@ ${svg}
});
parser.command('frame')
- // .describe('create a map frame at a given size')
+ .describe('create a rectangular map frame layer at a given display width')
+ .option('width', {
+ describe: 'width of frame (e.g. 5in, 10cm, 600px; default is 800px)'
+ })
+ .option('height', {
+ describe: '(optional) height of frame; similar to width= option'
+ })
+ .option('aspect-ratio', {
+ describe: '(optional) aspect ratio of frame, if height= or width= is omitted',
+ type: 'number'
+ })
.option('bbox', {
describe: 'frame coordinates (xmin,ymin,xmax,ymax)',
type: 'bbox'
})
- // .option('offset', offsetOpt)
- .option('width', {
- describe: 'width of output (default is 800px)'
+ .option('offset', {
+ describe: 'padding in display units or pct of width, e.g. 5cm 20px 5%',
+ type: 'strings'
+ })
+ .option('offsets', {
+ describe: 'separate offsets for each side, in l,b,r,t order',
+ type: 'strings'
})
+ .option('name', nameOpt)
+ .option('target', targetOpt);
+
// .option('height', {
// describe: 'pixel height of output (may be a range)'
// })
@@ -26083,7 +26116,6 @@ ${svg}
// .option('source', {
// describe: 'name of layer to enclose'
// })
- .option('name', nameOpt);
parser.command('fuzzy-join')
.describe('join points to polygons, with data fill and fuzzy match')
@@ -35780,7 +35812,10 @@ ${svg}
getColorizerFunction: getColorizerFunction
});
- cmd.comment = function() {}; // no-op, so -comment doesn't trigger a parsing error
+ cmd.comment = function(opts) {
+ // TODO: print the comment in verbose mode
+ // message('[comment]', opts.message);
+ }; // no-op, so -comment doesn't trigger a parsing error
cmd.dashlines = function(lyr, dataset, opts) {
var crs = getDatasetCRS(dataset);
@@ -38158,46 +38193,637 @@ ${svg}
return parsed[0];
}
- cmd.frame = function(catalog, source, opts) {
- var size, bounds, tmp, dataset;
- if (+opts.width > 0 === false && +opts.pixels > 0 === false) {
- stop("Missing a width or area");
- }
- if (opts.width && opts.height) {
- opts = utils.extend({}, opts);
- // Height is a string containing either a number or a
- // comma-sep. pair of numbers (range); here we convert height to
- // an aspect-ratio parameter for the rectangle() function
- opts.aspect_ratio = getAspectRatioArg(opts.width, opts.height);
- // TODO: currently returns max,min aspect ratio, should return in min,max order
- // (rectangle() function should handle max,min argument correctly now anyway)
- }
- tmp = cmd.rectangle(source, opts);
- bounds = getDatasetBounds(tmp);
- if (probablyDecimalDegreeBounds(bounds)) {
- stop('Frames require projected, not geographical coordinates');
- } else if (!getDatasetCRS(tmp)) {
- message('Warning: missing projection data. Assuming coordinates are meters and k (scale factor) is 1');
- }
- size = getFrameSize(bounds, opts);
- if (size[0] > 0 === false) {
- stop('Missing a valid frame width');
- }
- if (size[1] > 0 === false) {
- stop('Missing a valid frame height');
- }
- dataset = {info: {}, layers:[{
- name: opts.name || 'frame',
- data: new DataTable([{
- width: size[0],
- height: size[1],
- bbox: bounds.toArray(),
- type: 'frame'
- }])
- }]};
- catalog.addDataset(dataset);
+ // Returns number of arcs that were removed
+ function editArcs(arcs, onPoint) {
+ var nn2 = [],
+ xx2 = [],
+ yy2 = [],
+ errors = 0,
+ n;
+
+ arcs.forEach(function(arc, i) {
+ editArc(arc, onPoint);
+ });
+ arcs.updateVertexData(nn2, xx2, yy2);
+ return errors;
+
+ function append(p) {
+ if (p) {
+ xx2.push(p[0]);
+ yy2.push(p[1]);
+ n++;
+ }
+ }
+
+ function editArc(arc, cb) {
+ var x, y, xp, yp, retn;
+ var valid = true;
+ var i = 0;
+ n = 0;
+ while (arc.hasNext()) {
+ x = arc.x;
+ y = arc.y;
+ retn = cb(append, x, y, xp, yp, i++);
+ if (retn === false) {
+ valid = false;
+ // assumes that it's ok for the arc iterator to be interrupted.
+ break;
+ }
+ xp = x;
+ yp = y;
+ }
+ if (valid && n == 1) {
+ // only one valid point was added to this arc (invalid)
+ // e.g. this could happen during reprojection.
+ // making this arc empty
+ // error("An invalid arc was created");
+ message("An invalid arc was created");
+ valid = false;
+ }
+ if (valid) {
+ nn2.push(n);
+ } else {
+ // remove any points that were added for an invalid arc
+ while (n-- > 0) {
+ xx2.pop();
+ yy2.pop();
+ }
+ nn2.push(0); // add empty arc (to preserve mapping from paths to arcs)
+ errors++;
+ }
+ }
+ }
+
+ function DatasetEditor(dataset) {
+ var layers = [];
+ var arcs = [];
+
+ this.done = function() {
+ dataset.layers = layers;
+ if (arcs.length) {
+ dataset.arcs = new ArcCollection(arcs);
+ buildTopology(dataset);
+ }
+ };
+
+ this.editLayer = function(lyr, cb) {
+ var type = lyr.geometry_type;
+ if (dataset.layers.indexOf(lyr) != layers.length) {
+ error('Layer was edited out-of-order');
+ }
+ if (!type) {
+ layers.push(lyr);
+ return;
+ }
+ var shapes = lyr.shapes.map(function(shape, shpId) {
+ var shape2 = [], retn, input;
+ for (var i=0, n=shape ? shape.length : 0; i 0 ? shape2 : null;
+ });
+ layers.push(Object.assign(lyr, {shapes: shapes}));
+ };
+
+ function extendPathShape(shape, parts) {
+ for (var i=0; i interval + 1e-4) {
+ appendArr(coords2, interpolate(a, b));
+ }
+ coords2.push(b);
+ }
+ return coords2;
+ }
+
+ function getIntervalInterpolator(interval) {
+ return function(a, b) {
+ var points = [];
+ // var rev = a[0] == b[0] ? a[1] > b[1] : a[0] > b[0];
+ var dist = geom.distance2D(a[0], a[1], b[0], b[1]);
+ var n = Math.round(dist / interval) - 1;
+ var dx = (b[0] - a[0]) / (n + 1),
+ dy = (b[1] - a[1]) / (n + 1);
+ for (var i=1; i<=n; i++) {
+ points.push([a[0] + dx * i, a[1] + dy * i]);
+ }
+ return points;
+ };
+ }
+
+
+ // Interpolate the same points regardless of segment direction
+ function densifyAntimeridianSegment(a, b, interval) {
+ var y1, y2;
+ var coords = [];
+ var ascending = a[1] < b[1];
+ if (a[0] != b[0]) error('Expected an edge segment');
+ if (interval > 0 === false) error('Expected a positive interval');
+ if (ascending) {
+ y1 = a[1];
+ y2 = b[1];
+ } else {
+ y1 = b[1];
+ y2 = a[1];
+ }
+ var y = Math.floor(y1 / interval) * interval + interval;
+ while (y < y2) {
+ coords.push([a[0], y]);
+ y += interval;
+ }
+ if (!ascending) coords.reverse();
+ return coords;
+ }
+
+ function appendArr(dest, src) {
+ for (var i=0; i maxSq) maxSq = intSq;
+ }
+ return Math.sqrt(maxSq);
+ }
+
+ function projectAndDensifyArcs(arcs, proj) {
+ var interval = getDefaultDensifyInterval(arcs, proj);
+ var minIntervalSq = interval * interval * 25;
+ var p;
+ return editArcs(arcs, onPoint);
+
+ function onPoint(append, lng, lat, prevLng, prevLat, i) {
+ var pp = p;
+ p = proj(lng, lat);
+ if (!p) return false; // signal that current arc contains an error
+
+ // Don't try to densify shorter segments (optimization)
+ if (i > 0 && geom.distanceSq(p[0], p[1], pp[0], pp[1]) > minIntervalSq) {
+ densifySegment(prevLng, prevLat, pp[0], pp[1], lng, lat, p[0], p[1], proj, interval)
+ .forEach(append);
+ }
+ append(p);
+ }
+ }
+
+ // Use the median of intervals computed by projecting segments.
+ // We're probing a number of points, because @proj might only be valid in
+ // a sub-region of the dataset bbox (e.g. +proj=tpers)
+ function findDensifyInterval(bounds, xy, proj) {
+ var steps = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
+ var points = [];
+ for (var i=0; i 0 ? utils.findMedian(intervals) : Infinity;
+ }
+
+ // Kludgy way to get a useful interval for densifying a bounding box.
+ // Uses a fraction of average bbox side length)
+ // TODO: improve
+ function findDensifyInterval2(bb, proj) {
+ var a = proj(bb.centerX(), bb.centerY()),
+ c = proj(bb.centerX(), bb.ymin), // right center
+ d = proj(bb.xmax, bb.centerY()); // bottom center
+ var interval = a && c && d ? (geom.distance2D(a[0], a[1], c[0], c[1]) +
+ geom.distance2D(a[0], a[1], d[0], d[1])) / 5000 : Infinity;
+ return interval;
+ }
+
+ // Returns an interval in projected units
+ function getDefaultDensifyInterval(arcs, proj) {
+ var xy = getAvgSegment2(arcs),
+ bb = arcs.getBounds(),
+ intervalA = findDensifyInterval(bb, xy, proj),
+ intervalB = findDensifyInterval2(bb, proj),
+ interval = Math.min(intervalA, intervalB);
+ if (interval == Infinity) {
+ error('Densification error');
+ }
+ return interval;
+ }
+
+ // Interpolate points into a projected line segment if needed to prevent large
+ // deviations from path of original unprojected segment.
+ // @points (optional) array of accumulated points
+ function densifySegment(lng0, lat0, x0, y0, lng2, lat2, x2, y2, proj, interval, points) {
+ // Find midpoint between two endpoints and project it (assumes longitude does
+ // not wrap). TODO Consider bisecting along great circle path -- although this
+ // would not be good for boundaries that follow line of constant latitude.
+ var lng1 = (lng0 + lng2) / 2,
+ lat1 = (lat0 + lat2) / 2,
+ p = proj(lng1, lat1),
+ distSq;
+ if (!p) return; // TODO: consider if this is adequate for handling proj. errors
+ distSq = geom.pointSegDistSq2(p[0], p[1], x0, y0, x2, y2); // sq displacement
+ points = points || [];
+ // Bisect current segment if the projected midpoint deviates from original
+ // segment by more than the @interval parameter.
+ // ... but don't bisect very small segments to prevent infinite recursion
+ // (e.g. if projection function is discontinuous)
+ if (distSq > interval * interval * 0.25 && geom.distance2D(lng0, lat0, lng2, lat2) > 0.01) {
+ densifySegment(lng0, lat0, x0, y0, lng1, lat1, p[0], p[1], proj, interval, points);
+ points.push(p);
+ densifySegment(lng1, lat1, p[0], p[1], lng2, lat2, x2, y2, proj, interval, points);
+ }
+ return points;
+ }
+
+ // Create rectangles around each feature in a layer
+ cmd.rectangles = function(targetLyr, targetDataset, opts) {
+ var crsInfo = getDatasetCrsInfo(targetDataset);
+ var records = targetLyr.data ? targetLyr.data.getRecords() : null;
+ var geometries;
+
+ if (opts.bbox) {
+ geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset);
+
+ } else {
+ if (!layerHasGeometry(targetLyr)) {
+ stop("Layer is missing geometric shapes");
+ }
+ geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
+ }
+
+ var geojson = {
+ type: 'FeatureCollection',
+ features: geometries.map(function(geom, i) {
+ var rec = records && records[i] || null;
+ if (rec && opts.no_replace) {
+ rec = utils.extend({}, rec); // make a copy
+ }
+ return {
+ type: 'Feature',
+ properties: rec,
+ geometry: geom
+ };
+ })
+ };
+ var dataset = importGeoJSON(geojson, {});
+ setDatasetCrsInfo(dataset, crsInfo);
+ var outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
+ setOutputLayerName(outputLayers[0], targetLyr, null, opts);
+ return outputLayers;
+ };
+
+
+
+
+ function shapesToBoxGeometries(lyr, dataset, opts) {
+ var crsInfo = getDatasetCrsInfo(dataset);
+ return lyr.shapes.map(function(shp) {
+ var bounds = lyr.geometry_type == 'point' ?
+ getPointFeatureBounds(shp) : dataset.arcs.getMultiShapeBounds(shp);
+ bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
+ if (!bounds) return null;
+ return bboxToPolygon(bounds.toArray(), opts);
+ });
+ }
+
+ function bboxExpressionToGeometries(exp, lyr, dataset, opts) {
+ var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, {});
+ var n = getFeatureCount(lyr);
+ var result;
+ var geometries = [];
+ for (var i=0; i 0 === false) return null;
+ if (opts.aspect_ratio) {
+ bounds = applyAspectRatio(opts.aspect_ratio, bounds);
+ }
+ if (isGeoBox) {
+ bounds = clampToWorldBounds(bounds);
+ }
+ return bounds;
+ }
+
+ // opt: aspect ratio as a single number or a range (e.g. "1,2");
+ function applyAspectRatio(opt, bounds) {
+ var range = String(opt).split(',').map(parseFloat),
+ aspectRatio = bounds.width() / bounds.height(),
+ min, max; // min is height limit, max is width limit
+ if (range.length == 1) {
+ range.push(range[0]);
+ } else if (range[0] > range[1]) {
+ range.reverse();
+ }
+ min = range[0];
+ max = range[1];
+ if (!min && !max) return bounds;
+ if (!min) min = -Infinity;
+ if (!max) max = Infinity;
+ if (aspectRatio < min) {
+ bounds.fillOut(min);
+ } else if (aspectRatio > max) {
+ bounds.fillOut(max);
+ }
+ return bounds;
+ }
+
+ function applyBoundsOffset(offsetOpt, bounds, crs) {
+ var offsets = convertFourSides(offsetOpt, crs, bounds);
+ bounds.padBounds(offsets[0], offsets[1], offsets[2], offsets[3]);
+ return bounds;
+ }
+
+ function bboxToPolygon(bbox, optsArg) {
+ var opts = optsArg || {};
+ var coords = bboxToCoords(bbox);
+ if (opts.interval > 0) {
+ coords = densifyPathByInterval(coords, opts.interval);
+ }
+ return {
+ type: 'Polygon',
+ coordinates: [coords]
+ };
+ }
+
+ var Rectangle = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ applyAspectRatio: applyAspectRatio,
+ bboxToPolygon: bboxToPolygon
+ });
+
+ cmd.frame = function(catalog, targets, opts) {
+ var widthPx, heightPx, aspectRatio, bbox;
+ if (opts.width) {
+ widthPx = parseSizeParam(opts.width);
+ if (widthPx > 0 === false) {
+ stop('Invalid width parameter:', opts.width);
+ }
+ }
+ if (opts.height) {
+ heightPx = parseSizeParam(opts.height);
+ if (heightPx > 0 === false) {
+ stop('Invalid height parameter:', opts.height);
+ }
+ }
+ if (!widthPx && !heightPx) {
+ widthPx = 800;
+ message('Using default 800px frame width');
+ }
+
+ if (opts.aspect_ratio) {
+ if (opts.aspect_ratio > 0 === false) {
+ stop('Invalid aspect-ratio parameter:', opts.aspect_ratio);
+ }
+ if (!heightPx) {
+ heightPx = roundToDigits(widthPx / opts.aspect_ratio, 1);
+ } else if (!widthPx) {
+ widthPx = roundToDigits(heightPx * opts.aspect_ratio, 1);
+ }
+ }
+
+ if (opts.bbox) {
+ bbox = opts.bbox;
+ // TODO: validate
+ } else {
+ var datasets = utils.pluck(targets, 'dataset');
+ requireDatasetsHaveCompatibleCRS(datasets, 'Targets include both projected and unprojected coordinates');
+ bbox = getTargetBbox(targets);
+ if (!bbox) {
+ stop('Command target is missing geographical bounds');
+ }
+ }
+
+ applyPercentageOffsets(bbox, opts.offset || opts.offsets);
+ applyPixelOffsets(bbox, widthPx, heightPx, opts.offset || opts.offsets);
+
+ if (bbox[3] - bbox[1] > 0 === false || bbox[2] - bbox[0] > 0 === false) {
+ stop('Frame has a collapsed bbox');
+ }
+
+ aspectRatio = (bbox[2] - bbox[0]) / (bbox[3] - bbox[1]);
+ if (!widthPx) {
+ widthPx = roundToDigits(heightPx * aspectRatio, 1);
+ } else if (!heightPx) {
+ heightPx = roundToDigits(widthPx / aspectRatio, 1);
+ }
+
+ var feature = {
+ type: 'Feature',
+ properties: {type: 'frame', width: widthPx, height: heightPx},
+ geometry: bboxToPolygon(bbox)
+ };
+ var frameDataset = importGeoJSON(feature);
+ // set CRS from target dataset
+ // TODO: handle case: targets have different projections
+ // TODO: handle case: first target is missing CRS
+ if (targets.length > 0) {
+ var crsInfo = getDatasetCrsInfo(targets[0].dataset);
+ setDatasetCrsInfo(frameDataset, crsInfo);
+ }
+ frameDataset.layers[0].name = opts.name || 'frame';
+ catalog.addDataset(frameDataset);
+ };
+
+ function fillOutBbox(bbox, widthPx, heightPx) {
+ var hpad = 0, vpad = 0;
+ var w = bbox[2] - bbox[0];
+ var h = bbox[3] - bbox[1];
+ if (widthPx / heightPx > w / h) { // need to add horizontal padding
+ hpad = h * widthPx / heightPx - w;
+ } else {
+ vpad = w * heightPx / widthPx - h;
+ }
+ bbox[0] -= hpad / 2;
+ bbox[1] -= vpad / 2;
+ bbox[2] += hpad / 2;
+ bbox[3] += vpad / 2;
+ }
+
+ function applyPercentageOffsets(bbox, arg) {
+ var sides = getPctOffsets(arg);
+ var l = sides[0],
+ b = sides[1],
+ r = sides[2],
+ t = sides[3],
+ w2 = (bbox[2] - bbox[0]) / (1 - l - r),
+ h2 = (bbox[3] - bbox[1]) / (1 - t - b);
+ bbox[0] -= l * w2;
+ bbox[1] -= b * h2;
+ bbox[2] += r * w2;
+ bbox[3] += t * h2;
+ }
+
+ function applyPixelOffsets(bbox, widthPx, heightPx, arg) {
+ var sides = getPixelOffsets(arg);
+ var l = sides[0],
+ b = sides[1],
+ r = sides[2],
+ t = sides[3],
+ scale, w;
+
+ if (widthPx && heightPx) {
+ // add padding to bbox to match pixel dimensions, if needed
+ fillOutBbox(bbox, widthPx, heightPx);
+ }
+
+ w = bbox[2] - bbox[0];
+ bbox[3] - bbox[1];
+
+ if (widthPx) {
+ scale = w / (widthPx - l - r);
+ } else {
+ scale = w / (heightPx - t - b);
+ }
+
+ bbox[0] -= scale * l;
+ bbox[1] -= scale * b;
+ bbox[2] += scale * r;
+ bbox[3] += scale * t;
+ return scale;
+ }
+
+ function getPctOffsets(arg) {
+ return adjustOffsetsArg(arg).map(str => {
+ return str.includes('%') ? utils.parsePercent(str) : 0;
+ });
+ }
+
+ function getPixelOffsets(arg) {
+ return adjustOffsetsArg(arg).map(str => {
+ return str.includes('%') ? 0 : parseSizeParam(str);
+ });
+ }
+
+ function adjustOffsetsArg(arg) {
+ if (!arg) arg = ['0'];
+ if (arg.length == 1) {
+ return [arg[0], arg[0], arg[0], arg[0]];
+ }
+ if (arg.length != 4) {
+ stop('List of offsets should have 4 values');
+ }
+ return arg;
+ }
+
+ function getTargetBbox(targets) {
+ var expanded = expandCommandTargets(targets);
+ var bounds = expanded.reduce(function(memo, o) {
+ return memo.mergeBounds(getLayerBounds(o.layer, o.dataset.arcs));
+ }, new Bounds());
+ return bounds.hasBounds() ? bounds.toArray() : null;
+ }
// Convert width and height args to aspect ratio arg for the rectangle() function
function getAspectRatioArg(widthArg, heightArg) {
@@ -38211,21 +38837,6 @@ ${svg}
}).reverse().join(',');
}
- // export function renderFrame(d) {
- // var lineWidth = 1,
- // // inset stroke by half of line width
- // off = lineWidth / 2,
- // obj = importPolygon([[[off, off], [off, d.height - off],
- // [d.width - off, d.height - off],
- // [d.width - off, off], [off, off]]]);
- // utils.extend(obj.properties, {
- // fill: 'none',
- // stroke: d.stroke || 'black',
- // 'stroke-width': d['stroke-width'] || lineWidth
- // });
- // return [obj];
- // }
-
var Frame = /*#__PURE__*/Object.freeze({
__proto__: null,
getAspectRatioArg: getAspectRatioArg
@@ -38679,473 +39290,6 @@ ${svg}
return maxValue;
}
- // Returns number of arcs that were removed
- function editArcs(arcs, onPoint) {
- var nn2 = [],
- xx2 = [],
- yy2 = [],
- errors = 0,
- n;
-
- arcs.forEach(function(arc, i) {
- editArc(arc, onPoint);
- });
- arcs.updateVertexData(nn2, xx2, yy2);
- return errors;
-
- function append(p) {
- if (p) {
- xx2.push(p[0]);
- yy2.push(p[1]);
- n++;
- }
- }
-
- function editArc(arc, cb) {
- var x, y, xp, yp, retn;
- var valid = true;
- var i = 0;
- n = 0;
- while (arc.hasNext()) {
- x = arc.x;
- y = arc.y;
- retn = cb(append, x, y, xp, yp, i++);
- if (retn === false) {
- valid = false;
- // assumes that it's ok for the arc iterator to be interrupted.
- break;
- }
- xp = x;
- yp = y;
- }
- if (valid && n == 1) {
- // only one valid point was added to this arc (invalid)
- // e.g. this could happen during reprojection.
- // making this arc empty
- // error("An invalid arc was created");
- message("An invalid arc was created");
- valid = false;
- }
- if (valid) {
- nn2.push(n);
- } else {
- // remove any points that were added for an invalid arc
- while (n-- > 0) {
- xx2.pop();
- yy2.pop();
- }
- nn2.push(0); // add empty arc (to preserve mapping from paths to arcs)
- errors++;
- }
- }
- }
-
- function DatasetEditor(dataset) {
- var layers = [];
- var arcs = [];
-
- this.done = function() {
- dataset.layers = layers;
- if (arcs.length) {
- dataset.arcs = new ArcCollection(arcs);
- buildTopology(dataset);
- }
- };
-
- this.editLayer = function(lyr, cb) {
- var type = lyr.geometry_type;
- if (dataset.layers.indexOf(lyr) != layers.length) {
- error('Layer was edited out-of-order');
- }
- if (!type) {
- layers.push(lyr);
- return;
- }
- var shapes = lyr.shapes.map(function(shape, shpId) {
- var shape2 = [], retn, input;
- for (var i=0, n=shape ? shape.length : 0; i 0 ? shape2 : null;
- });
- layers.push(Object.assign(lyr, {shapes: shapes}));
- };
-
- function extendPathShape(shape, parts) {
- for (var i=0; i interval + 1e-4) {
- appendArr(coords2, interpolate(a, b));
- }
- coords2.push(b);
- }
- return coords2;
- }
-
- function getIntervalInterpolator(interval) {
- return function(a, b) {
- var points = [];
- // var rev = a[0] == b[0] ? a[1] > b[1] : a[0] > b[0];
- var dist = geom.distance2D(a[0], a[1], b[0], b[1]);
- var n = Math.round(dist / interval) - 1;
- var dx = (b[0] - a[0]) / (n + 1),
- dy = (b[1] - a[1]) / (n + 1);
- for (var i=1; i<=n; i++) {
- points.push([a[0] + dx * i, a[1] + dy * i]);
- }
- return points;
- };
- }
-
-
- // Interpolate the same points regardless of segment direction
- function densifyAntimeridianSegment(a, b, interval) {
- var y1, y2;
- var coords = [];
- var ascending = a[1] < b[1];
- if (a[0] != b[0]) error('Expected an edge segment');
- if (interval > 0 === false) error('Expected a positive interval');
- if (ascending) {
- y1 = a[1];
- y2 = b[1];
- } else {
- y1 = b[1];
- y2 = a[1];
- }
- var y = Math.floor(y1 / interval) * interval + interval;
- while (y < y2) {
- coords.push([a[0], y]);
- y += interval;
- }
- if (!ascending) coords.reverse();
- return coords;
- }
-
- function appendArr(dest, src) {
- for (var i=0; i maxSq) maxSq = intSq;
- }
- return Math.sqrt(maxSq);
- }
-
- function projectAndDensifyArcs(arcs, proj) {
- var interval = getDefaultDensifyInterval(arcs, proj);
- var minIntervalSq = interval * interval * 25;
- var p;
- return editArcs(arcs, onPoint);
-
- function onPoint(append, lng, lat, prevLng, prevLat, i) {
- var pp = p;
- p = proj(lng, lat);
- if (!p) return false; // signal that current arc contains an error
-
- // Don't try to densify shorter segments (optimization)
- if (i > 0 && geom.distanceSq(p[0], p[1], pp[0], pp[1]) > minIntervalSq) {
- densifySegment(prevLng, prevLat, pp[0], pp[1], lng, lat, p[0], p[1], proj, interval)
- .forEach(append);
- }
- append(p);
- }
- }
-
- // Use the median of intervals computed by projecting segments.
- // We're probing a number of points, because @proj might only be valid in
- // a sub-region of the dataset bbox (e.g. +proj=tpers)
- function findDensifyInterval(bounds, xy, proj) {
- var steps = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
- var points = [];
- for (var i=0; i 0 ? utils.findMedian(intervals) : Infinity;
- }
-
- // Kludgy way to get a useful interval for densifying a bounding box.
- // Uses a fraction of average bbox side length)
- // TODO: improve
- function findDensifyInterval2(bb, proj) {
- var a = proj(bb.centerX(), bb.centerY()),
- c = proj(bb.centerX(), bb.ymin), // right center
- d = proj(bb.xmax, bb.centerY()); // bottom center
- var interval = a && c && d ? (geom.distance2D(a[0], a[1], c[0], c[1]) +
- geom.distance2D(a[0], a[1], d[0], d[1])) / 5000 : Infinity;
- return interval;
- }
-
- // Returns an interval in projected units
- function getDefaultDensifyInterval(arcs, proj) {
- var xy = getAvgSegment2(arcs),
- bb = arcs.getBounds(),
- intervalA = findDensifyInterval(bb, xy, proj),
- intervalB = findDensifyInterval2(bb, proj),
- interval = Math.min(intervalA, intervalB);
- if (interval == Infinity) {
- error('Densification error');
- }
- return interval;
- }
-
- // Interpolate points into a projected line segment if needed to prevent large
- // deviations from path of original unprojected segment.
- // @points (optional) array of accumulated points
- function densifySegment(lng0, lat0, x0, y0, lng2, lat2, x2, y2, proj, interval, points) {
- // Find midpoint between two endpoints and project it (assumes longitude does
- // not wrap). TODO Consider bisecting along great circle path -- although this
- // would not be good for boundaries that follow line of constant latitude.
- var lng1 = (lng0 + lng2) / 2,
- lat1 = (lat0 + lat2) / 2,
- p = proj(lng1, lat1),
- distSq;
- if (!p) return; // TODO: consider if this is adequate for handling proj. errors
- distSq = geom.pointSegDistSq2(p[0], p[1], x0, y0, x2, y2); // sq displacement
- points = points || [];
- // Bisect current segment if the projected midpoint deviates from original
- // segment by more than the @interval parameter.
- // ... but don't bisect very small segments to prevent infinite recursion
- // (e.g. if projection function is discontinuous)
- if (distSq > interval * interval * 0.25 && geom.distance2D(lng0, lat0, lng2, lat2) > 0.01) {
- densifySegment(lng0, lat0, x0, y0, lng1, lat1, p[0], p[1], proj, interval, points);
- points.push(p);
- densifySegment(lng1, lat1, p[0], p[1], lng2, lat2, x2, y2, proj, interval, points);
- }
- return points;
- }
-
- // Create rectangles around each feature in a layer
- cmd.rectangles = function(targetLyr, targetDataset, opts) {
- var crsInfo = getDatasetCrsInfo(targetDataset);
- var records = targetLyr.data ? targetLyr.data.getRecords() : null;
- var geometries;
-
- if (opts.bbox) {
- geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset);
-
- } else {
- if (!layerHasGeometry(targetLyr)) {
- stop("Layer is missing geometric shapes");
- }
- geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
- }
-
- var geojson = {
- type: 'FeatureCollection',
- features: geometries.map(function(geom, i) {
- var rec = records && records[i] || null;
- if (rec && opts.no_replace) {
- rec = utils.extend({}, rec); // make a copy
- }
- return {
- type: 'Feature',
- properties: rec,
- geometry: geom
- };
- })
- };
- var dataset = importGeoJSON(geojson, {});
- setDatasetCrsInfo(dataset, crsInfo);
- var outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
- setOutputLayerName(outputLayers[0], targetLyr, null, opts);
- return outputLayers;
- };
-
- function shapesToBoxGeometries(lyr, dataset, opts) {
- var crsInfo = getDatasetCrsInfo(dataset);
- return lyr.shapes.map(function(shp) {
- var bounds = lyr.geometry_type == 'point' ?
- getPointFeatureBounds(shp) : dataset.arcs.getMultiShapeBounds(shp);
- bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
- if (!bounds) return null;
- return bboxToPolygon(bounds.toArray(), opts);
- });
- }
-
- function bboxExpressionToGeometries(exp, lyr, dataset, opts) {
- var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, {});
- var n = getFeatureCount(lyr);
- var result;
- var geometries = [];
- for (var i=0; i 0 === false) return null;
- if (opts.aspect_ratio) {
- bounds = applyAspectRatio(opts.aspect_ratio, bounds);
- }
- if (isGeoBox) {
- bounds = clampToWorldBounds(bounds);
- }
- return bounds;
- }
-
- // opt: aspect ratio as a single number or a range (e.g. "1,2");
- function applyAspectRatio(opt, bounds) {
- var range = String(opt).split(',').map(parseFloat),
- aspectRatio = bounds.width() / bounds.height(),
- min, max; // min is height limit, max is width limit
- if (range.length == 1) {
- range.push(range[0]);
- } else if (range[0] > range[1]) {
- range.reverse();
- }
- min = range[0];
- max = range[1];
- if (!min && !max) return bounds;
- if (!min) min = -Infinity;
- if (!max) max = Infinity;
- if (aspectRatio < min) {
- bounds.fillOut(min);
- } else if (aspectRatio > max) {
- bounds.fillOut(max);
- }
- return bounds;
- }
-
- function applyBoundsOffset(offsetOpt, bounds, crs) {
- var offsets = convertFourSides(offsetOpt, crs, bounds);
- bounds.padBounds(offsets[0], offsets[1], offsets[2], offsets[3]);
- return bounds;
- }
-
- function bboxToPolygon(bbox, optsArg) {
- var opts = optsArg || {};
- var coords = bboxToCoords(bbox);
- if (opts.interval > 0) {
- coords = densifyPathByInterval(coords, opts.interval);
- }
- return {
- type: 'Polygon',
- coordinates: [coords]
- };
- }
-
- var Rectangle = /*#__PURE__*/Object.freeze({
- __proto__: null,
- applyAspectRatio: applyAspectRatio,
- bboxToPolygon: bboxToPolygon
- });
-
function getSemiMinorAxis(P) {
return P.a * Math.sqrt(1 - (P.es || 0));
}
@@ -45259,14 +45403,16 @@ ${svg}
}
function commandAcceptsMultipleTargetDatasets(name) {
- return name == 'rotate' || name == 'info' || name == 'proj' || name == 'require' ||
- name == 'drop' || name == 'target' || name == 'if' || name == 'elif' ||
- name == 'else' || name == 'endif' || name == 'run' || name == 'i' || name == 'snap';
+ return name == 'rotate' || name == 'info' || name == 'proj' ||
+ name == 'require' || name == 'drop' || name == 'target' ||
+ name == 'if' || name == 'elif' || name == 'else' || name == 'endif' ||
+ name == 'run' || name == 'i' || name == 'snap' || name == 'frame' ||
+ name == 'comment';
}
function commandAcceptsEmptyTarget(name) {
return name == 'graticule' || name == 'i' || name == 'help' ||
- name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
+ name == 'point-grid' || name == 'shape' || name == 'rectangle' || name == 'frame' ||
name == 'require' || name == 'run' || name == 'define' ||
name == 'include' || name == 'print' || name == 'comment' || name == 'if' || name == 'elif' ||
name == 'else' || name == 'endif' || name == 'stop' || name == 'add-shape' ||
@@ -45290,12 +45436,14 @@ ${svg}
}
if (name == 'comment') {
+ cmd.comment(opts);
return done(null);
}
if (!job) job = new Job();
job.startCommand(command);
+
try { // catch errors from synchronous functions
T$1.start();
@@ -45394,8 +45542,8 @@ ${svg}
} else if (name == 'colorizer') {
outputLayers = cmd.colorizer(opts);
- } else if (name == 'comment') {
- // no-op
+ // } else if (name == 'comment') {
+ // // no-op
} else if (name == 'dashlines') {
applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
@@ -45453,9 +45601,8 @@ ${svg}
} else if (name == 'filter-slivers') {
applyCommandToEachLayer(cmd.filterSlivers, targetLayers, targetDataset, opts);
- // // 'frame' and 'rectangle' have merged
- // } else if (name == 'frame') {
- // cmd.frame(job.catalog, source, opts);
+ } else if (name == 'frame') {
+ cmd.frame(job.catalog, targets, opts);
} else if (name == 'fuzzy-join') {
applyCommandToEachLayer(cmd.fuzzyJoin, targetLayers, arcs, source, opts);
@@ -45557,10 +45704,7 @@ ${svg}
cmd.proj(targ.dataset, job.catalog, opts);
});
- } else if (name == 'rectangle' || name == 'frame') {
- if (name == 'frame' && !opts.width) {
- stop('Command requires a width= argument');
- }
+ } else if (name == 'rectangle') {
if (source || opts.bbox || targets.length === 0) {
job.catalog.addDataset(cmd.rectangle(source || targets?.[0], opts));
} else {
@@ -45726,7 +45870,7 @@ ${svg}
});
}
- var version = "0.6.99";
+ var version = "0.6.100";
// Parse command line args into commands and run them
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -45877,13 +46021,16 @@ ${svg}
function nextGroup(prevJob, commands, next) {
runParsedCommands(commands, new Job(), function(err, job) {
- err = filterError(err);
+ err = handleNonFatalError(err);
next(err, job);
});
}
function done(err, job) {
- err = filterError(err);
+ if (job && inControlBlock(job)) {
+ message('Warning: -if command is missing a matching -endif');
+ }
+ err = handleNonFatalError(err);
if (err) printError(err);
callback(err, job);
}
@@ -45960,7 +46107,7 @@ ${svg}
}
}
- function filterError(err) {
+ function handleNonFatalError(err) {
if (err && err.name == 'NonFatalError') {
printError(err);
return null;
diff --git a/page.css b/page.css
index 9ed608d0..fb67c40b 100644
--- a/page.css
+++ b/page.css
@@ -487,7 +487,8 @@ div.alert-box {
box-shadow: 0 1px 7px rgba(0,0,0,0.18);
background-color: #fff;
font-size: 13px;
- z-index: 40;
+ /* z-index: 40; */
+ z-index: 200;
padding: 4px 0;
}
@@ -535,7 +536,7 @@ div.alert-box {
font-size: 19px;
line-height: 1.1;
padding: 0;
- margin: 0 0 0.3em 0;
+ margin: 0 0 0.4em 0;
font-weight: normal;
}
@@ -757,7 +758,7 @@ body.simplify .layer-control-btn {
max-height: 500px;
overflow: hidden;
overflow-y: auto;
- padding: 8px 14px 9px 14px;
+ padding: 9px 14px 10px 14px;
pointer-events: auto;
border-radius: 9px;
}