mapshaper 0.7.62 → 0.7.63
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 +1772 -1008
- package/package.json +1 -1
- package/www/index.html +13 -1
- package/www/mapshaper-gui.js +1826 -137
- package/www/mapshaper.js +1772 -1008
- package/www/page.css +416 -12
package/mapshaper.js
CHANGED
|
@@ -25445,10 +25445,21 @@
|
|
|
25445
25445
|
|
|
25446
25446
|
|
|
25447
25447
|
function getFrameData(dataset, exportOpts) {
|
|
25448
|
-
var
|
|
25448
|
+
var frameTarget = resolveExportFrame({targetDataset: dataset, mode: 'cli'});
|
|
25449
|
+
var frameLyr = frameTarget && frameTarget.layer;
|
|
25449
25450
|
var data;
|
|
25450
|
-
if (
|
|
25451
|
-
data =
|
|
25451
|
+
if (exportOpts.gui_frame?.data) {
|
|
25452
|
+
data = Object.assign({}, exportOpts.gui_frame.data, {
|
|
25453
|
+
bbox: exportOpts.gui_frame.data.bbox.slice()
|
|
25454
|
+
});
|
|
25455
|
+
if (exportOpts.width > 0 || exportOpts.height > 0) {
|
|
25456
|
+
data = resizeFrameForExport(data, exportOpts);
|
|
25457
|
+
}
|
|
25458
|
+
} else if (frameLyr) {
|
|
25459
|
+
data = getFrameLayerData(frameLyr, dataset.arcs, getDatasetCRS(dataset));
|
|
25460
|
+
if (exportOpts.width > 0 || exportOpts.height > 0) {
|
|
25461
|
+
data = resizeFrameForExport(data, exportOpts);
|
|
25462
|
+
}
|
|
25452
25463
|
} else {
|
|
25453
25464
|
data = calcFrameData(dataset, exportOpts);
|
|
25454
25465
|
}
|
|
@@ -25457,6 +25468,32 @@
|
|
|
25457
25468
|
return data;
|
|
25458
25469
|
}
|
|
25459
25470
|
|
|
25471
|
+
function resizeFrameForExport(frame, opts) {
|
|
25472
|
+
var bounds = new Bounds(frame.bbox);
|
|
25473
|
+
// A stored frame is an exact geographic crop. The SVG exporter's default
|
|
25474
|
+
// 1px content margin applies only to frameless output.
|
|
25475
|
+
var outputOpts = Object.assign({}, opts, {margin: 0});
|
|
25476
|
+
var outputBounds = calcOutputBounds(bounds, outputOpts);
|
|
25477
|
+
var data = {
|
|
25478
|
+
type: 'frame',
|
|
25479
|
+
bbox: bounds.toArray(),
|
|
25480
|
+
bbox2: outputBounds.toArray(),
|
|
25481
|
+
width: Math.round(outputBounds.width()),
|
|
25482
|
+
height: Math.round(outputBounds.height()) || 1,
|
|
25483
|
+
// The authored unit still describes this frame, but an explicit output size
|
|
25484
|
+
// supersedes any fixed page shape, so the aspect is no longer pinned.
|
|
25485
|
+
aspect_ratio: null,
|
|
25486
|
+
units: frame.units || 'px'
|
|
25487
|
+
};
|
|
25488
|
+
if (data.width != frame.width || data.height != frame.height) {
|
|
25489
|
+
warn(
|
|
25490
|
+
`Output size ${data.width}×${data.height}px overrides the map frame's ` +
|
|
25491
|
+
`nominal size ${frame.width}×${frame.height}px; symbol and label sizes are not rescaled.`
|
|
25492
|
+
);
|
|
25493
|
+
}
|
|
25494
|
+
return data;
|
|
25495
|
+
}
|
|
25496
|
+
|
|
25460
25497
|
function fitDatasetToFrame(dataset, frame) {
|
|
25461
25498
|
var bounds = new Bounds(frame.bbox);
|
|
25462
25499
|
var bounds2 = frame.bbox2 ? new Bounds(frame.bbox2) : new Bounds(0, 0, frame.width, frame.height);
|
|
@@ -25467,18 +25504,29 @@
|
|
|
25467
25504
|
});
|
|
25468
25505
|
}
|
|
25469
25506
|
|
|
25470
|
-
function getFrameLayerData(lyr, arcs) {
|
|
25507
|
+
function getFrameLayerData(lyr, arcs, crs) {
|
|
25508
|
+
if (!isFrameLayer(lyr, arcs)) {
|
|
25509
|
+
error('Invalid map frame layer');
|
|
25510
|
+
}
|
|
25471
25511
|
var bounds = getLayerBounds(lyr, arcs);
|
|
25472
|
-
var d = lyr
|
|
25473
|
-
var w = d.width
|
|
25512
|
+
var d = getSingleFrameRecord(lyr);
|
|
25513
|
+
var w = d.width;
|
|
25514
|
+
var hasFixedAspect = utils.isFiniteNumber(d.frame_aspect_ratio) &&
|
|
25515
|
+
d.frame_aspect_ratio > 0;
|
|
25516
|
+
var aspectRatio = hasFixedAspect ? d.frame_aspect_ratio :
|
|
25517
|
+
bounds.width() / bounds.height();
|
|
25474
25518
|
// prevent rounding errors (like 1000.0000000002)
|
|
25475
|
-
var h = Math.round(w
|
|
25476
|
-
|
|
25519
|
+
var h = Math.round(w / aspectRatio);
|
|
25520
|
+
var data = {
|
|
25477
25521
|
type: 'frame',
|
|
25478
25522
|
width: w,
|
|
25479
25523
|
height: h,
|
|
25480
|
-
bbox: bounds.toArray()
|
|
25524
|
+
bbox: bounds.toArray(),
|
|
25525
|
+
aspect_ratio: hasFixedAspect ? d.frame_aspect_ratio : null,
|
|
25526
|
+
units: d.frame_units || 'px'
|
|
25481
25527
|
};
|
|
25528
|
+
if (crs) data.crs = crs;
|
|
25529
|
+
return data;
|
|
25482
25530
|
}
|
|
25483
25531
|
|
|
25484
25532
|
|
|
@@ -25515,9 +25563,63 @@
|
|
|
25515
25563
|
}
|
|
25516
25564
|
|
|
25517
25565
|
|
|
25566
|
+
var frameReservedFields =
|
|
25567
|
+
['type', 'width', 'height', 'frame_aspect_ratio', 'frame_units'];
|
|
25568
|
+
|
|
25569
|
+
function parseFrameSize(arg) {
|
|
25570
|
+
var str = String(arg).toLowerCase();
|
|
25571
|
+
var units = /px|pix/.test(str) && 'px' ||
|
|
25572
|
+
/pt|point/.test(str) && 'pt' ||
|
|
25573
|
+
/in/.test(str) && 'in' ||
|
|
25574
|
+
/cm/.test(str) && 'cm' ||
|
|
25575
|
+
'px';
|
|
25576
|
+
return {valuePx: parseSizeParam(arg), units: units};
|
|
25577
|
+
}
|
|
25578
|
+
|
|
25579
|
+
function formatFrameSizeForDisplay(frame) {
|
|
25580
|
+
var units = frame.units || 'px';
|
|
25581
|
+
var k = units == 'in' ? 72 :
|
|
25582
|
+
units == 'cm' ? 28.3465 : 1;
|
|
25583
|
+
return formatFrameDimension(frame.width / k) + ' × ' +
|
|
25584
|
+
formatFrameDimension(frame.height / k) + ' ' + units;
|
|
25585
|
+
}
|
|
25586
|
+
|
|
25587
|
+
function formatFrameDimension(value) {
|
|
25588
|
+
var rounded = Math.round(value * 100) / 100;
|
|
25589
|
+
return String(rounded);
|
|
25590
|
+
}
|
|
25591
|
+
|
|
25592
|
+
function isFrameReservedField(name) {
|
|
25593
|
+
return frameReservedFields.includes(name);
|
|
25594
|
+
}
|
|
25595
|
+
|
|
25596
|
+
function demoteFrameLayer(lyr, operation) {
|
|
25597
|
+
var rec = lyr.data && lyr.data.getRecords()[0];
|
|
25598
|
+
if (!rec) return;
|
|
25599
|
+
operation = operation || 'frame';
|
|
25600
|
+
lyr.data.captureTableBefore({operation: operation});
|
|
25601
|
+
frameReservedFields.forEach(function(field) {
|
|
25602
|
+
delete rec[field];
|
|
25603
|
+
});
|
|
25604
|
+
lyr.data.markChanged({operation: operation});
|
|
25605
|
+
}
|
|
25606
|
+
|
|
25607
|
+
function getSingleFrameRecord(lyr) {
|
|
25608
|
+
if (!lyr || !lyr.data || !lyr.shapes ||
|
|
25609
|
+
lyr.shapes.length != 1 || lyr.data.size() != 1) {
|
|
25610
|
+
return null;
|
|
25611
|
+
}
|
|
25612
|
+
return lyr.data.getReadOnlyRecordAt(0) || null;
|
|
25613
|
+
}
|
|
25614
|
+
|
|
25518
25615
|
// @lyr dataset layer
|
|
25519
25616
|
function isFrameLayer(lyr, arcs) {
|
|
25520
|
-
|
|
25617
|
+
var rec = getSingleFrameRecord(lyr);
|
|
25618
|
+
return !!rec &&
|
|
25619
|
+
lyr.geometry_type == 'polygon' &&
|
|
25620
|
+
rec.type == 'frame' &&
|
|
25621
|
+
utils.isFiniteNumber(rec.width) &&
|
|
25622
|
+
rec.width > 0 &&
|
|
25521
25623
|
layerIsRectangle(lyr, arcs);
|
|
25522
25624
|
}
|
|
25523
25625
|
|
|
@@ -25527,21 +25629,97 @@
|
|
|
25527
25629
|
});
|
|
25528
25630
|
}
|
|
25529
25631
|
|
|
25530
|
-
|
|
25632
|
+
function findFrames(catalog) {
|
|
25633
|
+
return catalog.getLayers().filter(function(o) {
|
|
25634
|
+
return isFrameLayer(o.layer, o.dataset.arcs);
|
|
25635
|
+
});
|
|
25636
|
+
}
|
|
25637
|
+
|
|
25638
|
+
// Remembers the frame conflict that was last reported, so that resolving the
|
|
25639
|
+
// frame on every map render does not repeat the same warning. Unlike
|
|
25640
|
+
// warnOnce(), this resets when the conflict clears, so a long-lived GUI session
|
|
25641
|
+
// warns again if the user undoes the change and then repeats it.
|
|
25642
|
+
var reportedFrameConflict = null;
|
|
25643
|
+
|
|
25644
|
+
// Generic field commands can promote an ordinary rectangle into a second frame
|
|
25645
|
+
// (e.g. -each 'type="frame", width=400'), and the GUI resolves the frame on
|
|
25646
|
+
// every render, so this has to degrade to a deterministic choice rather than an
|
|
25647
|
+
// error. Rejecting the change outright is assertSingleFrameUpdate()'s job,
|
|
25648
|
+
// where it can still be refused before it is committed.
|
|
25649
|
+
function getActiveFrame(catalog) {
|
|
25650
|
+
var frames = findFrames(catalog);
|
|
25651
|
+
var key;
|
|
25652
|
+
if (frames.length > 1) {
|
|
25653
|
+
key = frames.map(getFrameName).join(', ');
|
|
25654
|
+
if (key !== reportedFrameConflict) {
|
|
25655
|
+
reportedFrameConflict = key;
|
|
25656
|
+
warn('Multiple map frames are not supported; using',
|
|
25657
|
+
getFrameName(frames[0]) + '. Ignoring:',
|
|
25658
|
+
frames.slice(1).map(getFrameName).join(', '));
|
|
25659
|
+
}
|
|
25660
|
+
} else {
|
|
25661
|
+
reportedFrameConflict = null;
|
|
25662
|
+
}
|
|
25663
|
+
return frames[0] || null;
|
|
25664
|
+
}
|
|
25665
|
+
|
|
25531
25666
|
function findFrameDataset(catalog) {
|
|
25532
|
-
var target =
|
|
25667
|
+
var target = getActiveFrame(catalog);
|
|
25533
25668
|
return target && target.dataset || null;
|
|
25534
25669
|
}
|
|
25535
25670
|
|
|
25536
25671
|
function findFrameLayer(catalog) {
|
|
25537
|
-
var target =
|
|
25672
|
+
var target = getActiveFrame(catalog);
|
|
25538
25673
|
return target && target.layer || null;
|
|
25539
25674
|
}
|
|
25540
25675
|
|
|
25541
25676
|
function findFrame(catalog) {
|
|
25542
|
-
return
|
|
25543
|
-
|
|
25677
|
+
return getActiveFrame(catalog);
|
|
25678
|
+
}
|
|
25679
|
+
|
|
25680
|
+
function resolveExportFrame(opts) {
|
|
25681
|
+
if (opts.explicitFrame) return opts.explicitFrame;
|
|
25682
|
+
if (opts.targetDataset) {
|
|
25683
|
+
var lyr = findFrameLayerInDataset(opts.targetDataset);
|
|
25684
|
+
return lyr ? {layer: lyr, dataset: opts.targetDataset} : null;
|
|
25685
|
+
}
|
|
25686
|
+
return opts.mode == 'gui' && opts.catalog ? getActiveFrame(opts.catalog) : null;
|
|
25687
|
+
}
|
|
25688
|
+
|
|
25689
|
+
function assertSingleFrameUpdate(catalog, additions, removals) {
|
|
25690
|
+
var removed = removals || [];
|
|
25691
|
+
var frames = catalog.getLayers().filter(function(o) {
|
|
25692
|
+
return !removed.includes(o.layer) && isFrameLayer(o.layer, o.dataset.arcs);
|
|
25693
|
+
});
|
|
25694
|
+
(additions || []).forEach(function(o) {
|
|
25695
|
+
if (!removed.includes(o.layer) && isFrameLayer(o.layer, o.dataset.arcs)) {
|
|
25696
|
+
frames.push(o);
|
|
25697
|
+
}
|
|
25698
|
+
});
|
|
25699
|
+
frames = frames.filter(function(o, i) {
|
|
25700
|
+
return frames.findIndex(function(o2) {
|
|
25701
|
+
return o2.layer == o.layer;
|
|
25702
|
+
}) == i;
|
|
25703
|
+
});
|
|
25704
|
+
if (frames.length > 1) {
|
|
25705
|
+
stop$1('Multiple map frames are not supported:', frames.map(getFrameName).join(', '));
|
|
25706
|
+
}
|
|
25707
|
+
}
|
|
25708
|
+
|
|
25709
|
+
function assertCatalogCanAddDatasets(catalog, datasets, removals) {
|
|
25710
|
+
var existingDatasets = catalog.getDatasets();
|
|
25711
|
+
var additions = [];
|
|
25712
|
+
datasets.forEach(function(dataset) {
|
|
25713
|
+
if (existingDatasets.includes(dataset)) return;
|
|
25714
|
+
dataset.layers.forEach(function(lyr) {
|
|
25715
|
+
additions.push({layer: lyr, dataset: dataset});
|
|
25716
|
+
});
|
|
25544
25717
|
});
|
|
25718
|
+
assertSingleFrameUpdate(catalog, additions, removals);
|
|
25719
|
+
}
|
|
25720
|
+
|
|
25721
|
+
function getFrameName(o) {
|
|
25722
|
+
return o.layer.name || '[unnamed frame]';
|
|
25545
25723
|
}
|
|
25546
25724
|
|
|
25547
25725
|
function getFrameLayerBounds(lyr) {
|
|
@@ -25670,20 +25848,31 @@
|
|
|
25670
25848
|
|
|
25671
25849
|
var FrameUtils = /*#__PURE__*/Object.freeze({
|
|
25672
25850
|
__proto__: null,
|
|
25851
|
+
assertCatalogCanAddDatasets: assertCatalogCanAddDatasets,
|
|
25852
|
+
assertSingleFrameUpdate: assertSingleFrameUpdate,
|
|
25673
25853
|
calcFrameData: calcFrameData,
|
|
25674
25854
|
calcOutputBounds: calcOutputBounds,
|
|
25855
|
+
demoteFrameLayer: demoteFrameLayer,
|
|
25675
25856
|
findFrame: findFrame,
|
|
25676
25857
|
findFrameDataset: findFrameDataset,
|
|
25677
25858
|
findFrameLayer: findFrameLayer,
|
|
25678
25859
|
findFrameLayerInDataset: findFrameLayerInDataset,
|
|
25860
|
+
findFrames: findFrames,
|
|
25679
25861
|
fitDatasetToFrame: fitDatasetToFrame,
|
|
25862
|
+
formatFrameSizeForDisplay: formatFrameSizeForDisplay,
|
|
25863
|
+
frameReservedFields: frameReservedFields,
|
|
25864
|
+
getActiveFrame: getActiveFrame,
|
|
25680
25865
|
getFrameData: getFrameData,
|
|
25681
25866
|
getFrameLayerBounds: getFrameLayerBounds,
|
|
25682
25867
|
getFrameLayerData: getFrameLayerData,
|
|
25683
25868
|
getFrameSize: getFrameSize,
|
|
25684
25869
|
getMapFrameMetersPerPixel: getMapFrameMetersPerPixel,
|
|
25870
|
+
getSingleFrameRecord: getSingleFrameRecord,
|
|
25685
25871
|
isFrameLayer: isFrameLayer,
|
|
25686
|
-
|
|
25872
|
+
isFrameReservedField: isFrameReservedField,
|
|
25873
|
+
parseFrameSize: parseFrameSize,
|
|
25874
|
+
parseMarginOption: parseMarginOption,
|
|
25875
|
+
resolveExportFrame: resolveExportFrame
|
|
25687
25876
|
});
|
|
25688
25877
|
|
|
25689
25878
|
function addFurnitureLayer(lyr, catalog) {
|
|
@@ -28649,7 +28838,8 @@
|
|
|
28649
28838
|
function exportSVG(dataset, opts) {
|
|
28650
28839
|
var namespace = 'xmlns="http://www.w3.org/2000/svg"';
|
|
28651
28840
|
var defs = [];
|
|
28652
|
-
var frame,
|
|
28841
|
+
var frame, frameLyr, frameBackground, frameNeatline;
|
|
28842
|
+
var svg, layers, metadataJSON, files, svgFile;
|
|
28653
28843
|
var style = '';
|
|
28654
28844
|
|
|
28655
28845
|
// kludge for map keys
|
|
@@ -28686,6 +28876,21 @@
|
|
|
28686
28876
|
if (opts.scalebar) {
|
|
28687
28877
|
layers.push(getScalebarLayer({})); // default options
|
|
28688
28878
|
}
|
|
28879
|
+
frameLyr = layers.find(function(lyr) {
|
|
28880
|
+
return isFrameLayer(lyr, dataset.arcs);
|
|
28881
|
+
});
|
|
28882
|
+
if (frameLyr) {
|
|
28883
|
+
frameBackground = exportFrameStylePhase(frameLyr, dataset, opts, 'background');
|
|
28884
|
+
frameNeatline = exportFrameStylePhase(frameLyr, dataset, opts, 'neatline');
|
|
28885
|
+
layers = layers.filter(function(lyr) { return lyr != frameLyr; });
|
|
28886
|
+
} else if (opts.gui_frame?.style) {
|
|
28887
|
+
frameBackground = exportExplicitFrameStyle(
|
|
28888
|
+
frame, opts.gui_frame, 'background'
|
|
28889
|
+
);
|
|
28890
|
+
frameNeatline = exportExplicitFrameStyle(
|
|
28891
|
+
frame, opts.gui_frame, 'neatline'
|
|
28892
|
+
);
|
|
28893
|
+
}
|
|
28689
28894
|
svg = layers.map(function(lyr) {
|
|
28690
28895
|
var obj;
|
|
28691
28896
|
if (layerHasFurniture(lyr)) {
|
|
@@ -28698,6 +28903,8 @@
|
|
|
28698
28903
|
convertPropertiesToDefinitions(obj, defs);
|
|
28699
28904
|
return stringify(obj);
|
|
28700
28905
|
}).join('\n');
|
|
28906
|
+
if (frameBackground) svg = frameBackground + '\n' + svg;
|
|
28907
|
+
if (frameNeatline) svg += '\n' + frameNeatline;
|
|
28701
28908
|
|
|
28702
28909
|
if (metadataJSON) {
|
|
28703
28910
|
svg = getMetadataBlock(metadataJSON, [0, 0, frame.width, frame.height]) + svg;
|
|
@@ -28732,6 +28939,65 @@ ${svg}
|
|
|
28732
28939
|
return files;
|
|
28733
28940
|
}
|
|
28734
28941
|
|
|
28942
|
+
function exportFrameStylePhase(lyr, dataset, opts, phase) {
|
|
28943
|
+
var copy = copyLayer(lyr);
|
|
28944
|
+
var rec = copy.data && copy.data.getRecords()[0];
|
|
28945
|
+
var obj;
|
|
28946
|
+
if (!rec) return '';
|
|
28947
|
+
if (phase == 'background') {
|
|
28948
|
+
if (!rec.fill || rec.fill == 'none') return '';
|
|
28949
|
+
removeStylePrefix(rec, 'stroke');
|
|
28950
|
+
copy.name = (lyr.name || 'frame') + '-background';
|
|
28951
|
+
} else {
|
|
28952
|
+
if (!rec.stroke || rec.stroke == 'none' ||
|
|
28953
|
+
Number(rec['stroke-width']) <= 0) return '';
|
|
28954
|
+
removeStylePrefix(rec, 'fill');
|
|
28955
|
+
rec.fill = 'none';
|
|
28956
|
+
copy.name = (lyr.name || 'frame') + '-neatline';
|
|
28957
|
+
}
|
|
28958
|
+
obj = exportLayerForSVG(copy, dataset, opts);
|
|
28959
|
+
return stringify(obj);
|
|
28960
|
+
}
|
|
28961
|
+
|
|
28962
|
+
function removeStylePrefix(rec, prefix) {
|
|
28963
|
+
Object.keys(rec).forEach(function(key) {
|
|
28964
|
+
if (key == prefix || key.indexOf(prefix + '-') === 0) {
|
|
28965
|
+
delete rec[key];
|
|
28966
|
+
}
|
|
28967
|
+
});
|
|
28968
|
+
}
|
|
28969
|
+
|
|
28970
|
+
function exportExplicitFrameStyle(frame, context, phase) {
|
|
28971
|
+
var style = context.style || {};
|
|
28972
|
+
var properties = {
|
|
28973
|
+
id: (context.name || 'frame') + '-' + phase,
|
|
28974
|
+
x: 0,
|
|
28975
|
+
y: 0,
|
|
28976
|
+
width: frame.width,
|
|
28977
|
+
height: frame.height
|
|
28978
|
+
};
|
|
28979
|
+
if (phase == 'background') {
|
|
28980
|
+
if (!style.fill || style.fill == 'none') return '';
|
|
28981
|
+
copyProperties(properties, style,
|
|
28982
|
+
['fill', 'fill-opacity', 'fill-rule', 'opacity']);
|
|
28983
|
+
} else {
|
|
28984
|
+
if (!style.stroke || style.stroke == 'none' ||
|
|
28985
|
+
Number(style['stroke-width']) <= 0) return '';
|
|
28986
|
+
properties.fill = 'none';
|
|
28987
|
+
copyProperties(properties, style, [
|
|
28988
|
+
'stroke', 'stroke-width', 'stroke-opacity', 'stroke-linecap',
|
|
28989
|
+
'stroke-linejoin', 'stroke-dasharray', 'opacity'
|
|
28990
|
+
]);
|
|
28991
|
+
}
|
|
28992
|
+
return stringify({tag: 'rect', properties: properties});
|
|
28993
|
+
}
|
|
28994
|
+
|
|
28995
|
+
function copyProperties(dest, src, names) {
|
|
28996
|
+
names.forEach(function(name) {
|
|
28997
|
+
if (src[name] !== undefined) dest[name] = src[name];
|
|
28998
|
+
});
|
|
28999
|
+
}
|
|
29000
|
+
|
|
28735
29001
|
function getSvgFileBase(dataset, opts) {
|
|
28736
29002
|
var file = opts.file || getOutputFileBase(dataset) + '.svg';
|
|
28737
29003
|
return file.replace(/\.svg$/i, '');
|
|
@@ -28807,6 +29073,10 @@ ${svg}
|
|
|
28807
29073
|
|
|
28808
29074
|
function exportLayerForSVG(lyr, dataset, opts) {
|
|
28809
29075
|
var layerObj = getEmptyLayerForSVG(lyr, opts);
|
|
29076
|
+
if (isFrameLayer(lyr, dataset.arcs) && !frameHasExportStyle(lyr)) {
|
|
29077
|
+
layerObj.children = [];
|
|
29078
|
+
return layerObj;
|
|
29079
|
+
}
|
|
28810
29080
|
if (layerIsRectangle(lyr, dataset.arcs)) {
|
|
28811
29081
|
lyr = copyLayer(lyr);
|
|
28812
29082
|
adjustRectangleStyle(lyr);
|
|
@@ -28815,6 +29085,14 @@ ${svg}
|
|
|
28815
29085
|
return layerObj;
|
|
28816
29086
|
}
|
|
28817
29087
|
|
|
29088
|
+
function frameHasExportStyle(lyr) {
|
|
29089
|
+
var rec = lyr.data && lyr.data.getReadOnlyRecordAt(0);
|
|
29090
|
+
var fields = findStylePropertiesBySymbolGeom(Object.keys(rec || {}), 'polygon');
|
|
29091
|
+
return fields.some(function(name) {
|
|
29092
|
+
return rec[name] !== null && rec[name] !== undefined && rec[name] !== '';
|
|
29093
|
+
});
|
|
29094
|
+
}
|
|
29095
|
+
|
|
28818
29096
|
function exportRasterLayerForSVG(lyr, frame, opts) {
|
|
28819
29097
|
var raster = lyr.raster;
|
|
28820
29098
|
var rendered = renderRasterForSVG(raster, frame, opts);
|
|
@@ -42582,6 +42860,45 @@ ${svg}
|
|
|
42582
42860
|
// projected coordinate is not read as lat-long
|
|
42583
42861
|
.option('target', targetOpt);
|
|
42584
42862
|
|
|
42863
|
+
// used by GUI
|
|
42864
|
+
parser.command('update-frame')
|
|
42865
|
+
// .describe('update an existing map frame layer')
|
|
42866
|
+
.option('bbox', {
|
|
42867
|
+
describe: 'frame coordinates (xmin,ymin,xmax,ymax)',
|
|
42868
|
+
type: 'bbox'
|
|
42869
|
+
})
|
|
42870
|
+
.option('width', {
|
|
42871
|
+
describe: 'nominal frame width (e.g. 5in, 10cm, 600px)'
|
|
42872
|
+
})
|
|
42873
|
+
.option('height', {
|
|
42874
|
+
describe: 'nominal frame height'
|
|
42875
|
+
})
|
|
42876
|
+
.option('aspect-ratio', {
|
|
42877
|
+
describe: 'fixed page aspect ratio',
|
|
42878
|
+
type: 'number'
|
|
42879
|
+
})
|
|
42880
|
+
.option('auto-aspect', {
|
|
42881
|
+
describe: 'derive page height from the frame extent',
|
|
42882
|
+
type: 'flag'
|
|
42883
|
+
})
|
|
42884
|
+
.option('fix-scale', {
|
|
42885
|
+
describe: 'hold the map scale; derive the size from the new extent',
|
|
42886
|
+
type: 'flag'
|
|
42887
|
+
})
|
|
42888
|
+
.option('offset', {
|
|
42889
|
+
describe: 'padding in display units or pct of width/height, e.g. 5cm 20px 5%',
|
|
42890
|
+
type: 'strings'
|
|
42891
|
+
})
|
|
42892
|
+
.option('offsets', {
|
|
42893
|
+
describe: 'separate offsets for each side, in l,b,r,t order',
|
|
42894
|
+
type: 'strings'
|
|
42895
|
+
})
|
|
42896
|
+
.option('remove', {
|
|
42897
|
+
describe: 'demote the frame to an ordinary rectangle layer',
|
|
42898
|
+
type: 'flag'
|
|
42899
|
+
})
|
|
42900
|
+
.option('target', targetOpt);
|
|
42901
|
+
|
|
42585
42902
|
// used by GUI
|
|
42586
42903
|
parser.command('update-label')
|
|
42587
42904
|
// .describe('move the anchor or curve knots of an existing label')
|
|
@@ -42694,7 +43011,7 @@ ${svg}
|
|
|
42694
43011
|
type: 'bbox'
|
|
42695
43012
|
})
|
|
42696
43013
|
.option('offset', {
|
|
42697
|
-
describe: 'padding in display units or pct of width, e.g. 5cm 20px 5%',
|
|
43014
|
+
describe: 'padding in display units or pct of width/height, e.g. 5cm 20px 5%',
|
|
42698
43015
|
type: 'strings'
|
|
42699
43016
|
})
|
|
42700
43017
|
.option('offsets', {
|
|
@@ -42702,6 +43019,10 @@ ${svg}
|
|
|
42702
43019
|
type: 'strings'
|
|
42703
43020
|
})
|
|
42704
43021
|
.option('name', nameOpt)
|
|
43022
|
+
.option('replace', {
|
|
43023
|
+
describe: 'replace the existing map frame',
|
|
43024
|
+
type: 'flag'
|
|
43025
|
+
})
|
|
42705
43026
|
.option('target', targetOpt);
|
|
42706
43027
|
|
|
42707
43028
|
// .option('height', {
|
|
@@ -49327,12 +49648,13 @@ ${svg}
|
|
|
49327
49648
|
return layers;
|
|
49328
49649
|
};
|
|
49329
49650
|
|
|
49330
|
-
this.addDataset = function(dataset) {
|
|
49331
|
-
this.setDefaultTarget(dataset.layers, dataset);
|
|
49651
|
+
this.addDataset = function(dataset, opts) {
|
|
49652
|
+
this.setDefaultTarget(dataset.layers, dataset, opts);
|
|
49332
49653
|
return this;
|
|
49333
49654
|
};
|
|
49334
49655
|
|
|
49335
49656
|
this.addDatasets = function(datasets) {
|
|
49657
|
+
assertCatalogCanAddDatasets(this, datasets);
|
|
49336
49658
|
datasets.forEach(function(dataset) {
|
|
49337
49659
|
this.addDataset(dataset);
|
|
49338
49660
|
}, this);
|
|
@@ -49361,7 +49683,7 @@ ${svg}
|
|
|
49361
49683
|
return defaultTargets;
|
|
49362
49684
|
};
|
|
49363
49685
|
|
|
49364
|
-
this.setDefaultTarget = function(layers, dataset) {
|
|
49686
|
+
this.setDefaultTarget = function(layers, dataset, opts) {
|
|
49365
49687
|
this.setDefaultTargets([{
|
|
49366
49688
|
// Copy layers array, in case layers is a reference to dataset.layers.
|
|
49367
49689
|
// This prevents layers that are added to the dataset inside a command from
|
|
@@ -49369,12 +49691,19 @@ ${svg}
|
|
|
49369
49691
|
// by '-join unmatched unjoined'.
|
|
49370
49692
|
layers: layers.concat(),
|
|
49371
49693
|
dataset: dataset
|
|
49372
|
-
}]);
|
|
49694
|
+
}], opts);
|
|
49373
49695
|
};
|
|
49374
49696
|
|
|
49375
49697
|
// arr: array of target objects {layers:[], dataset:{}}
|
|
49376
|
-
this.setDefaultTargets = function(arr) {
|
|
49698
|
+
this.setDefaultTargets = function(arr, opts) {
|
|
49377
49699
|
if (targetsAreSame(defaultTargets, arr)) return;
|
|
49700
|
+
var newDatasets = arr.reduce(function(memo, target) {
|
|
49701
|
+
if (!datasets.includes(target.dataset) && !memo.includes(target.dataset)) {
|
|
49702
|
+
memo.push(target.dataset);
|
|
49703
|
+
}
|
|
49704
|
+
return memo;
|
|
49705
|
+
}, []);
|
|
49706
|
+
assertCatalogCanAddDatasets(this, newDatasets, opts && opts.removeLayers);
|
|
49378
49707
|
this.captureCatalogBefore({operation: 'setDefaultTargets'});
|
|
49379
49708
|
arr.forEach(function(target) {
|
|
49380
49709
|
if (datasets.indexOf(target.dataset) == -1) {
|
|
@@ -49824,173 +50153,1132 @@ ${svg}
|
|
|
49824
50153
|
return dataset;
|
|
49825
50154
|
}
|
|
49826
50155
|
|
|
49827
|
-
|
|
50156
|
+
// Returns number of arcs that were removed
|
|
50157
|
+
function editArcs(arcs, onPoint) {
|
|
50158
|
+
var nn2 = [],
|
|
50159
|
+
xx2 = [],
|
|
50160
|
+
yy2 = [],
|
|
50161
|
+
errors = 0,
|
|
50162
|
+
n;
|
|
49828
50163
|
|
|
49829
|
-
|
|
50164
|
+
arcs.forEach(function(arc, i) {
|
|
50165
|
+
editArc(arc, onPoint);
|
|
50166
|
+
});
|
|
50167
|
+
arcs.updateVertexData(nn2, xx2, yy2);
|
|
50168
|
+
return errors;
|
|
49830
50169
|
|
|
49831
|
-
|
|
49832
|
-
|
|
49833
|
-
|
|
49834
|
-
|
|
49835
|
-
|
|
49836
|
-
|
|
49837
|
-
// mutation with hand-rolled undo.
|
|
49838
|
-
//
|
|
49839
|
-
// See docs/development/label-tool-design.md.
|
|
49840
|
-
function updateLabel(targetLayers, dataset, opts) {
|
|
49841
|
-
var lyr, id, coords, rec;
|
|
49842
|
-
if (targetLayers.length > 1) {
|
|
49843
|
-
// One coordinate list describes one label, so there is no sensible reading
|
|
49844
|
-
// of this across layers.
|
|
49845
|
-
stop$1('Command expects a single target layer');
|
|
50170
|
+
function append(p) {
|
|
50171
|
+
if (p) {
|
|
50172
|
+
xx2.push(p[0]);
|
|
50173
|
+
yy2.push(p[1]);
|
|
50174
|
+
n++;
|
|
50175
|
+
}
|
|
49846
50176
|
}
|
|
49847
|
-
|
|
49848
|
-
|
|
49849
|
-
|
|
49850
|
-
|
|
49851
|
-
|
|
49852
|
-
|
|
49853
|
-
|
|
49854
|
-
|
|
49855
|
-
|
|
49856
|
-
|
|
50177
|
+
|
|
50178
|
+
function editArc(arc, cb) {
|
|
50179
|
+
var x, y, xp, yp, retn;
|
|
50180
|
+
var valid = true;
|
|
50181
|
+
var i = 0;
|
|
50182
|
+
n = 0;
|
|
50183
|
+
while (arc.hasNext()) {
|
|
50184
|
+
x = arc.x;
|
|
50185
|
+
y = arc.y;
|
|
50186
|
+
retn = cb(append, x, y, xp, yp, i++);
|
|
50187
|
+
if (retn === false) {
|
|
50188
|
+
valid = false;
|
|
50189
|
+
// assumes that it's ok for the arc iterator to be interrupted.
|
|
50190
|
+
break;
|
|
50191
|
+
}
|
|
50192
|
+
xp = x;
|
|
50193
|
+
yp = y;
|
|
50194
|
+
}
|
|
50195
|
+
if (valid && n == 1) {
|
|
50196
|
+
// only one valid point was added to this arc (invalid)
|
|
50197
|
+
// e.g. this could happen during reprojection.
|
|
50198
|
+
// making this arc empty
|
|
50199
|
+
// error("An invalid arc was created");
|
|
50200
|
+
message("An invalid arc was created");
|
|
50201
|
+
valid = false;
|
|
50202
|
+
}
|
|
50203
|
+
if (valid) {
|
|
50204
|
+
nn2.push(n);
|
|
50205
|
+
} else {
|
|
50206
|
+
// remove any points that were added for an invalid arc
|
|
50207
|
+
while (n-- > 0) {
|
|
50208
|
+
xx2.pop();
|
|
50209
|
+
yy2.pop();
|
|
50210
|
+
}
|
|
50211
|
+
nn2.push(0); // add empty arc (to preserve mapping from paths to arcs)
|
|
50212
|
+
errors++;
|
|
50213
|
+
}
|
|
49857
50214
|
}
|
|
49858
|
-
warnIfCurveIsUnprojected(dataset, coords.length);
|
|
49859
|
-
// Undo works from what a command declares it is about to change, so an edit
|
|
49860
|
-
// that does not say so is an edit that cannot be undone -- the shapes array
|
|
49861
|
-
// keeps its identity here, leaving nothing for the transaction to notice
|
|
49862
|
-
// afterwards.
|
|
49863
|
-
noteLayerWillChange(lyr, {operation: OPERATION, unit: 'shapes'});
|
|
49864
|
-
// Point shapes hold their coordinates directly, so this is the whole edit.
|
|
49865
|
-
// The pairs are copied rather than aliased: the GUI drags a live copy of the
|
|
49866
|
-
// knots and would otherwise keep a handle on the layer's own arrays.
|
|
49867
|
-
lyr.shapes[id] = coords.map(function(p) { return [p[0], p[1]]; });
|
|
49868
|
-
markLayerChanged(lyr, {operation: OPERATION, unit: 'shapes'});
|
|
49869
|
-
// Nothing about the label's text is touched, and nothing needs to be: a text
|
|
49870
|
-
// measurement describes the *text*, and moving a knot changes the length of
|
|
49871
|
-
// the *path*. The fit check compares the two at render time, against the
|
|
49872
|
-
// curve as it then stands, so a move can turn a fitting label into an
|
|
49873
|
-
// overflowing one with no measurement going out of date.
|
|
49874
50215
|
}
|
|
49875
50216
|
|
|
49876
|
-
|
|
49877
|
-
|
|
49878
|
-
|
|
49879
|
-
|
|
49880
|
-
|
|
49881
|
-
|
|
49882
|
-
|
|
49883
|
-
|
|
49884
|
-
|
|
49885
|
-
|
|
49886
|
-
|
|
49887
|
-
|
|
49888
|
-
|
|
49889
|
-
|
|
49890
|
-
|
|
50217
|
+
function DatasetEditor(dataset) {
|
|
50218
|
+
var layers = [];
|
|
50219
|
+
var arcs = [];
|
|
50220
|
+
|
|
50221
|
+
this.done = function() {
|
|
50222
|
+
dataset.layers = layers;
|
|
50223
|
+
if (arcs.length) {
|
|
50224
|
+
noteDatasetWillChange(dataset, {operation: 'DatasetEditor.done', unit: 'arcs'});
|
|
50225
|
+
dataset.arcs = new ArcCollection(arcs);
|
|
50226
|
+
markDatasetChanged(dataset, {operation: 'DatasetEditor.done', unit: 'arcs'});
|
|
50227
|
+
withActiveUndoTransaction(null, function() {
|
|
50228
|
+
buildTopology(dataset);
|
|
50229
|
+
});
|
|
50230
|
+
}
|
|
50231
|
+
};
|
|
50232
|
+
|
|
50233
|
+
this.editLayer = function(lyr, cb) {
|
|
50234
|
+
var type = lyr.geometry_type;
|
|
50235
|
+
if (dataset.layers.indexOf(lyr) != layers.length) {
|
|
50236
|
+
error('Layer was edited out-of-order');
|
|
50237
|
+
}
|
|
50238
|
+
if (!type) {
|
|
50239
|
+
layers.push(lyr);
|
|
50240
|
+
return;
|
|
50241
|
+
}
|
|
50242
|
+
var shapes = lyr.shapes.map(function(shape, shpId) {
|
|
50243
|
+
var shape2 = [], retn, input;
|
|
50244
|
+
for (var i=0, n=shape ? shape.length : 0; i<n; i++) {
|
|
50245
|
+
input = type == 'point' ? shape[i] : idsToCoords(shape[i]);
|
|
50246
|
+
retn = cb(input, i, shape);
|
|
50247
|
+
if (!Array.isArray(retn)) continue;
|
|
50248
|
+
if (type == 'point') {
|
|
50249
|
+
shape2.push(retn);
|
|
50250
|
+
} else if (type == 'polygon' || type == 'polyline') {
|
|
50251
|
+
extendPathShape(shape2, retn || []);
|
|
50252
|
+
}
|
|
50253
|
+
}
|
|
50254
|
+
return shape2.length > 0 ? shape2 : null;
|
|
50255
|
+
});
|
|
50256
|
+
noteLayerWillChange(lyr, {operation: 'DatasetEditor.editLayer', unit: 'shapes'});
|
|
50257
|
+
layers.push(Object.assign(lyr, {shapes: shapes}));
|
|
50258
|
+
markLayerChanged(lyr, {operation: 'DatasetEditor.editLayer', unit: 'shapes'});
|
|
50259
|
+
};
|
|
50260
|
+
|
|
50261
|
+
function extendPathShape(shape, parts) {
|
|
50262
|
+
for (var i=0; i<parts.length; i++) {
|
|
50263
|
+
shape.push([arcs.length]);
|
|
50264
|
+
arcs.push(parts[i]);
|
|
50265
|
+
}
|
|
49891
50266
|
}
|
|
49892
|
-
|
|
49893
|
-
|
|
50267
|
+
|
|
50268
|
+
function idsToCoords(ids) {
|
|
50269
|
+
var coords = [];
|
|
50270
|
+
var iter = dataset.arcs.getShapeIter(ids);
|
|
50271
|
+
while (iter.hasNext()) {
|
|
50272
|
+
coords.push([iter.x, iter.y]);
|
|
50273
|
+
}
|
|
50274
|
+
return coords;
|
|
49894
50275
|
}
|
|
49895
|
-
return id;
|
|
49896
50276
|
}
|
|
49897
50277
|
|
|
49898
|
-
|
|
49899
|
-
|
|
49900
|
-
|
|
50278
|
+
// Planar densification by an interval
|
|
50279
|
+
function densifyPathByInterval(coords, interval, interpolate) {
|
|
50280
|
+
if (findMaxPathInterval(coords) < interval) return coords;
|
|
50281
|
+
if (!interpolate) {
|
|
50282
|
+
interpolate = getIntervalInterpolator(interval);
|
|
49901
50283
|
}
|
|
49902
|
-
|
|
49903
|
-
|
|
49904
|
-
|
|
50284
|
+
var coords2 = [coords[0]], a, b;
|
|
50285
|
+
for (var i=1, n=coords.length; i<n; i++) {
|
|
50286
|
+
a = coords[i-1];
|
|
50287
|
+
b = coords[i];
|
|
50288
|
+
if (geom.distance2D(a[0], a[1], b[0], b[1]) > interval + 1e-4) {
|
|
50289
|
+
appendArr(coords2, interpolate(a, b));
|
|
50290
|
+
}
|
|
50291
|
+
coords2.push(b);
|
|
49905
50292
|
}
|
|
50293
|
+
return coords2;
|
|
49906
50294
|
}
|
|
49907
50295
|
|
|
49908
|
-
function
|
|
49909
|
-
return
|
|
50296
|
+
function getIntervalInterpolator(interval) {
|
|
50297
|
+
return function(a, b) {
|
|
50298
|
+
var points = [];
|
|
50299
|
+
// var rev = a[0] == b[0] ? a[1] > b[1] : a[0] > b[0];
|
|
50300
|
+
var dist = geom.distance2D(a[0], a[1], b[0], b[1]);
|
|
50301
|
+
var n = Math.round(dist / interval) - 1;
|
|
50302
|
+
var dx = (b[0] - a[0]) / (n + 1),
|
|
50303
|
+
dy = (b[1] - a[1]) / (n + 1);
|
|
50304
|
+
for (var i=1; i<=n; i++) {
|
|
50305
|
+
points.push([a[0] + dx * i, a[1] + dy * i]);
|
|
50306
|
+
}
|
|
50307
|
+
return points;
|
|
50308
|
+
};
|
|
49910
50309
|
}
|
|
49911
50310
|
|
|
49912
|
-
cmd.addShape = addShape;
|
|
49913
50311
|
|
|
49914
|
-
|
|
49915
|
-
|
|
49916
|
-
|
|
50312
|
+
// Interpolate the same points regardless of segment direction
|
|
50313
|
+
function densifyAntimeridianSegment(a, b, interval) {
|
|
50314
|
+
var y1, y2;
|
|
50315
|
+
var coords = [];
|
|
50316
|
+
var ascending = a[1] < b[1];
|
|
50317
|
+
if (a[0] != b[0]) error('Expected an edge segment');
|
|
50318
|
+
if (ascending) {
|
|
50319
|
+
y1 = a[1];
|
|
50320
|
+
y2 = b[1];
|
|
50321
|
+
} else {
|
|
50322
|
+
y1 = b[1];
|
|
50323
|
+
y2 = a[1];
|
|
49917
50324
|
}
|
|
49918
|
-
var
|
|
49919
|
-
|
|
49920
|
-
|
|
49921
|
-
|
|
49922
|
-
if (opts.no_replace || !targetLyr) {
|
|
49923
|
-
// create new layer
|
|
49924
|
-
setOutputLayerName(outputLyr, targetLyr && targetLyr.name, null, opts);
|
|
49925
|
-
return [outputLyr];
|
|
50325
|
+
var y = Math.floor(y1 / interval) * interval + interval;
|
|
50326
|
+
while (y < y2) {
|
|
50327
|
+
coords.push([a[0], y]);
|
|
50328
|
+
y += interval;
|
|
49926
50329
|
}
|
|
49927
|
-
|
|
49928
|
-
return
|
|
50330
|
+
if (!ascending) coords.reverse();
|
|
50331
|
+
return coords;
|
|
49929
50332
|
}
|
|
49930
50333
|
|
|
49931
|
-
function
|
|
49932
|
-
|
|
49933
|
-
|
|
49934
|
-
}
|
|
49935
|
-
|
|
49936
|
-
var geom = opts.coordinates && parseCoordsAsGeometry(opts.coordinates) || null;
|
|
50334
|
+
function appendArr(dest, src) {
|
|
50335
|
+
for (var i=0; i<src.length; i++) dest.push(src[i]);
|
|
50336
|
+
}
|
|
49937
50337
|
|
|
49938
|
-
|
|
49939
|
-
|
|
50338
|
+
function findMaxPathInterval(coords) {
|
|
50339
|
+
var maxSq = 0, intSq, a, b;
|
|
50340
|
+
for (var i=1, n=coords.length; i<n; i++) {
|
|
50341
|
+
a = coords[i-1];
|
|
50342
|
+
b = coords[i];
|
|
50343
|
+
intSq = geom.distanceSq(a[0], a[1], b[0], b[1]);
|
|
50344
|
+
if (intSq > maxSq) maxSq = intSq;
|
|
49940
50345
|
}
|
|
50346
|
+
return Math.sqrt(maxSq);
|
|
50347
|
+
}
|
|
49941
50348
|
|
|
49942
|
-
|
|
49943
|
-
|
|
49944
|
-
|
|
50349
|
+
function projectAndDensifyArcs(arcs, proj) {
|
|
50350
|
+
var interval = getDefaultDensifyInterval(arcs, proj);
|
|
50351
|
+
var minIntervalSq = interval * interval * 25;
|
|
50352
|
+
var p;
|
|
50353
|
+
return editArcs(arcs, onPoint);
|
|
49945
50354
|
|
|
49946
|
-
|
|
49947
|
-
|
|
49948
|
-
|
|
50355
|
+
function onPoint(append, lng, lat, prevLng, prevLat, i) {
|
|
50356
|
+
var pp = p;
|
|
50357
|
+
p = proj(lng, lat);
|
|
50358
|
+
if (!p) return false; // signal that current arc contains an error
|
|
49949
50359
|
|
|
49950
|
-
|
|
49951
|
-
if (geom.
|
|
49952
|
-
|
|
49953
|
-
|
|
49954
|
-
} else {
|
|
49955
|
-
stop$1('Expected polyline coordinates, received', geom.type);
|
|
50360
|
+
// Don't try to densify shorter segments (optimization)
|
|
50361
|
+
if (i > 0 && geom.distanceSq(p[0], p[1], pp[0], pp[1]) > minIntervalSq) {
|
|
50362
|
+
densifySegment(prevLng, prevLat, pp[0], pp[1], lng, lat, p[0], p[1], proj, interval)
|
|
50363
|
+
.forEach(append);
|
|
49956
50364
|
}
|
|
50365
|
+
append(p);
|
|
49957
50366
|
}
|
|
49958
|
-
|
|
49959
|
-
return {
|
|
49960
|
-
type: 'Feature',
|
|
49961
|
-
properties: parseProperties(opts.properties),
|
|
49962
|
-
geometry: geom
|
|
49963
|
-
};
|
|
49964
50367
|
}
|
|
49965
50368
|
|
|
49966
|
-
|
|
49967
|
-
|
|
50369
|
+
// Use the median of intervals computed by projecting segments.
|
|
50370
|
+
// We're probing a number of points, because @proj might only be valid in
|
|
50371
|
+
// a sub-region of the dataset bbox (e.g. +proj=tpers)
|
|
50372
|
+
function findDensifyInterval(bounds, xy, proj) {
|
|
50373
|
+
var steps = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
|
|
50374
|
+
var points = [];
|
|
50375
|
+
for (var i=0; i<steps.length; i++) {
|
|
50376
|
+
for (var j=0; j<steps.length; j++) {
|
|
50377
|
+
points.push([steps[i], steps[j]]);
|
|
50378
|
+
}
|
|
50379
|
+
}
|
|
50380
|
+
var intervals = points.map(function(pos) {
|
|
50381
|
+
var x = bounds.xmin + bounds.width() * pos[0];
|
|
50382
|
+
var y = bounds.ymin + bounds.height() * pos[1];
|
|
50383
|
+
var a = proj(x, y);
|
|
50384
|
+
var b = proj(x + xy[0], y + xy[1]);
|
|
50385
|
+
return a && b ? geom.distance2D(a[0], a[1], b[0], b[1]) : Infinity;
|
|
50386
|
+
}).filter(function(int) {return int < Infinity;});
|
|
50387
|
+
return intervals.length > 0 ? utils.findMedian(intervals) : Infinity;
|
|
49968
50388
|
}
|
|
49969
50389
|
|
|
49970
|
-
|
|
49971
|
-
|
|
49972
|
-
|
|
50390
|
+
// Kludgy way to get a useful interval for densifying a bounding box.
|
|
50391
|
+
// Uses a fraction of average bbox side length)
|
|
50392
|
+
// TODO: improve
|
|
50393
|
+
function findDensifyInterval2(bb, proj) {
|
|
50394
|
+
var a = proj(bb.centerX(), bb.centerY()),
|
|
50395
|
+
c = proj(bb.centerX(), bb.ymin), // right center
|
|
50396
|
+
d = proj(bb.xmax, bb.centerY()); // bottom center
|
|
50397
|
+
var interval = a && c && d ? (geom.distance2D(a[0], a[1], c[0], c[1]) +
|
|
50398
|
+
geom.distance2D(a[0], a[1], d[0], d[1])) / 5000 : Infinity;
|
|
50399
|
+
return interval;
|
|
49973
50400
|
}
|
|
49974
50401
|
|
|
49975
|
-
|
|
49976
|
-
|
|
50402
|
+
// Returns an interval in projected units
|
|
50403
|
+
function getDefaultDensifyInterval(arcs, proj) {
|
|
50404
|
+
var xy = getAvgSegment2(arcs),
|
|
50405
|
+
bb = arcs.getBounds(),
|
|
50406
|
+
intervalA = findDensifyInterval(bb, xy, proj),
|
|
50407
|
+
intervalB = findDensifyInterval2(bb, proj),
|
|
50408
|
+
interval = Math.min(intervalA, intervalB);
|
|
50409
|
+
if (interval == Infinity) {
|
|
50410
|
+
error('Densification error');
|
|
50411
|
+
}
|
|
50412
|
+
return interval;
|
|
49977
50413
|
}
|
|
49978
50414
|
|
|
49979
|
-
|
|
49980
|
-
|
|
50415
|
+
// Interpolate points into a projected line segment if needed to prevent large
|
|
50416
|
+
// deviations from path of original unprojected segment.
|
|
50417
|
+
// @points (optional) array of accumulated points
|
|
50418
|
+
function densifySegment(lng0, lat0, x0, y0, lng2, lat2, x2, y2, proj, interval, points) {
|
|
50419
|
+
// Find midpoint between two endpoints and project it (assumes longitude does
|
|
50420
|
+
// not wrap). TODO Consider bisecting along great circle path -- although this
|
|
50421
|
+
// would not be good for boundaries that follow line of constant latitude.
|
|
50422
|
+
var lng1 = (lng0 + lng2) / 2,
|
|
50423
|
+
lat1 = (lat0 + lat2) / 2,
|
|
50424
|
+
p = proj(lng1, lat1),
|
|
50425
|
+
distSq;
|
|
50426
|
+
if (!p) return; // TODO: consider if this is adequate for handling proj. errors
|
|
50427
|
+
distSq = geom.pointSegDistSq2(p[0], p[1], x0, y0, x2, y2); // sq displacement
|
|
50428
|
+
points = points || [];
|
|
50429
|
+
// Bisect current segment if the projected midpoint deviates from original
|
|
50430
|
+
// segment by more than the @interval parameter.
|
|
50431
|
+
// ... but don't bisect very small segments to prevent infinite recursion
|
|
50432
|
+
// (e.g. if projection function is discontinuous)
|
|
50433
|
+
if (distSq > interval * interval * 0.25 && geom.distance2D(lng0, lat0, lng2, lat2) > 0.01) {
|
|
50434
|
+
densifySegment(lng0, lat0, x0, y0, lng1, lat1, p[0], p[1], proj, interval, points);
|
|
50435
|
+
points.push(p);
|
|
50436
|
+
densifySegment(lng1, lat1, p[0], p[1], lng2, lat2, x2, y2, proj, interval, points);
|
|
50437
|
+
}
|
|
50438
|
+
return points;
|
|
49981
50439
|
}
|
|
49982
50440
|
|
|
49983
|
-
|
|
49984
|
-
|
|
49985
|
-
|
|
50441
|
+
// Create rectangles around each feature in a layer
|
|
50442
|
+
cmd.rectangles = function(targetLyr, targetDataset, opts) {
|
|
50443
|
+
var crsInfo = getDatasetCrsInfo(targetDataset);
|
|
50444
|
+
var records = targetLyr.data ? targetLyr.data.getRecords() : null;
|
|
50445
|
+
var geometries;
|
|
49986
50446
|
|
|
49987
|
-
|
|
49988
|
-
|
|
49989
|
-
}
|
|
50447
|
+
if (opts.bbox) {
|
|
50448
|
+
geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset);
|
|
49990
50449
|
|
|
49991
|
-
|
|
49992
|
-
|
|
49993
|
-
|
|
50450
|
+
} else {
|
|
50451
|
+
if (!layerHasGeometry$1(targetLyr)) {
|
|
50452
|
+
stop$1("Layer is missing geometric shapes");
|
|
50453
|
+
}
|
|
50454
|
+
geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
|
|
50455
|
+
}
|
|
50456
|
+
|
|
50457
|
+
var geojson = {
|
|
50458
|
+
type: 'FeatureCollection',
|
|
50459
|
+
features: geometries.map(function(geom, i) {
|
|
50460
|
+
var rec = records && records[i] || null;
|
|
50461
|
+
if (rec && opts.no_replace) {
|
|
50462
|
+
rec = utils.extend({}, rec); // make a copy
|
|
50463
|
+
}
|
|
50464
|
+
return {
|
|
50465
|
+
type: 'Feature',
|
|
50466
|
+
properties: rec,
|
|
50467
|
+
geometry: geom
|
|
50468
|
+
};
|
|
50469
|
+
})
|
|
50470
|
+
};
|
|
50471
|
+
var dataset = importGeoJSON(geojson, {});
|
|
50472
|
+
setDatasetCrsInfo(dataset, crsInfo);
|
|
50473
|
+
var outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
|
|
50474
|
+
setOutputLayerName(outputLayers[0], targetLyr, null, opts);
|
|
50475
|
+
return outputLayers;
|
|
50476
|
+
};
|
|
50477
|
+
|
|
50478
|
+
|
|
50479
|
+
|
|
50480
|
+
|
|
50481
|
+
function shapesToBoxGeometries(lyr, dataset, opts) {
|
|
50482
|
+
var crsInfo = getDatasetCrsInfo(dataset);
|
|
50483
|
+
return lyr.shapes.map(function(shp) {
|
|
50484
|
+
var bounds = lyr.geometry_type == 'point' ?
|
|
50485
|
+
getPointFeatureBounds(shp) : dataset.arcs.getMultiShapeBounds(shp);
|
|
50486
|
+
bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
|
|
50487
|
+
if (!bounds) return null;
|
|
50488
|
+
return bboxToPolygon(bounds.toArray(), opts);
|
|
50489
|
+
});
|
|
50490
|
+
}
|
|
50491
|
+
|
|
50492
|
+
function bboxExpressionToGeometries(exp, lyr, dataset, opts) {
|
|
50493
|
+
var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, {});
|
|
50494
|
+
var n = getFeatureCount(lyr);
|
|
50495
|
+
var result;
|
|
50496
|
+
var geometries = [];
|
|
50497
|
+
for (var i=0; i<n; i++) {
|
|
50498
|
+
result = compiled(i);
|
|
50499
|
+
if (!looksLikeBbox(result)) {
|
|
50500
|
+
stop$1('Invalid bbox value (expected a GeoJSON-type bbox):', result);
|
|
50501
|
+
}
|
|
50502
|
+
geometries.push(bboxToPolygon(result));
|
|
50503
|
+
}
|
|
50504
|
+
return geometries;
|
|
50505
|
+
}
|
|
50506
|
+
|
|
50507
|
+
function looksLikeBbox(o) {
|
|
50508
|
+
if (!o || o.length != 4) return false;
|
|
50509
|
+
if (o.some(isNaN)) return false;
|
|
50510
|
+
if (o[0] <= o[2] == false || o[1] <= o[3] == false) return false;
|
|
50511
|
+
return true;
|
|
50512
|
+
}
|
|
50513
|
+
|
|
50514
|
+
// Create rectangles around one or more target layers
|
|
50515
|
+
//
|
|
50516
|
+
cmd.rectangle2 = function(target, opts) {
|
|
50517
|
+
// if target layer is a rectangle and we're applying frame properties,
|
|
50518
|
+
// turn the target into a frame instead of creating a new rectangle
|
|
50519
|
+
if (target.layers.length == 1 && opts.width &&
|
|
50520
|
+
layerIsRectangle(target.layers[0], target.dataset.arcs)) {
|
|
50521
|
+
applyFrameProperties(target.layers[0], opts);
|
|
50522
|
+
return;
|
|
50523
|
+
}
|
|
50524
|
+
var datasets = target.layers.map(function(lyr) {
|
|
50525
|
+
var dataset = cmd.rectangle({layer: lyr, dataset: target.dataset}, opts);
|
|
50526
|
+
setOutputLayerName(dataset.layers[0], lyr, null, opts);
|
|
50527
|
+
if (!opts.no_replace) {
|
|
50528
|
+
dataset.layers[0].name = lyr.name || dataset.layers[0].name;
|
|
50529
|
+
}
|
|
50530
|
+
return dataset;
|
|
50531
|
+
});
|
|
50532
|
+
return mergeDatasetsIntoDataset(target.dataset, datasets);
|
|
50533
|
+
};
|
|
50534
|
+
|
|
50535
|
+
cmd.rectangle = function(target, opts) {
|
|
50536
|
+
var bounds, crsInfo;
|
|
50537
|
+
if (opts.bbox) {
|
|
50538
|
+
bounds = new Bounds(opts.bbox);
|
|
50539
|
+
crsInfo = target && getDatasetCrsInfo(target.dataset) ||
|
|
50540
|
+
probablyDecimalDegreeBounds(bounds) && getCrsInfo('wgs84') || {};
|
|
50541
|
+
} else if (target) {
|
|
50542
|
+
bounds = getLayerBounds(target.layer, target.dataset.arcs);
|
|
50543
|
+
crsInfo = getDatasetCrsInfo(target.dataset);
|
|
50544
|
+
}
|
|
50545
|
+
bounds = bounds && applyRectangleOptions(bounds, crsInfo.crs, opts);
|
|
50546
|
+
if (!bounds || !bounds.hasBounds()) {
|
|
50547
|
+
stop$1('Missing rectangle extent');
|
|
50548
|
+
}
|
|
50549
|
+
var feature = {
|
|
50550
|
+
type: 'Feature',
|
|
50551
|
+
properties: {},
|
|
50552
|
+
geometry: bboxToPolygon(bounds.toArray(), opts)
|
|
50553
|
+
};
|
|
50554
|
+
var dataset = importGeoJSON(feature, {});
|
|
50555
|
+
applyFrameProperties(dataset.layers[0], opts);
|
|
50556
|
+
dataset.layers[0].name = opts.name || 'rectangle';
|
|
50557
|
+
setDatasetCrsInfo(dataset, crsInfo);
|
|
50558
|
+
return dataset;
|
|
50559
|
+
};
|
|
50560
|
+
|
|
50561
|
+
function applyFrameProperties(lyr, opts) {
|
|
50562
|
+
if (!opts.width) return;
|
|
50563
|
+
if (!lyr.data) initDataTable(lyr);
|
|
50564
|
+
var d = lyr.data.getRecords()[0] || {};
|
|
50565
|
+
var size = parseFrameSize(opts.width);
|
|
50566
|
+
d.width = size.valuePx;
|
|
50567
|
+
d.type = 'frame';
|
|
50568
|
+
d.frame_units = size.units;
|
|
50569
|
+
}
|
|
50570
|
+
|
|
50571
|
+
function applyRectangleOptions(bounds, crs, opts) {
|
|
50572
|
+
var isGeoBox = probablyDecimalDegreeBounds(bounds);
|
|
50573
|
+
if (opts.offset) {
|
|
50574
|
+
bounds = applyBoundsOffset(opts.offset, bounds, crs);
|
|
50575
|
+
}
|
|
50576
|
+
if (bounds.area() > 0 === false) return null;
|
|
50577
|
+
if (opts.aspect_ratio) {
|
|
50578
|
+
bounds = applyAspectRatio(opts.aspect_ratio, bounds);
|
|
50579
|
+
}
|
|
50580
|
+
if (isGeoBox) {
|
|
50581
|
+
bounds = clampToWorldBounds(bounds);
|
|
50582
|
+
}
|
|
50583
|
+
return bounds;
|
|
50584
|
+
}
|
|
50585
|
+
|
|
50586
|
+
// opt: aspect ratio as a single number or a range (e.g. "1,2");
|
|
50587
|
+
function applyAspectRatio(opt, bounds) {
|
|
50588
|
+
var range = String(opt).split(',').map(parseFloat),
|
|
50589
|
+
aspectRatio = bounds.width() / bounds.height(),
|
|
50590
|
+
min, max; // min is height limit, max is width limit
|
|
50591
|
+
if (range.length == 1) {
|
|
50592
|
+
range.push(range[0]);
|
|
50593
|
+
} else if (range[0] > range[1]) {
|
|
50594
|
+
range.reverse();
|
|
50595
|
+
}
|
|
50596
|
+
min = range[0];
|
|
50597
|
+
max = range[1];
|
|
50598
|
+
if (!min && !max) return bounds;
|
|
50599
|
+
if (!min) min = -Infinity;
|
|
50600
|
+
if (!max) max = Infinity;
|
|
50601
|
+
if (aspectRatio < min) {
|
|
50602
|
+
bounds.fillOut(min);
|
|
50603
|
+
} else if (aspectRatio > max) {
|
|
50604
|
+
bounds.fillOut(max);
|
|
50605
|
+
}
|
|
50606
|
+
return bounds;
|
|
50607
|
+
}
|
|
50608
|
+
|
|
50609
|
+
function applyBoundsOffset(offsetOpt, bounds, crs) {
|
|
50610
|
+
var offsets = convertFourSides(offsetOpt, crs, bounds);
|
|
50611
|
+
bounds.padBounds(offsets[0], offsets[1], offsets[2], offsets[3]);
|
|
50612
|
+
return bounds;
|
|
50613
|
+
}
|
|
50614
|
+
|
|
50615
|
+
function bboxToPolygon(bbox, optsArg) {
|
|
50616
|
+
var opts = optsArg || {};
|
|
50617
|
+
var coords = bboxToCoords(bbox);
|
|
50618
|
+
if (opts.interval > 0) {
|
|
50619
|
+
coords = densifyPathByInterval(coords, opts.interval);
|
|
50620
|
+
}
|
|
50621
|
+
return {
|
|
50622
|
+
type: 'Polygon',
|
|
50623
|
+
coordinates: [coords]
|
|
50624
|
+
};
|
|
50625
|
+
}
|
|
50626
|
+
|
|
50627
|
+
var Rectangle = /*#__PURE__*/Object.freeze({
|
|
50628
|
+
__proto__: null,
|
|
50629
|
+
applyAspectRatio: applyAspectRatio,
|
|
50630
|
+
bboxToPolygon: bboxToPolygon
|
|
50631
|
+
});
|
|
50632
|
+
|
|
50633
|
+
cmd.frame = function(catalog, targets, opts) {
|
|
50634
|
+
var widthPx, heightPx, aspectRatio, bbox;
|
|
50635
|
+
var existingFrame = getActiveFrame(catalog);
|
|
50636
|
+
if (opts.width) {
|
|
50637
|
+
widthPx = parseFrameSize(opts.width).valuePx;
|
|
50638
|
+
if (widthPx > 0 === false) {
|
|
50639
|
+
stop$1('Invalid width parameter:', opts.width);
|
|
50640
|
+
}
|
|
50641
|
+
}
|
|
50642
|
+
if (opts.height) {
|
|
50643
|
+
heightPx = parseFrameSize(opts.height).valuePx;
|
|
50644
|
+
if (heightPx > 0 === false) {
|
|
50645
|
+
stop$1('Invalid height parameter:', opts.height);
|
|
50646
|
+
}
|
|
50647
|
+
}
|
|
50648
|
+
if (!widthPx && !heightPx) {
|
|
50649
|
+
widthPx = 800;
|
|
50650
|
+
message('Using default 800px frame width');
|
|
50651
|
+
}
|
|
50652
|
+
|
|
50653
|
+
if (opts.aspect_ratio) {
|
|
50654
|
+
if (opts.aspect_ratio > 0 === false) {
|
|
50655
|
+
stop$1('Invalid aspect-ratio parameter:', opts.aspect_ratio);
|
|
50656
|
+
}
|
|
50657
|
+
if (!heightPx) {
|
|
50658
|
+
heightPx = roundToDigits(widthPx / opts.aspect_ratio, 1);
|
|
50659
|
+
} else if (!widthPx) {
|
|
50660
|
+
widthPx = roundToDigits(heightPx * opts.aspect_ratio, 1);
|
|
50661
|
+
}
|
|
50662
|
+
}
|
|
50663
|
+
|
|
50664
|
+
if (opts.bbox) {
|
|
50665
|
+
bbox = opts.bbox;
|
|
50666
|
+
// TODO: validate
|
|
50667
|
+
} else {
|
|
50668
|
+
var datasets = utils.pluck(targets, 'dataset');
|
|
50669
|
+
requireDatasetsHaveCompatibleCRS(datasets, 'Targets include both projected and unprojected coordinates');
|
|
50670
|
+
bbox = getTargetBbox(targets);
|
|
50671
|
+
if (!bbox) {
|
|
50672
|
+
stop$1('Command target is missing geographical bounds');
|
|
50673
|
+
}
|
|
50674
|
+
}
|
|
50675
|
+
|
|
50676
|
+
applyPercentageOffsets(bbox, opts.offset || opts.offsets);
|
|
50677
|
+
applyPixelOffsets(bbox, widthPx, heightPx, opts.offset || opts.offsets);
|
|
50678
|
+
|
|
50679
|
+
if (bbox[3] - bbox[1] > 0 === false || bbox[2] - bbox[0] > 0 === false) {
|
|
50680
|
+
stop$1('Frame has a collapsed bbox');
|
|
50681
|
+
}
|
|
50682
|
+
|
|
50683
|
+
aspectRatio = (bbox[2] - bbox[0]) / (bbox[3] - bbox[1]);
|
|
50684
|
+
if (!widthPx) {
|
|
50685
|
+
widthPx = roundToDigits(heightPx * aspectRatio, 1);
|
|
50686
|
+
} else if (!heightPx) {
|
|
50687
|
+
heightPx = roundToDigits(widthPx / aspectRatio, 1);
|
|
50688
|
+
}
|
|
50689
|
+
|
|
50690
|
+
var feature = {
|
|
50691
|
+
type: 'Feature',
|
|
50692
|
+
properties: getFrameProperties(widthPx, heightPx, opts),
|
|
50693
|
+
geometry: bboxToPolygon(bbox)
|
|
50694
|
+
};
|
|
50695
|
+
var frameDataset = importGeoJSON(feature);
|
|
50696
|
+
// set CRS from target dataset
|
|
50697
|
+
// TODO: handle case: targets have different projections
|
|
50698
|
+
// TODO: handle case: first target is missing CRS
|
|
50699
|
+
if (targets.length > 0) {
|
|
50700
|
+
var crsInfo = getDatasetCrsInfo(targets[0].dataset);
|
|
50701
|
+
setDatasetCrsInfo(frameDataset, crsInfo);
|
|
50702
|
+
}
|
|
50703
|
+
frameDataset.layers[0].name = opts.name || 'frame';
|
|
50704
|
+
if (existingFrame) {
|
|
50705
|
+
if (!opts.replace) {
|
|
50706
|
+
stop$1('A map frame already exists:', existingFrame.layer.name || '[unnamed frame]');
|
|
50707
|
+
}
|
|
50708
|
+
demoteFrameLayer(existingFrame.layer);
|
|
50709
|
+
}
|
|
50710
|
+
catalog.addDataset(frameDataset);
|
|
50711
|
+
};
|
|
50712
|
+
|
|
50713
|
+
function getFrameProperties(width, height, opts) {
|
|
50714
|
+
var properties = {
|
|
50715
|
+
type: 'frame',
|
|
50716
|
+
width: width,
|
|
50717
|
+
height: height,
|
|
50718
|
+
frame_units: opts.width || opts.height ?
|
|
50719
|
+
parseFrameSize(opts.width || opts.height).units : 'px'
|
|
50720
|
+
};
|
|
50721
|
+
if (opts.aspect_ratio > 0 || opts.width && opts.height) {
|
|
50722
|
+
properties.frame_aspect_ratio = width / height;
|
|
50723
|
+
}
|
|
50724
|
+
return properties;
|
|
50725
|
+
}
|
|
50726
|
+
|
|
50727
|
+
function fillOutBbox(bbox, widthPx, heightPx) {
|
|
50728
|
+
var hpad = 0, vpad = 0;
|
|
50729
|
+
var w = bbox[2] - bbox[0];
|
|
50730
|
+
var h = bbox[3] - bbox[1];
|
|
50731
|
+
if (widthPx / heightPx > w / h) { // need to add horizontal padding
|
|
50732
|
+
hpad = h * widthPx / heightPx - w;
|
|
50733
|
+
} else {
|
|
50734
|
+
vpad = w * heightPx / widthPx - h;
|
|
50735
|
+
}
|
|
50736
|
+
bbox[0] -= hpad / 2;
|
|
50737
|
+
bbox[1] -= vpad / 2;
|
|
50738
|
+
bbox[2] += hpad / 2;
|
|
50739
|
+
bbox[3] += vpad / 2;
|
|
50740
|
+
}
|
|
50741
|
+
|
|
50742
|
+
function applyPercentageOffsets(bbox, arg) {
|
|
50743
|
+
var sides = getPctOffsets(arg);
|
|
50744
|
+
var l = sides[0],
|
|
50745
|
+
b = sides[1],
|
|
50746
|
+
r = sides[2],
|
|
50747
|
+
t = sides[3],
|
|
50748
|
+
w2 = (bbox[2] - bbox[0]) / (1 - l - r),
|
|
50749
|
+
h2 = (bbox[3] - bbox[1]) / (1 - t - b);
|
|
50750
|
+
bbox[0] -= l * w2;
|
|
50751
|
+
bbox[1] -= b * h2;
|
|
50752
|
+
bbox[2] += r * w2;
|
|
50753
|
+
bbox[3] += t * h2;
|
|
50754
|
+
}
|
|
50755
|
+
|
|
50756
|
+
function applyPixelOffsets(bbox, widthPx, heightPx, arg) {
|
|
50757
|
+
var sides = getPixelOffsets(arg);
|
|
50758
|
+
var l = sides[0],
|
|
50759
|
+
b = sides[1],
|
|
50760
|
+
r = sides[2],
|
|
50761
|
+
t = sides[3],
|
|
50762
|
+
scale, w;
|
|
50763
|
+
|
|
50764
|
+
if (widthPx && heightPx) {
|
|
50765
|
+
// add padding to bbox to match pixel dimensions, if needed
|
|
50766
|
+
fillOutBbox(bbox, widthPx, heightPx);
|
|
50767
|
+
}
|
|
50768
|
+
|
|
50769
|
+
w = bbox[2] - bbox[0];
|
|
50770
|
+
bbox[3] - bbox[1];
|
|
50771
|
+
|
|
50772
|
+
if (widthPx) {
|
|
50773
|
+
scale = w / (widthPx - l - r);
|
|
50774
|
+
} else {
|
|
50775
|
+
scale = w / (heightPx - t - b);
|
|
50776
|
+
}
|
|
50777
|
+
|
|
50778
|
+
bbox[0] -= scale * l;
|
|
50779
|
+
bbox[1] -= scale * b;
|
|
50780
|
+
bbox[2] += scale * r;
|
|
50781
|
+
bbox[3] += scale * t;
|
|
50782
|
+
return scale;
|
|
50783
|
+
}
|
|
50784
|
+
|
|
50785
|
+
function getPctOffsets(arg) {
|
|
50786
|
+
return adjustOffsetsArg(arg).map(str => {
|
|
50787
|
+
return str.includes('%') ? parsePercent(str) : 0;
|
|
50788
|
+
});
|
|
50789
|
+
}
|
|
50790
|
+
|
|
50791
|
+
function getPixelOffsets(arg) {
|
|
50792
|
+
return adjustOffsetsArg(arg).map(str => {
|
|
50793
|
+
return str.includes('%') ? 0 : parseSizeParam(str);
|
|
50794
|
+
});
|
|
50795
|
+
}
|
|
50796
|
+
|
|
50797
|
+
function adjustOffsetsArg(arg) {
|
|
50798
|
+
if (!arg) arg = ['0'];
|
|
50799
|
+
if (arg.length == 1) {
|
|
50800
|
+
return [arg[0], arg[0], arg[0], arg[0]];
|
|
50801
|
+
}
|
|
50802
|
+
if (arg.length != 4) {
|
|
50803
|
+
stop$1('List of offsets should have 4 values');
|
|
50804
|
+
}
|
|
50805
|
+
return arg;
|
|
50806
|
+
}
|
|
50807
|
+
|
|
50808
|
+
function getTargetBbox(targets) {
|
|
50809
|
+
var expanded = expandCommandTargets(targets);
|
|
50810
|
+
var bounds = expanded.reduce(function(memo, o) {
|
|
50811
|
+
return memo.mergeBounds(getLayerBounds(o.layer, o.dataset.arcs));
|
|
50812
|
+
}, new Bounds());
|
|
50813
|
+
return bounds.hasBounds() ? bounds.toArray() : null;
|
|
50814
|
+
}
|
|
50815
|
+
|
|
50816
|
+
// Convert width and height args to aspect ratio arg for the rectangle() function
|
|
50817
|
+
function getAspectRatioArg(widthArg, heightArg) {
|
|
50818
|
+
// heightArg is a string containing either a number or a
|
|
50819
|
+
// comma-sep. pair of numbers (range);
|
|
50820
|
+
return heightArg.split(',').map(function(opt) {
|
|
50821
|
+
var height = Number(opt),
|
|
50822
|
+
width = Number(widthArg);
|
|
50823
|
+
if (!opt) return '';
|
|
50824
|
+
return width / height;
|
|
50825
|
+
}).reverse().join(',');
|
|
50826
|
+
}
|
|
50827
|
+
|
|
50828
|
+
var Frame = /*#__PURE__*/Object.freeze({
|
|
50829
|
+
__proto__: null,
|
|
50830
|
+
applyPercentageOffsets: applyPercentageOffsets,
|
|
50831
|
+
applyPixelOffsets: applyPixelOffsets,
|
|
50832
|
+
fillOutBbox: fillOutBbox,
|
|
50833
|
+
getAspectRatioArg: getAspectRatioArg
|
|
50834
|
+
});
|
|
50835
|
+
|
|
50836
|
+
// A frame is a viewport, not data, so it can legitimately extend past the
|
|
50837
|
+
// edges of the globe: fitting a near-global extent to a fixed aspect ratio pads
|
|
50838
|
+
// it with whitespace, and that padding can reach beyond a pole. Those
|
|
50839
|
+
// coordinates have no projected equivalent, and projectAndDensifyArcs() drops a
|
|
50840
|
+
// whole arc when any of its vertices fails -- which took out three sides of the
|
|
50841
|
+
// rectangle and left a zero-area bbox. Clamping first keeps the frame
|
|
50842
|
+
// projectable; the off-globe padding is what gets lost.
|
|
50843
|
+
function clampLatLngBbox(bbox) {
|
|
50844
|
+
return [
|
|
50845
|
+
utils.clamp(bbox[0], -180, 180),
|
|
50846
|
+
utils.clamp(bbox[1], -90, 90),
|
|
50847
|
+
utils.clamp(bbox[2], -180, 180),
|
|
50848
|
+
utils.clamp(bbox[3], -90, 90)
|
|
50849
|
+
];
|
|
50850
|
+
}
|
|
50851
|
+
|
|
50852
|
+
// Project a rectangular frame as a sampled boundary, rather than projecting
|
|
50853
|
+
// only its corners. Curved projections can reach their x/y extrema between
|
|
50854
|
+
// corners, so a corner-only bbox can crop content that was inside the frame.
|
|
50855
|
+
function getProjectedFrameBounds(bbox, project, srcIsLatLng) {
|
|
50856
|
+
var b = new Bounds(srcIsLatLng ? clampLatLngBbox(bbox) : bbox);
|
|
50857
|
+
var xmin = b.xmin;
|
|
50858
|
+
var ymin = b.ymin;
|
|
50859
|
+
var xmax = b.xmax;
|
|
50860
|
+
var ymax = b.ymax;
|
|
50861
|
+
var arcs = new ArcCollection([
|
|
50862
|
+
[[xmin, ymin], [xmax, ymin]],
|
|
50863
|
+
[[xmax, ymin], [xmax, ymax]],
|
|
50864
|
+
[[xmax, ymax], [xmin, ymax]],
|
|
50865
|
+
[[xmin, ymax], [xmin, ymin]]
|
|
50866
|
+
]);
|
|
50867
|
+
projectAndDensifyArcs(arcs, project);
|
|
50868
|
+
var projected = arcs.getBounds();
|
|
50869
|
+
if (!projected.hasBounds() || projected.area() > 0 === false) {
|
|
50870
|
+
error('Unable to project map frame');
|
|
50871
|
+
}
|
|
50872
|
+
return projected;
|
|
50873
|
+
}
|
|
50874
|
+
|
|
50875
|
+
// Snapshot frame bounds before the dataset arcs are projected.
|
|
50876
|
+
function getFrameProjectionSnapshots(dataset) {
|
|
50877
|
+
return dataset.layers.reduce(function(memo, lyr) {
|
|
50878
|
+
if (isFrameLayer(lyr, dataset.arcs)) {
|
|
50879
|
+
memo.push({
|
|
50880
|
+
layer: lyr,
|
|
50881
|
+
bbox: getLayerBounds(lyr, dataset.arcs).toArray()
|
|
50882
|
+
});
|
|
50883
|
+
}
|
|
50884
|
+
return memo;
|
|
50885
|
+
}, []);
|
|
50886
|
+
}
|
|
50887
|
+
|
|
50888
|
+
// Rebuild a projected frame through the normal topology-aware replacement
|
|
50889
|
+
// path. This is safe when the frame shares an ArcCollection with other layers.
|
|
50890
|
+
function rebuildProjectedFrameLayer(snapshot, dataset, project, srcIsLatLng) {
|
|
50891
|
+
var bounds = getProjectedFrameBounds(snapshot.bbox, project, srcIsLatLng);
|
|
50892
|
+
var rec = getSingleFrameRecord(snapshot.layer) || {};
|
|
50893
|
+
var fixedAspect = utils.isFiniteNumber(rec.frame_aspect_ratio) &&
|
|
50894
|
+
rec.frame_aspect_ratio > 0 ? rec.frame_aspect_ratio : null;
|
|
50895
|
+
var props;
|
|
50896
|
+
if (fixedAspect) {
|
|
50897
|
+
// Projecting a rectangle's boundary and taking its bounds does not preserve
|
|
50898
|
+
// the rectangle's shape, so a frame that declares a ratio has to be padded
|
|
50899
|
+
// back out to it -- otherwise the geometry and the page disagree.
|
|
50900
|
+
var bbox = bounds.toArray();
|
|
50901
|
+
fillOutBbox(bbox, fixedAspect, 1);
|
|
50902
|
+
bounds = new Bounds(bbox);
|
|
50903
|
+
} else {
|
|
50904
|
+
// Height is derived from the extent, so it goes stale when the extent is
|
|
50905
|
+
// reprojected. getFrameLayerData() recomputes it, but the record is what
|
|
50906
|
+
// gets written to an output file.
|
|
50907
|
+
props = {height: Math.round(rec.width * bounds.height() / bounds.width())};
|
|
50908
|
+
}
|
|
50909
|
+
rebuildFrameLayerGeometry(snapshot.layer, dataset, bounds, props);
|
|
50910
|
+
}
|
|
50911
|
+
|
|
50912
|
+
function rebuildFrameLayerGeometry(lyr, dataset, bounds, props) {
|
|
50913
|
+
bounds = bounds instanceof Bounds ? bounds : new Bounds(bounds);
|
|
50914
|
+
var frameDataset = createFrameRectangleDataset(lyr, bounds, props);
|
|
50915
|
+
// The replacement rectangle is imported from bare GeoJSON, so it carries no
|
|
50916
|
+
// CRS. Merging it into the host dataset runs a projected/unprojected
|
|
50917
|
+
// compatibility check, and during -proj the host still reports its source CRS
|
|
50918
|
+
// while the new rectangle already holds projected coordinates -- so the
|
|
50919
|
+
// check has to compare like with like.
|
|
50920
|
+
setDatasetCrsInfo(frameDataset, getDatasetCrsInfo(dataset));
|
|
50921
|
+
replaceLayerContents(lyr, dataset, frameDataset);
|
|
50922
|
+
}
|
|
50923
|
+
|
|
50924
|
+
// Return an isolated rectangular display layer. Dynamic display projection
|
|
50925
|
+
// must not mutate source shapes or the ArcCollection shared by other layers.
|
|
50926
|
+
function getProjectedFrameDisplayLayer(lyr, arcs, project, srcIsLatLng) {
|
|
50927
|
+
if (!isFrameLayer(lyr, arcs)) return null;
|
|
50928
|
+
var bbox = getLayerBounds(lyr, arcs).toArray();
|
|
50929
|
+
var bounds = getProjectedFrameBounds(bbox, project, srcIsLatLng);
|
|
50930
|
+
var dataset = createFrameRectangleDataset(lyr, bounds);
|
|
50931
|
+
return {
|
|
50932
|
+
layer: dataset.layers[0],
|
|
50933
|
+
arcs: dataset.arcs
|
|
50934
|
+
};
|
|
50935
|
+
}
|
|
50936
|
+
|
|
50937
|
+
function createFrameRectangleDataset(lyr, bounds, props) {
|
|
50938
|
+
var record = lyr.data && lyr.data.getReadOnlyRecordAt(0);
|
|
50939
|
+
var feature = {
|
|
50940
|
+
type: 'Feature',
|
|
50941
|
+
properties: Object.assign({}, record || {}, props || {}),
|
|
50942
|
+
geometry: bboxToPolygon(bounds.toArray())
|
|
50943
|
+
};
|
|
50944
|
+
var dataset = importGeoJSON(feature);
|
|
50945
|
+
dataset.layers[0].name = lyr.name;
|
|
50946
|
+
return dataset;
|
|
50947
|
+
}
|
|
50948
|
+
|
|
50949
|
+
var FrameProjection = /*#__PURE__*/Object.freeze({
|
|
50950
|
+
__proto__: null,
|
|
50951
|
+
getFrameProjectionSnapshots: getFrameProjectionSnapshots,
|
|
50952
|
+
getProjectedFrameBounds: getProjectedFrameBounds,
|
|
50953
|
+
getProjectedFrameDisplayLayer: getProjectedFrameDisplayLayer,
|
|
50954
|
+
rebuildFrameLayerGeometry: rebuildFrameLayerGeometry,
|
|
50955
|
+
rebuildProjectedFrameLayer: rebuildProjectedFrameLayer
|
|
50956
|
+
});
|
|
50957
|
+
|
|
50958
|
+
var OPERATION$1 = 'update-frame';
|
|
50959
|
+
|
|
50960
|
+
function updateFrame(targetLayers, dataset, opts) {
|
|
50961
|
+
if (!targetLayers || targetLayers.length != 1) {
|
|
50962
|
+
stop$1('-update-frame expects a single target layer');
|
|
50963
|
+
}
|
|
50964
|
+
var lyr = targetLayers[0];
|
|
50965
|
+
if (!isFrameLayer(lyr, dataset.arcs)) {
|
|
50966
|
+
stop$1('Target layer is not a map frame:', lyr.name || '[unnamed layer]');
|
|
50967
|
+
}
|
|
50968
|
+
if (opts.remove) {
|
|
50969
|
+
if (hasUpdateOptions(opts)) {
|
|
50970
|
+
stop$1('The remove option cannot be combined with frame update options');
|
|
50971
|
+
}
|
|
50972
|
+
demoteFrameLayer(lyr, OPERATION$1);
|
|
50973
|
+
return;
|
|
50974
|
+
}
|
|
50975
|
+
if (!hasUpdateOptions(opts)) {
|
|
50976
|
+
stop$1('Missing frame update option(s)');
|
|
50977
|
+
}
|
|
50978
|
+
if (opts.aspect_ratio !== undefined && opts.auto_aspect) {
|
|
50979
|
+
stop$1('aspect-ratio= and auto-aspect are mutually exclusive');
|
|
50980
|
+
}
|
|
50981
|
+
if (opts.fix_scale && (opts.width !== undefined || opts.height !== undefined)) {
|
|
50982
|
+
stop$1('fix-scale cannot be combined with width= or height=');
|
|
50983
|
+
}
|
|
50984
|
+
|
|
50985
|
+
var rec = lyr.data.getRecords()[0];
|
|
50986
|
+
var frame = getFrameLayerData(lyr, dataset.arcs);
|
|
50987
|
+
var bbox = (opts.bbox || frame.bbox).slice();
|
|
50988
|
+
var offsetArg = opts.offset || opts.offsets;
|
|
50989
|
+
var fixedAspect = getFixedAspect(rec);
|
|
50990
|
+
var width = frame.width;
|
|
50991
|
+
var units = rec.frame_units || 'px';
|
|
50992
|
+
var widthSize, heightSize, height, effectiveAspect;
|
|
50993
|
+
|
|
50994
|
+
// 1. Aspect mode, resolved first because whether the page shape is fixed
|
|
50995
|
+
// decides whether it may constrain the extent while offsets are applied.
|
|
50996
|
+
if (opts.auto_aspect) {
|
|
50997
|
+
fixedAspect = null;
|
|
50998
|
+
} else if (opts.aspect_ratio !== undefined) {
|
|
50999
|
+
if (!utils.isFiniteNumber(opts.aspect_ratio) || opts.aspect_ratio <= 0) {
|
|
51000
|
+
stop$1('Invalid aspect-ratio parameter:', opts.aspect_ratio);
|
|
51001
|
+
}
|
|
51002
|
+
fixedAspect = opts.aspect_ratio;
|
|
51003
|
+
}
|
|
51004
|
+
|
|
51005
|
+
// 2. Extent
|
|
51006
|
+
if (offsetArg) {
|
|
51007
|
+
applyPercentageOffsets(bbox, offsetArg);
|
|
51008
|
+
// Pass a page height only when the shape is fixed. A derived height
|
|
51009
|
+
// follows the extent, so letting it pad the bbox here would hold a
|
|
51010
|
+
// re-fitted frame to its old shape instead of its new bounds.
|
|
51011
|
+
applyPixelOffsets(bbox, frame.width,
|
|
51012
|
+
fixedAspect ? frame.width / fixedAspect : null, offsetArg);
|
|
51013
|
+
}
|
|
51014
|
+
requireValidBbox(bbox);
|
|
51015
|
+
if (fixedAspect) {
|
|
51016
|
+
fillOutBbox(bbox, fixedAspect, 1);
|
|
51017
|
+
}
|
|
51018
|
+
|
|
51019
|
+
// 3. Nominal size
|
|
51020
|
+
if (opts.width !== undefined) {
|
|
51021
|
+
widthSize = parseFrameSize(opts.width);
|
|
51022
|
+
requirePositiveSize(widthSize.valuePx, 'width', opts.width);
|
|
51023
|
+
}
|
|
51024
|
+
if (opts.height !== undefined) {
|
|
51025
|
+
heightSize = parseFrameSize(opts.height);
|
|
51026
|
+
requirePositiveSize(heightSize.valuePx, 'height', opts.height);
|
|
51027
|
+
}
|
|
51028
|
+
if (widthSize && heightSize) {
|
|
51029
|
+
effectiveAspect = widthSize.valuePx / heightSize.valuePx;
|
|
51030
|
+
if (opts.aspect_ratio !== undefined &&
|
|
51031
|
+
ratiosDiffer(effectiveAspect, opts.aspect_ratio)) {
|
|
51032
|
+
stop$1('Contradictory width, height and aspect-ratio values');
|
|
51033
|
+
}
|
|
51034
|
+
width = widthSize.valuePx;
|
|
51035
|
+
units = widthSize.units;
|
|
51036
|
+
fixedAspect = effectiveAspect;
|
|
51037
|
+
fillOutBbox(bbox, fixedAspect, 1);
|
|
51038
|
+
} else if (widthSize) {
|
|
51039
|
+
width = widthSize.valuePx;
|
|
51040
|
+
units = widthSize.units;
|
|
51041
|
+
} else if (heightSize) {
|
|
51042
|
+
effectiveAspect = fixedAspect || getBboxAspect(bbox);
|
|
51043
|
+
width = heightSize.valuePx * effectiveAspect;
|
|
51044
|
+
units = heightSize.units;
|
|
51045
|
+
} else if (opts.fix_scale) {
|
|
51046
|
+
// Hold ground units per output pixel, so the page grows and shrinks with
|
|
51047
|
+
// the extent instead of the scale changing to fit the extent on it.
|
|
51048
|
+
width = frame.width * getBboxWidth(bbox) / getBboxWidth(frame.bbox);
|
|
51049
|
+
requirePositiveSize(width, 'fix-scale', width);
|
|
51050
|
+
}
|
|
51051
|
+
|
|
51052
|
+
requireValidBbox(bbox);
|
|
51053
|
+
effectiveAspect = fixedAspect || getBboxAspect(bbox);
|
|
51054
|
+
height = Math.round(width / effectiveAspect);
|
|
51055
|
+
|
|
51056
|
+
noteLayerWillChange(lyr, {operation: OPERATION$1, unit: 'shapes'});
|
|
51057
|
+
lyr.data.captureTableBefore({operation: OPERATION$1});
|
|
51058
|
+
rec.width = width;
|
|
51059
|
+
rec.height = height;
|
|
51060
|
+
rec.frame_units = units;
|
|
51061
|
+
if (fixedAspect) {
|
|
51062
|
+
rec.frame_aspect_ratio = fixedAspect;
|
|
51063
|
+
} else {
|
|
51064
|
+
delete rec.frame_aspect_ratio;
|
|
51065
|
+
}
|
|
51066
|
+
lyr.data.markChanged({operation: OPERATION$1});
|
|
51067
|
+
rebuildFrameLayerGeometry(lyr, dataset, new Bounds(bbox));
|
|
51068
|
+
markLayerChanged(lyr, {operation: OPERATION$1, unit: 'shapes'});
|
|
51069
|
+
}
|
|
51070
|
+
|
|
51071
|
+
function hasUpdateOptions(opts) {
|
|
51072
|
+
return opts.bbox !== undefined ||
|
|
51073
|
+
opts.width !== undefined ||
|
|
51074
|
+
opts.height !== undefined ||
|
|
51075
|
+
opts.aspect_ratio !== undefined ||
|
|
51076
|
+
opts.auto_aspect ||
|
|
51077
|
+
opts.fix_scale ||
|
|
51078
|
+
opts.offset !== undefined ||
|
|
51079
|
+
opts.offsets !== undefined;
|
|
51080
|
+
}
|
|
51081
|
+
|
|
51082
|
+
function getBboxWidth(bbox) {
|
|
51083
|
+
return bbox[2] - bbox[0];
|
|
51084
|
+
}
|
|
51085
|
+
|
|
51086
|
+
function getFixedAspect(rec) {
|
|
51087
|
+
return utils.isFiniteNumber(rec.frame_aspect_ratio) &&
|
|
51088
|
+
rec.frame_aspect_ratio > 0 ? rec.frame_aspect_ratio : null;
|
|
51089
|
+
}
|
|
51090
|
+
|
|
51091
|
+
function getBboxAspect(bbox) {
|
|
51092
|
+
return (bbox[2] - bbox[0]) / (bbox[3] - bbox[1]);
|
|
51093
|
+
}
|
|
51094
|
+
|
|
51095
|
+
function requireValidBbox(bbox) {
|
|
51096
|
+
if (!bbox.every(utils.isFiniteNumber) ||
|
|
51097
|
+
bbox[2] - bbox[0] <= 0 ||
|
|
51098
|
+
bbox[3] - bbox[1] <= 0) {
|
|
51099
|
+
stop$1('Frame has a collapsed bbox');
|
|
51100
|
+
}
|
|
51101
|
+
}
|
|
51102
|
+
|
|
51103
|
+
function requirePositiveSize(value, name, arg) {
|
|
51104
|
+
if (!utils.isFiniteNumber(value) || value <= 0) {
|
|
51105
|
+
stop$1('Invalid ' + name + ' parameter:', arg);
|
|
51106
|
+
}
|
|
51107
|
+
}
|
|
51108
|
+
|
|
51109
|
+
function ratiosDiffer(a, b) {
|
|
51110
|
+
return Math.abs(a - b) > 1e-10 * Math.max(1, Math.abs(a), Math.abs(b));
|
|
51111
|
+
}
|
|
51112
|
+
|
|
51113
|
+
cmd.updateFrame = updateFrame;
|
|
51114
|
+
|
|
51115
|
+
var OPERATION = 'update-label';
|
|
51116
|
+
|
|
51117
|
+
cmd.updateLabel = updateLabel;
|
|
51118
|
+
|
|
51119
|
+
// Rewrites the knots of a label that already exists.
|
|
51120
|
+
//
|
|
51121
|
+
// -add-label creates and -style sets properties, so neither can move a label:
|
|
51122
|
+
// a label's knots are its geometry, not an attribute. This is the command the
|
|
51123
|
+
// GUI emits when a knot or an anchor is dragged, so that an interactive move is
|
|
51124
|
+
// an ordinary command with undo and session history rather than a direct
|
|
51125
|
+
// mutation with hand-rolled undo.
|
|
51126
|
+
//
|
|
51127
|
+
// See docs/development/label-tool-design.md.
|
|
51128
|
+
function updateLabel(targetLayers, dataset, opts) {
|
|
51129
|
+
var lyr, id, coords, rec;
|
|
51130
|
+
if (targetLayers.length > 1) {
|
|
51131
|
+
// One coordinate list describes one label, so there is no sensible reading
|
|
51132
|
+
// of this across layers.
|
|
51133
|
+
stop$1('Command expects a single target layer');
|
|
51134
|
+
}
|
|
51135
|
+
lyr = targetLayers[0];
|
|
51136
|
+
id = getTargetId(lyr, opts);
|
|
51137
|
+
coords = parseLabelCoords(opts.coordinates);
|
|
51138
|
+
rec = getLayerDataTable(lyr).getRecords()[id];
|
|
51139
|
+
if (!featureIsLabel(rec)) {
|
|
51140
|
+
// Moving an arbitrary point through a command named -update-label would be
|
|
51141
|
+
// a surprising way to succeed, and in the GUI it would mean the hit test
|
|
51142
|
+
// handed over the wrong feature.
|
|
51143
|
+
stop$1('Feature', id, 'in layer "' + layerName(lyr) + '" is not a label ' +
|
|
51144
|
+
'(it has no label-text property)');
|
|
51145
|
+
}
|
|
51146
|
+
warnIfCurveIsUnprojected(dataset, coords.length);
|
|
51147
|
+
// Undo works from what a command declares it is about to change, so an edit
|
|
51148
|
+
// that does not say so is an edit that cannot be undone -- the shapes array
|
|
51149
|
+
// keeps its identity here, leaving nothing for the transaction to notice
|
|
51150
|
+
// afterwards.
|
|
51151
|
+
noteLayerWillChange(lyr, {operation: OPERATION, unit: 'shapes'});
|
|
51152
|
+
// Point shapes hold their coordinates directly, so this is the whole edit.
|
|
51153
|
+
// The pairs are copied rather than aliased: the GUI drags a live copy of the
|
|
51154
|
+
// knots and would otherwise keep a handle on the layer's own arrays.
|
|
51155
|
+
lyr.shapes[id] = coords.map(function(p) { return [p[0], p[1]]; });
|
|
51156
|
+
markLayerChanged(lyr, {operation: OPERATION, unit: 'shapes'});
|
|
51157
|
+
// Nothing about the label's text is touched, and nothing needs to be: a text
|
|
51158
|
+
// measurement describes the *text*, and moving a knot changes the length of
|
|
51159
|
+
// the *path*. The fit check compares the two at render time, against the
|
|
51160
|
+
// curve as it then stands, so a move can turn a fitting label into an
|
|
51161
|
+
// overflowing one with no measurement going out of date.
|
|
51162
|
+
}
|
|
51163
|
+
|
|
51164
|
+
// Which feature to move. One coordinate list can only describe one label, so
|
|
51165
|
+
// this takes a single id rather than the ids= list that -style accepts.
|
|
51166
|
+
function getTargetId(lyr, opts) {
|
|
51167
|
+
var ids = opts.ids;
|
|
51168
|
+
var id;
|
|
51169
|
+
requirePointLayer(lyr);
|
|
51170
|
+
if (!ids || ids.length === 0) {
|
|
51171
|
+
stop$1('Missing required ids parameter (the feature id of the label to update)');
|
|
51172
|
+
}
|
|
51173
|
+
if (ids.length > 1) {
|
|
51174
|
+
stop$1('Command expects a single feature id; received', ids.join(','));
|
|
51175
|
+
}
|
|
51176
|
+
id = ids[0];
|
|
51177
|
+
if (id >= 0 === false || id >= getFeatureCount(lyr)) {
|
|
51178
|
+
stop$1('Layer "' + layerName(lyr) + '" has no feature with id', id);
|
|
51179
|
+
}
|
|
51180
|
+
if (!lyr.shapes || !lyr.shapes[id]) {
|
|
51181
|
+
stop$1('Feature', id, 'in layer "' + layerName(lyr) + '" has no geometry');
|
|
51182
|
+
}
|
|
51183
|
+
return id;
|
|
51184
|
+
}
|
|
51185
|
+
|
|
51186
|
+
function requirePointLayer(lyr) {
|
|
51187
|
+
if (!lyr || getFeatureCount(lyr) === 0) {
|
|
51188
|
+
stop$1('Missing a target layer containing labels');
|
|
51189
|
+
}
|
|
51190
|
+
if (lyr.geometry_type != 'point') {
|
|
51191
|
+
stop$1('Labels can only be updated in a point layer; target "' +
|
|
51192
|
+
layerName(lyr) + '" contains ' + (lyr.geometry_type || 'no geometry'));
|
|
51193
|
+
}
|
|
51194
|
+
}
|
|
51195
|
+
|
|
51196
|
+
function layerName(lyr) {
|
|
51197
|
+
return lyr && lyr.name || '[unnamed]';
|
|
51198
|
+
}
|
|
51199
|
+
|
|
51200
|
+
cmd.addShape = addShape;
|
|
51201
|
+
|
|
51202
|
+
function addShape(targetLayers, targetDataset, opts) {
|
|
51203
|
+
if (targetLayers.length > 1) {
|
|
51204
|
+
stop$1('Command expects a single target layer');
|
|
51205
|
+
}
|
|
51206
|
+
var targetLyr = targetLayers[0]; // may be undefined
|
|
51207
|
+
var targetType = !opts.no_replace && targetLyr && targetLyr.geometry_type || null;
|
|
51208
|
+
var dataset = importGeoJSON(toFeature(opts, targetType));
|
|
51209
|
+
var outputLyr = mergeDatasetsIntoDataset(targetDataset, [dataset])[0];
|
|
51210
|
+
if (opts.no_replace || !targetLyr) {
|
|
51211
|
+
// create new layer
|
|
51212
|
+
setOutputLayerName(outputLyr, targetLyr && targetLyr.name, null, opts);
|
|
51213
|
+
return [outputLyr];
|
|
51214
|
+
}
|
|
51215
|
+
// merge into target layer
|
|
51216
|
+
return cmd.mergeLayers([targetLyr, outputLyr], {force: true});
|
|
51217
|
+
}
|
|
51218
|
+
|
|
51219
|
+
function toFeature(opts, geomType) {
|
|
51220
|
+
if (opts.geojson) {
|
|
51221
|
+
return parseArg(opts.geojson);
|
|
51222
|
+
}
|
|
51223
|
+
|
|
51224
|
+
var geom = opts.coordinates && parseCoordsAsGeometry(opts.coordinates) || null;
|
|
51225
|
+
|
|
51226
|
+
if (!geom) {
|
|
51227
|
+
stop$1('Missing required shape coordinates');
|
|
51228
|
+
}
|
|
51229
|
+
|
|
51230
|
+
if (geomType == 'point' && geom.type != 'Point') {
|
|
51231
|
+
stop$1('Expected point coordinates, received', geom.type);
|
|
51232
|
+
}
|
|
51233
|
+
|
|
51234
|
+
if (geomType == 'polygon' && geom.type != 'Polygon') {
|
|
51235
|
+
stop$1('Expected polygon coordinates, received', geom.type);
|
|
51236
|
+
}
|
|
51237
|
+
|
|
51238
|
+
if (geomType == 'polyline') {
|
|
51239
|
+
if (geom.type == 'Polygon') {
|
|
51240
|
+
geom.coordinates = geom.coordinates[0];
|
|
51241
|
+
geom.type = 'LineString';
|
|
51242
|
+
} else {
|
|
51243
|
+
stop$1('Expected polyline coordinates, received', geom.type);
|
|
51244
|
+
}
|
|
51245
|
+
}
|
|
51246
|
+
|
|
51247
|
+
return {
|
|
51248
|
+
type: 'Feature',
|
|
51249
|
+
properties: parseProperties(opts.properties),
|
|
51250
|
+
geometry: geom
|
|
51251
|
+
};
|
|
51252
|
+
}
|
|
51253
|
+
|
|
51254
|
+
function parseArg(obj) {
|
|
51255
|
+
return typeof obj == 'string' ? JSON.parse(obj) : obj;
|
|
51256
|
+
}
|
|
51257
|
+
|
|
51258
|
+
function parseProperties(arg) {
|
|
51259
|
+
if (!arg) return null;
|
|
51260
|
+
return parseArg(arg);
|
|
51261
|
+
}
|
|
51262
|
+
|
|
51263
|
+
function isArrayOfNumbers(arr) {
|
|
51264
|
+
return arr.length >= 2 && arr.every(utils.isNumber);
|
|
51265
|
+
}
|
|
51266
|
+
|
|
51267
|
+
function isClosedPath$1(arr) {
|
|
51268
|
+
return isArrayOfPoints(arr) && arr.length > 3 && samePoint$2(arr[0], arr[arr.length - 1]);
|
|
51269
|
+
}
|
|
51270
|
+
|
|
51271
|
+
function samePoint$2(a, b) {
|
|
51272
|
+
return a[0] == b[0] && a[1] == b[1];
|
|
51273
|
+
}
|
|
51274
|
+
|
|
51275
|
+
function isArrayOfPoints(arr) {
|
|
51276
|
+
return arr.every(isPoint);
|
|
51277
|
+
}
|
|
51278
|
+
|
|
51279
|
+
function isPoint(arr) {
|
|
51280
|
+
return arr && arr.length == 2 && isArrayOfNumbers(arr);
|
|
51281
|
+
}
|
|
49994
51282
|
|
|
49995
51283
|
function transposeCoords(arr) {
|
|
49996
51284
|
var coords = [];
|
|
@@ -54277,67 +55565,6 @@ ${svg}
|
|
|
54277
55565
|
|
|
54278
55566
|
}
|
|
54279
55567
|
|
|
54280
|
-
function DatasetEditor(dataset) {
|
|
54281
|
-
var layers = [];
|
|
54282
|
-
var arcs = [];
|
|
54283
|
-
|
|
54284
|
-
this.done = function() {
|
|
54285
|
-
dataset.layers = layers;
|
|
54286
|
-
if (arcs.length) {
|
|
54287
|
-
noteDatasetWillChange(dataset, {operation: 'DatasetEditor.done', unit: 'arcs'});
|
|
54288
|
-
dataset.arcs = new ArcCollection(arcs);
|
|
54289
|
-
markDatasetChanged(dataset, {operation: 'DatasetEditor.done', unit: 'arcs'});
|
|
54290
|
-
withActiveUndoTransaction(null, function() {
|
|
54291
|
-
buildTopology(dataset);
|
|
54292
|
-
});
|
|
54293
|
-
}
|
|
54294
|
-
};
|
|
54295
|
-
|
|
54296
|
-
this.editLayer = function(lyr, cb) {
|
|
54297
|
-
var type = lyr.geometry_type;
|
|
54298
|
-
if (dataset.layers.indexOf(lyr) != layers.length) {
|
|
54299
|
-
error('Layer was edited out-of-order');
|
|
54300
|
-
}
|
|
54301
|
-
if (!type) {
|
|
54302
|
-
layers.push(lyr);
|
|
54303
|
-
return;
|
|
54304
|
-
}
|
|
54305
|
-
var shapes = lyr.shapes.map(function(shape, shpId) {
|
|
54306
|
-
var shape2 = [], retn, input;
|
|
54307
|
-
for (var i=0, n=shape ? shape.length : 0; i<n; i++) {
|
|
54308
|
-
input = type == 'point' ? shape[i] : idsToCoords(shape[i]);
|
|
54309
|
-
retn = cb(input, i, shape);
|
|
54310
|
-
if (!Array.isArray(retn)) continue;
|
|
54311
|
-
if (type == 'point') {
|
|
54312
|
-
shape2.push(retn);
|
|
54313
|
-
} else if (type == 'polygon' || type == 'polyline') {
|
|
54314
|
-
extendPathShape(shape2, retn || []);
|
|
54315
|
-
}
|
|
54316
|
-
}
|
|
54317
|
-
return shape2.length > 0 ? shape2 : null;
|
|
54318
|
-
});
|
|
54319
|
-
noteLayerWillChange(lyr, {operation: 'DatasetEditor.editLayer', unit: 'shapes'});
|
|
54320
|
-
layers.push(Object.assign(lyr, {shapes: shapes}));
|
|
54321
|
-
markLayerChanged(lyr, {operation: 'DatasetEditor.editLayer', unit: 'shapes'});
|
|
54322
|
-
};
|
|
54323
|
-
|
|
54324
|
-
function extendPathShape(shape, parts) {
|
|
54325
|
-
for (var i=0; i<parts.length; i++) {
|
|
54326
|
-
shape.push([arcs.length]);
|
|
54327
|
-
arcs.push(parts[i]);
|
|
54328
|
-
}
|
|
54329
|
-
}
|
|
54330
|
-
|
|
54331
|
-
function idsToCoords(ids) {
|
|
54332
|
-
var coords = [];
|
|
54333
|
-
var iter = dataset.arcs.getShapeIter(ids);
|
|
54334
|
-
while (iter.hasNext()) {
|
|
54335
|
-
coords.push([iter.x, iter.y]);
|
|
54336
|
-
}
|
|
54337
|
-
return coords;
|
|
54338
|
-
}
|
|
54339
|
-
}
|
|
54340
|
-
|
|
54341
55568
|
function makePolylineBuffer(lyr, dataset, opts) {
|
|
54342
55569
|
time('buffer');
|
|
54343
55570
|
var spherical = isLatLngCRS(getDatasetCRS(dataset));
|
|
@@ -61481,625 +62708,211 @@ ${svg}
|
|
|
61481
62708
|
if (data.pointCount === 0) return [];
|
|
61482
62709
|
return groupPolygonRings(data.pathData, arcs, false).map(function(paths) {
|
|
61483
62710
|
return paths.map(function(path) {
|
|
61484
|
-
return path.points.map(function(p) {
|
|
61485
|
-
return p.concat();
|
|
61486
|
-
});
|
|
61487
|
-
});
|
|
61488
|
-
});
|
|
61489
|
-
}
|
|
61490
|
-
|
|
61491
|
-
function getPolygonGeometry(shape, arcs) {
|
|
61492
|
-
var coords = getPolygonMultiPolygonCoords(shape, arcs);
|
|
61493
|
-
return coords.length > 0 ? {
|
|
61494
|
-
type: 'MultiPolygon',
|
|
61495
|
-
coordinates: coords
|
|
61496
|
-
} : null;
|
|
61497
|
-
}
|
|
61498
|
-
|
|
61499
|
-
function getBufferDataset(coords) {
|
|
61500
|
-
return importGeoJSON({
|
|
61501
|
-
type: 'GeometryCollection',
|
|
61502
|
-
geometries: [{
|
|
61503
|
-
type: 'MultiPolygon',
|
|
61504
|
-
coordinates: coords
|
|
61505
|
-
}]
|
|
61506
|
-
}, {type: 'polygon'});
|
|
61507
|
-
}
|
|
61508
|
-
|
|
61509
|
-
// Union a set of winding-fill offset rings (which self-overlap) into clean,
|
|
61510
|
-
// non-self-overlapping MultiPolygon coordinates, via the winding-number
|
|
61511
|
-
// dissolve. Used by the topological pipeline to feed an ordinary polygon into
|
|
61512
|
-
// the shared mosaic (whose boundary-flood membership cannot resolve the
|
|
61513
|
-
// self-overlapping construction ring directly).
|
|
61514
|
-
function dissolveOffsetRingsToCoords(coords, opts, outlineDissolve) {
|
|
61515
|
-
if (!coords || coords.length === 0) return [];
|
|
61516
|
-
var dataset = getBufferDataset(coords);
|
|
61517
|
-
if (!dataset.arcs) return [];
|
|
61518
|
-
var dissolveOpts = outlineDissolve ?
|
|
61519
|
-
getOutlineBufferDissolveOpts(opts) :
|
|
61520
|
-
Object.assign({}, opts, {winding_fill: true});
|
|
61521
|
-
dissolveBufferDataset2(dataset, dissolveOpts);
|
|
61522
|
-
var lyr = dataset.layers[0];
|
|
61523
|
-
var shape = lyr.shapes && lyr.shapes[0];
|
|
61524
|
-
return shape ? getPolygonMultiPolygonCoords(shape, dataset.arcs) : [];
|
|
61525
|
-
}
|
|
61526
|
-
|
|
61527
|
-
function getBufferMultiPolygonCoords(paths, distance, bufferMaker) {
|
|
61528
|
-
var features, coords = [];
|
|
61529
|
-
if (paths.length === 0) return coords;
|
|
61530
|
-
features = bufferMaker(paths, distance) || [];
|
|
61531
|
-
features.forEach(function(feat) {
|
|
61532
|
-
var geom = feat && feat.geometry;
|
|
61533
|
-
if (geom && geom.type == 'MultiPolygon') {
|
|
61534
|
-
coords = coords.concat(geom.coordinates);
|
|
61535
|
-
}
|
|
61536
|
-
});
|
|
61537
|
-
return coords;
|
|
61538
|
-
}
|
|
61539
|
-
|
|
61540
|
-
function getPolygonBufferPathData(shape, uniqueArcTest) {
|
|
61541
|
-
var data = {paths: [], split: false};
|
|
61542
|
-
(shape || []).forEach(function(path) {
|
|
61543
|
-
var paths = uniqueArcTest ? splitPathAtSharedArcs(path, uniqueArcTest) :
|
|
61544
|
-
[path.concat()];
|
|
61545
|
-
if (paths.length != 1 || paths[0].length != path.length ||
|
|
61546
|
-
!paths[0].every(function(arcId, i) {
|
|
61547
|
-
return arcId == path[i];
|
|
61548
|
-
})) {
|
|
61549
|
-
data.split = true;
|
|
61550
|
-
}
|
|
61551
|
-
data.paths = data.paths.concat(paths);
|
|
61552
|
-
});
|
|
61553
|
-
return data;
|
|
61554
|
-
}
|
|
61555
|
-
|
|
61556
|
-
function splitPathAtSharedArcs(path, uniqueArcTest) {
|
|
61557
|
-
var flags = path.map(uniqueArcTest);
|
|
61558
|
-
var firstShared = flags.indexOf(false);
|
|
61559
|
-
var chains = [];
|
|
61560
|
-
var chain = [];
|
|
61561
|
-
var start, i, arcId;
|
|
61562
|
-
if (firstShared == -1) return [path.concat()];
|
|
61563
|
-
start = (firstShared + 1) % path.length;
|
|
61564
|
-
for (i = 0; i < path.length; i++) {
|
|
61565
|
-
arcId = path[(start + i) % path.length];
|
|
61566
|
-
if (uniqueArcTest(arcId)) {
|
|
61567
|
-
chain.push(arcId);
|
|
61568
|
-
} else if (chain.length > 0) {
|
|
61569
|
-
chains.push(chain);
|
|
61570
|
-
chain = [];
|
|
61571
|
-
}
|
|
61572
|
-
}
|
|
61573
|
-
if (chain.length > 0) {
|
|
61574
|
-
chains.push(chain);
|
|
61575
|
-
}
|
|
61576
|
-
return chains;
|
|
61577
|
-
}
|
|
61578
|
-
|
|
61579
|
-
function getUniqueArcTest(lyr, arcs) {
|
|
61580
|
-
var classify = getArcClassifier(lyr, arcs, {reusable: true})(function(a, b) {
|
|
61581
|
-
return b == -1 ? 'unique' : null;
|
|
61582
|
-
});
|
|
61583
|
-
return function(arcId) {
|
|
61584
|
-
return !!classify(arcId);
|
|
61585
|
-
};
|
|
61586
|
-
}
|
|
61587
|
-
|
|
61588
|
-
function ringArea(ring) {
|
|
61589
|
-
var iter = new PointIter(ring);
|
|
61590
|
-
return getSphericalPathArea2(iter);
|
|
61591
|
-
}
|
|
61592
|
-
|
|
61593
|
-
function makePointBuffer(lyr, dataset, opts) {
|
|
61594
|
-
var geojson = makePointBufferGeoJSON(lyr, dataset, opts);
|
|
61595
|
-
return importGeoJSON(geojson, {});
|
|
61596
|
-
}
|
|
61597
|
-
|
|
61598
|
-
// Make a single geodetic circle
|
|
61599
|
-
function getCircleGeoJSON(center, radius, vertices, opts) {
|
|
61600
|
-
var n = 360;
|
|
61601
|
-
var geod = getGeodeticSegmentFunction(parseCrsString$1('wgs84')); // ?
|
|
61602
|
-
if (opts.inset) {
|
|
61603
|
-
radius -= opts.inset;
|
|
61604
|
-
}
|
|
61605
|
-
return opts.geometry_type == 'polyline' ?
|
|
61606
|
-
getPointBufferLineString([center], radius, n, geod) :
|
|
61607
|
-
getPointBufferPolygon([center], radius, n, geod, true);
|
|
61608
|
-
}
|
|
61609
|
-
|
|
61610
|
-
// Convert a point layer to circles
|
|
61611
|
-
function makePointBufferGeoJSON(lyr, dataset, opts) {
|
|
61612
|
-
var vertices = opts.vertices || 72;
|
|
61613
|
-
var distanceFn = getBufferDistanceFunction(lyr, dataset, opts);
|
|
61614
|
-
var crs = getDatasetCRS(dataset);
|
|
61615
|
-
var spherical = isLatLngCRS(crs);
|
|
61616
|
-
var geod = getGeodeticSegmentFunction(crs);
|
|
61617
|
-
var geometries = lyr.shapes.map(function(shape, i) {
|
|
61618
|
-
var dist = distanceFn(i);
|
|
61619
|
-
if (!dist || !shape) return null;
|
|
61620
|
-
return getPointBufferPolygon(shape, dist, vertices, geod, spherical);
|
|
61621
|
-
});
|
|
61622
|
-
// TODO: make sure that importer supports null geometries (nonstandard GeoJSON);
|
|
61623
|
-
return {
|
|
61624
|
-
type: 'GeometryCollection',
|
|
61625
|
-
geometries: geometries
|
|
61626
|
-
};
|
|
61627
|
-
}
|
|
61628
|
-
|
|
61629
|
-
function getPointBufferPolygon(points, distance, vertices, geod, spherical) {
|
|
61630
|
-
var rings = [], coords, coords2;
|
|
61631
|
-
if (!points || !points.length) return null;
|
|
61632
|
-
for (var i=0; i<points.length; i++) {
|
|
61633
|
-
coords = getPointBufferCoordinates(points[i], distance, vertices, geod);
|
|
61634
|
-
if (!spherical) {
|
|
61635
|
-
rings.push([coords]);
|
|
61636
|
-
} else if (countCrosses(coords) > 0) {
|
|
61637
|
-
coords2 = removePolygonCrosses([coords]);
|
|
61638
|
-
while (coords2.length > 0) rings.push([coords2.pop()]); // geojson polygon coords, no hole
|
|
61639
|
-
} else if (ringArea(coords) < 0) {
|
|
61640
|
-
// negative spherical area: CCW ring, indicating a circle of >180 degrees
|
|
61641
|
-
// that fully encloses both poles and the antimeridian.
|
|
61642
|
-
// need to add an enclosure around the entire sphere
|
|
61643
|
-
// TODO: compare to distance param as a sanity check
|
|
61644
|
-
rings.push([
|
|
61645
|
-
[[180, 90], [180, -90], [0, -90], [-180, -90], [-180, 90], [0, 90], [180, 90]],
|
|
61646
|
-
coords
|
|
61647
|
-
]);
|
|
61648
|
-
} else {
|
|
61649
|
-
rings.push([coords]);
|
|
61650
|
-
}
|
|
61651
|
-
}
|
|
61652
|
-
return {
|
|
61653
|
-
type: 'MultiPolygon',
|
|
61654
|
-
coordinates: rings
|
|
61655
|
-
};
|
|
61656
|
-
}
|
|
61657
|
-
|
|
61658
|
-
function getPointBufferLineString(points, distance, vertices, geod) {
|
|
61659
|
-
var rings = [], coords;
|
|
61660
|
-
if (!points || !points.length) return null;
|
|
61661
|
-
for (var i=0; i<points.length; i++) {
|
|
61662
|
-
coords = getPointBufferCoordinates(points[i], distance, vertices, geod);
|
|
61663
|
-
coords = removePolylineCrosses(coords);
|
|
61664
|
-
while (coords.length > 0) rings.push(coords.pop());
|
|
61665
|
-
}
|
|
61666
|
-
return rings.length == 1 ? {
|
|
61667
|
-
type: 'LineString',
|
|
61668
|
-
coordinates: rings[0]
|
|
61669
|
-
} : {
|
|
61670
|
-
type: 'MultiLineString',
|
|
61671
|
-
coordinates: rings
|
|
61672
|
-
};
|
|
61673
|
-
}
|
|
61674
|
-
|
|
61675
|
-
// Returns array of [x, y] coordinates in a closed ring
|
|
61676
|
-
function getPointBufferCoordinates(center, meterDist, vertices, geod) {
|
|
61677
|
-
var coords = [],
|
|
61678
|
-
angle = 360 / vertices,
|
|
61679
|
-
theta;
|
|
61680
|
-
for (var i=0; i<vertices; i++) {
|
|
61681
|
-
// offsetting by half a step so 4 sides are flat, not pointy
|
|
61682
|
-
// (looks better on low-vertex circles)
|
|
61683
|
-
theta = (i + 0.5) * angle % 360;
|
|
61684
|
-
coords.push(geod(center[0], center[1], theta, meterDist));
|
|
61685
|
-
}
|
|
61686
|
-
coords.push(coords[0].concat());
|
|
61687
|
-
return coords;
|
|
61688
|
-
}
|
|
61689
|
-
|
|
61690
|
-
// Returns number of arcs that were removed
|
|
61691
|
-
function editArcs(arcs, onPoint) {
|
|
61692
|
-
var nn2 = [],
|
|
61693
|
-
xx2 = [],
|
|
61694
|
-
yy2 = [],
|
|
61695
|
-
errors = 0,
|
|
61696
|
-
n;
|
|
61697
|
-
|
|
61698
|
-
arcs.forEach(function(arc, i) {
|
|
61699
|
-
editArc(arc, onPoint);
|
|
61700
|
-
});
|
|
61701
|
-
arcs.updateVertexData(nn2, xx2, yy2);
|
|
61702
|
-
return errors;
|
|
61703
|
-
|
|
61704
|
-
function append(p) {
|
|
61705
|
-
if (p) {
|
|
61706
|
-
xx2.push(p[0]);
|
|
61707
|
-
yy2.push(p[1]);
|
|
61708
|
-
n++;
|
|
61709
|
-
}
|
|
61710
|
-
}
|
|
61711
|
-
|
|
61712
|
-
function editArc(arc, cb) {
|
|
61713
|
-
var x, y, xp, yp, retn;
|
|
61714
|
-
var valid = true;
|
|
61715
|
-
var i = 0;
|
|
61716
|
-
n = 0;
|
|
61717
|
-
while (arc.hasNext()) {
|
|
61718
|
-
x = arc.x;
|
|
61719
|
-
y = arc.y;
|
|
61720
|
-
retn = cb(append, x, y, xp, yp, i++);
|
|
61721
|
-
if (retn === false) {
|
|
61722
|
-
valid = false;
|
|
61723
|
-
// assumes that it's ok for the arc iterator to be interrupted.
|
|
61724
|
-
break;
|
|
61725
|
-
}
|
|
61726
|
-
xp = x;
|
|
61727
|
-
yp = y;
|
|
61728
|
-
}
|
|
61729
|
-
if (valid && n == 1) {
|
|
61730
|
-
// only one valid point was added to this arc (invalid)
|
|
61731
|
-
// e.g. this could happen during reprojection.
|
|
61732
|
-
// making this arc empty
|
|
61733
|
-
// error("An invalid arc was created");
|
|
61734
|
-
message("An invalid arc was created");
|
|
61735
|
-
valid = false;
|
|
61736
|
-
}
|
|
61737
|
-
if (valid) {
|
|
61738
|
-
nn2.push(n);
|
|
61739
|
-
} else {
|
|
61740
|
-
// remove any points that were added for an invalid arc
|
|
61741
|
-
while (n-- > 0) {
|
|
61742
|
-
xx2.pop();
|
|
61743
|
-
yy2.pop();
|
|
61744
|
-
}
|
|
61745
|
-
nn2.push(0); // add empty arc (to preserve mapping from paths to arcs)
|
|
61746
|
-
errors++;
|
|
61747
|
-
}
|
|
61748
|
-
}
|
|
61749
|
-
}
|
|
61750
|
-
|
|
61751
|
-
// Planar densification by an interval
|
|
61752
|
-
function densifyPathByInterval(coords, interval, interpolate) {
|
|
61753
|
-
if (findMaxPathInterval(coords) < interval) return coords;
|
|
61754
|
-
if (!interpolate) {
|
|
61755
|
-
interpolate = getIntervalInterpolator(interval);
|
|
61756
|
-
}
|
|
61757
|
-
var coords2 = [coords[0]], a, b;
|
|
61758
|
-
for (var i=1, n=coords.length; i<n; i++) {
|
|
61759
|
-
a = coords[i-1];
|
|
61760
|
-
b = coords[i];
|
|
61761
|
-
if (geom.distance2D(a[0], a[1], b[0], b[1]) > interval + 1e-4) {
|
|
61762
|
-
appendArr(coords2, interpolate(a, b));
|
|
61763
|
-
}
|
|
61764
|
-
coords2.push(b);
|
|
61765
|
-
}
|
|
61766
|
-
return coords2;
|
|
61767
|
-
}
|
|
61768
|
-
|
|
61769
|
-
function getIntervalInterpolator(interval) {
|
|
61770
|
-
return function(a, b) {
|
|
61771
|
-
var points = [];
|
|
61772
|
-
// var rev = a[0] == b[0] ? a[1] > b[1] : a[0] > b[0];
|
|
61773
|
-
var dist = geom.distance2D(a[0], a[1], b[0], b[1]);
|
|
61774
|
-
var n = Math.round(dist / interval) - 1;
|
|
61775
|
-
var dx = (b[0] - a[0]) / (n + 1),
|
|
61776
|
-
dy = (b[1] - a[1]) / (n + 1);
|
|
61777
|
-
for (var i=1; i<=n; i++) {
|
|
61778
|
-
points.push([a[0] + dx * i, a[1] + dy * i]);
|
|
61779
|
-
}
|
|
61780
|
-
return points;
|
|
61781
|
-
};
|
|
61782
|
-
}
|
|
61783
|
-
|
|
61784
|
-
|
|
61785
|
-
// Interpolate the same points regardless of segment direction
|
|
61786
|
-
function densifyAntimeridianSegment(a, b, interval) {
|
|
61787
|
-
var y1, y2;
|
|
61788
|
-
var coords = [];
|
|
61789
|
-
var ascending = a[1] < b[1];
|
|
61790
|
-
if (a[0] != b[0]) error('Expected an edge segment');
|
|
61791
|
-
if (ascending) {
|
|
61792
|
-
y1 = a[1];
|
|
61793
|
-
y2 = b[1];
|
|
61794
|
-
} else {
|
|
61795
|
-
y1 = b[1];
|
|
61796
|
-
y2 = a[1];
|
|
61797
|
-
}
|
|
61798
|
-
var y = Math.floor(y1 / interval) * interval + interval;
|
|
61799
|
-
while (y < y2) {
|
|
61800
|
-
coords.push([a[0], y]);
|
|
61801
|
-
y += interval;
|
|
61802
|
-
}
|
|
61803
|
-
if (!ascending) coords.reverse();
|
|
61804
|
-
return coords;
|
|
61805
|
-
}
|
|
61806
|
-
|
|
61807
|
-
function appendArr(dest, src) {
|
|
61808
|
-
for (var i=0; i<src.length; i++) dest.push(src[i]);
|
|
61809
|
-
}
|
|
61810
|
-
|
|
61811
|
-
function findMaxPathInterval(coords) {
|
|
61812
|
-
var maxSq = 0, intSq, a, b;
|
|
61813
|
-
for (var i=1, n=coords.length; i<n; i++) {
|
|
61814
|
-
a = coords[i-1];
|
|
61815
|
-
b = coords[i];
|
|
61816
|
-
intSq = geom.distanceSq(a[0], a[1], b[0], b[1]);
|
|
61817
|
-
if (intSq > maxSq) maxSq = intSq;
|
|
61818
|
-
}
|
|
61819
|
-
return Math.sqrt(maxSq);
|
|
61820
|
-
}
|
|
61821
|
-
|
|
61822
|
-
function projectAndDensifyArcs(arcs, proj) {
|
|
61823
|
-
var interval = getDefaultDensifyInterval(arcs, proj);
|
|
61824
|
-
var minIntervalSq = interval * interval * 25;
|
|
61825
|
-
var p;
|
|
61826
|
-
return editArcs(arcs, onPoint);
|
|
61827
|
-
|
|
61828
|
-
function onPoint(append, lng, lat, prevLng, prevLat, i) {
|
|
61829
|
-
var pp = p;
|
|
61830
|
-
p = proj(lng, lat);
|
|
61831
|
-
if (!p) return false; // signal that current arc contains an error
|
|
61832
|
-
|
|
61833
|
-
// Don't try to densify shorter segments (optimization)
|
|
61834
|
-
if (i > 0 && geom.distanceSq(p[0], p[1], pp[0], pp[1]) > minIntervalSq) {
|
|
61835
|
-
densifySegment(prevLng, prevLat, pp[0], pp[1], lng, lat, p[0], p[1], proj, interval)
|
|
61836
|
-
.forEach(append);
|
|
61837
|
-
}
|
|
61838
|
-
append(p);
|
|
61839
|
-
}
|
|
61840
|
-
}
|
|
61841
|
-
|
|
61842
|
-
// Use the median of intervals computed by projecting segments.
|
|
61843
|
-
// We're probing a number of points, because @proj might only be valid in
|
|
61844
|
-
// a sub-region of the dataset bbox (e.g. +proj=tpers)
|
|
61845
|
-
function findDensifyInterval(bounds, xy, proj) {
|
|
61846
|
-
var steps = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
|
|
61847
|
-
var points = [];
|
|
61848
|
-
for (var i=0; i<steps.length; i++) {
|
|
61849
|
-
for (var j=0; j<steps.length; j++) {
|
|
61850
|
-
points.push([steps[i], steps[j]]);
|
|
61851
|
-
}
|
|
61852
|
-
}
|
|
61853
|
-
var intervals = points.map(function(pos) {
|
|
61854
|
-
var x = bounds.xmin + bounds.width() * pos[0];
|
|
61855
|
-
var y = bounds.ymin + bounds.height() * pos[1];
|
|
61856
|
-
var a = proj(x, y);
|
|
61857
|
-
var b = proj(x + xy[0], y + xy[1]);
|
|
61858
|
-
return a && b ? geom.distance2D(a[0], a[1], b[0], b[1]) : Infinity;
|
|
61859
|
-
}).filter(function(int) {return int < Infinity;});
|
|
61860
|
-
return intervals.length > 0 ? utils.findMedian(intervals) : Infinity;
|
|
61861
|
-
}
|
|
61862
|
-
|
|
61863
|
-
// Kludgy way to get a useful interval for densifying a bounding box.
|
|
61864
|
-
// Uses a fraction of average bbox side length)
|
|
61865
|
-
// TODO: improve
|
|
61866
|
-
function findDensifyInterval2(bb, proj) {
|
|
61867
|
-
var a = proj(bb.centerX(), bb.centerY()),
|
|
61868
|
-
c = proj(bb.centerX(), bb.ymin), // right center
|
|
61869
|
-
d = proj(bb.xmax, bb.centerY()); // bottom center
|
|
61870
|
-
var interval = a && c && d ? (geom.distance2D(a[0], a[1], c[0], c[1]) +
|
|
61871
|
-
geom.distance2D(a[0], a[1], d[0], d[1])) / 5000 : Infinity;
|
|
61872
|
-
return interval;
|
|
61873
|
-
}
|
|
61874
|
-
|
|
61875
|
-
// Returns an interval in projected units
|
|
61876
|
-
function getDefaultDensifyInterval(arcs, proj) {
|
|
61877
|
-
var xy = getAvgSegment2(arcs),
|
|
61878
|
-
bb = arcs.getBounds(),
|
|
61879
|
-
intervalA = findDensifyInterval(bb, xy, proj),
|
|
61880
|
-
intervalB = findDensifyInterval2(bb, proj),
|
|
61881
|
-
interval = Math.min(intervalA, intervalB);
|
|
61882
|
-
if (interval == Infinity) {
|
|
61883
|
-
error('Densification error');
|
|
61884
|
-
}
|
|
61885
|
-
return interval;
|
|
61886
|
-
}
|
|
61887
|
-
|
|
61888
|
-
// Interpolate points into a projected line segment if needed to prevent large
|
|
61889
|
-
// deviations from path of original unprojected segment.
|
|
61890
|
-
// @points (optional) array of accumulated points
|
|
61891
|
-
function densifySegment(lng0, lat0, x0, y0, lng2, lat2, x2, y2, proj, interval, points) {
|
|
61892
|
-
// Find midpoint between two endpoints and project it (assumes longitude does
|
|
61893
|
-
// not wrap). TODO Consider bisecting along great circle path -- although this
|
|
61894
|
-
// would not be good for boundaries that follow line of constant latitude.
|
|
61895
|
-
var lng1 = (lng0 + lng2) / 2,
|
|
61896
|
-
lat1 = (lat0 + lat2) / 2,
|
|
61897
|
-
p = proj(lng1, lat1),
|
|
61898
|
-
distSq;
|
|
61899
|
-
if (!p) return; // TODO: consider if this is adequate for handling proj. errors
|
|
61900
|
-
distSq = geom.pointSegDistSq2(p[0], p[1], x0, y0, x2, y2); // sq displacement
|
|
61901
|
-
points = points || [];
|
|
61902
|
-
// Bisect current segment if the projected midpoint deviates from original
|
|
61903
|
-
// segment by more than the @interval parameter.
|
|
61904
|
-
// ... but don't bisect very small segments to prevent infinite recursion
|
|
61905
|
-
// (e.g. if projection function is discontinuous)
|
|
61906
|
-
if (distSq > interval * interval * 0.25 && geom.distance2D(lng0, lat0, lng2, lat2) > 0.01) {
|
|
61907
|
-
densifySegment(lng0, lat0, x0, y0, lng1, lat1, p[0], p[1], proj, interval, points);
|
|
61908
|
-
points.push(p);
|
|
61909
|
-
densifySegment(lng1, lat1, p[0], p[1], lng2, lat2, x2, y2, proj, interval, points);
|
|
61910
|
-
}
|
|
61911
|
-
return points;
|
|
61912
|
-
}
|
|
61913
|
-
|
|
61914
|
-
// Create rectangles around each feature in a layer
|
|
61915
|
-
cmd.rectangles = function(targetLyr, targetDataset, opts) {
|
|
61916
|
-
var crsInfo = getDatasetCrsInfo(targetDataset);
|
|
61917
|
-
var records = targetLyr.data ? targetLyr.data.getRecords() : null;
|
|
61918
|
-
var geometries;
|
|
61919
|
-
|
|
61920
|
-
if (opts.bbox) {
|
|
61921
|
-
geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset);
|
|
61922
|
-
|
|
61923
|
-
} else {
|
|
61924
|
-
if (!layerHasGeometry$1(targetLyr)) {
|
|
61925
|
-
stop$1("Layer is missing geometric shapes");
|
|
61926
|
-
}
|
|
61927
|
-
geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
|
|
61928
|
-
}
|
|
61929
|
-
|
|
61930
|
-
var geojson = {
|
|
61931
|
-
type: 'FeatureCollection',
|
|
61932
|
-
features: geometries.map(function(geom, i) {
|
|
61933
|
-
var rec = records && records[i] || null;
|
|
61934
|
-
if (rec && opts.no_replace) {
|
|
61935
|
-
rec = utils.extend({}, rec); // make a copy
|
|
61936
|
-
}
|
|
61937
|
-
return {
|
|
61938
|
-
type: 'Feature',
|
|
61939
|
-
properties: rec,
|
|
61940
|
-
geometry: geom
|
|
61941
|
-
};
|
|
61942
|
-
})
|
|
61943
|
-
};
|
|
61944
|
-
var dataset = importGeoJSON(geojson, {});
|
|
61945
|
-
setDatasetCrsInfo(dataset, crsInfo);
|
|
61946
|
-
var outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
|
|
61947
|
-
setOutputLayerName(outputLayers[0], targetLyr, null, opts);
|
|
61948
|
-
return outputLayers;
|
|
61949
|
-
};
|
|
61950
|
-
|
|
61951
|
-
|
|
61952
|
-
|
|
61953
|
-
|
|
61954
|
-
function shapesToBoxGeometries(lyr, dataset, opts) {
|
|
61955
|
-
var crsInfo = getDatasetCrsInfo(dataset);
|
|
61956
|
-
return lyr.shapes.map(function(shp) {
|
|
61957
|
-
var bounds = lyr.geometry_type == 'point' ?
|
|
61958
|
-
getPointFeatureBounds(shp) : dataset.arcs.getMultiShapeBounds(shp);
|
|
61959
|
-
bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
|
|
61960
|
-
if (!bounds) return null;
|
|
61961
|
-
return bboxToPolygon(bounds.toArray(), opts);
|
|
62711
|
+
return path.points.map(function(p) {
|
|
62712
|
+
return p.concat();
|
|
62713
|
+
});
|
|
62714
|
+
});
|
|
61962
62715
|
});
|
|
61963
62716
|
}
|
|
61964
62717
|
|
|
61965
|
-
function
|
|
61966
|
-
var
|
|
61967
|
-
|
|
61968
|
-
|
|
61969
|
-
|
|
61970
|
-
|
|
61971
|
-
result = compiled(i);
|
|
61972
|
-
if (!looksLikeBbox(result)) {
|
|
61973
|
-
stop$1('Invalid bbox value (expected a GeoJSON-type bbox):', result);
|
|
61974
|
-
}
|
|
61975
|
-
geometries.push(bboxToPolygon(result));
|
|
61976
|
-
}
|
|
61977
|
-
return geometries;
|
|
62718
|
+
function getPolygonGeometry(shape, arcs) {
|
|
62719
|
+
var coords = getPolygonMultiPolygonCoords(shape, arcs);
|
|
62720
|
+
return coords.length > 0 ? {
|
|
62721
|
+
type: 'MultiPolygon',
|
|
62722
|
+
coordinates: coords
|
|
62723
|
+
} : null;
|
|
61978
62724
|
}
|
|
61979
62725
|
|
|
61980
|
-
function
|
|
61981
|
-
|
|
61982
|
-
|
|
61983
|
-
|
|
61984
|
-
|
|
62726
|
+
function getBufferDataset(coords) {
|
|
62727
|
+
return importGeoJSON({
|
|
62728
|
+
type: 'GeometryCollection',
|
|
62729
|
+
geometries: [{
|
|
62730
|
+
type: 'MultiPolygon',
|
|
62731
|
+
coordinates: coords
|
|
62732
|
+
}]
|
|
62733
|
+
}, {type: 'polygon'});
|
|
61985
62734
|
}
|
|
61986
62735
|
|
|
61987
|
-
//
|
|
61988
|
-
//
|
|
61989
|
-
|
|
61990
|
-
|
|
61991
|
-
|
|
61992
|
-
|
|
61993
|
-
|
|
61994
|
-
|
|
61995
|
-
|
|
61996
|
-
|
|
61997
|
-
|
|
61998
|
-
|
|
61999
|
-
|
|
62000
|
-
|
|
62001
|
-
|
|
62736
|
+
// Union a set of winding-fill offset rings (which self-overlap) into clean,
|
|
62737
|
+
// non-self-overlapping MultiPolygon coordinates, via the winding-number
|
|
62738
|
+
// dissolve. Used by the topological pipeline to feed an ordinary polygon into
|
|
62739
|
+
// the shared mosaic (whose boundary-flood membership cannot resolve the
|
|
62740
|
+
// self-overlapping construction ring directly).
|
|
62741
|
+
function dissolveOffsetRingsToCoords(coords, opts, outlineDissolve) {
|
|
62742
|
+
if (!coords || coords.length === 0) return [];
|
|
62743
|
+
var dataset = getBufferDataset(coords);
|
|
62744
|
+
if (!dataset.arcs) return [];
|
|
62745
|
+
var dissolveOpts = outlineDissolve ?
|
|
62746
|
+
getOutlineBufferDissolveOpts(opts) :
|
|
62747
|
+
Object.assign({}, opts, {winding_fill: true});
|
|
62748
|
+
dissolveBufferDataset2(dataset, dissolveOpts);
|
|
62749
|
+
var lyr = dataset.layers[0];
|
|
62750
|
+
var shape = lyr.shapes && lyr.shapes[0];
|
|
62751
|
+
return shape ? getPolygonMultiPolygonCoords(shape, dataset.arcs) : [];
|
|
62752
|
+
}
|
|
62753
|
+
|
|
62754
|
+
function getBufferMultiPolygonCoords(paths, distance, bufferMaker) {
|
|
62755
|
+
var features, coords = [];
|
|
62756
|
+
if (paths.length === 0) return coords;
|
|
62757
|
+
features = bufferMaker(paths, distance) || [];
|
|
62758
|
+
features.forEach(function(feat) {
|
|
62759
|
+
var geom = feat && feat.geometry;
|
|
62760
|
+
if (geom && geom.type == 'MultiPolygon') {
|
|
62761
|
+
coords = coords.concat(geom.coordinates);
|
|
62002
62762
|
}
|
|
62003
|
-
return dataset;
|
|
62004
62763
|
});
|
|
62005
|
-
return
|
|
62006
|
-
}
|
|
62764
|
+
return coords;
|
|
62765
|
+
}
|
|
62007
62766
|
|
|
62008
|
-
|
|
62009
|
-
var
|
|
62010
|
-
|
|
62011
|
-
|
|
62012
|
-
|
|
62013
|
-
|
|
62014
|
-
|
|
62015
|
-
|
|
62016
|
-
|
|
62767
|
+
function getPolygonBufferPathData(shape, uniqueArcTest) {
|
|
62768
|
+
var data = {paths: [], split: false};
|
|
62769
|
+
(shape || []).forEach(function(path) {
|
|
62770
|
+
var paths = uniqueArcTest ? splitPathAtSharedArcs(path, uniqueArcTest) :
|
|
62771
|
+
[path.concat()];
|
|
62772
|
+
if (paths.length != 1 || paths[0].length != path.length ||
|
|
62773
|
+
!paths[0].every(function(arcId, i) {
|
|
62774
|
+
return arcId == path[i];
|
|
62775
|
+
})) {
|
|
62776
|
+
data.split = true;
|
|
62777
|
+
}
|
|
62778
|
+
data.paths = data.paths.concat(paths);
|
|
62779
|
+
});
|
|
62780
|
+
return data;
|
|
62781
|
+
}
|
|
62782
|
+
|
|
62783
|
+
function splitPathAtSharedArcs(path, uniqueArcTest) {
|
|
62784
|
+
var flags = path.map(uniqueArcTest);
|
|
62785
|
+
var firstShared = flags.indexOf(false);
|
|
62786
|
+
var chains = [];
|
|
62787
|
+
var chain = [];
|
|
62788
|
+
var start, i, arcId;
|
|
62789
|
+
if (firstShared == -1) return [path.concat()];
|
|
62790
|
+
start = (firstShared + 1) % path.length;
|
|
62791
|
+
for (i = 0; i < path.length; i++) {
|
|
62792
|
+
arcId = path[(start + i) % path.length];
|
|
62793
|
+
if (uniqueArcTest(arcId)) {
|
|
62794
|
+
chain.push(arcId);
|
|
62795
|
+
} else if (chain.length > 0) {
|
|
62796
|
+
chains.push(chain);
|
|
62797
|
+
chain = [];
|
|
62798
|
+
}
|
|
62017
62799
|
}
|
|
62018
|
-
|
|
62019
|
-
|
|
62020
|
-
stop$1('Missing rectangle extent');
|
|
62800
|
+
if (chain.length > 0) {
|
|
62801
|
+
chains.push(chain);
|
|
62021
62802
|
}
|
|
62022
|
-
|
|
62023
|
-
|
|
62024
|
-
|
|
62025
|
-
|
|
62803
|
+
return chains;
|
|
62804
|
+
}
|
|
62805
|
+
|
|
62806
|
+
function getUniqueArcTest(lyr, arcs) {
|
|
62807
|
+
var classify = getArcClassifier(lyr, arcs, {reusable: true})(function(a, b) {
|
|
62808
|
+
return b == -1 ? 'unique' : null;
|
|
62809
|
+
});
|
|
62810
|
+
return function(arcId) {
|
|
62811
|
+
return !!classify(arcId);
|
|
62026
62812
|
};
|
|
62027
|
-
|
|
62028
|
-
applyFrameProperties(dataset.layers[0], opts);
|
|
62029
|
-
dataset.layers[0].name = opts.name || 'rectangle';
|
|
62030
|
-
setDatasetCrsInfo(dataset, crsInfo);
|
|
62031
|
-
return dataset;
|
|
62032
|
-
};
|
|
62813
|
+
}
|
|
62033
62814
|
|
|
62034
|
-
function
|
|
62035
|
-
|
|
62036
|
-
|
|
62037
|
-
var d = lyr.data.getRecords()[0] || {};
|
|
62038
|
-
d.width = parseSizeParam(opts.width);
|
|
62039
|
-
d.type = 'frame';
|
|
62815
|
+
function ringArea(ring) {
|
|
62816
|
+
var iter = new PointIter(ring);
|
|
62817
|
+
return getSphericalPathArea2(iter);
|
|
62040
62818
|
}
|
|
62041
62819
|
|
|
62042
|
-
function
|
|
62043
|
-
var
|
|
62044
|
-
|
|
62045
|
-
bounds = applyBoundsOffset(opts.offset, bounds, crs);
|
|
62046
|
-
}
|
|
62047
|
-
if (bounds.area() > 0 === false) return null;
|
|
62048
|
-
if (opts.aspect_ratio) {
|
|
62049
|
-
bounds = applyAspectRatio(opts.aspect_ratio, bounds);
|
|
62050
|
-
}
|
|
62051
|
-
if (isGeoBox) {
|
|
62052
|
-
bounds = clampToWorldBounds(bounds);
|
|
62053
|
-
}
|
|
62054
|
-
return bounds;
|
|
62820
|
+
function makePointBuffer(lyr, dataset, opts) {
|
|
62821
|
+
var geojson = makePointBufferGeoJSON(lyr, dataset, opts);
|
|
62822
|
+
return importGeoJSON(geojson, {});
|
|
62055
62823
|
}
|
|
62056
62824
|
|
|
62057
|
-
//
|
|
62058
|
-
function
|
|
62059
|
-
var
|
|
62060
|
-
|
|
62061
|
-
|
|
62062
|
-
|
|
62063
|
-
range.push(range[0]);
|
|
62064
|
-
} else if (range[0] > range[1]) {
|
|
62065
|
-
range.reverse();
|
|
62066
|
-
}
|
|
62067
|
-
min = range[0];
|
|
62068
|
-
max = range[1];
|
|
62069
|
-
if (!min && !max) return bounds;
|
|
62070
|
-
if (!min) min = -Infinity;
|
|
62071
|
-
if (!max) max = Infinity;
|
|
62072
|
-
if (aspectRatio < min) {
|
|
62073
|
-
bounds.fillOut(min);
|
|
62074
|
-
} else if (aspectRatio > max) {
|
|
62075
|
-
bounds.fillOut(max);
|
|
62825
|
+
// Make a single geodetic circle
|
|
62826
|
+
function getCircleGeoJSON(center, radius, vertices, opts) {
|
|
62827
|
+
var n = 360;
|
|
62828
|
+
var geod = getGeodeticSegmentFunction(parseCrsString$1('wgs84')); // ?
|
|
62829
|
+
if (opts.inset) {
|
|
62830
|
+
radius -= opts.inset;
|
|
62076
62831
|
}
|
|
62077
|
-
return
|
|
62832
|
+
return opts.geometry_type == 'polyline' ?
|
|
62833
|
+
getPointBufferLineString([center], radius, n, geod) :
|
|
62834
|
+
getPointBufferPolygon([center], radius, n, geod, true);
|
|
62078
62835
|
}
|
|
62079
62836
|
|
|
62080
|
-
|
|
62081
|
-
|
|
62082
|
-
|
|
62083
|
-
|
|
62837
|
+
// Convert a point layer to circles
|
|
62838
|
+
function makePointBufferGeoJSON(lyr, dataset, opts) {
|
|
62839
|
+
var vertices = opts.vertices || 72;
|
|
62840
|
+
var distanceFn = getBufferDistanceFunction(lyr, dataset, opts);
|
|
62841
|
+
var crs = getDatasetCRS(dataset);
|
|
62842
|
+
var spherical = isLatLngCRS(crs);
|
|
62843
|
+
var geod = getGeodeticSegmentFunction(crs);
|
|
62844
|
+
var geometries = lyr.shapes.map(function(shape, i) {
|
|
62845
|
+
var dist = distanceFn(i);
|
|
62846
|
+
if (!dist || !shape) return null;
|
|
62847
|
+
return getPointBufferPolygon(shape, dist, vertices, geod, spherical);
|
|
62848
|
+
});
|
|
62849
|
+
// TODO: make sure that importer supports null geometries (nonstandard GeoJSON);
|
|
62850
|
+
return {
|
|
62851
|
+
type: 'GeometryCollection',
|
|
62852
|
+
geometries: geometries
|
|
62853
|
+
};
|
|
62084
62854
|
}
|
|
62085
62855
|
|
|
62086
|
-
function
|
|
62087
|
-
var
|
|
62088
|
-
|
|
62089
|
-
|
|
62090
|
-
coords =
|
|
62856
|
+
function getPointBufferPolygon(points, distance, vertices, geod, spherical) {
|
|
62857
|
+
var rings = [], coords, coords2;
|
|
62858
|
+
if (!points || !points.length) return null;
|
|
62859
|
+
for (var i=0; i<points.length; i++) {
|
|
62860
|
+
coords = getPointBufferCoordinates(points[i], distance, vertices, geod);
|
|
62861
|
+
if (!spherical) {
|
|
62862
|
+
rings.push([coords]);
|
|
62863
|
+
} else if (countCrosses(coords) > 0) {
|
|
62864
|
+
coords2 = removePolygonCrosses([coords]);
|
|
62865
|
+
while (coords2.length > 0) rings.push([coords2.pop()]); // geojson polygon coords, no hole
|
|
62866
|
+
} else if (ringArea(coords) < 0) {
|
|
62867
|
+
// negative spherical area: CCW ring, indicating a circle of >180 degrees
|
|
62868
|
+
// that fully encloses both poles and the antimeridian.
|
|
62869
|
+
// need to add an enclosure around the entire sphere
|
|
62870
|
+
// TODO: compare to distance param as a sanity check
|
|
62871
|
+
rings.push([
|
|
62872
|
+
[[180, 90], [180, -90], [0, -90], [-180, -90], [-180, 90], [0, 90], [180, 90]],
|
|
62873
|
+
coords
|
|
62874
|
+
]);
|
|
62875
|
+
} else {
|
|
62876
|
+
rings.push([coords]);
|
|
62877
|
+
}
|
|
62091
62878
|
}
|
|
62092
62879
|
return {
|
|
62093
|
-
type: '
|
|
62094
|
-
coordinates:
|
|
62880
|
+
type: 'MultiPolygon',
|
|
62881
|
+
coordinates: rings
|
|
62095
62882
|
};
|
|
62096
62883
|
}
|
|
62097
62884
|
|
|
62098
|
-
|
|
62099
|
-
|
|
62100
|
-
|
|
62101
|
-
|
|
62102
|
-
|
|
62885
|
+
function getPointBufferLineString(points, distance, vertices, geod) {
|
|
62886
|
+
var rings = [], coords;
|
|
62887
|
+
if (!points || !points.length) return null;
|
|
62888
|
+
for (var i=0; i<points.length; i++) {
|
|
62889
|
+
coords = getPointBufferCoordinates(points[i], distance, vertices, geod);
|
|
62890
|
+
coords = removePolylineCrosses(coords);
|
|
62891
|
+
while (coords.length > 0) rings.push(coords.pop());
|
|
62892
|
+
}
|
|
62893
|
+
return rings.length == 1 ? {
|
|
62894
|
+
type: 'LineString',
|
|
62895
|
+
coordinates: rings[0]
|
|
62896
|
+
} : {
|
|
62897
|
+
type: 'MultiLineString',
|
|
62898
|
+
coordinates: rings
|
|
62899
|
+
};
|
|
62900
|
+
}
|
|
62901
|
+
|
|
62902
|
+
// Returns array of [x, y] coordinates in a closed ring
|
|
62903
|
+
function getPointBufferCoordinates(center, meterDist, vertices, geod) {
|
|
62904
|
+
var coords = [],
|
|
62905
|
+
angle = 360 / vertices,
|
|
62906
|
+
theta;
|
|
62907
|
+
for (var i=0; i<vertices; i++) {
|
|
62908
|
+
// offsetting by half a step so 4 sides are flat, not pointy
|
|
62909
|
+
// (looks better on low-vertex circles)
|
|
62910
|
+
theta = (i + 0.5) * angle % 360;
|
|
62911
|
+
coords.push(geod(center[0], center[1], theta, meterDist));
|
|
62912
|
+
}
|
|
62913
|
+
coords.push(coords[0].concat());
|
|
62914
|
+
return coords;
|
|
62915
|
+
}
|
|
62103
62916
|
|
|
62104
62917
|
function getSemiMinorAxis(P) {
|
|
62105
62918
|
return P.a * Math.sqrt(1 - (P.es || 0));
|
|
@@ -63342,6 +64155,22 @@ ${svg}
|
|
|
63342
64155
|
// TODO: add more projections
|
|
63343
64156
|
//
|
|
63344
64157
|
function expandProjDefn(str, dataset, targetLayers) {
|
|
64158
|
+
return expandProjDefnWithBounds(str, function() {
|
|
64159
|
+
return getBBox(dataset, targetLayers); // TODO: support projected datasets
|
|
64160
|
+
});
|
|
64161
|
+
}
|
|
64162
|
+
|
|
64163
|
+
// Variant for a command with more than one target dataset. Auto-fitted
|
|
64164
|
+
// parameters describe the combined extent of the targets, so that a single
|
|
64165
|
+
// -proj command produces one destination CRS instead of fitting a different
|
|
64166
|
+
// projection to each dataset it happens to visit.
|
|
64167
|
+
function expandProjDefnForTargets(str, targets) {
|
|
64168
|
+
return expandProjDefnWithBounds(str, function() {
|
|
64169
|
+
return getTargetsBBox(targets);
|
|
64170
|
+
});
|
|
64171
|
+
}
|
|
64172
|
+
|
|
64173
|
+
function expandProjDefnWithBounds(str, getTargetBBox) {
|
|
63345
64174
|
var mproj = require$1('mproj');
|
|
63346
64175
|
var proj4, params, bbox, isConic2SP, isCentered, isUtm, decimals;
|
|
63347
64176
|
if (str in mproj.internal.pj_list === false) {
|
|
@@ -63353,7 +64182,7 @@ ${svg}
|
|
|
63353
64182
|
isUtm = str == 'utm';
|
|
63354
64183
|
proj4 = '+proj=' + str;
|
|
63355
64184
|
if (isConic2SP || isCentered || isUtm) {
|
|
63356
|
-
bbox =
|
|
64185
|
+
bbox = getTargetBBox();
|
|
63357
64186
|
decimals = getBoundsPrecisionForDisplay(bbox);
|
|
63358
64187
|
if (isUtm) {
|
|
63359
64188
|
params = getUtmParams(bbox);
|
|
@@ -63366,6 +64195,17 @@ ${svg}
|
|
|
63366
64195
|
return proj4;
|
|
63367
64196
|
}
|
|
63368
64197
|
|
|
64198
|
+
function getTargetsBBox(targets) {
|
|
64199
|
+
var bounds = new Bounds();
|
|
64200
|
+
targets.forEach(function(targ) {
|
|
64201
|
+
bounds.mergeBounds(getBBox(targ.dataset, targ.layers));
|
|
64202
|
+
});
|
|
64203
|
+
if (!bounds.hasBounds()) {
|
|
64204
|
+
stop$1('Command target is missing geographical bounds');
|
|
64205
|
+
}
|
|
64206
|
+
return bounds.toArray();
|
|
64207
|
+
}
|
|
64208
|
+
|
|
63369
64209
|
function getBBox(dataset, targetLayers) {
|
|
63370
64210
|
var source = targetLayers && targetLayers.length > 0 ? {
|
|
63371
64211
|
arcs: dataset.arcs,
|
|
@@ -63475,6 +64315,7 @@ ${svg}
|
|
|
63475
64315
|
var ProjectionParams = /*#__PURE__*/Object.freeze({
|
|
63476
64316
|
__proto__: null,
|
|
63477
64317
|
expandProjDefn: expandProjDefn,
|
|
64318
|
+
expandProjDefnForTargets: expandProjDefnForTargets,
|
|
63478
64319
|
getAutoFitBBox: getAutoFitBBox,
|
|
63479
64320
|
getCenterParams: getCenterParams,
|
|
63480
64321
|
getConicParams: getConicParams,
|
|
@@ -64936,32 +65777,112 @@ ${svg}
|
|
|
64936
65777
|
return a && b && a[0] == b[0] && a[1] == b[1];
|
|
64937
65778
|
}
|
|
64938
65779
|
|
|
64939
|
-
|
|
64940
|
-
|
|
65780
|
+
// Run -proj over all the targets of a single command.
|
|
65781
|
+
// The destination CRS is resolved once here rather than separately for each
|
|
65782
|
+
// target, for two reasons. Auto-fitted parameters (e.g. the standard parallels
|
|
65783
|
+
// that "lcc" expands to) must describe the whole command target, not whichever
|
|
65784
|
+
// dataset happens to be projected first. And the map frame must receive the
|
|
65785
|
+
// same CRS as the content instead of re-deriving parameters from its own extent.
|
|
65786
|
+
cmd.projTargets = function(targets, catalog, opts) {
|
|
65787
|
+
var destInfo;
|
|
65788
|
+
targets.forEach(function(targ) {
|
|
65789
|
+
applyProjInit(targ.dataset, catalog, opts);
|
|
65790
|
+
});
|
|
65791
|
+
destInfo = resolveProjDestInfo(targets, catalog, opts);
|
|
65792
|
+
targets.forEach(function(targ) {
|
|
65793
|
+
projectTarget(targ.dataset, opts, targ.layers, destInfo);
|
|
65794
|
+
});
|
|
65795
|
+
if (destInfo && destInfo.crs) {
|
|
65796
|
+
projectMapFrame(targets, catalog, opts, destInfo);
|
|
65797
|
+
warnAboutUnprojectedLayers(catalog, destInfo);
|
|
65798
|
+
}
|
|
65799
|
+
};
|
|
65800
|
+
|
|
65801
|
+
cmd.proj = function(dataset, catalog, opts, targetLayers, destInfo) {
|
|
65802
|
+
applyProjInit(dataset, catalog, opts);
|
|
65803
|
+
if (!destInfo) {
|
|
65804
|
+
destInfo = resolveProjDestInfo([{dataset: dataset, layers: targetLayers}], catalog, opts);
|
|
65805
|
+
}
|
|
65806
|
+
projectTarget(dataset, opts, targetLayers, destInfo);
|
|
65807
|
+
};
|
|
65808
|
+
|
|
65809
|
+
function projectTarget(dataset, opts, targetLayers, destInfo) {
|
|
64941
65810
|
var implicitlyProjectedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasGeometry$1);
|
|
64942
65811
|
// A preserved GeoJSON "crs" member becomes invalid once the CRS changes.
|
|
64943
65812
|
deleteGeoJSONMetadataCRS(dataset);
|
|
64944
|
-
if (
|
|
64945
|
-
|
|
64946
|
-
|
|
64947
|
-
|
|
65813
|
+
if (!destInfo) return;
|
|
65814
|
+
var didProject = projCmd(dataset, destInfo, opts);
|
|
65815
|
+
if (didProject && implicitlyProjectedNames.length > 0) {
|
|
65816
|
+
message(
|
|
65817
|
+
'Also projected non-target layer' + utils.pluralSuffix(implicitlyProjectedNames.length) +
|
|
65818
|
+
' from the same dataset: ' + implicitlyProjectedNames.join(', ')
|
|
65819
|
+
);
|
|
64948
65820
|
}
|
|
65821
|
+
}
|
|
65822
|
+
|
|
65823
|
+
function applyProjInit(dataset, catalog, opts) {
|
|
65824
|
+
if (!opts.init) return;
|
|
65825
|
+
var srcInfo = fetchCrsInfo(opts.init, catalog);
|
|
65826
|
+
if (!srcInfo.crs) stop$1("Unknown projection source:", opts.init);
|
|
65827
|
+
setDatasetCrsInfo(dataset, srcInfo);
|
|
65828
|
+
}
|
|
65829
|
+
|
|
65830
|
+
// @targets [{dataset, layers}, ...] -- all the targets of one -proj command
|
|
65831
|
+
function resolveProjDestInfo(targets, catalog, opts) {
|
|
64949
65832
|
if (opts.match) {
|
|
64950
|
-
|
|
64951
|
-
}
|
|
64952
|
-
|
|
64953
|
-
|
|
64954
|
-
|
|
64955
|
-
|
|
64956
|
-
|
|
64957
|
-
|
|
64958
|
-
|
|
64959
|
-
|
|
64960
|
-
|
|
64961
|
-
|
|
64962
|
-
|
|
65833
|
+
return fetchCrsInfo(opts.match, catalog);
|
|
65834
|
+
}
|
|
65835
|
+
if (!opts.crs) return null;
|
|
65836
|
+
return getCrsInfo(expandProjDefnForTargets(opts.crs, targets));
|
|
65837
|
+
}
|
|
65838
|
+
|
|
65839
|
+
// A map frame is composition state, not content: its rectangle describes a
|
|
65840
|
+
// region of the map's coordinate space, so it has to follow the map's CRS even
|
|
65841
|
+
// when the command targeted only a data layer. The GUI deliberately keeps the
|
|
65842
|
+
// frame out of default targets, so nothing else reaches it.
|
|
65843
|
+
function projectMapFrame(targets, catalog, opts, destInfo) {
|
|
65844
|
+
var frame = catalog ? getActiveFrame(catalog) : null;
|
|
65845
|
+
var frameOpts = opts;
|
|
65846
|
+
var frameCrs;
|
|
65847
|
+
if (!frame) return;
|
|
65848
|
+
if (targets.some(function(targ) { return targ.dataset == frame.dataset; })) {
|
|
65849
|
+
return; // already projected as an ordinary target
|
|
65850
|
+
}
|
|
65851
|
+
frameCrs = getDatasetCrsInfo(frame.dataset).crs;
|
|
65852
|
+
if (frameCrs && crsAreEqual(frameCrs, destInfo.crs)) {
|
|
65853
|
+
return; // already in the destination CRS; say nothing
|
|
65854
|
+
}
|
|
65855
|
+
if (!frameCrs) {
|
|
65856
|
+
warn('Unable to reproject the map frame: its coordinate system is unknown.');
|
|
65857
|
+
return;
|
|
64963
65858
|
}
|
|
64964
|
-
|
|
65859
|
+
if (opts.init) {
|
|
65860
|
+
// init= describes the CRS of the targeted data, not the frame's.
|
|
65861
|
+
frameOpts = Object.assign({}, opts);
|
|
65862
|
+
delete frameOpts.init;
|
|
65863
|
+
}
|
|
65864
|
+
cmd.proj(frame.dataset, catalog, frameOpts, frame.dataset.layers, destInfo);
|
|
65865
|
+
message('Also reprojected the map frame.');
|
|
65866
|
+
}
|
|
65867
|
+
|
|
65868
|
+
// -proj only applies to the command's targets, so layers in other datasets are
|
|
65869
|
+
// left behind in the old CRS. That usually surfaces much later as a blank
|
|
65870
|
+
// export or an "unable to combine" error, so name them while the cause is clear.
|
|
65871
|
+
function warnAboutUnprojectedLayers(catalog, destInfo) {
|
|
65872
|
+
var names = [];
|
|
65873
|
+
if (!catalog) return;
|
|
65874
|
+
catalog.getLayers().forEach(function(o) {
|
|
65875
|
+
var crs;
|
|
65876
|
+
if (!layerHasGeometry$1(o.layer) && !layerHasRaster(o.layer)) return;
|
|
65877
|
+
crs = getDatasetCrsInfo(o.dataset).crs;
|
|
65878
|
+
if (crs && !crsAreEqual(crs, destInfo.crs)) {
|
|
65879
|
+
names.push(o.layer.name || '[unnamed]');
|
|
65880
|
+
}
|
|
65881
|
+
});
|
|
65882
|
+
if (names.length === 0) return;
|
|
65883
|
+
warn(`Layer${utils.pluralSuffix(names.length)} not projected by this command: ` +
|
|
65884
|
+
names.join(', ') + '. Use target=* to project all layers.');
|
|
65885
|
+
}
|
|
64965
65886
|
|
|
64966
65887
|
function projCmd(dataset, destInfo, opts) {
|
|
64967
65888
|
// modify copy of coordinate data when running in web UI, so original shapes
|
|
@@ -65082,6 +66003,7 @@ ${svg}
|
|
|
65082
66003
|
stop$1('Unable to project from a coordinate system that has no inverse transform');
|
|
65083
66004
|
}
|
|
65084
66005
|
var proj = getProjTransform2(src, dest); // v2 returns null points instead of throwing an error
|
|
66006
|
+
var frameSnapshots = getFrameProjectionSnapshots(dataset);
|
|
65085
66007
|
var badArcs = 0;
|
|
65086
66008
|
var badPoints = 0;
|
|
65087
66009
|
var healAntimeridian = isLatLngCRS(src) &&
|
|
@@ -65115,6 +66037,10 @@ ${svg}
|
|
|
65115
66037
|
cleanProjectedPathLayers(dataset, {heal_antimeridian: healAntimeridian});
|
|
65116
66038
|
}
|
|
65117
66039
|
|
|
66040
|
+
frameSnapshots.forEach(function(snapshot) {
|
|
66041
|
+
rebuildProjectedFrameLayer(snapshot, dataset, proj, isLatLngCRS(src));
|
|
66042
|
+
});
|
|
66043
|
+
|
|
65118
66044
|
if (badArcs > 0 && !opts.quiet) {
|
|
65119
66045
|
message(`Removed ${badArcs} ${badArcs == 1 ? 'path' : 'paths'} containing unprojectable vertices.`);
|
|
65120
66046
|
}
|
|
@@ -73064,185 +73990,6 @@ ${svg}
|
|
|
73064
73990
|
return parsed[0];
|
|
73065
73991
|
}
|
|
73066
73992
|
|
|
73067
|
-
cmd.frame = function(catalog, targets, opts) {
|
|
73068
|
-
var widthPx, heightPx, aspectRatio, bbox;
|
|
73069
|
-
if (opts.width) {
|
|
73070
|
-
widthPx = parseSizeParam(opts.width);
|
|
73071
|
-
if (widthPx > 0 === false) {
|
|
73072
|
-
stop$1('Invalid width parameter:', opts.width);
|
|
73073
|
-
}
|
|
73074
|
-
}
|
|
73075
|
-
if (opts.height) {
|
|
73076
|
-
heightPx = parseSizeParam(opts.height);
|
|
73077
|
-
if (heightPx > 0 === false) {
|
|
73078
|
-
stop$1('Invalid height parameter:', opts.height);
|
|
73079
|
-
}
|
|
73080
|
-
}
|
|
73081
|
-
if (!widthPx && !heightPx) {
|
|
73082
|
-
widthPx = 800;
|
|
73083
|
-
message('Using default 800px frame width');
|
|
73084
|
-
}
|
|
73085
|
-
|
|
73086
|
-
if (opts.aspect_ratio) {
|
|
73087
|
-
if (opts.aspect_ratio > 0 === false) {
|
|
73088
|
-
stop$1('Invalid aspect-ratio parameter:', opts.aspect_ratio);
|
|
73089
|
-
}
|
|
73090
|
-
if (!heightPx) {
|
|
73091
|
-
heightPx = roundToDigits(widthPx / opts.aspect_ratio, 1);
|
|
73092
|
-
} else if (!widthPx) {
|
|
73093
|
-
widthPx = roundToDigits(heightPx * opts.aspect_ratio, 1);
|
|
73094
|
-
}
|
|
73095
|
-
}
|
|
73096
|
-
|
|
73097
|
-
if (opts.bbox) {
|
|
73098
|
-
bbox = opts.bbox;
|
|
73099
|
-
// TODO: validate
|
|
73100
|
-
} else {
|
|
73101
|
-
var datasets = utils.pluck(targets, 'dataset');
|
|
73102
|
-
requireDatasetsHaveCompatibleCRS(datasets, 'Targets include both projected and unprojected coordinates');
|
|
73103
|
-
bbox = getTargetBbox(targets);
|
|
73104
|
-
if (!bbox) {
|
|
73105
|
-
stop$1('Command target is missing geographical bounds');
|
|
73106
|
-
}
|
|
73107
|
-
}
|
|
73108
|
-
|
|
73109
|
-
applyPercentageOffsets(bbox, opts.offset || opts.offsets);
|
|
73110
|
-
applyPixelOffsets(bbox, widthPx, heightPx, opts.offset || opts.offsets);
|
|
73111
|
-
|
|
73112
|
-
if (bbox[3] - bbox[1] > 0 === false || bbox[2] - bbox[0] > 0 === false) {
|
|
73113
|
-
stop$1('Frame has a collapsed bbox');
|
|
73114
|
-
}
|
|
73115
|
-
|
|
73116
|
-
aspectRatio = (bbox[2] - bbox[0]) / (bbox[3] - bbox[1]);
|
|
73117
|
-
if (!widthPx) {
|
|
73118
|
-
widthPx = roundToDigits(heightPx * aspectRatio, 1);
|
|
73119
|
-
} else if (!heightPx) {
|
|
73120
|
-
heightPx = roundToDigits(widthPx / aspectRatio, 1);
|
|
73121
|
-
}
|
|
73122
|
-
|
|
73123
|
-
var feature = {
|
|
73124
|
-
type: 'Feature',
|
|
73125
|
-
properties: {type: 'frame', width: widthPx, height: heightPx},
|
|
73126
|
-
geometry: bboxToPolygon(bbox)
|
|
73127
|
-
};
|
|
73128
|
-
var frameDataset = importGeoJSON(feature);
|
|
73129
|
-
// set CRS from target dataset
|
|
73130
|
-
// TODO: handle case: targets have different projections
|
|
73131
|
-
// TODO: handle case: first target is missing CRS
|
|
73132
|
-
if (targets.length > 0) {
|
|
73133
|
-
var crsInfo = getDatasetCrsInfo(targets[0].dataset);
|
|
73134
|
-
setDatasetCrsInfo(frameDataset, crsInfo);
|
|
73135
|
-
}
|
|
73136
|
-
frameDataset.layers[0].name = opts.name || 'frame';
|
|
73137
|
-
catalog.addDataset(frameDataset);
|
|
73138
|
-
};
|
|
73139
|
-
|
|
73140
|
-
function fillOutBbox(bbox, widthPx, heightPx) {
|
|
73141
|
-
var hpad = 0, vpad = 0;
|
|
73142
|
-
var w = bbox[2] - bbox[0];
|
|
73143
|
-
var h = bbox[3] - bbox[1];
|
|
73144
|
-
if (widthPx / heightPx > w / h) { // need to add horizontal padding
|
|
73145
|
-
hpad = h * widthPx / heightPx - w;
|
|
73146
|
-
} else {
|
|
73147
|
-
vpad = w * heightPx / widthPx - h;
|
|
73148
|
-
}
|
|
73149
|
-
bbox[0] -= hpad / 2;
|
|
73150
|
-
bbox[1] -= vpad / 2;
|
|
73151
|
-
bbox[2] += hpad / 2;
|
|
73152
|
-
bbox[3] += vpad / 2;
|
|
73153
|
-
}
|
|
73154
|
-
|
|
73155
|
-
function applyPercentageOffsets(bbox, arg) {
|
|
73156
|
-
var sides = getPctOffsets(arg);
|
|
73157
|
-
var l = sides[0],
|
|
73158
|
-
b = sides[1],
|
|
73159
|
-
r = sides[2],
|
|
73160
|
-
t = sides[3],
|
|
73161
|
-
w2 = (bbox[2] - bbox[0]) / (1 - l - r),
|
|
73162
|
-
h2 = (bbox[3] - bbox[1]) / (1 - t - b);
|
|
73163
|
-
bbox[0] -= l * w2;
|
|
73164
|
-
bbox[1] -= b * h2;
|
|
73165
|
-
bbox[2] += r * w2;
|
|
73166
|
-
bbox[3] += t * h2;
|
|
73167
|
-
}
|
|
73168
|
-
|
|
73169
|
-
function applyPixelOffsets(bbox, widthPx, heightPx, arg) {
|
|
73170
|
-
var sides = getPixelOffsets(arg);
|
|
73171
|
-
var l = sides[0],
|
|
73172
|
-
b = sides[1],
|
|
73173
|
-
r = sides[2],
|
|
73174
|
-
t = sides[3],
|
|
73175
|
-
scale, w;
|
|
73176
|
-
|
|
73177
|
-
if (widthPx && heightPx) {
|
|
73178
|
-
// add padding to bbox to match pixel dimensions, if needed
|
|
73179
|
-
fillOutBbox(bbox, widthPx, heightPx);
|
|
73180
|
-
}
|
|
73181
|
-
|
|
73182
|
-
w = bbox[2] - bbox[0];
|
|
73183
|
-
bbox[3] - bbox[1];
|
|
73184
|
-
|
|
73185
|
-
if (widthPx) {
|
|
73186
|
-
scale = w / (widthPx - l - r);
|
|
73187
|
-
} else {
|
|
73188
|
-
scale = w / (heightPx - t - b);
|
|
73189
|
-
}
|
|
73190
|
-
|
|
73191
|
-
bbox[0] -= scale * l;
|
|
73192
|
-
bbox[1] -= scale * b;
|
|
73193
|
-
bbox[2] += scale * r;
|
|
73194
|
-
bbox[3] += scale * t;
|
|
73195
|
-
return scale;
|
|
73196
|
-
}
|
|
73197
|
-
|
|
73198
|
-
function getPctOffsets(arg) {
|
|
73199
|
-
return adjustOffsetsArg(arg).map(str => {
|
|
73200
|
-
return str.includes('%') ? parsePercent(str) : 0;
|
|
73201
|
-
});
|
|
73202
|
-
}
|
|
73203
|
-
|
|
73204
|
-
function getPixelOffsets(arg) {
|
|
73205
|
-
return adjustOffsetsArg(arg).map(str => {
|
|
73206
|
-
return str.includes('%') ? 0 : parseSizeParam(str);
|
|
73207
|
-
});
|
|
73208
|
-
}
|
|
73209
|
-
|
|
73210
|
-
function adjustOffsetsArg(arg) {
|
|
73211
|
-
if (!arg) arg = ['0'];
|
|
73212
|
-
if (arg.length == 1) {
|
|
73213
|
-
return [arg[0], arg[0], arg[0], arg[0]];
|
|
73214
|
-
}
|
|
73215
|
-
if (arg.length != 4) {
|
|
73216
|
-
stop$1('List of offsets should have 4 values');
|
|
73217
|
-
}
|
|
73218
|
-
return arg;
|
|
73219
|
-
}
|
|
73220
|
-
|
|
73221
|
-
function getTargetBbox(targets) {
|
|
73222
|
-
var expanded = expandCommandTargets(targets);
|
|
73223
|
-
var bounds = expanded.reduce(function(memo, o) {
|
|
73224
|
-
return memo.mergeBounds(getLayerBounds(o.layer, o.dataset.arcs));
|
|
73225
|
-
}, new Bounds());
|
|
73226
|
-
return bounds.hasBounds() ? bounds.toArray() : null;
|
|
73227
|
-
}
|
|
73228
|
-
|
|
73229
|
-
// Convert width and height args to aspect ratio arg for the rectangle() function
|
|
73230
|
-
function getAspectRatioArg(widthArg, heightArg) {
|
|
73231
|
-
// heightArg is a string containing either a number or a
|
|
73232
|
-
// comma-sep. pair of numbers (range);
|
|
73233
|
-
return heightArg.split(',').map(function(opt) {
|
|
73234
|
-
var height = Number(opt),
|
|
73235
|
-
width = Number(widthArg);
|
|
73236
|
-
if (!opt) return '';
|
|
73237
|
-
return width / height;
|
|
73238
|
-
}).reverse().join(',');
|
|
73239
|
-
}
|
|
73240
|
-
|
|
73241
|
-
var Frame = /*#__PURE__*/Object.freeze({
|
|
73242
|
-
__proto__: null,
|
|
73243
|
-
getAspectRatioArg: getAspectRatioArg
|
|
73244
|
-
});
|
|
73245
|
-
|
|
73246
73993
|
// Remove intricate sub-scale detail from a single arc so that -smooth has a
|
|
73247
73994
|
// clean line to work with, WITHOUT thinning the rest of the line: smooth makes a
|
|
73248
73995
|
// better approximation of the original when it has more detail to work with, so
|
|
@@ -78009,7 +78756,7 @@ ${svg}
|
|
|
78009
78756
|
if (lyr) return getFrameLayerData(lyr, dataset.arcs);
|
|
78010
78757
|
// -frame adds the frame it creates to the catalog as a separate dataset, so a
|
|
78011
78758
|
// catalog-wide search is needed to find it (-scalebar does the same).
|
|
78012
|
-
target = catalog ?
|
|
78759
|
+
target = catalog ? getActiveFrame(catalog) : null;
|
|
78013
78760
|
return target ? getFrameLayerData(target.layer, target.dataset.arcs) : null;
|
|
78014
78761
|
}
|
|
78015
78762
|
|
|
@@ -81404,6 +82151,9 @@ ${svg}
|
|
|
81404
82151
|
job.catalog.captureCatalogBefore({operation: 'addLayer'});
|
|
81405
82152
|
job.catalog.addDataset(cmd.addLayer(targetDataset, opts));
|
|
81406
82153
|
|
|
82154
|
+
} else if (name == 'update-frame') {
|
|
82155
|
+
cmd.updateFrame(targetLayers, targetDataset, opts);
|
|
82156
|
+
|
|
81407
82157
|
} else if (name == 'update-label') {
|
|
81408
82158
|
cmd.updateLabel(targetLayers, targetDataset, opts);
|
|
81409
82159
|
|
|
@@ -81625,9 +82375,7 @@ ${svg}
|
|
|
81625
82375
|
} else if (name == 'proj') {
|
|
81626
82376
|
await initProjLibrary(opts);
|
|
81627
82377
|
job.resumeCommand();
|
|
81628
|
-
targets.
|
|
81629
|
-
cmd.proj(targ.dataset, job.catalog, opts, targ.layers);
|
|
81630
|
-
});
|
|
82378
|
+
cmd.projTargets(targets, job.catalog, opts);
|
|
81631
82379
|
|
|
81632
82380
|
} else if (name == 'rectangle') {
|
|
81633
82381
|
if (source || opts.bbox || targets.length === 0) {
|
|
@@ -81733,7 +82481,15 @@ ${svg}
|
|
|
81733
82481
|
}
|
|
81734
82482
|
|
|
81735
82483
|
if (outputDataset) {
|
|
81736
|
-
|
|
82484
|
+
var removedLayers = targetLayers && !opts.no_replace ? targetLayers : [];
|
|
82485
|
+
assertSingleFrameUpdate(
|
|
82486
|
+
job.catalog,
|
|
82487
|
+
outputDataset.layers.map(function(lyr) {
|
|
82488
|
+
return {layer: lyr, dataset: outputDataset};
|
|
82489
|
+
}),
|
|
82490
|
+
removedLayers
|
|
82491
|
+
);
|
|
82492
|
+
job.catalog.addDataset(outputDataset, {removeLayers: removedLayers}); // also sets default target
|
|
81737
82493
|
outputLayers = outputDataset.layers;
|
|
81738
82494
|
if (targetLayers && !opts.no_replace) {
|
|
81739
82495
|
// remove target layers from target dataset
|
|
@@ -81743,6 +82499,13 @@ ${svg}
|
|
|
81743
82499
|
}
|
|
81744
82500
|
} else if (outputLayers && targetDataset && outputLayers != targetDataset.layers) {
|
|
81745
82501
|
// integrate output layers into the target dataset
|
|
82502
|
+
assertSingleFrameUpdate(
|
|
82503
|
+
job.catalog,
|
|
82504
|
+
outputLayers.map(function(lyr) {
|
|
82505
|
+
return {layer: lyr, dataset: targetDataset};
|
|
82506
|
+
}),
|
|
82507
|
+
opts.no_replace ? [] : targetLayers
|
|
82508
|
+
);
|
|
81746
82509
|
if (opts.no_replace) {
|
|
81747
82510
|
// make sure commands do not return input layers with 'no_replace' option
|
|
81748
82511
|
if (!outputLayersAreDifferent(outputLayers, targetLayers || [])) {
|
|
@@ -81955,7 +82718,7 @@ ${svg}
|
|
|
81955
82718
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
81956
82719
|
}
|
|
81957
82720
|
|
|
81958
|
-
var version = "0.7.
|
|
82721
|
+
var version = "0.7.63";
|
|
81959
82722
|
|
|
81960
82723
|
// Parse command line args into commands and run them
|
|
81961
82724
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -84639,6 +85402,7 @@ ${svg}
|
|
|
84639
85402
|
FileTypes,
|
|
84640
85403
|
FilterGeom,
|
|
84641
85404
|
Frame,
|
|
85405
|
+
FrameProjection,
|
|
84642
85406
|
FrameUtils,
|
|
84643
85407
|
Furniture,
|
|
84644
85408
|
Geodesic,
|