mapshaper 0.7.62 → 0.7.64
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 +1846 -1011
- package/package.json +1 -1
- package/www/index.html +13 -1
- package/www/mapshaper-gui.js +1826 -137
- package/www/mapshaper.js +1846 -1011
- package/www/page.css +416 -12
package/www/mapshaper-gui.js
CHANGED
|
@@ -1153,6 +1153,11 @@
|
|
|
1153
1153
|
if (opts.max_width) {
|
|
1154
1154
|
infoBox.node().style.maxWidth = opts.max_width;
|
|
1155
1155
|
}
|
|
1156
|
+
// For a popup that needs to style or unclip the box itself, not just its
|
|
1157
|
+
// content -- the box scrolls its overflow, which would cut off a tooltip.
|
|
1158
|
+
if (opts.classname) {
|
|
1159
|
+
infoBox.addClass(opts.classname);
|
|
1160
|
+
}
|
|
1156
1161
|
var container = El('div').appendTo(infoBox);
|
|
1157
1162
|
if (!title && warningRxp.test(msg)) {
|
|
1158
1163
|
title = 'Warning';
|
|
@@ -3140,12 +3145,12 @@
|
|
|
3140
3145
|
return decimals >= 0 ? num.toFixed(decimals) : String(num);
|
|
3141
3146
|
}
|
|
3142
3147
|
|
|
3143
|
-
function formatNumber(val) {
|
|
3148
|
+
function formatNumber$2(val) {
|
|
3144
3149
|
return val + '';
|
|
3145
3150
|
}
|
|
3146
3151
|
|
|
3147
3152
|
function formatIntlNumber(val) {
|
|
3148
|
-
var str = formatNumber(val);
|
|
3153
|
+
var str = formatNumber$2(val);
|
|
3149
3154
|
return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
|
|
3150
3155
|
}
|
|
3151
3156
|
|
|
@@ -3694,7 +3699,7 @@
|
|
|
3694
3699
|
endsWith, every, expandoBuffer, extend, extendBuffer,
|
|
3695
3700
|
find, findMedian, findQuantile, findRankByValue, findStringPrefix,
|
|
3696
3701
|
findValueByPct, findValueByRank, forEach, forEachProperty, format,
|
|
3697
|
-
formatDateISO, formatIntlNumber, formatNumber, formatNumberForDisplay,
|
|
3702
|
+
formatDateISO, formatIntlNumber, formatNumber: formatNumber$2, formatNumberForDisplay,
|
|
3698
3703
|
formatVersionedName, formatter,
|
|
3699
3704
|
genericSort, getArrayBounds, getGenericComparator, getKeyComparator,
|
|
3700
3705
|
getSortedIds, getUniqueName, groupBy,
|
|
@@ -8564,6 +8569,24 @@
|
|
|
8564
8569
|
return copy;
|
|
8565
8570
|
}
|
|
8566
8571
|
|
|
8572
|
+
// Return the point transform used to calculate dynamic-display frame bounds.
|
|
8573
|
+
// This mirrors the WGS84 pivot and Web Mercator latitude clamping used above.
|
|
8574
|
+
function getDisplayProjectionTransform(src, dest) {
|
|
8575
|
+
var wgs84 = internal.parseCrsString('wgs84');
|
|
8576
|
+
var toWGS84 = internal.isWGS84(src) ? null :
|
|
8577
|
+
internal.getProjTransform2(src, wgs84);
|
|
8578
|
+
var fromWGS84 = internal.getProjTransform2(wgs84, dest);
|
|
8579
|
+
var clampLat = internal.isWebMercator(dest);
|
|
8580
|
+
return function(x, y) {
|
|
8581
|
+
var p = toWGS84 ? toWGS84(x, y) : [x, y];
|
|
8582
|
+
if (!p) return null;
|
|
8583
|
+
if (clampLat) {
|
|
8584
|
+
p = [p[0], Math.max(-89.9, Math.min(89.9, p[1]))];
|
|
8585
|
+
}
|
|
8586
|
+
return fromWGS84(p[0], p[1]);
|
|
8587
|
+
};
|
|
8588
|
+
}
|
|
8589
|
+
|
|
8567
8590
|
function clampY(arcs) {
|
|
8568
8591
|
var max = 89.9,
|
|
8569
8592
|
min = -89.9,
|
|
@@ -8758,10 +8781,21 @@
|
|
|
8758
8781
|
if (internal.layerHasPoints(layer)) {
|
|
8759
8782
|
gui.displayLayer = projectPointsForDisplay(layer, sourceCRS, displayCRS);
|
|
8760
8783
|
} else if (internal.layerHasPaths(layer)) {
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
|
|
8784
|
+
if (internal.isFrameLayer(layer, dataset.arcs)) {
|
|
8785
|
+
var projectedFrame = internal.getProjectedFrameDisplayLayer(
|
|
8786
|
+
layer,
|
|
8787
|
+
dataset.arcs,
|
|
8788
|
+
getDisplayProjectionTransform(sourceCRS, displayCRS),
|
|
8789
|
+
internal.isLatLngCRS(sourceCRS)
|
|
8790
|
+
);
|
|
8791
|
+
gui.displayLayer = projectedFrame.layer;
|
|
8792
|
+
gui.displayArcs = projectedFrame.arcs;
|
|
8793
|
+
} else {
|
|
8794
|
+
emptyArcs = findEmptyArcs(displayArcs);
|
|
8795
|
+
if (emptyArcs.length > 0) {
|
|
8796
|
+
// Don't try to draw paths containing coordinates that failed to project
|
|
8797
|
+
gui.displayLayer = internal.filterPathLayerByArcIds(gui.displayLayer, emptyArcs);
|
|
8798
|
+
}
|
|
8765
8799
|
}
|
|
8766
8800
|
} else if (internal.layerHasRaster(layer)) {
|
|
8767
8801
|
gui.bounds = getProjectedRasterDisplayBounds(layer, sourceCRS, displayCRS);
|
|
@@ -10678,6 +10712,8 @@
|
|
|
10678
10712
|
var toggleBtn = null; // checkbox <input> for toggling layer selection
|
|
10679
10713
|
var exportBtn = gui.container.findChild('.export-btn').addClass('disabled');
|
|
10680
10714
|
var ofileName = gui.container.findChild('#ofile-name');
|
|
10715
|
+
var frameInfo = menu.findChild('.export-frame-info').hide();
|
|
10716
|
+
menu.findChild('.advanced-options').on('input', updateFrameInfo);
|
|
10681
10717
|
new SimpleButton(menu.findChild('.close2-btn')).on('click', gui.clearMode);
|
|
10682
10718
|
|
|
10683
10719
|
if (!GUI.exportIsSupported()) {
|
|
@@ -10746,6 +10782,7 @@
|
|
|
10746
10782
|
formatPickedByUser = false;
|
|
10747
10783
|
// initZipOption();
|
|
10748
10784
|
initFormatMenu();
|
|
10785
|
+
updateFrameInfo();
|
|
10749
10786
|
updateExportCheckboxes();
|
|
10750
10787
|
menu.show();
|
|
10751
10788
|
}
|
|
@@ -10827,6 +10864,10 @@
|
|
|
10827
10864
|
var snapshot = gui.session.getHistorySnapshot();
|
|
10828
10865
|
snapshot.savedAtIndex = snapshot.commands.length;
|
|
10829
10866
|
opts.history = snapshot;
|
|
10867
|
+
targets = addFrameTarget(targets);
|
|
10868
|
+
}
|
|
10869
|
+
if (opts.format == 'svg' || opts.format == 'topojson') {
|
|
10870
|
+
opts.gui_frame = getGuiFrameContext();
|
|
10830
10871
|
}
|
|
10831
10872
|
try {
|
|
10832
10873
|
var files = await internal.exportTargetLayers(model, targets, opts);
|
|
@@ -10892,7 +10933,9 @@
|
|
|
10892
10933
|
|
|
10893
10934
|
function initLayerMenu() {
|
|
10894
10935
|
var list = menu.findChild('.export-layer-list').empty();
|
|
10895
|
-
var layers = model.getLayers()
|
|
10936
|
+
var layers = model.getLayers().filter(function(o) {
|
|
10937
|
+
return !internal.isFrameLayer(o.layer, o.dataset.arcs);
|
|
10938
|
+
});
|
|
10896
10939
|
sortLayersForMenuDisplay(layers);
|
|
10897
10940
|
|
|
10898
10941
|
if (layers.length > 2) {
|
|
@@ -11011,11 +11054,13 @@
|
|
|
11011
11054
|
// changes afterwards.
|
|
11012
11055
|
formatPickedByUser = true;
|
|
11013
11056
|
updateExportCheckboxes();
|
|
11057
|
+
updateFrameInfo();
|
|
11014
11058
|
}
|
|
11015
11059
|
|
|
11016
11060
|
function setSelectedFormat(fmt) {
|
|
11017
11061
|
var el = menu.findChild('.export-formats input[value="' + fmt + '"]');
|
|
11018
11062
|
if (el) el.node().checked = true;
|
|
11063
|
+
updateFrameInfo();
|
|
11019
11064
|
}
|
|
11020
11065
|
|
|
11021
11066
|
// Which formats apply depends on what is checked for export, so unchecking
|
|
@@ -11059,6 +11104,62 @@
|
|
|
11059
11104
|
}, []);
|
|
11060
11105
|
}
|
|
11061
11106
|
|
|
11107
|
+
function getGuiFrameContext() {
|
|
11108
|
+
var target = internal.getActiveFrame(model);
|
|
11109
|
+
var rec, style;
|
|
11110
|
+
if (!target) return null;
|
|
11111
|
+
rec = target.layer.data.getReadOnlyRecordAt(0) || {};
|
|
11112
|
+
style = {};
|
|
11113
|
+
['fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity',
|
|
11114
|
+
'stroke-linecap', 'stroke-linejoin', 'stroke-dasharray', 'opacity'
|
|
11115
|
+
].forEach(function(field) {
|
|
11116
|
+
if (rec[field] !== undefined && rec[field] !== null && rec[field] !== '') {
|
|
11117
|
+
style[field] = rec[field];
|
|
11118
|
+
}
|
|
11119
|
+
});
|
|
11120
|
+
return {
|
|
11121
|
+
data: internal.getFrameLayerData(
|
|
11122
|
+
target.layer,
|
|
11123
|
+
target.dataset.arcs,
|
|
11124
|
+
internal.getDatasetCRS(target.dataset)
|
|
11125
|
+
),
|
|
11126
|
+
name: target.layer.name || 'frame',
|
|
11127
|
+
style: style
|
|
11128
|
+
};
|
|
11129
|
+
}
|
|
11130
|
+
|
|
11131
|
+
function addFrameTarget(targets) {
|
|
11132
|
+
var frame = internal.getActiveFrame(model);
|
|
11133
|
+
var group;
|
|
11134
|
+
if (!frame) return targets;
|
|
11135
|
+
targets = targets.map(function(target) {
|
|
11136
|
+
return Object.assign({}, target, {layers: target.layers.slice()});
|
|
11137
|
+
});
|
|
11138
|
+
group = targets.find(function(target) {
|
|
11139
|
+
return target.dataset == frame.dataset;
|
|
11140
|
+
});
|
|
11141
|
+
if (group) {
|
|
11142
|
+
if (!group.layers.includes(frame.layer)) group.layers.push(frame.layer);
|
|
11143
|
+
} else {
|
|
11144
|
+
targets.push({dataset: frame.dataset, layers: [frame.layer]});
|
|
11145
|
+
}
|
|
11146
|
+
return targets;
|
|
11147
|
+
}
|
|
11148
|
+
|
|
11149
|
+
function updateFrameInfo() {
|
|
11150
|
+
if (!frameInfo) return;
|
|
11151
|
+
var context = getGuiFrameContext();
|
|
11152
|
+
var format = getSelectedFormat();
|
|
11153
|
+
var topoUsesPixels = format == 'topojson' &&
|
|
11154
|
+
/\b(?:width|height)\s*=/.test(getExportOptsAsString());
|
|
11155
|
+
if (context && (format == 'svg' || topoUsesPixels)) {
|
|
11156
|
+
frameInfo.text('Map frame: ' +
|
|
11157
|
+
internal.formatFrameSizeForDisplay(context.data)).show();
|
|
11158
|
+
} else {
|
|
11159
|
+
frameInfo.hide();
|
|
11160
|
+
}
|
|
11161
|
+
}
|
|
11162
|
+
|
|
11062
11163
|
};
|
|
11063
11164
|
|
|
11064
11165
|
function DomCache() {
|
|
@@ -11247,6 +11348,15 @@
|
|
|
11247
11348
|
}
|
|
11248
11349
|
openMenu$1 = this;
|
|
11249
11350
|
|
|
11351
|
+
if (e.frameProperties) {
|
|
11352
|
+
addMenuItem('frame properties', e.frameProperties, '');
|
|
11353
|
+
}
|
|
11354
|
+
if (e.resizeFrame) {
|
|
11355
|
+
addMenuItem('resize frame', e.resizeFrame, '');
|
|
11356
|
+
}
|
|
11357
|
+
if (e.deleteFrame) {
|
|
11358
|
+
addMenuItem('delete frame', e.deleteFrame, '');
|
|
11359
|
+
}
|
|
11250
11360
|
if (e.deleteLayer) {
|
|
11251
11361
|
addMenuItem('delete layer', e.deleteLayer, '');
|
|
11252
11362
|
}
|
|
@@ -11454,6 +11564,18 @@
|
|
|
11454
11564
|
var isOpen = false;
|
|
11455
11565
|
var cache = new DomCache();
|
|
11456
11566
|
var pinAll = el.findChild('.pin-all'); // button for toggling layer visibility
|
|
11567
|
+
var frameEmpty = el.findChild('.map-frame-empty')
|
|
11568
|
+
.on('click', openFrameCreateDialog)
|
|
11569
|
+
.on('keydown', function(e) {
|
|
11570
|
+
if (e.key == 'Enter' || e.key == ' ') {
|
|
11571
|
+
e.preventDefault();
|
|
11572
|
+
openFrameCreateDialog();
|
|
11573
|
+
}
|
|
11574
|
+
});
|
|
11575
|
+
|
|
11576
|
+
function openFrameCreateDialog() {
|
|
11577
|
+
if (gui.frameTool) gui.frameTool.openCreateDialog();
|
|
11578
|
+
}
|
|
11457
11579
|
|
|
11458
11580
|
// layer repositioning
|
|
11459
11581
|
var dragTargetId = null;
|
|
@@ -11537,7 +11659,7 @@
|
|
|
11537
11659
|
function testAllLayersPinned() {
|
|
11538
11660
|
var allPinned = true;
|
|
11539
11661
|
model.forEachLayer(function(lyr, dataset) {
|
|
11540
|
-
if (isPinnable(lyr) && !lyr.pinned) {
|
|
11662
|
+
if (isPinnable(lyr, dataset) && !lyr.pinned) {
|
|
11541
11663
|
allPinned = false;
|
|
11542
11664
|
}
|
|
11543
11665
|
});
|
|
@@ -11620,10 +11742,14 @@
|
|
|
11620
11742
|
|
|
11621
11743
|
function renderLayerList() {
|
|
11622
11744
|
var list = el.findChild('.layer-list');
|
|
11745
|
+
var frameSection = el.findChild('.map-frame-section');
|
|
11746
|
+
var frameList = el.findChild('.map-frame-list');
|
|
11623
11747
|
var uniqIds = {};
|
|
11624
11748
|
var pinnableCount = 0;
|
|
11625
11749
|
var layerCount = 0;
|
|
11750
|
+
var frameCount = 0;
|
|
11626
11751
|
list.empty();
|
|
11752
|
+
frameList.empty();
|
|
11627
11753
|
model.forEachLayer(function(lyr, dataset) {
|
|
11628
11754
|
// Assign a unique id to each layer, so html strings
|
|
11629
11755
|
// can be used as unique identifiers for caching rendered HTML, and as
|
|
@@ -11632,7 +11758,7 @@
|
|
|
11632
11758
|
lyr.menu_id = utils$1.getUniqueName();
|
|
11633
11759
|
}
|
|
11634
11760
|
uniqIds[lyr.menu_id] = true;
|
|
11635
|
-
if (isPinnable(lyr)) pinnableCount++;
|
|
11761
|
+
if (isPinnable(lyr, dataset)) pinnableCount++;
|
|
11636
11762
|
layerCount++;
|
|
11637
11763
|
});
|
|
11638
11764
|
|
|
@@ -11645,9 +11771,10 @@
|
|
|
11645
11771
|
|
|
11646
11772
|
sortLayersForMenuDisplay(model.getLayers()).forEach(function(o) {
|
|
11647
11773
|
var lyr = o.layer;
|
|
11774
|
+
var isFrame = internal.isFrameLayer(lyr, o.dataset.arcs);
|
|
11648
11775
|
var opts = {
|
|
11649
11776
|
show_source: layerCount < 5,
|
|
11650
|
-
pinnable: pinnableCount > 0 && isPinnable(lyr)
|
|
11777
|
+
pinnable: pinnableCount > 0 && isPinnable(lyr, o.dataset)
|
|
11651
11778
|
};
|
|
11652
11779
|
var html, element;
|
|
11653
11780
|
html = renderLayer(lyr, o.dataset, opts);
|
|
@@ -11658,14 +11785,22 @@
|
|
|
11658
11785
|
initMouseEvents(element, lyr.menu_id, opts.pinnable);
|
|
11659
11786
|
cache.add(html, element);
|
|
11660
11787
|
}
|
|
11661
|
-
|
|
11788
|
+
if (isFrame) {
|
|
11789
|
+
frameList.appendChild(element);
|
|
11790
|
+
frameCount++;
|
|
11791
|
+
} else {
|
|
11792
|
+
list.appendChild(element);
|
|
11793
|
+
}
|
|
11662
11794
|
});
|
|
11795
|
+
frameSection.classed('hidden', layerCount === 0);
|
|
11796
|
+
frameEmpty.classed('hidden', frameCount > 0);
|
|
11663
11797
|
}
|
|
11664
11798
|
|
|
11665
11799
|
cache.cleanup();
|
|
11666
11800
|
|
|
11667
11801
|
function renderLayer(lyr, dataset, opts) {
|
|
11668
11802
|
var classes = 'layer-item';
|
|
11803
|
+
var isFrame = internal.isFrameLayer(lyr, dataset.arcs);
|
|
11669
11804
|
var entry, html;
|
|
11670
11805
|
|
|
11671
11806
|
if (opts.pinnable) classes += ' pinnable';
|
|
@@ -11675,7 +11810,7 @@
|
|
|
11675
11810
|
|
|
11676
11811
|
html = '<!-- ' + lyr.menu_id + '--><div class="' + classes + '">';
|
|
11677
11812
|
html += rowHTML('name', '<span class="layer-name colored-text dot-underline">' + formatLayerNameForDisplay(lyr.name) + '</span>', 'row1');
|
|
11678
|
-
html += rowHTML('contents', describeLyr(lyr, dataset));
|
|
11813
|
+
html += rowHTML(isFrame ? 'size' : 'contents', describeLyr(lyr, dataset));
|
|
11679
11814
|
html += '<span class="more-btn layer-btn" role="button" aria-label="More layer options"></span>';
|
|
11680
11815
|
if (opts.pinnable) {
|
|
11681
11816
|
html += '<img class="eye-btn black-eye layer-btn" draggable="false" src="images/eye.png">';
|
|
@@ -11794,7 +11929,18 @@
|
|
|
11794
11929
|
if (!target) return;
|
|
11795
11930
|
popup = showPopupAlert('', 'Layer info');
|
|
11796
11931
|
content = popup.container().addClass('layer-info-popup');
|
|
11797
|
-
content.node().appendChild(renderLayerInfo(
|
|
11932
|
+
content.node().appendChild(renderLayerInfo(
|
|
11933
|
+
internal.getLayerInfo(target.layer, target.dataset)
|
|
11934
|
+
));
|
|
11935
|
+
}
|
|
11936
|
+
|
|
11937
|
+
function openFrameProperties() {
|
|
11938
|
+
var target = findLayerById(id);
|
|
11939
|
+
if (target && gui.frameProperties) gui.frameProperties.open(target);
|
|
11940
|
+
}
|
|
11941
|
+
|
|
11942
|
+
function resizeFrame() {
|
|
11943
|
+
if (gui.frameTool) gui.frameTool.open();
|
|
11798
11944
|
}
|
|
11799
11945
|
|
|
11800
11946
|
function styleLayer() {
|
|
@@ -11827,10 +11973,18 @@
|
|
|
11827
11973
|
pageY: rect.top + rect.height / 2
|
|
11828
11974
|
};
|
|
11829
11975
|
}
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11976
|
+
var isFrame = target &&
|
|
11977
|
+
internal.isFrameLayer(target.layer, target.dataset.arcs);
|
|
11978
|
+
if (isFrame) {
|
|
11979
|
+
menuEvent.frameProperties = openFrameProperties;
|
|
11980
|
+
menuEvent.resizeFrame = resizeFrame;
|
|
11981
|
+
menuEvent.deleteFrame = deleteLayer;
|
|
11982
|
+
} else {
|
|
11983
|
+
menuEvent.deleteLayer = deleteLayer;
|
|
11984
|
+
menuEvent.duplicateLayer = duplicateLayer;
|
|
11985
|
+
menuEvent.showLayerInfo = showLayerInfo;
|
|
11986
|
+
}
|
|
11987
|
+
if (!isFrame && target && layerCanBeStyled(target.layer)) {
|
|
11834
11988
|
menuEvent.styleLayer = styleLayer;
|
|
11835
11989
|
if (internal.layerHasLabels(target.layer)) {
|
|
11836
11990
|
menuEvent.styleLayerName = 'edit labels';
|
|
@@ -11895,7 +12049,13 @@
|
|
|
11895
12049
|
|
|
11896
12050
|
// init click-to-select
|
|
11897
12051
|
GUI.onClick(entry, function() {
|
|
11898
|
-
|
|
12052
|
+
var target = findLayerById(id);
|
|
12053
|
+
if (target && internal.isFrameLayer(target.layer, target.dataset.arcs) &&
|
|
12054
|
+
gui.frameProperties) {
|
|
12055
|
+
gui.frameProperties.open(target);
|
|
12056
|
+
} else {
|
|
12057
|
+
selectLayer();
|
|
12058
|
+
}
|
|
11899
12059
|
});
|
|
11900
12060
|
|
|
11901
12061
|
}
|
|
@@ -11912,7 +12072,9 @@
|
|
|
11912
12072
|
type = 'raster layer';
|
|
11913
12073
|
}
|
|
11914
12074
|
if (isFrame) {
|
|
11915
|
-
str =
|
|
12075
|
+
str = internal.formatFrameSizeForDisplay(
|
|
12076
|
+
internal.getFrameLayerData(lyr, dataset.arcs)
|
|
12077
|
+
);
|
|
11916
12078
|
} else if (internal.layerHasRaster(lyr)) {
|
|
11917
12079
|
str = utils$1.format('%,d x %,d %s', internal.getRasterWidth(lyr.raster), internal.getRasterHeight(lyr.raster), type);
|
|
11918
12080
|
} else if (type) {
|
|
@@ -11965,7 +12127,7 @@
|
|
|
11965
12127
|
title.className = 'console-info-subtitle';
|
|
11966
12128
|
title.textContent = 'Attribute data';
|
|
11967
12129
|
wrapper.appendChild(title);
|
|
11968
|
-
if (!fields) {
|
|
12130
|
+
if (!fields || fields.length === 0) {
|
|
11969
12131
|
var none = document.createElement('div');
|
|
11970
12132
|
none.className = 'console-info-empty';
|
|
11971
12133
|
none.textContent = '[none]';
|
|
@@ -12041,7 +12203,8 @@
|
|
|
12041
12203
|
}
|
|
12042
12204
|
}
|
|
12043
12205
|
|
|
12044
|
-
function isPinnable(lyr) {
|
|
12206
|
+
function isPinnable(lyr, dataset) {
|
|
12207
|
+
if (dataset && internal.isFrameLayer(lyr, dataset.arcs)) return false;
|
|
12045
12208
|
return internal.layerIsGeometric(lyr) || internal.layerHasRaster(lyr) || internal.layerHasFurniture(lyr);
|
|
12046
12209
|
}
|
|
12047
12210
|
|
|
@@ -13947,6 +14110,8 @@
|
|
|
13947
14110
|
vertices: 'edit vertices',
|
|
13948
14111
|
selection: 'selection tool',
|
|
13949
14112
|
ruler: 'measure distance',
|
|
14113
|
+
frame: 'edit map frame',
|
|
14114
|
+
frame_draw: 'draw map frame',
|
|
13950
14115
|
'add-points': 'add points',
|
|
13951
14116
|
rectangles: 'drag-to-resize',
|
|
13952
14117
|
off: 'turn off'
|
|
@@ -14021,7 +14186,7 @@
|
|
|
14021
14186
|
};
|
|
14022
14187
|
|
|
14023
14188
|
this.modeSupportsUndo = function(mode) {
|
|
14024
|
-
return ['data', 'label', 'label_style', 'point_style', 'line_style', 'polygon_style', 'edit_points', 'edit_lines', 'edit_polygons', 'snip_lines', 'vertices', 'rectangles'].includes(mode);
|
|
14189
|
+
return ['data', 'frame', 'label', 'label_style', 'point_style', 'line_style', 'polygon_style', 'edit_points', 'edit_lines', 'edit_polygons', 'snip_lines', 'vertices', 'rectangles'].includes(mode);
|
|
14025
14190
|
};
|
|
14026
14191
|
|
|
14027
14192
|
this.getMode = getInteractionMode;
|
|
@@ -14048,7 +14213,8 @@
|
|
|
14048
14213
|
// A mode with a panel of its own: hovering the button must not open the mode
|
|
14049
14214
|
// menu over the panel.
|
|
14050
14215
|
function stylePanelIsActive() {
|
|
14051
|
-
return _editMode == '
|
|
14216
|
+
return _editMode == 'frame' || _editMode == 'frame_draw' ||
|
|
14217
|
+
_editMode == 'label' || _editMode == 'label_style' ||
|
|
14052
14218
|
_editMode == 'point_style' || _editMode == 'line_style' ||
|
|
14053
14219
|
_editMode == 'polygon_style';
|
|
14054
14220
|
}
|
|
@@ -14115,11 +14281,18 @@
|
|
|
14115
14281
|
// if current editing mode is not available, turn off the tool
|
|
14116
14282
|
function updateCurrentMode() {
|
|
14117
14283
|
var modes = getAvailableModes();
|
|
14118
|
-
if (modes.indexOf(_editMode) == -1 && !labelModeIsAvailable() && !labelStyleModeIsAvailable() && !layerStyleModeIsAvailable() && !pointStyleModeIsAvailable()) {
|
|
14284
|
+
if (modes.indexOf(_editMode) == -1 && !inFrameMode() && !labelModeIsAvailable() && !labelStyleModeIsAvailable() && !layerStyleModeIsAvailable() && !pointStyleModeIsAvailable()) {
|
|
14119
14285
|
setMode('off');
|
|
14120
14286
|
}
|
|
14121
14287
|
}
|
|
14122
14288
|
|
|
14289
|
+
// The frame modes are reached from the layer panel and the frame menu, never
|
|
14290
|
+
// from this button's menu, so they neither belong to the active layer's tool
|
|
14291
|
+
// list nor light the button up.
|
|
14292
|
+
function inFrameMode() {
|
|
14293
|
+
return _editMode == 'frame' || _editMode == 'frame_draw';
|
|
14294
|
+
}
|
|
14295
|
+
|
|
14123
14296
|
// Whether a label made now would join the active layer rather than starting a
|
|
14124
14297
|
// layer of its own, which is the label tool's own rule for what a label layer
|
|
14125
14298
|
// is: one with a label-text field, or an empty point layer.
|
|
@@ -14206,7 +14379,7 @@
|
|
|
14206
14379
|
}
|
|
14207
14380
|
btn.classed('hover', _menuOpen);
|
|
14208
14381
|
// btn.classed('selected', active() && !_menuOpen);
|
|
14209
|
-
btn.classed('selected', active());
|
|
14382
|
+
btn.classed('selected', active() && !inFrameMode());
|
|
14210
14383
|
}
|
|
14211
14384
|
|
|
14212
14385
|
function updateSelectionHighlight() {
|
|
@@ -14232,6 +14405,9 @@
|
|
|
14232
14405
|
//
|
|
14233
14406
|
// API:
|
|
14234
14407
|
// toolbar.addButton(iconRef, opts) -> ToolbarButton
|
|
14408
|
+
// toolbar.addTextButton(label, opts) -> ToolbarButton
|
|
14409
|
+
// toolbar.addSegmentedControl(caption, items, opts) -> ToolbarSegmentedControl
|
|
14410
|
+
// toolbar.addTextField(caption, opts) -> ToolbarTextField
|
|
14235
14411
|
// toolbar.addSeparator()
|
|
14236
14412
|
// toolbar.show()
|
|
14237
14413
|
// toolbar.hide()
|
|
@@ -14263,6 +14439,25 @@
|
|
|
14263
14439
|
return new ToolbarButton(content, iconRef, btnOpts || {});
|
|
14264
14440
|
};
|
|
14265
14441
|
|
|
14442
|
+
this.addTextButton = function(label, btnOpts) {
|
|
14443
|
+
var button = new ToolbarButton(content, null, btnOpts || {});
|
|
14444
|
+
button.setText(label);
|
|
14445
|
+
return button;
|
|
14446
|
+
};
|
|
14447
|
+
|
|
14448
|
+
// A choice between modes with every label visible and the active one lit.
|
|
14449
|
+
// Preferred over a button that rewrites its own label, which leaves no way
|
|
14450
|
+
// to tell the mode you are in from the mode a click would put you in.
|
|
14451
|
+
// items: [{value, label, tooltip}]
|
|
14452
|
+
this.addSegmentedControl = function(caption, items, ctrlOpts) {
|
|
14453
|
+
return new ToolbarSegmentedControl(content, caption, items, ctrlOpts || {});
|
|
14454
|
+
};
|
|
14455
|
+
|
|
14456
|
+
// A short typed value, committed on Enter or on leaving the field.
|
|
14457
|
+
this.addTextField = function(caption, fieldOpts) {
|
|
14458
|
+
return new ToolbarTextField(content, caption, fieldOpts || {});
|
|
14459
|
+
};
|
|
14460
|
+
|
|
14266
14461
|
this.addSeparator = function() {
|
|
14267
14462
|
return El('div').addClass('floating-toolbar-separator').appendTo(content);
|
|
14268
14463
|
};
|
|
@@ -14311,6 +14506,111 @@
|
|
|
14311
14506
|
}
|
|
14312
14507
|
}
|
|
14313
14508
|
|
|
14509
|
+
function ToolbarSegmentedControl(parent, caption, items, opts) {
|
|
14510
|
+
var el = El('div').addClass('floating-toolbar-group').appendTo(parent);
|
|
14511
|
+
var changeHandlers = [];
|
|
14512
|
+
var value = null;
|
|
14513
|
+
var segments;
|
|
14514
|
+
if (caption) {
|
|
14515
|
+
El('span').addClass('floating-toolbar-caption').appendTo(el).text(caption);
|
|
14516
|
+
}
|
|
14517
|
+
if (opts.classname) el.addClass(opts.classname);
|
|
14518
|
+
var group = El('div').addClass('floating-toolbar-segments').appendTo(el);
|
|
14519
|
+
segments = items.map(function(item) {
|
|
14520
|
+
var button = new ToolbarButton(group, null, {tooltip: item.tooltip});
|
|
14521
|
+
button.setText(item.label);
|
|
14522
|
+
button.on('click', function() {
|
|
14523
|
+
if (value === item.value) return;
|
|
14524
|
+
setValue(item.value);
|
|
14525
|
+
changeHandlers.forEach(function(fn) { fn(value); });
|
|
14526
|
+
});
|
|
14527
|
+
return {value: item.value, button: button};
|
|
14528
|
+
});
|
|
14529
|
+
|
|
14530
|
+
function setValue(val) {
|
|
14531
|
+
value = val;
|
|
14532
|
+
segments.forEach(function(o) {
|
|
14533
|
+
o.button.setSelected(o.value === val);
|
|
14534
|
+
});
|
|
14535
|
+
}
|
|
14536
|
+
|
|
14537
|
+
// Sets the lit segment without firing 'change', for syncing to outside state.
|
|
14538
|
+
this.setValue = function(val) {
|
|
14539
|
+
setValue(val);
|
|
14540
|
+
return this;
|
|
14541
|
+
};
|
|
14542
|
+
|
|
14543
|
+
this.getValue = function() {
|
|
14544
|
+
return value;
|
|
14545
|
+
};
|
|
14546
|
+
|
|
14547
|
+
this.on = function(event, fn) {
|
|
14548
|
+
if (event == 'change') changeHandlers.push(fn);
|
|
14549
|
+
return this;
|
|
14550
|
+
};
|
|
14551
|
+
|
|
14552
|
+
this.node = function() {
|
|
14553
|
+
return el.node();
|
|
14554
|
+
};
|
|
14555
|
+
}
|
|
14556
|
+
|
|
14557
|
+
function ToolbarTextField(parent, caption, opts) {
|
|
14558
|
+
var el = El('label').addClass('floating-toolbar-group')
|
|
14559
|
+
.addClass('floating-toolbar-field').appendTo(parent);
|
|
14560
|
+
var changeHandlers = [];
|
|
14561
|
+
var committed = '';
|
|
14562
|
+
var input;
|
|
14563
|
+
if (caption) {
|
|
14564
|
+
El('span').addClass('floating-toolbar-caption').appendTo(el).text(caption);
|
|
14565
|
+
}
|
|
14566
|
+
input = El('input').attr('type', 'text').appendTo(el);
|
|
14567
|
+
if (opts.classname) input.addClass(opts.classname);
|
|
14568
|
+
if (opts.placeholder) input.attr('placeholder', opts.placeholder);
|
|
14569
|
+
if (opts.width) input.css('width', opts.width);
|
|
14570
|
+
if (opts.tooltip) el.attr('data-tooltip', opts.tooltip);
|
|
14571
|
+
|
|
14572
|
+
// Keyboard events reach gui.keyboard through the document, so map shortcuts
|
|
14573
|
+
// would fire while the field has focus. Enter commits, Escape abandons the
|
|
14574
|
+
// edit and leaves the field rather than closing the tool around it.
|
|
14575
|
+
input.on('keydown', function(e) {
|
|
14576
|
+
e.stopPropagation();
|
|
14577
|
+
if (e.keyCode == 13) {
|
|
14578
|
+
input.node().blur();
|
|
14579
|
+
} else if (e.keyCode == 27) {
|
|
14580
|
+
input.node().value = committed;
|
|
14581
|
+
input.node().blur();
|
|
14582
|
+
}
|
|
14583
|
+
});
|
|
14584
|
+
|
|
14585
|
+
// Native change fires on Enter and on leaving an edited field, and not when
|
|
14586
|
+
// Escape has put the original value back.
|
|
14587
|
+
input.on('change', function() {
|
|
14588
|
+
var value = input.node().value.trim();
|
|
14589
|
+
committed = value;
|
|
14590
|
+
changeHandlers.forEach(function(fn) { fn(value); });
|
|
14591
|
+
});
|
|
14592
|
+
|
|
14593
|
+
// Sets the displayed value without firing 'change'.
|
|
14594
|
+
this.setValue = function(value) {
|
|
14595
|
+
committed = value == null ? '' : String(value);
|
|
14596
|
+
input.node().value = committed;
|
|
14597
|
+
return this;
|
|
14598
|
+
};
|
|
14599
|
+
|
|
14600
|
+
this.getValue = function() {
|
|
14601
|
+
return input.node().value.trim();
|
|
14602
|
+
};
|
|
14603
|
+
|
|
14604
|
+
this.on = function(event, fn) {
|
|
14605
|
+
if (event == 'change') changeHandlers.push(fn);
|
|
14606
|
+
return this;
|
|
14607
|
+
};
|
|
14608
|
+
|
|
14609
|
+
this.node = function() {
|
|
14610
|
+
return input.node();
|
|
14611
|
+
};
|
|
14612
|
+
}
|
|
14613
|
+
|
|
14314
14614
|
function ToolbarButton(parent, iconRef, opts) {
|
|
14315
14615
|
var btn = El('div').addClass('floating-toolbar-btn').appendTo(parent);
|
|
14316
14616
|
if (iconRef) {
|
|
@@ -14369,6 +14669,11 @@
|
|
|
14369
14669
|
|
|
14370
14670
|
this.setTooltip = setTooltip;
|
|
14371
14671
|
|
|
14672
|
+
this.setText = function(text) {
|
|
14673
|
+
btn.addClass('text-btn').text(text);
|
|
14674
|
+
return this;
|
|
14675
|
+
};
|
|
14676
|
+
|
|
14372
14677
|
this.node = function() {
|
|
14373
14678
|
return btn.node();
|
|
14374
14679
|
};
|
|
@@ -15125,8 +15430,22 @@
|
|
|
15125
15430
|
indigoRamp.concat(purpleRamp)
|
|
15126
15431
|
];
|
|
15127
15432
|
|
|
15433
|
+
// The pickers that are open, so that opening one can close the rest. They
|
|
15434
|
+
// float over their panel and overlap each other -- in Frame properties,
|
|
15435
|
+
// Background and Neatline are close enough together that two open pickers just
|
|
15436
|
+
// cover one another. Only open pickers are listed and hide() delists them, so
|
|
15437
|
+
// this holds at most one entry.
|
|
15438
|
+
var openPickers = [];
|
|
15439
|
+
|
|
15440
|
+
function closeOpenPickers(except) {
|
|
15441
|
+
openPickers.slice().forEach(function(picker) {
|
|
15442
|
+
if (picker !== except) picker.hide();
|
|
15443
|
+
});
|
|
15444
|
+
}
|
|
15445
|
+
|
|
15128
15446
|
function ColorPicker(parent, opts) {
|
|
15129
15447
|
opts = opts || {};
|
|
15448
|
+
var self = this;
|
|
15130
15449
|
var colorPicker = El('div').addClass('label-color-picker').appendTo(parent).hide();
|
|
15131
15450
|
var sbCanvas, hueCanvas, sbMarker, hueMarker, pickerHsbInputs;
|
|
15132
15451
|
var pickerColor = {h: 0, s: 0, b: 0};
|
|
@@ -15138,13 +15457,18 @@
|
|
|
15138
15457
|
if (colorPicker.visible()) {
|
|
15139
15458
|
this.hide();
|
|
15140
15459
|
} else {
|
|
15460
|
+
closeOpenPickers(self);
|
|
15141
15461
|
colorPicker.show();
|
|
15462
|
+
positionPicker();
|
|
15142
15463
|
drawColorPicker();
|
|
15464
|
+
if (openPickers.indexOf(self) == -1) openPickers.push(self);
|
|
15143
15465
|
}
|
|
15144
15466
|
};
|
|
15145
15467
|
|
|
15146
15468
|
this.hide = function() {
|
|
15147
15469
|
colorPicker.hide();
|
|
15470
|
+
var i = openPickers.indexOf(self);
|
|
15471
|
+
if (i > -1) openPickers.splice(i, 1);
|
|
15148
15472
|
};
|
|
15149
15473
|
|
|
15150
15474
|
this.visible = function() {
|
|
@@ -15161,6 +15485,24 @@
|
|
|
15161
15485
|
return hsbToHex(pickerColor);
|
|
15162
15486
|
};
|
|
15163
15487
|
|
|
15488
|
+
// The picker is placed in viewport coordinates (see .label-color-picker), so
|
|
15489
|
+
// it has to be put against the field it belongs to each time it opens. The
|
|
15490
|
+
// offsets reproduce what the stylesheet used to do with `top: 48px; right: 0`
|
|
15491
|
+
// against that field, plus a clamp so that a panel low on the screen cannot
|
|
15492
|
+
// push the picker off the bottom.
|
|
15493
|
+
function positionPicker() {
|
|
15494
|
+
var node = colorPicker.node();
|
|
15495
|
+
var anchor = node.parentNode.getBoundingClientRect();
|
|
15496
|
+
var margin = 8;
|
|
15497
|
+
var top = anchor.top + 48;
|
|
15498
|
+
var left = anchor.right - node.offsetWidth;
|
|
15499
|
+
if (top + node.offsetHeight + margin > window.innerHeight) {
|
|
15500
|
+
top = window.innerHeight - margin - node.offsetHeight;
|
|
15501
|
+
}
|
|
15502
|
+
colorPicker.css('top', Math.round(Math.max(margin, top)) + 'px');
|
|
15503
|
+
colorPicker.css('left', Math.round(Math.max(margin, left)) + 'px');
|
|
15504
|
+
}
|
|
15505
|
+
|
|
15164
15506
|
function init() {
|
|
15165
15507
|
var sbWrap = El('div').addClass('label-color-canvas-wrap').appendTo(colorPicker);
|
|
15166
15508
|
sbCanvas = El('canvas').attr('width', '256').attr('height', '256').appendTo(sbWrap);
|
|
@@ -16070,6 +16412,17 @@
|
|
|
16070
16412
|
control.chit.css('background-color', isHexColor(color) ? color : 'transparent');
|
|
16071
16413
|
};
|
|
16072
16414
|
|
|
16415
|
+
// What a panel calls when it refreshes from the data: the picker has to start
|
|
16416
|
+
// from the colour that is set, not from wherever it was left. A picker that
|
|
16417
|
+
// has never been opened is still on its default, so without this it opens on
|
|
16418
|
+
// black rather than on the colour beside it. Kept apart from setColor(),
|
|
16419
|
+
// which is also the picker's own preview callback and must not feed back into
|
|
16420
|
+
// it mid-drag.
|
|
16421
|
+
control.showColor = function(color) {
|
|
16422
|
+
control.setColor(color);
|
|
16423
|
+
if (isHexColor(color)) control.picker.setColor(color);
|
|
16424
|
+
};
|
|
16425
|
+
|
|
16073
16426
|
El('span').appendTo(colorCell).text(opts.label);
|
|
16074
16427
|
control.chit = El('div').addClass('label-color-chit').attr('role', 'button')
|
|
16075
16428
|
.on('click', function() {
|
|
@@ -18078,13 +18431,13 @@
|
|
|
18078
18431
|
return control;
|
|
18079
18432
|
}
|
|
18080
18433
|
|
|
18081
|
-
// In the
|
|
18082
|
-
//
|
|
18083
|
-
//
|
|
18434
|
+
// In the narrow column, under the stroke's opacity. Stepping runs up a ladder
|
|
18435
|
+
// of widths rather than by a fixed amount, because the useful ones are close
|
|
18436
|
+
// together at the hairline end and far apart above 2px.
|
|
18084
18437
|
function addStrokeWidthControl(parent) {
|
|
18085
18438
|
var row = El('div').addClass('label-style-row label-split-row').appendTo(parent);
|
|
18086
|
-
var cell = El('div').addClass('label-split-cell').appendTo(row);
|
|
18087
18439
|
El('div').addClass('label-split-cell').appendTo(row);
|
|
18440
|
+
var cell = El('div').addClass('label-split-cell').appendTo(row);
|
|
18088
18441
|
El('span').appendTo(cell).text('Stroke width');
|
|
18089
18442
|
return new SizeField(cell, {
|
|
18090
18443
|
min: 0,
|
|
@@ -18123,13 +18476,10 @@
|
|
|
18123
18476
|
|
|
18124
18477
|
function updateColorControl(control) {
|
|
18125
18478
|
var value = getCommonStyleValue(control.field);
|
|
18126
|
-
|
|
18479
|
+
control.showColor(value);
|
|
18127
18480
|
updateOpacityControl(control);
|
|
18128
|
-
|
|
18129
|
-
|
|
18130
|
-
} else {
|
|
18131
|
-
control.picker.hide();
|
|
18132
|
-
}
|
|
18481
|
+
// Nothing for the picker to sit on when the selection has no one colour.
|
|
18482
|
+
if (!isHexColor(value)) control.picker.hide();
|
|
18133
18483
|
}
|
|
18134
18484
|
|
|
18135
18485
|
function updateOpacityControl(control) {
|
|
@@ -18138,10 +18488,6 @@
|
|
|
18138
18488
|
formatOpacityPct(value === '' || value === undefined || value === null ? 1 : value);
|
|
18139
18489
|
}
|
|
18140
18490
|
|
|
18141
|
-
function setColorControlValue(control, value) {
|
|
18142
|
-
control.setColor(value);
|
|
18143
|
-
}
|
|
18144
|
-
|
|
18145
18491
|
function updateStrokeWidthControl() {
|
|
18146
18492
|
var value = getCommonStyleValue('stroke-width');
|
|
18147
18493
|
strokeWidthField.setValue(
|
|
@@ -18542,8 +18888,10 @@
|
|
|
18542
18888
|
// The two sizes of a circle side by side, in the shape the panel uses for
|
|
18543
18889
|
// every other pair.
|
|
18544
18890
|
var sizeRow = El('div').addClass('label-style-row label-split-row').appendTo(circlesSection);
|
|
18545
|
-
|
|
18891
|
+
// Radius in the wide column, stroke width in the narrow one under the
|
|
18892
|
+
// stroke's opacity, matching the line and polygon panels.
|
|
18546
18893
|
var radiusCell = El('div').addClass('label-split-cell').appendTo(sizeRow);
|
|
18894
|
+
var widthCell = El('div').addClass('label-split-cell').appendTo(sizeRow);
|
|
18547
18895
|
El('span').appendTo(widthCell).text('Stroke width');
|
|
18548
18896
|
circleStrokeWidthField = new SizeField(widthCell, {
|
|
18549
18897
|
min: 0,
|
|
@@ -18933,7 +19281,7 @@
|
|
|
18933
19281
|
}
|
|
18934
19282
|
|
|
18935
19283
|
function setCircleColor(control, color) {
|
|
18936
|
-
control.
|
|
19284
|
+
control.showColor(color);
|
|
18937
19285
|
}
|
|
18938
19286
|
|
|
18939
19287
|
function formatNumberValue(val) {
|
|
@@ -18980,8 +19328,28 @@
|
|
|
18980
19328
|
function Model(gui) {
|
|
18981
19329
|
var self = new internal.Catalog();
|
|
18982
19330
|
var deleteLayer = self.deleteLayer;
|
|
19331
|
+
var setDefaultTargets = self.setDefaultTargets;
|
|
18983
19332
|
utils$1.extend(self, EventDispatcher.prototype);
|
|
18984
19333
|
|
|
19334
|
+
// A map frame is composition state, not editable content. Commands may
|
|
19335
|
+
// explicitly target it, but it must not replace the GUI's active content
|
|
19336
|
+
// layer or become the implicit target of the next console command.
|
|
19337
|
+
self.setDefaultTargets = function(targets, opts) {
|
|
19338
|
+
var previousContent = getContentTargets(self.getDefaultTargets());
|
|
19339
|
+
var requestedContent = getContentTargets(targets);
|
|
19340
|
+
setDefaultTargets.call(self, targets, opts);
|
|
19341
|
+
if (countTargetLayers(requestedContent) < countTargetLayers(targets)) {
|
|
19342
|
+
var contentTargets = requestedContent.length ?
|
|
19343
|
+
requestedContent : previousContent;
|
|
19344
|
+
if (!contentTargets.length) {
|
|
19345
|
+
contentTargets = getFirstContentTarget();
|
|
19346
|
+
}
|
|
19347
|
+
if (contentTargets.length) {
|
|
19348
|
+
setDefaultTargets.call(self, contentTargets);
|
|
19349
|
+
}
|
|
19350
|
+
}
|
|
19351
|
+
};
|
|
19352
|
+
|
|
18985
19353
|
// override Catalog method (so -drop command will work in web console)
|
|
18986
19354
|
self.deleteLayer = function(lyr, dataset) {
|
|
18987
19355
|
var active, flags;
|
|
@@ -19037,6 +19405,32 @@
|
|
|
19037
19405
|
};
|
|
19038
19406
|
|
|
19039
19407
|
return self;
|
|
19408
|
+
|
|
19409
|
+
function getContentTargets(targets) {
|
|
19410
|
+
return (targets || []).map(function(target) {
|
|
19411
|
+
return {
|
|
19412
|
+
dataset: target.dataset,
|
|
19413
|
+
layers: target.layers.filter(function(lyr) {
|
|
19414
|
+
return !internal.isFrameLayer(lyr, target.dataset.arcs);
|
|
19415
|
+
})
|
|
19416
|
+
};
|
|
19417
|
+
}).filter(function(target) {
|
|
19418
|
+
return target.layers.length > 0;
|
|
19419
|
+
});
|
|
19420
|
+
}
|
|
19421
|
+
|
|
19422
|
+
function getFirstContentTarget() {
|
|
19423
|
+
var target = self.getLayers().find(function(o) {
|
|
19424
|
+
return !internal.isFrameLayer(o.layer, o.dataset.arcs);
|
|
19425
|
+
});
|
|
19426
|
+
return target ? [{dataset: target.dataset, layers: [target.layer]}] : [];
|
|
19427
|
+
}
|
|
19428
|
+
|
|
19429
|
+
function countTargetLayers(targets) {
|
|
19430
|
+
return (targets || []).reduce(function(sum, target) {
|
|
19431
|
+
return sum + target.layers.length;
|
|
19432
|
+
}, 0);
|
|
19433
|
+
}
|
|
19040
19434
|
}
|
|
19041
19435
|
|
|
19042
19436
|
function absArcId(arcId) {
|
|
@@ -20883,6 +21277,95 @@
|
|
|
20883
21277
|
});
|
|
20884
21278
|
}
|
|
20885
21279
|
|
|
21280
|
+
// How an aspect ratio is read, written and held to, in one place. The Add map
|
|
21281
|
+
// frame dialog takes one typed into a field, the Frame properties panel shows
|
|
21282
|
+
// the one a frame ended up with, so a ratio entered as 3:2 reads back as 3:2,
|
|
21283
|
+
// and the box drawn on the map is kept to it while it is dragged.
|
|
21284
|
+
|
|
21285
|
+
// Ratios common enough to be worth naming when one is shown.
|
|
21286
|
+
var namedRatios = [
|
|
21287
|
+
[1, '1:1'],
|
|
21288
|
+
[4 / 3, '4:3'],
|
|
21289
|
+
[3 / 2, '3:2'],
|
|
21290
|
+
[16 / 9, '16:9']
|
|
21291
|
+
];
|
|
21292
|
+
|
|
21293
|
+
// Accepts "5:4" or a bare number. Returns NaN for anything else, including a
|
|
21294
|
+
// ratio with a zero or negative term.
|
|
21295
|
+
function parseFrameAspectRatio(value) {
|
|
21296
|
+
var parts = String(value).trim().split(':').map(Number);
|
|
21297
|
+
if (parts.length == 2) {
|
|
21298
|
+
return parts[0] > 0 && parts[1] > 0 ? parts[0] / parts[1] : NaN;
|
|
21299
|
+
}
|
|
21300
|
+
return parts.length == 1 ? parts[0] : NaN;
|
|
21301
|
+
}
|
|
21302
|
+
|
|
21303
|
+
// "3:2" for a ratio that has a name, "1.62" for one that does not.
|
|
21304
|
+
function formatFrameAspectRatio(aspect) {
|
|
21305
|
+
var match = namedRatios.find(function(item) {
|
|
21306
|
+
return Math.abs(item[0] - aspect) < 1e-10;
|
|
21307
|
+
});
|
|
21308
|
+
return match ? match[1] : String(Math.round(aspect * 100) / 100);
|
|
21309
|
+
}
|
|
21310
|
+
|
|
21311
|
+
// Move the pointer-side corner of a box being dragged out from (x1, y1) so the
|
|
21312
|
+
// box has the given width/height ratio. Grows the short side rather than
|
|
21313
|
+
// trimming the long one, so the box always reaches the pointer on one axis and
|
|
21314
|
+
// never shrinks away from the drag. Works in screen pixels, where y runs
|
|
21315
|
+
// downwards; only the magnitudes matter, so the sign of each axis is kept.
|
|
21316
|
+
function getCornerForRatio(x1, y1, x2, y2, ratio) {
|
|
21317
|
+
var sx = x2 < x1 ? -1 : 1,
|
|
21318
|
+
sy = y2 < y1 ? -1 : 1,
|
|
21319
|
+
w = Math.abs(x2 - x1),
|
|
21320
|
+
h = Math.abs(y2 - y1);
|
|
21321
|
+
if (w / h > ratio) { // also catches h == 0
|
|
21322
|
+
h = w / ratio;
|
|
21323
|
+
} else if (w > 0 || h > 0) {
|
|
21324
|
+
w = h * ratio;
|
|
21325
|
+
}
|
|
21326
|
+
return [x1 + sx * w, y1 + sy * h];
|
|
21327
|
+
}
|
|
21328
|
+
|
|
21329
|
+
// Reshape a [minx, miny, maxx, maxy] box that a handle drag has pulled off
|
|
21330
|
+
// ratio. A corner handle grows whichever side is short, anchored on the corner
|
|
21331
|
+
// opposite the handle; an edge handle drives the axis it moves along and the
|
|
21332
|
+
// other axis grows from the centre, so the box widens in place instead of
|
|
21333
|
+
// walking sideways. Mutates bbox.
|
|
21334
|
+
function applyAspectRatio(bbox, handle, ratio) {
|
|
21335
|
+
var w = bbox[2] - bbox[0],
|
|
21336
|
+
h = bbox[3] - bbox[1],
|
|
21337
|
+
cx, cy;
|
|
21338
|
+
// Leave an inverted or empty box alone; the drag has flipped it past an edge
|
|
21339
|
+
// and the caller puts the corners back in order when the drag ends.
|
|
21340
|
+
if (!(w > 0) || !(h > 0) || handle.type == 'center') return;
|
|
21341
|
+
if (handle.type == 'corner') {
|
|
21342
|
+
if (w / h > ratio) h = w / ratio;
|
|
21343
|
+
else w = h * ratio;
|
|
21344
|
+
} else if (handle.col == 'left' || handle.col == 'right') {
|
|
21345
|
+
h = w / ratio;
|
|
21346
|
+
} else {
|
|
21347
|
+
w = h * ratio;
|
|
21348
|
+
}
|
|
21349
|
+
if (handle.col == 'left') {
|
|
21350
|
+
bbox[0] = bbox[2] - w;
|
|
21351
|
+
} else if (handle.col == 'right') {
|
|
21352
|
+
bbox[2] = bbox[0] + w;
|
|
21353
|
+
} else {
|
|
21354
|
+
cx = (bbox[0] + bbox[2]) / 2;
|
|
21355
|
+
bbox[0] = cx - w / 2;
|
|
21356
|
+
bbox[2] = cx + w / 2;
|
|
21357
|
+
}
|
|
21358
|
+
if (handle.row == 'top') {
|
|
21359
|
+
bbox[3] = bbox[1] + h;
|
|
21360
|
+
} else if (handle.row == 'bottom') {
|
|
21361
|
+
bbox[1] = bbox[3] - h;
|
|
21362
|
+
} else {
|
|
21363
|
+
cy = (bbox[1] + bbox[3]) / 2;
|
|
21364
|
+
bbox[1] = cy - h / 2;
|
|
21365
|
+
bbox[3] = cy + h / 2;
|
|
21366
|
+
}
|
|
21367
|
+
}
|
|
21368
|
+
|
|
20886
21369
|
function HighlightBox(gui, optsArg) {
|
|
20887
21370
|
var el = El('div').addClass('zoom-box').appendTo('body'),
|
|
20888
21371
|
opts = Object.assign({
|
|
@@ -20891,11 +21374,12 @@
|
|
|
20891
21374
|
persistent: false,
|
|
20892
21375
|
draggable: false // does dragging the map draw a box
|
|
20893
21376
|
}, optsArg),
|
|
20894
|
-
clickToStart = opts.name == 'box-tool',
|
|
21377
|
+
clickToStart = opts.clickToStart || opts.name == 'box-tool',
|
|
20895
21378
|
box = new EventDispatcher(),
|
|
20896
21379
|
stroke = 2,
|
|
20897
21380
|
activeHandle = null,
|
|
20898
21381
|
prevXY = null,
|
|
21382
|
+
aspectRatio = null,
|
|
20899
21383
|
boxCoords = null,
|
|
20900
21384
|
_on = false,
|
|
20901
21385
|
_visible = false,
|
|
@@ -20990,18 +21474,25 @@
|
|
|
20990
21474
|
}
|
|
20991
21475
|
prevXY = xy;
|
|
20992
21476
|
redraw();
|
|
20993
|
-
box.dispatchEvent('handle_drag'
|
|
21477
|
+
box.dispatchEvent('handle_drag', {
|
|
21478
|
+
handle: copyHandle(activeHandle),
|
|
21479
|
+
map_bbox: boxCoords.slice()
|
|
21480
|
+
});
|
|
20994
21481
|
});
|
|
20995
21482
|
|
|
20996
21483
|
gui.map.getMouse().on('mouseup', function(e) {
|
|
20997
21484
|
if (activeHandle && _on) {
|
|
21485
|
+
var handle = copyHandle(activeHandle);
|
|
20998
21486
|
activeHandle.el.css('background', null);
|
|
20999
21487
|
activeHandle = null;
|
|
21000
21488
|
prevXY = null;
|
|
21001
|
-
box.dispatchEvent('handle_up');
|
|
21002
21489
|
// reset box if it has been inverted (by dragging)
|
|
21003
21490
|
fixBounds(boxCoords);
|
|
21004
21491
|
redraw();
|
|
21492
|
+
box.dispatchEvent('handle_up', {
|
|
21493
|
+
handle: handle,
|
|
21494
|
+
map_bbox: boxCoords.slice()
|
|
21495
|
+
});
|
|
21005
21496
|
}
|
|
21006
21497
|
});
|
|
21007
21498
|
}
|
|
@@ -21050,6 +21541,9 @@
|
|
|
21050
21541
|
boxCoords[1] += dy;
|
|
21051
21542
|
if (centered) boxCoords[3] -= dy;
|
|
21052
21543
|
}
|
|
21544
|
+
if (aspectRatio) {
|
|
21545
|
+
applyAspectRatio(boxCoords, activeHandle, aspectRatio);
|
|
21546
|
+
}
|
|
21053
21547
|
}
|
|
21054
21548
|
|
|
21055
21549
|
function rescaleBox(x, y) {
|
|
@@ -21064,6 +21558,14 @@
|
|
|
21064
21558
|
boxCoords = [cx - dx, cy - dy, cx + dx, cy + dy];
|
|
21065
21559
|
}
|
|
21066
21560
|
|
|
21561
|
+
// Hold the box to a width/height ratio while it is drawn or resized. The
|
|
21562
|
+
// ratio is in screen pixels, which is what the user is aiming at; the map
|
|
21563
|
+
// draws both axes at one scale, so for a projected view it is also the ratio
|
|
21564
|
+
// of the coordinates the box reports back.
|
|
21565
|
+
box.setAspectRatio = function(ratio) {
|
|
21566
|
+
aspectRatio = ratio > 0 ? Number(ratio) : null;
|
|
21567
|
+
};
|
|
21568
|
+
|
|
21067
21569
|
box.setDataCoords = function(bbox) {
|
|
21068
21570
|
boxCoords = bbox;
|
|
21069
21571
|
// Box is being placed programmatically (e.g. around a pinned rectangle);
|
|
@@ -21081,6 +21583,12 @@
|
|
|
21081
21583
|
return dataBox;
|
|
21082
21584
|
};
|
|
21083
21585
|
|
|
21586
|
+
// Coordinates in the map's display CRS. This avoids routing frame-tool
|
|
21587
|
+
// coordinates through the currently active content layer.
|
|
21588
|
+
box.getDisplayCoords = function() {
|
|
21589
|
+
return boxCoords ? boxCoords.slice() : null;
|
|
21590
|
+
};
|
|
21591
|
+
|
|
21084
21592
|
box.turnOn = function() {
|
|
21085
21593
|
_on = true;
|
|
21086
21594
|
};
|
|
@@ -21138,34 +21646,16 @@
|
|
|
21138
21646
|
|
|
21139
21647
|
// get bbox coords in the display CRS
|
|
21140
21648
|
function getBoxCoords(p1, p2) {
|
|
21141
|
-
|
|
21142
|
-
|
|
21649
|
+
// A set ratio wins over shift, which is just the 1:1 case of the same rule.
|
|
21650
|
+
var ratio = aspectRatio || (gui.keyboard.shiftIsPressed() ? 1 : 0);
|
|
21651
|
+
if (ratio) {
|
|
21652
|
+
p2 = getCornerForRatio(p1[0], p1[1], p2[0], p2[1], ratio);
|
|
21143
21653
|
}
|
|
21144
21654
|
var bbox = pixToCoords(p1.concat(p2), gui.map.getExtent());
|
|
21145
21655
|
fixBounds(bbox);
|
|
21146
21656
|
return bbox;
|
|
21147
21657
|
}
|
|
21148
21658
|
|
|
21149
|
-
function getSquareCorner(x1, y1, x2, y2) {
|
|
21150
|
-
var dx = x2 - x1;
|
|
21151
|
-
var dy = y2 - y1;
|
|
21152
|
-
if (dy === 0 && dx === 0) {
|
|
21153
|
-
return [x2, y2];
|
|
21154
|
-
}
|
|
21155
|
-
if (dy === 0) {
|
|
21156
|
-
dy = 1;
|
|
21157
|
-
}
|
|
21158
|
-
if (dx === 0) {
|
|
21159
|
-
dx = 1;
|
|
21160
|
-
}
|
|
21161
|
-
if (Math.abs(dx) > Math.abs(dy)) {
|
|
21162
|
-
dy = dy * Math.abs(dx / dy);
|
|
21163
|
-
} else {
|
|
21164
|
-
dx = dx * Math.abs(dy / dx);
|
|
21165
|
-
}
|
|
21166
|
-
return [x1 + dx, y1 + dy];
|
|
21167
|
-
}
|
|
21168
|
-
|
|
21169
21659
|
function redraw() {
|
|
21170
21660
|
if (!boxCoords) return;
|
|
21171
21661
|
var ext = gui.map.getExtent();
|
|
@@ -21179,6 +21669,14 @@
|
|
|
21179
21669
|
return box;
|
|
21180
21670
|
}
|
|
21181
21671
|
|
|
21672
|
+
function copyHandle(handle) {
|
|
21673
|
+
return handle ? {
|
|
21674
|
+
type: handle.type,
|
|
21675
|
+
col: handle.col,
|
|
21676
|
+
row: handle.row
|
|
21677
|
+
} : null;
|
|
21678
|
+
}
|
|
21679
|
+
|
|
21182
21680
|
function coordsToPix(bbox, ext) {
|
|
21183
21681
|
var a = ext.translateCoords(bbox[0], bbox[1]);
|
|
21184
21682
|
var b = ext.translateCoords(bbox[2], bbox[3]);
|
|
@@ -21739,7 +22237,11 @@
|
|
|
21739
22237
|
var recIds = id >= 0 ? [id] : ids;
|
|
21740
22238
|
var el = content;
|
|
21741
22239
|
var table = lyr.data; // table can be null (e.g. if layer has no attribute data)
|
|
21742
|
-
var
|
|
22240
|
+
var isFrame = layerIsFrame(lyr);
|
|
22241
|
+
var hasCustomFrameFields = isFrame && table && table.getFields().some(function(field) {
|
|
22242
|
+
return !internal.isFrameReservedField(field);
|
|
22243
|
+
});
|
|
22244
|
+
var tableEl = table ? renderTable(recIds, table, editable, isFrame) : null;
|
|
21743
22245
|
el.empty(); // clean up if panel is already open
|
|
21744
22246
|
if (tableEl) {
|
|
21745
22247
|
tableEl.appendTo(el);
|
|
@@ -21753,6 +22255,8 @@
|
|
|
21753
22255
|
e.preventDefault(); // don't copy original string with tabs
|
|
21754
22256
|
}
|
|
21755
22257
|
});
|
|
22258
|
+
} else if (isFrame) {
|
|
22259
|
+
el.html('<div class="note">Map frame settings are edited separately from attribute data.</div>');
|
|
21756
22260
|
} else {
|
|
21757
22261
|
// Some individual features can have undefined values for some or all of
|
|
21758
22262
|
// their data properties (properties are set to undefined when an input JSON file
|
|
@@ -21762,13 +22266,13 @@
|
|
|
21762
22266
|
}
|
|
21763
22267
|
|
|
21764
22268
|
var footer = El('div').appendTo(el);
|
|
21765
|
-
if (editable) {
|
|
22269
|
+
if (editable && !isFrame) {
|
|
21766
22270
|
// render "add field" button
|
|
21767
22271
|
El('span').addClass('add-field-btn').appendTo(footer).on('click', async function(e) {
|
|
21768
22272
|
// show "add field" dialog
|
|
21769
22273
|
openAddFieldPopup(gui, recIds, lyr);
|
|
21770
22274
|
}).text('+ add field');
|
|
21771
|
-
} else if (pinned) {
|
|
22275
|
+
} else if (pinned && (!isFrame || hasCustomFrameFields)) {
|
|
21772
22276
|
// render "Click to edit" button
|
|
21773
22277
|
El('span').addClass('edit-data-btn').appendTo(footer).on('click', async function(e) {
|
|
21774
22278
|
self.show(id, ids, lyr, true, true);
|
|
@@ -21776,7 +22280,7 @@
|
|
|
21776
22280
|
}
|
|
21777
22281
|
}
|
|
21778
22282
|
|
|
21779
|
-
function renderTable(recIds, table, editable) {
|
|
22283
|
+
function renderTable(recIds, table, editable, isFrame) {
|
|
21780
22284
|
var tableEl = El('table').addClass('selectable');
|
|
21781
22285
|
var rows = 0;
|
|
21782
22286
|
var rec;
|
|
@@ -21791,6 +22295,7 @@
|
|
|
21791
22295
|
utils$1.forEachProperty(rec, function(v, k) {
|
|
21792
22296
|
// missing GeoJSON fields are set to undefined on import; skip these
|
|
21793
22297
|
if (v === undefined) return;
|
|
22298
|
+
if (isFrame && internal.isFrameReservedField(k)) return;
|
|
21794
22299
|
var rowEl = renderRow(k, v, recIds, table, editable);
|
|
21795
22300
|
if (rowEl) {
|
|
21796
22301
|
rowEl.appendTo(tableEl);
|
|
@@ -21800,6 +22305,11 @@
|
|
|
21800
22305
|
return rows > 0 ? tableEl : null;
|
|
21801
22306
|
}
|
|
21802
22307
|
|
|
22308
|
+
function layerIsFrame(lyr) {
|
|
22309
|
+
var dataset = lyr.gui && lyr.gui.source && lyr.gui.source.dataset;
|
|
22310
|
+
return !!dataset && internal.isFrameLayer(lyr, dataset.arcs);
|
|
22311
|
+
}
|
|
22312
|
+
|
|
21803
22313
|
function getMultiRecord(recIds, table) {
|
|
21804
22314
|
var fields = table.getFields();
|
|
21805
22315
|
var rec = {};
|
|
@@ -22474,7 +22984,7 @@
|
|
|
22474
22984
|
function showInstructions() {
|
|
22475
22985
|
var isMac = navigator.userAgent.includes('Mac');
|
|
22476
22986
|
var symbol = isMac ? '⌘' : '^';
|
|
22477
|
-
var msg = `
|
|
22987
|
+
var msg = `Click on the map to add points.<br>Move points by dragging.`;
|
|
22478
22988
|
alert = showPopupAlert(msg, null, { non_blocking: true, max_width: '290px'});
|
|
22479
22989
|
}
|
|
22480
22990
|
|
|
@@ -22692,7 +23202,7 @@
|
|
|
22692
23202
|
function showInstructions() {
|
|
22693
23203
|
var isMac = navigator.userAgent.includes('Mac');
|
|
22694
23204
|
var undoKey = isMac ? '⌘' : '^';
|
|
22695
|
-
var msg = `
|
|
23205
|
+
var msg = `Click to add points to a path or click and drag to draw continuously. Drag vertices to reshape a path.`;
|
|
22696
23206
|
alert = showPopupAlert(msg, null, {
|
|
22697
23207
|
non_blocking: true, max_width: '350px'});
|
|
22698
23208
|
}
|
|
@@ -23269,7 +23779,7 @@
|
|
|
23269
23779
|
}
|
|
23270
23780
|
|
|
23271
23781
|
function showInstructions() {
|
|
23272
|
-
var msg = '
|
|
23782
|
+
var msg = 'Click a line to snip it apart. Snipping a ring ' +
|
|
23273
23783
|
'takes two clicks -- the ring divides at the second one.';
|
|
23274
23784
|
alert = showPopupAlert(msg, null, {non_blocking: true, max_width: '350px'});
|
|
23275
23785
|
}
|
|
@@ -24052,7 +24562,7 @@
|
|
|
24052
24562
|
//
|
|
24053
24563
|
// See docs/development/label-tool-design.md.
|
|
24054
24564
|
|
|
24055
|
-
var SVG_NS$
|
|
24565
|
+
var SVG_NS$3 = 'http://www.w3.org/2000/svg';
|
|
24056
24566
|
var XML_NS = 'http://www.w3.org/XML/1998/namespace';
|
|
24057
24567
|
var BOX_PADDING$1 = 3;
|
|
24058
24568
|
|
|
@@ -24350,7 +24860,7 @@
|
|
|
24350
24860
|
}
|
|
24351
24861
|
removePendingGroup(o);
|
|
24352
24862
|
o.pending.markup = markup;
|
|
24353
|
-
o.pending.group = document.createElementNS(SVG_NS$
|
|
24863
|
+
o.pending.group = document.createElementNS(SVG_NS$3, 'g');
|
|
24354
24864
|
o.pending.group.setAttribute('class', 'label-edit-pending');
|
|
24355
24865
|
o.pending.group.innerHTML = markup;
|
|
24356
24866
|
container.appendChild(o.pending.group);
|
|
@@ -24432,7 +24942,7 @@
|
|
|
24432
24942
|
lines = getRenderedLines(o.text);
|
|
24433
24943
|
content.appendChild(document.createTextNode(lines[0]));
|
|
24434
24944
|
for (i = 1; i < lines.length; i++) {
|
|
24435
|
-
tspan = document.createElementNS(SVG_NS$
|
|
24945
|
+
tspan = document.createElementNS(SVG_NS$3, 'tspan');
|
|
24436
24946
|
tspan.setAttribute('x', o.nodes.text.getAttribute('x') || 0);
|
|
24437
24947
|
tspan.setAttribute('dy', getLineHeight(o));
|
|
24438
24948
|
tspan.appendChild(document.createTextNode(lines[i]));
|
|
@@ -24542,7 +25052,7 @@
|
|
|
24542
25052
|
// nothing on screen at all. Drawn from the same <defs> path the <textPath>
|
|
24543
25053
|
// references, so it cannot drift from the text it belongs to.
|
|
24544
25054
|
function ghostPath(pathId) {
|
|
24545
|
-
var el = document.createElementNS(SVG_NS$
|
|
25055
|
+
var el = document.createElementNS(SVG_NS$3, 'use');
|
|
24546
25056
|
el.setAttribute('href', '#' + pathId);
|
|
24547
25057
|
el.setAttribute('class', 'label-edit-path');
|
|
24548
25058
|
return el;
|
|
@@ -24555,7 +25065,7 @@
|
|
|
24555
25065
|
if (!pathId) return;
|
|
24556
25066
|
// A thickened, transparent copy of the baseline: about one em wide, so that
|
|
24557
25067
|
// the region follows the curve rather than boxing it.
|
|
24558
|
-
el = document.createElementNS(SVG_NS$
|
|
25068
|
+
el = document.createElementNS(SVG_NS$3, 'use');
|
|
24559
25069
|
el.setAttribute('href', '#' + pathId);
|
|
24560
25070
|
el.setAttribute('class', 'label-edit-hit-baseline');
|
|
24561
25071
|
el.setAttribute('stroke-width', caret ? caret.ascent + caret.descent : 12);
|
|
@@ -24616,7 +25126,7 @@
|
|
|
24616
25126
|
}
|
|
24617
25127
|
|
|
24618
25128
|
function makeGroup(className) {
|
|
24619
|
-
var g = document.createElementNS(SVG_NS$
|
|
25129
|
+
var g = document.createElementNS(SVG_NS$3, 'g');
|
|
24620
25130
|
g.setAttribute('class', className);
|
|
24621
25131
|
return g;
|
|
24622
25132
|
}
|
|
@@ -24651,7 +25161,7 @@
|
|
|
24651
25161
|
}
|
|
24652
25162
|
|
|
24653
25163
|
function rect(box, className) {
|
|
24654
|
-
var el = document.createElementNS(SVG_NS$
|
|
25164
|
+
var el = document.createElementNS(SVG_NS$3, 'rect');
|
|
24655
25165
|
el.setAttribute('x', box.x);
|
|
24656
25166
|
el.setAttribute('y', box.y);
|
|
24657
25167
|
el.setAttribute('width', box.width);
|
|
@@ -24661,7 +25171,7 @@
|
|
|
24661
25171
|
}
|
|
24662
25172
|
|
|
24663
25173
|
function caretLine(caret) {
|
|
24664
|
-
var el = document.createElementNS(SVG_NS$
|
|
25174
|
+
var el = document.createElementNS(SVG_NS$3, 'line');
|
|
24665
25175
|
el.setAttribute('x1', caret.x);
|
|
24666
25176
|
el.setAttribute('y1', caret.y - caret.ascent);
|
|
24667
25177
|
el.setAttribute('x2', caret.x);
|
|
@@ -24821,7 +25331,7 @@
|
|
|
24821
25331
|
//
|
|
24822
25332
|
// See docs/development/label-tool-design.md.
|
|
24823
25333
|
|
|
24824
|
-
var SVG_NS$
|
|
25334
|
+
var SVG_NS$2 = 'http://www.w3.org/2000/svg';
|
|
24825
25335
|
var BOX_PADDING = 3;
|
|
24826
25336
|
var ANCHOR_RADIUS = 3.5;
|
|
24827
25337
|
var KNOT_RADIUS = 3;
|
|
@@ -24995,7 +25505,7 @@
|
|
|
24995
25505
|
// moves and hides with it, and inserted before it so the outline paints
|
|
24996
25506
|
// beneath the glyphs.
|
|
24997
25507
|
function makeGroup(nodes, className) {
|
|
24998
|
-
var g = document.createElementNS(SVG_NS$
|
|
25508
|
+
var g = document.createElementNS(SVG_NS$2, 'g');
|
|
24999
25509
|
var transform = nodes.symbol.getAttribute('transform');
|
|
25000
25510
|
var display = nodes.symbol.getAttribute('display');
|
|
25001
25511
|
g.setAttribute('class', 'label-cue ' + className);
|
|
@@ -25014,7 +25524,7 @@
|
|
|
25014
25524
|
}
|
|
25015
25525
|
|
|
25016
25526
|
function rect(box, pad) {
|
|
25017
|
-
var el = document.createElementNS(SVG_NS$
|
|
25527
|
+
var el = document.createElementNS(SVG_NS$2, 'rect');
|
|
25018
25528
|
el.setAttribute('x', box.x - pad);
|
|
25019
25529
|
el.setAttribute('y', box.y - pad);
|
|
25020
25530
|
el.setAttribute('width', box.width + pad * 2);
|
|
@@ -25024,7 +25534,7 @@
|
|
|
25024
25534
|
}
|
|
25025
25535
|
|
|
25026
25536
|
function anchorMarker() {
|
|
25027
|
-
var el = document.createElementNS(SVG_NS$
|
|
25537
|
+
var el = document.createElementNS(SVG_NS$2, 'circle');
|
|
25028
25538
|
// The group's own origin is the anchor point, so the marker sits at 0,0.
|
|
25029
25539
|
el.setAttribute('cx', 0);
|
|
25030
25540
|
el.setAttribute('cy', 0);
|
|
@@ -25037,7 +25547,7 @@
|
|
|
25037
25547
|
// of its box rather than to the middle of it: a line to the middle would run
|
|
25038
25548
|
// underneath the glyphs it is pointing at.
|
|
25039
25549
|
function tether(box) {
|
|
25040
|
-
var el = document.createElementNS(SVG_NS$
|
|
25550
|
+
var el = document.createElementNS(SVG_NS$2, 'line');
|
|
25041
25551
|
el.setAttribute('x1', 0);
|
|
25042
25552
|
el.setAttribute('y1', 0);
|
|
25043
25553
|
el.setAttribute('x2', clamp(0, box.x - BOX_PADDING, box.x + box.width + BOX_PADDING));
|
|
@@ -25051,7 +25561,7 @@
|
|
|
25051
25561
|
}
|
|
25052
25562
|
|
|
25053
25563
|
function knotHandle(p) {
|
|
25054
|
-
var el = document.createElementNS(SVG_NS$
|
|
25564
|
+
var el = document.createElementNS(SVG_NS$2, 'circle');
|
|
25055
25565
|
el.setAttribute('cx', p[0]);
|
|
25056
25566
|
el.setAttribute('cy', p[1]);
|
|
25057
25567
|
el.setAttribute('r', KNOT_RADIUS);
|
|
@@ -25060,7 +25570,7 @@
|
|
|
25060
25570
|
}
|
|
25061
25571
|
|
|
25062
25572
|
function curve(pathId) {
|
|
25063
|
-
var el = document.createElementNS(SVG_NS$
|
|
25573
|
+
var el = document.createElementNS(SVG_NS$2, 'use');
|
|
25064
25574
|
el.setAttribute('href', '#' + pathId);
|
|
25065
25575
|
el.setAttribute('class', 'label-cue-curve');
|
|
25066
25576
|
return el;
|
|
@@ -25407,9 +25917,9 @@
|
|
|
25407
25917
|
hideInstructions();
|
|
25408
25918
|
if (!armed) return;
|
|
25409
25919
|
alert = showPopupAlert(armed == 'anchor' ?
|
|
25410
|
-
'
|
|
25411
|
-
'
|
|
25412
|
-
'
|
|
25920
|
+
'Click on the map to place a label.' :
|
|
25921
|
+
'Click to draw a curved path. Type Esc or double-click ' +
|
|
25922
|
+
'to finish. Backspace removes the last point.',
|
|
25413
25923
|
null, {non_blocking: true, max_width: '330px'});
|
|
25414
25924
|
}
|
|
25415
25925
|
|
|
@@ -26705,6 +27215,18 @@
|
|
|
26705
27215
|
return bounds2.width() / _frame.width;
|
|
26706
27216
|
};
|
|
26707
27217
|
|
|
27218
|
+
this.zoomToFrameMagnification = function(targetScale) {
|
|
27219
|
+
if (!_frame || !(targetScale > 0)) return;
|
|
27220
|
+
var currentScale = this.getSymbolScale();
|
|
27221
|
+
var frameBounds = new Bounds$1(_frame.bbox);
|
|
27222
|
+
var p = this.translateCoords(frameBounds.centerX(), frameBounds.centerY());
|
|
27223
|
+
this.zoomByPct(
|
|
27224
|
+
targetScale / currentScale,
|
|
27225
|
+
p[0] / this.width(),
|
|
27226
|
+
p[1] / this.height()
|
|
27227
|
+
);
|
|
27228
|
+
};
|
|
27229
|
+
|
|
26708
27230
|
// convert pixel coords (0,0 is top left corner of map) to display CRS coords
|
|
26709
27231
|
this.pixCoordsToMapCoords = function(x, y) {
|
|
26710
27232
|
return this.getTransform().invert().transform(x, y);
|
|
@@ -28576,7 +29098,7 @@
|
|
|
28576
29098
|
function showInstructions() {
|
|
28577
29099
|
var isMac = navigator.userAgent.includes('Mac');
|
|
28578
29100
|
var symbol = isMac ? '⌘' : '^';
|
|
28579
|
-
var msg = `
|
|
29101
|
+
var msg = `Click to start a rectangle. Drag handles to resize. Press shift key to resize symmetrically.`;
|
|
28580
29102
|
alert = showPopupAlert(msg, null, { non_blocking: true, max_width: '360px'});
|
|
28581
29103
|
}
|
|
28582
29104
|
|
|
@@ -28590,11 +29112,12 @@
|
|
|
28590
29112
|
return !_on && gui.getMode() != 'selection_tool';
|
|
28591
29113
|
}
|
|
28592
29114
|
|
|
28593
|
-
function runCommand(cmd) {
|
|
29115
|
+
function runCommand(cmd, onSuccess) {
|
|
28594
29116
|
if (gui.console) {
|
|
28595
29117
|
gui.console.runMapshaperCommands(cmd, function(err) {
|
|
28596
29118
|
reset();
|
|
28597
29119
|
gui.clearMode();
|
|
29120
|
+
if (!err && onSuccess) onSuccess();
|
|
28598
29121
|
});
|
|
28599
29122
|
}
|
|
28600
29123
|
// reset(); // TODO: exit interactive mode
|
|
@@ -28662,9 +29185,7 @@
|
|
|
28662
29185
|
var popup = showPopupAlert('', 'Add a map frame');
|
|
28663
29186
|
var el = popup.container();
|
|
28664
29187
|
el.addClass('option-menu');
|
|
28665
|
-
var html = `<p>Enter
|
|
28666
|
-
for setting the size of the map for symbol scaling in the
|
|
28667
|
-
GUI and setting the size and crop of SVG output.</p><div><input type="text" class="frame-width text-input" placeholder="examples: 600px 5in"></div>
|
|
29188
|
+
var html = `<p>Enter an output width in px, cm or inches.</p><div><input type="text" class="frame-width text-input" placeholder="examples: 600px 5in"></div>
|
|
28668
29189
|
<div class="btn dialog-btn">Create</div></span>`;
|
|
28669
29190
|
el.html(html);
|
|
28670
29191
|
var input = el.findChild('.frame-width');
|
|
@@ -28676,8 +29197,10 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
28676
29197
|
input.node().value = '';
|
|
28677
29198
|
return;
|
|
28678
29199
|
}
|
|
28679
|
-
var cmd = `-
|
|
28680
|
-
runCommand(cmd)
|
|
29200
|
+
var cmd = `-frame name=frame bbox='${bbox.join(',')}' width='${widthStr}'`;
|
|
29201
|
+
runCommand(cmd, function() {
|
|
29202
|
+
if (gui.frameTool) gui.frameTool.open();
|
|
29203
|
+
});
|
|
28681
29204
|
popup.close();
|
|
28682
29205
|
});
|
|
28683
29206
|
}
|
|
@@ -28861,7 +29384,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
28861
29384
|
return Math.max(min, Math.min(max, val));
|
|
28862
29385
|
}
|
|
28863
29386
|
|
|
28864
|
-
var SVG_NS = 'http://www.w3.org/2000/svg';
|
|
29387
|
+
var SVG_NS$1 = 'http://www.w3.org/2000/svg';
|
|
28865
29388
|
var OUT_OF_RANGE_MESSAGE = 'out of range';
|
|
28866
29389
|
var GEODESIC_PIXEL_TOLERANCE = 1.5;
|
|
28867
29390
|
var GEODESIC_MAX_DEPTH = 14;
|
|
@@ -28874,11 +29397,11 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
28874
29397
|
function RulerTool(gui, ext) {
|
|
28875
29398
|
var mapLayers = gui.container.findChild('.map-layers');
|
|
28876
29399
|
var parent = gui.container.findChild('.mshp-main-map');
|
|
28877
|
-
var svg = createSvgNode('svg');
|
|
28878
|
-
var directPath = createSvgNode('path');
|
|
28879
|
-
var geodesicPath = createSvgNode('path');
|
|
28880
|
-
var startMarker = createSvgNode('circle');
|
|
28881
|
-
var endMarker = createSvgNode('circle');
|
|
29400
|
+
var svg = createSvgNode$1('svg');
|
|
29401
|
+
var directPath = createSvgNode$1('path');
|
|
29402
|
+
var geodesicPath = createSvgNode$1('path');
|
|
29403
|
+
var startMarker = createSvgNode$1('circle');
|
|
29404
|
+
var endMarker = createSvgNode$1('circle');
|
|
28882
29405
|
var vertexMarkers = [];
|
|
28883
29406
|
var popup = El('div').addClass('ruler-popup rollover').appendTo(parent).hide();
|
|
28884
29407
|
var content = El('div').addClass('ruler-popup-content').appendTo(popup);
|
|
@@ -29560,7 +30083,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
29560
30083
|
function getVertexMarker(i) {
|
|
29561
30084
|
var marker;
|
|
29562
30085
|
while (vertexMarkers.length <= i) {
|
|
29563
|
-
marker = createSvgNode('circle');
|
|
30086
|
+
marker = createSvgNode$1('circle');
|
|
29564
30087
|
marker.classList.add('ruler-endpoint');
|
|
29565
30088
|
svg.insertBefore(marker, endMarker);
|
|
29566
30089
|
vertexMarkers.push(marker);
|
|
@@ -29592,8 +30115,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
29592
30115
|
}
|
|
29593
30116
|
}
|
|
29594
30117
|
|
|
29595
|
-
function createSvgNode(name) {
|
|
29596
|
-
return document.createElementNS(SVG_NS, name);
|
|
30118
|
+
function createSvgNode$1(name) {
|
|
30119
|
+
return document.createElementNS(SVG_NS$1, name);
|
|
29597
30120
|
}
|
|
29598
30121
|
|
|
29599
30122
|
function round(n) {
|
|
@@ -29766,6 +30289,24 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
29766
30289
|
this.getSvgRoot = function() { return _renderer ? _renderer.getSvgRoot() : null; };
|
|
29767
30290
|
this.getActiveLayer = function() { return _activeLyr; };
|
|
29768
30291
|
this.getHitControl = function() { return _hit; };
|
|
30292
|
+
this.getCompositionLayers = function() { return getContentLayers().slice(); };
|
|
30293
|
+
this.getPreviewFrameData = getFrameLayerData;
|
|
30294
|
+
this.isPreviewView = isPreviewView;
|
|
30295
|
+
this.setPreviewMode = function(on, fitPage) {
|
|
30296
|
+
var hasFrame = !!internal.getActiveFrame(model);
|
|
30297
|
+
var next = !!on && hasFrame;
|
|
30298
|
+
var changed = gui.state.preview_mode != next;
|
|
30299
|
+
gui.state.preview_mode = next;
|
|
30300
|
+
_ext.setFrameData(isPreviewView() ? getFrameLayerData() : null);
|
|
30301
|
+
updateFullBounds();
|
|
30302
|
+
if (fitPage !== false) {
|
|
30303
|
+
_ext.home();
|
|
30304
|
+
}
|
|
30305
|
+
drawLayers();
|
|
30306
|
+
if (changed) {
|
|
30307
|
+
gui.dispatchEvent('preview_mode_change', {enabled: next});
|
|
30308
|
+
}
|
|
30309
|
+
};
|
|
29769
30310
|
// this.getViewData = function() {
|
|
29770
30311
|
// return {
|
|
29771
30312
|
// isPreview: isPreviewView(),
|
|
@@ -29784,7 +30325,6 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
29784
30325
|
// Set or clear a CRS to use for display, without reprojecting the underlying dataset(s).
|
|
29785
30326
|
// crs: a CRS object or string, or null to clear the current setting
|
|
29786
30327
|
this.setDisplayCRS = function(crs) {
|
|
29787
|
-
// TODO: update bounds of frame layer, if there is a frame layer
|
|
29788
30328
|
var oldCRS = this.getDisplayCRS();
|
|
29789
30329
|
var newCRS = utils$1.isString(crs) ? internal.parseCrsString(crs) : crs;
|
|
29790
30330
|
// TODO: handle case that old and new CRS are the same
|
|
@@ -29798,6 +30338,13 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
29798
30338
|
getContentLayers().concat(_intersectionLyr || []).concat(_compareLyr || []).forEach(function(lyr) {
|
|
29799
30339
|
projectLayerForDisplay(lyr, newCRS);
|
|
29800
30340
|
});
|
|
30341
|
+
var frameTarget = internal.getActiveFrame(model);
|
|
30342
|
+
if (frameTarget) {
|
|
30343
|
+
if (!frameTarget.layer.gui) {
|
|
30344
|
+
enhanceLayerForDisplay(frameTarget.layer, frameTarget.dataset, getDisplayOptions());
|
|
30345
|
+
}
|
|
30346
|
+
projectLayerForDisplay(frameTarget.layer, newCRS);
|
|
30347
|
+
}
|
|
29801
30348
|
|
|
29802
30349
|
// Update map extent (also triggers redraw)
|
|
29803
30350
|
projectMapExtent(_ext, oldCRS, this.getDisplayCRS(), calcFullBounds());
|
|
@@ -29890,7 +30437,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
29890
30437
|
return;
|
|
29891
30438
|
}
|
|
29892
30439
|
|
|
29893
|
-
|
|
30440
|
+
var arcsChanged = arcsMayHaveChanged(e.flags);
|
|
30441
|
+
if (arcsChanged) {
|
|
29894
30442
|
// regenerate filtered arcs the next time they are needed for rendering
|
|
29895
30443
|
// delete e.dataset.gui.displayArcs
|
|
29896
30444
|
clearAllDisplayArcs();
|
|
@@ -29901,6 +30449,19 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
29901
30449
|
updated.dataset.arcs.setRetainedPct(1);
|
|
29902
30450
|
}
|
|
29903
30451
|
}
|
|
30452
|
+
// The frame's display copy is built from its coordinates, so it has to go
|
|
30453
|
+
// whenever those can have moved. -update-frame is the obvious case; -proj
|
|
30454
|
+
// is the one that was missed, because it rewrites the frame through the
|
|
30455
|
+
// same sweep that projects the data, and the display copy left behind
|
|
30456
|
+
// still held the pre-projection rectangle. findFrameLayer() rebuilds it on
|
|
30457
|
+
// demand and it is one rectangle, so discarding it freely costs nothing.
|
|
30458
|
+
if (e.flags['update-frame'] || arcsChanged) {
|
|
30459
|
+
var frameTarget = internal.getActiveFrame(model);
|
|
30460
|
+
if (frameTarget) {
|
|
30461
|
+
delete frameTarget.layer.gui;
|
|
30462
|
+
delete frameTarget.dataset.gui;
|
|
30463
|
+
}
|
|
30464
|
+
}
|
|
29904
30465
|
|
|
29905
30466
|
if (e.flags.simplify_method) { // no redraw needed
|
|
29906
30467
|
return false;
|
|
@@ -29926,7 +30487,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
29926
30487
|
} else if (_hit) {
|
|
29927
30488
|
_hit.clearSelection();
|
|
29928
30489
|
}
|
|
29929
|
-
_hit.setLayer(_activeLyr)
|
|
30490
|
+
_hit.setLayer(isFrameMapLayer(_activeLyr) ? null : _activeLyr);
|
|
30491
|
+
// need this every time, to support dynamic reprojection
|
|
29930
30492
|
|
|
29931
30493
|
updateVisibleMapLayers();
|
|
29932
30494
|
fullBounds = calcFullBounds();
|
|
@@ -30046,19 +30608,28 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
30046
30608
|
}
|
|
30047
30609
|
|
|
30048
30610
|
function findFrameLayer() {
|
|
30049
|
-
|
|
30050
|
-
|
|
30051
|
-
|
|
30611
|
+
var target = internal.getActiveFrame(model);
|
|
30612
|
+
if (!target) return null;
|
|
30613
|
+
if (!target.layer.gui) {
|
|
30614
|
+
enhanceLayerForDisplay(target.layer, target.dataset, getDisplayOptions());
|
|
30615
|
+
}
|
|
30616
|
+
return target.layer;
|
|
30052
30617
|
}
|
|
30053
30618
|
|
|
30054
30619
|
// Preview view: symbols are scaled based on display size of frame layer
|
|
30055
30620
|
function isPreviewView() {
|
|
30056
|
-
return !isTableView() && !!getFrameLayerData();
|
|
30621
|
+
return !isTableView() && !!gui.state.preview_mode && !!getFrameLayerData();
|
|
30057
30622
|
}
|
|
30058
30623
|
|
|
30059
30624
|
function getFrameLayerData() {
|
|
30060
30625
|
var lyr = findFrameLayer();
|
|
30061
|
-
|
|
30626
|
+
var crs = lyr && (lyr.gui.dynamic_crs ||
|
|
30627
|
+
internal.getDatasetCRS(lyr.gui.source.dataset));
|
|
30628
|
+
return lyr && internal.getFrameLayerData(
|
|
30629
|
+
lyr.gui.displayLayer,
|
|
30630
|
+
lyr.gui.displayArcs,
|
|
30631
|
+
crs
|
|
30632
|
+
) || null;
|
|
30062
30633
|
}
|
|
30063
30634
|
|
|
30064
30635
|
function clearAllDisplayArcs() {
|
|
@@ -30100,10 +30671,15 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
30100
30671
|
return findActiveLayer(layers);
|
|
30101
30672
|
}
|
|
30102
30673
|
return layers.filter(function(o) {
|
|
30103
|
-
return !!o.gui.geographic;
|
|
30674
|
+
return !!o.gui.geographic && !isFrameMapLayer(o);
|
|
30104
30675
|
});
|
|
30105
30676
|
}
|
|
30106
30677
|
|
|
30678
|
+
function isFrameMapLayer(lyr) {
|
|
30679
|
+
var dataset = lyr && lyr.gui && lyr.gui.source && lyr.gui.source.dataset;
|
|
30680
|
+
return !!dataset && internal.isFrameLayer(lyr, dataset.arcs);
|
|
30681
|
+
}
|
|
30682
|
+
|
|
30107
30683
|
function getDrawableContentLayers() {
|
|
30108
30684
|
return getContentLayers().filter(function(lyr) {
|
|
30109
30685
|
if (isActiveLayer(lyr) && lyr.hidden) return false;
|
|
@@ -30173,9 +30749,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
30173
30749
|
console.error("Collapsed map container, unable to draw.");
|
|
30174
30750
|
return;
|
|
30175
30751
|
}
|
|
30752
|
+
_ext.setFrameData(isPreviewView() ? getFrameLayerData() : null);
|
|
30176
30753
|
if (layersMayHaveChanged) {
|
|
30177
30754
|
// kludge to handle layer visibility toggling
|
|
30178
|
-
_ext.setFrameData(isPreviewView() ? getFrameLayerData() : null);
|
|
30179
30755
|
updateFullBounds();
|
|
30180
30756
|
updateLayerStyles(contentLayers);
|
|
30181
30757
|
updateLayerStackOrder(model.getLayers());// update menu_order property of all layers
|
|
@@ -30217,32 +30793,1107 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
30217
30793
|
}
|
|
30218
30794
|
}
|
|
30219
30795
|
|
|
30220
|
-
|
|
30221
|
-
// interactive editing modes and other interface modes
|
|
30222
|
-
// (by default, interactive modes stay on when, e.g., the user clicks
|
|
30223
|
-
// "Export" or "Console").
|
|
30224
|
-
//
|
|
30225
|
-
function initModeRules(gui) {
|
|
30796
|
+
var SVG_NS = 'http://www.w3.org/2000/svg';
|
|
30226
30797
|
|
|
30227
|
-
|
|
30228
|
-
|
|
30229
|
-
|
|
30798
|
+
function PreviewMode(gui) {
|
|
30799
|
+
var self = this;
|
|
30800
|
+
var ext = gui.map.getExtent();
|
|
30801
|
+
var mapLayers = gui.container.findChild('.map-layers').node();
|
|
30802
|
+
var map = gui.container.findChild('.mshp-main-map');
|
|
30803
|
+
var toggle = gui.buttons.addButton('#frame-tool-icon')
|
|
30804
|
+
.addClass('menu-btn preview-toggle')
|
|
30805
|
+
.attr('title', 'Toggle map preview');
|
|
30806
|
+
var readout = El('div')
|
|
30807
|
+
.addClass('preview-readout')
|
|
30808
|
+
.appendTo(map)
|
|
30809
|
+
.hide();
|
|
30810
|
+
var readoutLabel = El('span').addClass('preview-readout-label').appendTo(readout);
|
|
30811
|
+
El('span').addClass('preview-readout-arrow').text('⌄').appendTo(readout);
|
|
30812
|
+
var menu = El('div').addClass('preview-scale-menu').appendTo(readout);
|
|
30813
|
+
var temporaryReadout = null;
|
|
30814
|
+
var backgroundSvg = createSvgNode('svg');
|
|
30815
|
+
var background = createSvgNode('rect');
|
|
30816
|
+
var svg = createSvgNode('svg');
|
|
30817
|
+
var mask = createSvgNode('path');
|
|
30818
|
+
var neatline = createSvgNode('rect');
|
|
30819
|
+
var border = createSvgNode('rect');
|
|
30820
|
+
|
|
30821
|
+
gui.state.preview_mode = false;
|
|
30822
|
+
gui.previewMode = this;
|
|
30823
|
+
|
|
30824
|
+
backgroundSvg.classList.add('preview-background-overlay');
|
|
30825
|
+
background.classList.add('preview-page-background');
|
|
30826
|
+
backgroundSvg.appendChild(background);
|
|
30827
|
+
mapLayers.insertBefore(backgroundSvg, mapLayers.firstChild);
|
|
30828
|
+
svg.classList.add('preview-overlay');
|
|
30829
|
+
mask.classList.add('preview-outside-mask');
|
|
30830
|
+
mask.setAttribute('fill-rule', 'evenodd');
|
|
30831
|
+
neatline.classList.add('preview-page-neatline');
|
|
30832
|
+
border.classList.add('preview-page-border');
|
|
30833
|
+
svg.appendChild(mask);
|
|
30834
|
+
svg.appendChild(neatline);
|
|
30835
|
+
svg.appendChild(border);
|
|
30836
|
+
mapLayers.appendChild(svg);
|
|
30837
|
+
hideOverlay();
|
|
30838
|
+
|
|
30839
|
+
[
|
|
30840
|
+
{label: '50%', scale: 0.5},
|
|
30841
|
+
{label: '67%', scale: 0.67},
|
|
30842
|
+
{label: '100%', scale: 1},
|
|
30843
|
+
{label: '150%', scale: 1.5},
|
|
30844
|
+
{label: '200%', scale: 2},
|
|
30845
|
+
{label: 'Fit page', fit: true}
|
|
30846
|
+
].forEach(function(item) {
|
|
30847
|
+
El('div')
|
|
30848
|
+
.addClass('preview-scale-menu-item')
|
|
30849
|
+
.attr('data-preview-scale', item.fit ? 'fit' : String(item.scale))
|
|
30850
|
+
.text(item.label)
|
|
30851
|
+
.appendTo(menu)
|
|
30852
|
+
.on('click', function(e) {
|
|
30853
|
+
e.stopPropagation();
|
|
30854
|
+
closeMenu();
|
|
30855
|
+
if (item.fit) {
|
|
30856
|
+
ext.home();
|
|
30857
|
+
} else {
|
|
30858
|
+
ext.zoomToFrameMagnification(item.scale);
|
|
30859
|
+
}
|
|
30860
|
+
gui.dispatchEvent('map_interaction_end');
|
|
30861
|
+
});
|
|
30862
|
+
});
|
|
30230
30863
|
|
|
30231
|
-
|
|
30232
|
-
|
|
30233
|
-
|
|
30864
|
+
toggle.on('click', function(e) {
|
|
30865
|
+
e.stopPropagation();
|
|
30866
|
+
if (!hasFrame()) {
|
|
30867
|
+
if (gui.frameTool) gui.frameTool.openCreateDialog();
|
|
30868
|
+
return;
|
|
30869
|
+
}
|
|
30870
|
+
self.setOn(!self.isOn());
|
|
30871
|
+
});
|
|
30234
30872
|
|
|
30235
|
-
|
|
30236
|
-
|
|
30237
|
-
|
|
30873
|
+
readout.on('click', function(e) {
|
|
30874
|
+
e.stopPropagation();
|
|
30875
|
+
readout.classed('open', !readout.hasClass('open'));
|
|
30876
|
+
});
|
|
30238
30877
|
|
|
30878
|
+
gui.on('map_click', closeMenu);
|
|
30879
|
+
gui.on('map_rendered', function() {
|
|
30880
|
+
refreshControls();
|
|
30881
|
+
renderOverlay();
|
|
30882
|
+
});
|
|
30883
|
+
gui.on('preview_mode_change', refreshControls);
|
|
30884
|
+
gui.model.on('update', function() {
|
|
30885
|
+
if (!hasFrame() && self.isOn()) {
|
|
30886
|
+
gui.map.setPreviewMode(false, false);
|
|
30239
30887
|
}
|
|
30240
|
-
|
|
30888
|
+
refreshControls();
|
|
30241
30889
|
});
|
|
30890
|
+
ext.on('change', updateReadout);
|
|
30242
30891
|
|
|
30243
|
-
|
|
30244
|
-
|
|
30245
|
-
|
|
30892
|
+
this.isOn = function() {
|
|
30893
|
+
return !!gui.state.preview_mode;
|
|
30894
|
+
};
|
|
30895
|
+
|
|
30896
|
+
this.setOn = function(on) {
|
|
30897
|
+
gui.map.setPreviewMode(!!on, true);
|
|
30898
|
+
refreshControls();
|
|
30899
|
+
};
|
|
30900
|
+
|
|
30901
|
+
this.getReadoutText = function() {
|
|
30902
|
+
return readoutLabel.text();
|
|
30903
|
+
};
|
|
30904
|
+
|
|
30905
|
+
this.setTemporaryReadout = function(frame, scale) {
|
|
30906
|
+
temporaryReadout = {frame: frame, scale: scale};
|
|
30907
|
+
updateReadout();
|
|
30908
|
+
};
|
|
30909
|
+
|
|
30910
|
+
this.clearTemporaryReadout = function() {
|
|
30911
|
+
temporaryReadout = null;
|
|
30912
|
+
updateReadout();
|
|
30913
|
+
};
|
|
30914
|
+
|
|
30915
|
+
refreshControls();
|
|
30916
|
+
|
|
30917
|
+
function hasFrame() {
|
|
30918
|
+
return !!internal.getActiveFrame(gui.model);
|
|
30919
|
+
}
|
|
30920
|
+
|
|
30921
|
+
function refreshControls() {
|
|
30922
|
+
var available = hasFrame();
|
|
30923
|
+
toggle.removeClass('disabled');
|
|
30924
|
+
toggle.classed('selected', available && self.isOn());
|
|
30925
|
+
toggle.attr('title', available ? 'Toggle map preview' : 'Add map frame');
|
|
30926
|
+
toggle.attr('aria-disabled', 'false');
|
|
30927
|
+
toggle.attr('aria-pressed', available && self.isOn() ? 'true' : 'false');
|
|
30928
|
+
if (gui.map.isPreviewView()) {
|
|
30929
|
+
readout.show();
|
|
30930
|
+
updateReadout();
|
|
30931
|
+
} else {
|
|
30932
|
+
readout.hide();
|
|
30933
|
+
closeMenu();
|
|
30934
|
+
}
|
|
30935
|
+
}
|
|
30936
|
+
|
|
30937
|
+
function updateReadout() {
|
|
30938
|
+
if (!gui.map.isPreviewView()) return;
|
|
30939
|
+
var frame = temporaryReadout ?
|
|
30940
|
+
temporaryReadout.frame : gui.map.getPreviewFrameData();
|
|
30941
|
+
if (!frame) return;
|
|
30942
|
+
var size = internal.formatFrameSizeForDisplay(frame);
|
|
30943
|
+
var scale = temporaryReadout ?
|
|
30944
|
+
temporaryReadout.scale : ext.getSymbolScale();
|
|
30945
|
+
var pct = Math.round(scale * 100);
|
|
30946
|
+
readoutLabel.text(size + ' · ' + pct + '%');
|
|
30947
|
+
}
|
|
30948
|
+
|
|
30949
|
+
function renderOverlay() {
|
|
30950
|
+
if (!gui.map.isPreviewView()) {
|
|
30951
|
+
hideOverlay();
|
|
30952
|
+
return;
|
|
30953
|
+
}
|
|
30954
|
+
var frame = gui.map.getPreviewFrameData();
|
|
30955
|
+
if (!frame) {
|
|
30956
|
+
hideOverlay();
|
|
30957
|
+
return;
|
|
30958
|
+
}
|
|
30959
|
+
var p1 = ext.translateCoords(frame.bbox[0], frame.bbox[3]);
|
|
30960
|
+
var p2 = ext.translateCoords(frame.bbox[2], frame.bbox[1]);
|
|
30961
|
+
var x = Math.min(p1[0], p2[0]);
|
|
30962
|
+
var y = Math.min(p1[1], p2[1]);
|
|
30963
|
+
var w = Math.abs(p2[0] - p1[0]);
|
|
30964
|
+
var h = Math.abs(p2[1] - p1[1]);
|
|
30965
|
+
var viewW = ext.width();
|
|
30966
|
+
var viewH = ext.height();
|
|
30967
|
+
var style = getFrameStyle();
|
|
30968
|
+
backgroundSvg.setAttribute('width', viewW);
|
|
30969
|
+
backgroundSvg.setAttribute('height', viewH);
|
|
30970
|
+
backgroundSvg.setAttribute('viewBox', '0 0 ' + viewW + ' ' + viewH);
|
|
30971
|
+
svg.setAttribute('width', viewW);
|
|
30972
|
+
svg.setAttribute('height', viewH);
|
|
30973
|
+
svg.setAttribute('viewBox', '0 0 ' + viewW + ' ' + viewH);
|
|
30974
|
+
mask.setAttribute(
|
|
30975
|
+
'd',
|
|
30976
|
+
'M0 0H' + viewW + 'V' + viewH + 'H0Z ' +
|
|
30977
|
+
'M' + x + ' ' + y + 'V' + (y + h) + 'H' + (x + w) +
|
|
30978
|
+
'V' + y + 'H' + x + 'Z'
|
|
30979
|
+
);
|
|
30980
|
+
border.setAttribute('x', x);
|
|
30981
|
+
border.setAttribute('y', y);
|
|
30982
|
+
border.setAttribute('width', w);
|
|
30983
|
+
border.setAttribute('height', h);
|
|
30984
|
+
setRectGeometry(background, x, y, w, h);
|
|
30985
|
+
setRectGeometry(neatline, x, y, w, h);
|
|
30986
|
+
applyFrameStyle(style);
|
|
30987
|
+
svg.style.display = '';
|
|
30988
|
+
}
|
|
30989
|
+
|
|
30990
|
+
function hideOverlay() {
|
|
30991
|
+
backgroundSvg.style.display = 'none';
|
|
30992
|
+
svg.style.display = 'none';
|
|
30993
|
+
}
|
|
30994
|
+
|
|
30995
|
+
function getFrameStyle() {
|
|
30996
|
+
var target = internal.getActiveFrame(gui.model);
|
|
30997
|
+
return target && target.layer.data ?
|
|
30998
|
+
target.layer.data.getReadOnlyRecordAt(0) || {} : {};
|
|
30999
|
+
}
|
|
31000
|
+
|
|
31001
|
+
function applyFrameStyle(style) {
|
|
31002
|
+
var strokeWidth = Number(style['stroke-width']);
|
|
31003
|
+
var hasFill = style.fill && style.fill != 'none';
|
|
31004
|
+
var hasStroke = style.stroke && style.stroke != 'none' && strokeWidth > 0;
|
|
31005
|
+
if (hasFill) {
|
|
31006
|
+
background.setAttribute('fill', style.fill);
|
|
31007
|
+
background.setAttribute('fill-opacity',
|
|
31008
|
+
style['fill-opacity'] === undefined ? 1 : style['fill-opacity']);
|
|
31009
|
+
backgroundSvg.style.display = '';
|
|
31010
|
+
} else {
|
|
31011
|
+
backgroundSvg.style.display = 'none';
|
|
31012
|
+
}
|
|
31013
|
+
if (hasStroke) {
|
|
31014
|
+
neatline.setAttribute('fill', 'none');
|
|
31015
|
+
neatline.setAttribute('stroke', style.stroke);
|
|
31016
|
+
// Stroke width is in output pixels, so it has to be scaled to the size
|
|
31017
|
+
// the page is drawn at or the neatline reads thinner than it will print.
|
|
31018
|
+
neatline.setAttribute('stroke-width', strokeWidth * ext.getSymbolScale());
|
|
31019
|
+
neatline.setAttribute('stroke-opacity',
|
|
31020
|
+
style['stroke-opacity'] === undefined ? 1 : style['stroke-opacity']);
|
|
31021
|
+
neatline.style.display = '';
|
|
31022
|
+
} else {
|
|
31023
|
+
neatline.style.display = 'none';
|
|
31024
|
+
}
|
|
31025
|
+
// The border is chrome, there to show where the page is when nothing else
|
|
31026
|
+
// marks it. A neatline is the page edge, and since the border is painted
|
|
31027
|
+
// last it was covering every neatline with the same dark grey line.
|
|
31028
|
+
border.style.display = hasStroke ? 'none' : '';
|
|
31029
|
+
}
|
|
31030
|
+
|
|
31031
|
+
function closeMenu() {
|
|
31032
|
+
readout.removeClass('open');
|
|
31033
|
+
}
|
|
31034
|
+
}
|
|
31035
|
+
|
|
31036
|
+
function createSvgNode(name) {
|
|
31037
|
+
return document.createElementNS(SVG_NS, name);
|
|
31038
|
+
}
|
|
31039
|
+
|
|
31040
|
+
function setRectGeometry(rect, x, y, width, height) {
|
|
31041
|
+
rect.setAttribute('x', x);
|
|
31042
|
+
rect.setAttribute('y', y);
|
|
31043
|
+
rect.setAttribute('width', width);
|
|
31044
|
+
rect.setAttribute('height', height);
|
|
31045
|
+
}
|
|
31046
|
+
|
|
31047
|
+
function FrameResizeTool(gui) {
|
|
31048
|
+
var ext = gui.map.getExtent();
|
|
31049
|
+
var lockSize = false;
|
|
31050
|
+
var on = false;
|
|
31051
|
+
var drawing = false;
|
|
31052
|
+
var drawingRequested = false;
|
|
31053
|
+
var drawWidth = '800px';
|
|
31054
|
+
var drawAspect = '';
|
|
31055
|
+
var drawSource = null;
|
|
31056
|
+
var drawInstructions = null;
|
|
31057
|
+
var frameBox = new HighlightBox(gui, {
|
|
31058
|
+
name: 'frame-editor',
|
|
31059
|
+
classname: 'frame-edit-box',
|
|
31060
|
+
persistent: true,
|
|
31061
|
+
handles: true,
|
|
31062
|
+
draggable: false
|
|
31063
|
+
});
|
|
31064
|
+
var drawBox = new HighlightBox(gui, {
|
|
31065
|
+
name: 'frame-draw',
|
|
31066
|
+
classname: 'frame-draw-box',
|
|
31067
|
+
clickToStart: true,
|
|
31068
|
+
persistent: true,
|
|
31069
|
+
handles: true,
|
|
31070
|
+
draggable: true
|
|
31071
|
+
});
|
|
31072
|
+
var toolbar = new FloatingToolbar(gui, {name: 'frame-toolbar'});
|
|
31073
|
+
// Named by what stays put, not by the action: changing the extent -- by
|
|
31074
|
+
// dragging a handle or by fitting -- is the same gesture either way, and all
|
|
31075
|
+
// that differs is which quantity absorbs it. Both labels stay visible so the
|
|
31076
|
+
// lit one reads as the current mode rather than as the result of a click.
|
|
31077
|
+
var sizeModeControl = toolbar.addSegmentedControl(null, [
|
|
31078
|
+
{value: 'scale', label: 'Fix scale',
|
|
31079
|
+
tooltip: 'Keep the map scale; the output gets bigger or smaller'},
|
|
31080
|
+
{value: 'output', label: 'Fix output',
|
|
31081
|
+
tooltip: 'Keep the output size; the map scale changes'}
|
|
31082
|
+
], {classname: 'frame-size-mode'}).on('change', function(value) {
|
|
31083
|
+
setLockSize(value == 'output');
|
|
31084
|
+
});
|
|
31085
|
+
var aspectField = toolbar.addTextField('Ratio', {
|
|
31086
|
+
classname: 'frame-toolbar-aspect-input',
|
|
31087
|
+
placeholder: 'free',
|
|
31088
|
+
tooltip: 'Hold the frame to a shape: 16:9, 1.5, or blank for none'
|
|
31089
|
+
}).on('change', commitAspectRatio);
|
|
31090
|
+
toolbar.addSeparator();
|
|
31091
|
+
toolbar.addTextButton('Fit', {
|
|
31092
|
+
tooltip: 'Fit the frame to the visible layers'
|
|
31093
|
+
}).on('click', fitVisibleLayers);
|
|
31094
|
+
// Padding is spent on the fit rather than stored on the frame, so it sits
|
|
31095
|
+
// beside the button it modifies, as it does in the creation dialog.
|
|
31096
|
+
var marginField = toolbar.addTextField('Margin', {
|
|
31097
|
+
classname: 'frame-toolbar-margin-input',
|
|
31098
|
+
placeholder: '0',
|
|
31099
|
+
tooltip: 'Padding added when fitting: 20px, 1cm, ' +
|
|
31100
|
+
"or a percentage of the frame's width and height"
|
|
31101
|
+
});
|
|
31102
|
+
toolbar.addSeparator();
|
|
31103
|
+
toolbar.addTextButton('Done', {
|
|
31104
|
+
tooltip: 'Finish resizing'
|
|
31105
|
+
}).on('click', function() {
|
|
31106
|
+
gui.interaction.turnOff();
|
|
31107
|
+
});
|
|
31108
|
+
var drawToolbar = new FloatingToolbar(gui, {name: 'frame-draw-toolbar'});
|
|
31109
|
+
var drawDoneButton = drawToolbar.addTextButton('Done')
|
|
31110
|
+
.setEnabled(false)
|
|
31111
|
+
.on('click', finishFrameDrawing);
|
|
31112
|
+
drawToolbar.addTextButton('Cancel').on('click', function() {
|
|
31113
|
+
gui.interaction.turnOff();
|
|
31114
|
+
});
|
|
31115
|
+
|
|
31116
|
+
gui.frameTool = this;
|
|
31117
|
+
setLockSize(lockSize); // light the default segment
|
|
31118
|
+
|
|
31119
|
+
this.open = function() {
|
|
31120
|
+
if (getFrameTarget()) {
|
|
31121
|
+
gui.interaction.setMode('frame');
|
|
31122
|
+
} else {
|
|
31123
|
+
openCreateDialog();
|
|
31124
|
+
}
|
|
31125
|
+
};
|
|
31126
|
+
|
|
31127
|
+
this.openCreateDialog = openCreateDialog;
|
|
31128
|
+
this.isLockSize = function() { return lockSize; };
|
|
31129
|
+
this.setLockSize = setLockSize;
|
|
31130
|
+
|
|
31131
|
+
frameBox.on('handle_drag', previewFrameDrag);
|
|
31132
|
+
frameBox.on('handle_up', commitFrameDrag);
|
|
31133
|
+
drawBox.on('dragend', function() {
|
|
31134
|
+
drawDoneButton.enable();
|
|
31135
|
+
hideFrameDrawingInstructions('fade');
|
|
31136
|
+
});
|
|
31137
|
+
|
|
31138
|
+
gui.on('interaction_mode_change', function(e) {
|
|
31139
|
+
if (e.mode == 'frame' || e.mode == 'frame_draw') turnOn();
|
|
31140
|
+
else turnOff();
|
|
31141
|
+
});
|
|
31142
|
+
|
|
31143
|
+
// Resizing is a preview-mode gesture: the handles sit on the page boundary
|
|
31144
|
+
// that preview draws, so turning preview off leaves nothing to resize and
|
|
31145
|
+
// the tool has to close with it rather than stranding its toolbar and
|
|
31146
|
+
// handles over an ordinary map view.
|
|
31147
|
+
gui.on('preview_mode_change', function(e) {
|
|
31148
|
+
if (on && !e.enabled) gui.interaction.turnOff();
|
|
31149
|
+
});
|
|
31150
|
+
|
|
31151
|
+
gui.model.on('update', function() {
|
|
31152
|
+
if (on) refreshOrClose();
|
|
31153
|
+
});
|
|
31154
|
+
|
|
31155
|
+
gui.on('undo_redo_post', function() {
|
|
31156
|
+
if (on) refreshOrClose();
|
|
31157
|
+
});
|
|
31158
|
+
|
|
31159
|
+
// Deleting the frame takes the tool's subject away. Preview mode drops
|
|
31160
|
+
// itself on the same event, but the order of model listeners is not a
|
|
31161
|
+
// contract worth relying on, so the tool checks for itself.
|
|
31162
|
+
function refreshOrClose() {
|
|
31163
|
+
if (!getFrameTarget()) {
|
|
31164
|
+
gui.interaction.turnOff();
|
|
31165
|
+
} else {
|
|
31166
|
+
syncFrameOverlay();
|
|
31167
|
+
}
|
|
31168
|
+
}
|
|
31169
|
+
|
|
31170
|
+
gui.keyboard.on('keydown', function(e) {
|
|
31171
|
+
if ((on || drawing) && e.keyName == 'esc') {
|
|
31172
|
+
gui.interaction.turnOff();
|
|
31173
|
+
e.stopPropagation();
|
|
31174
|
+
}
|
|
31175
|
+
}, 10);
|
|
31176
|
+
|
|
31177
|
+
function turnOn() {
|
|
31178
|
+
if (drawingRequested) {
|
|
31179
|
+
drawingRequested = false;
|
|
31180
|
+
drawing = true;
|
|
31181
|
+
drawBox.turnOn();
|
|
31182
|
+
drawDoneButton.disable();
|
|
31183
|
+
drawToolbar.show();
|
|
31184
|
+
showFrameDrawingInstructions();
|
|
31185
|
+
return;
|
|
31186
|
+
}
|
|
31187
|
+
if (!getFrameTarget()) {
|
|
31188
|
+
gui.interaction.turnOff();
|
|
31189
|
+
openCreateDialog();
|
|
31190
|
+
return;
|
|
31191
|
+
}
|
|
31192
|
+
on = true;
|
|
31193
|
+
frameBox.turnOn();
|
|
31194
|
+
toolbar.show();
|
|
31195
|
+
if (gui.previewMode && !gui.previewMode.isOn()) {
|
|
31196
|
+
gui.previewMode.setOn(true);
|
|
31197
|
+
}
|
|
31198
|
+
syncFrameOverlay();
|
|
31199
|
+
}
|
|
31200
|
+
|
|
31201
|
+
function turnOff() {
|
|
31202
|
+
if (on) {
|
|
31203
|
+
on = false;
|
|
31204
|
+
frameBox.turnOff();
|
|
31205
|
+
toolbar.hide();
|
|
31206
|
+
if (gui.previewMode) gui.previewMode.clearTemporaryReadout();
|
|
31207
|
+
}
|
|
31208
|
+
if (drawing) {
|
|
31209
|
+
drawing = false;
|
|
31210
|
+
drawBox.turnOff();
|
|
31211
|
+
drawBox.setAspectRatio(null);
|
|
31212
|
+
drawToolbar.hide();
|
|
31213
|
+
hideFrameDrawingInstructions();
|
|
31214
|
+
drawSource = null;
|
|
31215
|
+
}
|
|
31216
|
+
}
|
|
31217
|
+
|
|
31218
|
+
function setLockSize(value) {
|
|
31219
|
+
lockSize = !!value;
|
|
31220
|
+
sizeModeControl.setValue(lockSize ? 'output' : 'scale');
|
|
31221
|
+
}
|
|
31222
|
+
|
|
31223
|
+
function syncFrameOverlay() {
|
|
31224
|
+
var frame = gui.map.isPreviewView() && gui.map.getPreviewFrameData();
|
|
31225
|
+
if (frame) {
|
|
31226
|
+
frameBox.setDataCoords(frame.bbox.slice());
|
|
31227
|
+
// Only a frame with a fixed ratio constrains; otherwise the shape is
|
|
31228
|
+
// whatever the extent gives and the handles are free.
|
|
31229
|
+
frameBox.setAspectRatio(frame.aspect_ratio || null);
|
|
31230
|
+
} else {
|
|
31231
|
+
frameBox.hide();
|
|
31232
|
+
}
|
|
31233
|
+
syncAspectField();
|
|
31234
|
+
}
|
|
31235
|
+
|
|
31236
|
+
function previewFrameDrag(e) {
|
|
31237
|
+
var frame = gui.map.getPreviewFrameData();
|
|
31238
|
+
if (!frame || !e.map_bbox) return;
|
|
31239
|
+
var width = getDragWidth(frame, e.map_bbox, e.handle);
|
|
31240
|
+
var aspect = frame.aspect_ratio ||
|
|
31241
|
+
getBboxWidth(e.map_bbox) / getBboxHeight(e.map_bbox);
|
|
31242
|
+
var temporaryFrame = Object.assign({}, frame, {
|
|
31243
|
+
bbox: e.map_bbox.slice(),
|
|
31244
|
+
width: width,
|
|
31245
|
+
height: Math.round(width / aspect)
|
|
31246
|
+
});
|
|
31247
|
+
gui.previewMode.setTemporaryReadout(
|
|
31248
|
+
temporaryFrame,
|
|
31249
|
+
getPixelWidth(e.map_bbox) / width
|
|
31250
|
+
);
|
|
31251
|
+
}
|
|
31252
|
+
|
|
31253
|
+
function commitFrameDrag(e) {
|
|
31254
|
+
var target = getFrameTarget();
|
|
31255
|
+
var displayFrame = gui.map.getPreviewFrameData();
|
|
31256
|
+
var width = null;
|
|
31257
|
+
var targetScale = null;
|
|
31258
|
+
if (!target || !displayFrame || !e.map_bbox) return;
|
|
31259
|
+
if (e.handle?.type != 'center' && !lockSize) {
|
|
31260
|
+
var frame = internal.getFrameLayerData(target.layer, target.dataset.arcs);
|
|
31261
|
+
var widthPx = getDragWidth(displayFrame, e.map_bbox, e.handle);
|
|
31262
|
+
width = formatNumber$1(widthPx / getUnitFactor$1(frame.units)) + frame.units;
|
|
31263
|
+
targetScale = getPixelWidth(e.map_bbox) / widthPx;
|
|
31264
|
+
}
|
|
31265
|
+
gui.previewMode.clearTemporaryReadout();
|
|
31266
|
+
updateFrameBounds(
|
|
31267
|
+
target,
|
|
31268
|
+
getDisplayBoundsInLayerCRS(target.layer, e.map_bbox),
|
|
31269
|
+
width,
|
|
31270
|
+
targetScale
|
|
31271
|
+
);
|
|
31272
|
+
}
|
|
31273
|
+
|
|
31274
|
+
function getDragWidth(frame, bbox, handle) {
|
|
31275
|
+
if (handle?.type == 'center' || lockSize) return frame.width;
|
|
31276
|
+
return frame.width * getBboxWidth(bbox) / getBboxWidth(frame.bbox);
|
|
31277
|
+
}
|
|
31278
|
+
|
|
31279
|
+
function updateFrameBounds(target, bbox, width, targetScale) {
|
|
31280
|
+
var parts = [
|
|
31281
|
+
'-update-frame',
|
|
31282
|
+
'bbox=' + quoteCommandValue(bbox.join(','))
|
|
31283
|
+
];
|
|
31284
|
+
if (width) parts.push('width=' + quoteCommandValue(width));
|
|
31285
|
+
parts.push(getTargetOption(target));
|
|
31286
|
+
runGuiEditCommand(gui, parts.join(' '), {
|
|
31287
|
+
title: 'Resize map frame',
|
|
31288
|
+
onSuccess: function() {
|
|
31289
|
+
if (targetScale) {
|
|
31290
|
+
ext.zoomToFrameMagnification(targetScale);
|
|
31291
|
+
gui.dispatchEvent('map_interaction_end');
|
|
31292
|
+
}
|
|
31293
|
+
},
|
|
31294
|
+
onError: syncFrameOverlay
|
|
31295
|
+
});
|
|
31296
|
+
}
|
|
31297
|
+
|
|
31298
|
+
// Blank clears the fixed ratio and returns the frame to its extent's shape.
|
|
31299
|
+
// Anything that is not a ratio is refused and the field snaps back, rather
|
|
31300
|
+
// than quietly reshaping the frame to NaN.
|
|
31301
|
+
function commitAspectRatio(text) {
|
|
31302
|
+
var target = getFrameTarget();
|
|
31303
|
+
var value;
|
|
31304
|
+
if (!target) return;
|
|
31305
|
+
if (!text) {
|
|
31306
|
+
runAspectUpdate(target, 'auto-aspect');
|
|
31307
|
+
return;
|
|
31308
|
+
}
|
|
31309
|
+
value = parseFrameAspectRatio(text);
|
|
31310
|
+
if (!(value > 0)) {
|
|
31311
|
+
syncAspectField();
|
|
31312
|
+
return;
|
|
31313
|
+
}
|
|
31314
|
+
runAspectUpdate(target, 'aspect-ratio=' + value);
|
|
31315
|
+
}
|
|
31316
|
+
|
|
31317
|
+
function runAspectUpdate(target, option) {
|
|
31318
|
+
runGuiEditCommand(gui, ['-update-frame', option, getTargetOption(target)]
|
|
31319
|
+
.join(' '), {
|
|
31320
|
+
title: 'Set frame aspect ratio',
|
|
31321
|
+
onError: syncAspectField
|
|
31322
|
+
});
|
|
31323
|
+
}
|
|
31324
|
+
|
|
31325
|
+
// Shows the ratio the frame actually has, so a ratio typed as 3:2 reads back
|
|
31326
|
+
// as 3:2 and one the frame never took does not linger in the field.
|
|
31327
|
+
function syncAspectField() {
|
|
31328
|
+
var frame = gui.map.getPreviewFrameData();
|
|
31329
|
+
var aspect = frame && frame.aspect_ratio;
|
|
31330
|
+
aspectField.setValue(aspect > 0 ? formatFrameAspectRatio(aspect) : '');
|
|
31331
|
+
}
|
|
31332
|
+
|
|
31333
|
+
// Fitting obeys the same mode as a handle drag: holding the scale grows the
|
|
31334
|
+
// output to cover the new extent, holding the output rescales onto it. The
|
|
31335
|
+
// command works out both the padding and the held-scale width, so the
|
|
31336
|
+
// margin's unit handling lives in one place.
|
|
31337
|
+
function fitVisibleLayers() {
|
|
31338
|
+
var target = getFrameTarget();
|
|
31339
|
+
var entries = getCompositionEntries();
|
|
31340
|
+
var margin = getMargin();
|
|
31341
|
+
var parts, bounds;
|
|
31342
|
+
if (!target || !entries.length) return;
|
|
31343
|
+
bounds = entries.reduce(function(memo, o) {
|
|
31344
|
+
return memo.mergeBounds(
|
|
31345
|
+
internal.getLayerBounds(o.layer, o.dataset.arcs)
|
|
31346
|
+
);
|
|
31347
|
+
}, new internal.Bounds());
|
|
31348
|
+
parts = [
|
|
31349
|
+
'-update-frame',
|
|
31350
|
+
'bbox=' + quoteCommandValue(bounds.toArray().join(','))
|
|
31351
|
+
];
|
|
31352
|
+
if (margin) parts.push('offset=' + quoteCommandValue(margin));
|
|
31353
|
+
if (!lockSize) parts.push('fix-scale');
|
|
31354
|
+
parts.push(getTargetOption(target));
|
|
31355
|
+
runGuiEditCommand(gui, parts.join(' '), {
|
|
31356
|
+
title: 'Fit map frame',
|
|
31357
|
+
onError: syncFrameOverlay
|
|
31358
|
+
});
|
|
31359
|
+
}
|
|
31360
|
+
|
|
31361
|
+
// Anything that is not a positive length is dropped, and the field is
|
|
31362
|
+
// cleared so the fit that just ran matches what the toolbar shows.
|
|
31363
|
+
function getMargin() {
|
|
31364
|
+
var value = marginField.getValue();
|
|
31365
|
+
if (parseFloat(value) > 0) return value;
|
|
31366
|
+
if (value) marginField.setValue('');
|
|
31367
|
+
return '';
|
|
31368
|
+
}
|
|
31369
|
+
|
|
31370
|
+
function openCreateDialog() {
|
|
31371
|
+
if (getFrameTarget()) {
|
|
31372
|
+
gui.interaction.setMode('frame');
|
|
31373
|
+
return;
|
|
31374
|
+
}
|
|
31375
|
+
if (!getSourceLayer()) {
|
|
31376
|
+
showPopupAlert(
|
|
31377
|
+
'Add one or more layers before creating a map frame.',
|
|
31378
|
+
'Map frame'
|
|
31379
|
+
);
|
|
31380
|
+
return;
|
|
31381
|
+
}
|
|
31382
|
+
var popup = showPopupAlert('', 'Add map frame', {classname: 'frame-create-box'});
|
|
31383
|
+
// label-style-panel is where the flat field look lives -- no bezel, and a
|
|
31384
|
+
// focus ring that is a border rather than a glow. frame-create-form turns
|
|
31385
|
+
// off the chrome the class also carries, as the properties panel does.
|
|
31386
|
+
var content = El('div').addClass('label-style-panel frame-create-form')
|
|
31387
|
+
.appendTo(popup.container().addClass('frame-create-popup'));
|
|
31388
|
+
El('p').appendTo(content)
|
|
31389
|
+
.text('Sets the size and crop of exported maps.');
|
|
31390
|
+
|
|
31391
|
+
var widthInput = makeCreateField(content, 'Output width',
|
|
31392
|
+
'Width of the exported map.\nExamples: 800px, 5in, 10cm.')
|
|
31393
|
+
.addClass('frame-create-width-input');
|
|
31394
|
+
widthInput.node().value = '800px';
|
|
31395
|
+
|
|
31396
|
+
var aspectInput = makeCreateField(content, 'Aspect ratio',
|
|
31397
|
+
'Width divided by height, as a number or\na ratio: 1.5 or 3:2. ' +
|
|
31398
|
+
'Leave blank to vary freely.')
|
|
31399
|
+
.addClass('frame-create-aspect-input');
|
|
31400
|
+
|
|
31401
|
+
// Two ways to say where the frame goes. The heading is what makes them read
|
|
31402
|
+
// as a choice rather than as two loose actions; the margin sits on the row
|
|
31403
|
+
// it modifies, because it pads layer bounds and a drawn box is already the
|
|
31404
|
+
// extent the user meant.
|
|
31405
|
+
var section = makePanelSection(content, 'Frame area', {minor: true});
|
|
31406
|
+
var fitRow = El('div').addClass('frame-create-option-row').appendTo(section);
|
|
31407
|
+
addButton(fitRow, 'Fit visible layers', function() {
|
|
31408
|
+
var aspect = getAspect();
|
|
31409
|
+
if (aspect === null) return;
|
|
31410
|
+
popup.close();
|
|
31411
|
+
createFromVisibleLayers(getWidth(), aspect, getMargin());
|
|
31412
|
+
});
|
|
31413
|
+
var marginCell = El('label').addClass('frame-create-margin').appendTo(fitRow);
|
|
31414
|
+
El('span').appendTo(marginCell).text('Margin');
|
|
31415
|
+
var marginInput = El('input').attr('type', 'text').appendTo(marginCell);
|
|
31416
|
+
marginInput.node().value = '2%';
|
|
31417
|
+
makeFieldTip(fitRow,
|
|
31418
|
+
'Space around the layers: 2%, 20px, 1cm.\n' +
|
|
31419
|
+
"A percentage is of the frame's width on the\n" +
|
|
31420
|
+
'side and of its height on top and bottom.');
|
|
31421
|
+
|
|
31422
|
+
var drawRow = El('div').addClass('frame-create-option-row').appendTo(section);
|
|
31423
|
+
addButton(drawRow, 'Draw on the map', function() {
|
|
31424
|
+
var aspect = getAspect();
|
|
31425
|
+
if (aspect === null) return;
|
|
31426
|
+
popup.close();
|
|
31427
|
+
beginFrameDrawing(getWidth(), aspect);
|
|
31428
|
+
});
|
|
31429
|
+
|
|
31430
|
+
function getWidth() {
|
|
31431
|
+
var value = widthInput.node().value.trim();
|
|
31432
|
+
return parseFloat(value) > 0 ? value : '800px';
|
|
31433
|
+
}
|
|
31434
|
+
|
|
31435
|
+
// Returns a ratio, '' when the field is blank -- the frame area then gives
|
|
31436
|
+
// the shape -- or null if the field holds something that is not a ratio, in
|
|
31437
|
+
// which case the dialog stays open on the bad value.
|
|
31438
|
+
function getAspect() {
|
|
31439
|
+
var text = aspectInput.node().value.trim();
|
|
31440
|
+
var value;
|
|
31441
|
+
if (!text) return '';
|
|
31442
|
+
value = parseFrameAspectRatio(text);
|
|
31443
|
+
if (!(value > 0)) {
|
|
31444
|
+
aspectInput.node().focus();
|
|
31445
|
+
return null;
|
|
31446
|
+
}
|
|
31447
|
+
return String(value);
|
|
31448
|
+
}
|
|
31449
|
+
|
|
31450
|
+
function getMargin() {
|
|
31451
|
+
var value = marginInput.node().value.trim();
|
|
31452
|
+
return parseFloat(value) > 0 ? value : '';
|
|
31453
|
+
}
|
|
31454
|
+
}
|
|
31455
|
+
|
|
31456
|
+
// A captioned field with a tip beside it. The caption and field are a label
|
|
31457
|
+
// so that clicking the caption focuses the field; the tip is outside it, or
|
|
31458
|
+
// opening the tip would focus the field too.
|
|
31459
|
+
function makeCreateField(parent, label, tip) {
|
|
31460
|
+
var row = El('div').addClass('frame-create-row').appendTo(parent);
|
|
31461
|
+
var cell = El('label').addClass('frame-field-row').appendTo(row);
|
|
31462
|
+
El('span').appendTo(cell).text(label);
|
|
31463
|
+
var input = El('input').attr('type', 'text').appendTo(cell);
|
|
31464
|
+
makeFieldTip(row, tip);
|
|
31465
|
+
return input;
|
|
31466
|
+
}
|
|
31467
|
+
|
|
31468
|
+
function beginFrameDrawing(width, aspect) {
|
|
31469
|
+
drawSource = getSourceLayer();
|
|
31470
|
+
if (!drawSource) {
|
|
31471
|
+
showPopupAlert(
|
|
31472
|
+
'Add or select a geographic layer before creating a frame.',
|
|
31473
|
+
'Map frame'
|
|
31474
|
+
);
|
|
31475
|
+
return;
|
|
31476
|
+
}
|
|
31477
|
+
drawWidth = width;
|
|
31478
|
+
drawAspect = aspect || '';
|
|
31479
|
+
// Without this the box is drawn freehand and -frame silently pads it out to
|
|
31480
|
+
// the ratio, so the frame that appears is bigger than the one drawn.
|
|
31481
|
+
drawBox.setAspectRatio(drawAspect ? Number(drawAspect) : null);
|
|
31482
|
+
drawingRequested = true;
|
|
31483
|
+
gui.interaction.setMode('frame_draw');
|
|
31484
|
+
}
|
|
31485
|
+
|
|
31486
|
+
function showFrameDrawingInstructions() {
|
|
31487
|
+
var text = 'Click to place the first corner, then click the opposite ' +
|
|
31488
|
+
'corner. Drag the handles to resize the frame.';
|
|
31489
|
+
if (drawAspect) {
|
|
31490
|
+
text += ' The box is held to ' +
|
|
31491
|
+
formatFrameAspectRatio(Number(drawAspect)) + '.';
|
|
31492
|
+
}
|
|
31493
|
+
drawInstructions = showPopupAlert(text, null,
|
|
31494
|
+
{non_blocking: true, max_width: '380px'});
|
|
31495
|
+
}
|
|
31496
|
+
|
|
31497
|
+
function hideFrameDrawingInstructions(action) {
|
|
31498
|
+
if (!drawInstructions) return;
|
|
31499
|
+
drawInstructions.close(action);
|
|
31500
|
+
drawInstructions = null;
|
|
31501
|
+
}
|
|
31502
|
+
|
|
31503
|
+
function finishFrameDrawing() {
|
|
31504
|
+
var displayBounds = drawBox.getDisplayCoords();
|
|
31505
|
+
var source = drawSource;
|
|
31506
|
+
if (!displayBounds || !source) return;
|
|
31507
|
+
var bbox = getDisplayBoundsInLayerCRS(source, displayBounds);
|
|
31508
|
+
gui.interaction.turnOff();
|
|
31509
|
+
var parts = [
|
|
31510
|
+
'-frame',
|
|
31511
|
+
'bbox=' + quoteCommandValue(bbox.join(',')),
|
|
31512
|
+
'width=' + quoteCommandValue(drawWidth)
|
|
31513
|
+
];
|
|
31514
|
+
if (drawAspect) parts.push('aspect-ratio=' + drawAspect);
|
|
31515
|
+
parts.push('name=frame');
|
|
31516
|
+
parts.push('target=' +
|
|
31517
|
+
internal.formatOptionValue(internal.getLayerTargetId(gui.model, source)));
|
|
31518
|
+
runCreateCommand(parts.join(' '));
|
|
31519
|
+
}
|
|
31520
|
+
|
|
31521
|
+
function createFromVisibleLayers(width, aspect, margin) {
|
|
31522
|
+
var entries = getCompositionEntries();
|
|
31523
|
+
if (!entries.length) {
|
|
31524
|
+
showPopupAlert('No visible geographic layers are available.', 'Map frame');
|
|
31525
|
+
return;
|
|
31526
|
+
}
|
|
31527
|
+
var ids = entries.map(function(o) {
|
|
31528
|
+
return internal.getLayerTargetId(gui.model, o.layer);
|
|
31529
|
+
});
|
|
31530
|
+
var parts = ['-frame', 'width=' + quoteCommandValue(width)];
|
|
31531
|
+
if (aspect) parts.push('aspect-ratio=' + aspect);
|
|
31532
|
+
if (margin) parts.push('offset=' + quoteCommandValue(margin));
|
|
31533
|
+
parts.push('name=frame');
|
|
31534
|
+
parts.push('target=' + internal.formatOptionValue(ids.join(',')));
|
|
31535
|
+
runCreateCommand(parts.join(' '));
|
|
31536
|
+
}
|
|
31537
|
+
|
|
31538
|
+
function runCreateCommand(cmd) {
|
|
31539
|
+
runGuiEditCommand(gui, cmd, {
|
|
31540
|
+
title: 'Create map frame',
|
|
31541
|
+
onSuccess: function() {
|
|
31542
|
+
gui.previewMode.setOn(true);
|
|
31543
|
+
}
|
|
31544
|
+
});
|
|
31545
|
+
}
|
|
31546
|
+
|
|
31547
|
+
function getFrameTarget() {
|
|
31548
|
+
return internal.getActiveFrame(gui.model);
|
|
31549
|
+
}
|
|
31550
|
+
|
|
31551
|
+
function getTargetOption(target) {
|
|
31552
|
+
return 'target=' +
|
|
31553
|
+
internal.formatOptionValue(internal.getLayerTargetId(gui.model, target.layer));
|
|
31554
|
+
}
|
|
31555
|
+
|
|
31556
|
+
function getSourceLayer() {
|
|
31557
|
+
var active = gui.model.getActiveLayer();
|
|
31558
|
+
if (active && active.layer && active.layer.geometry_type &&
|
|
31559
|
+
!internal.isFrameLayer(active.layer, active.dataset.arcs)) {
|
|
31560
|
+
return active.layer;
|
|
31561
|
+
}
|
|
31562
|
+
var entries = getCompositionEntries();
|
|
31563
|
+
return entries.length ? entries[0].layer : null;
|
|
31564
|
+
}
|
|
31565
|
+
|
|
31566
|
+
function getCompositionEntries() {
|
|
31567
|
+
var layers = gui.map.getCompositionLayers();
|
|
31568
|
+
return gui.model.getLayers().filter(function(o) {
|
|
31569
|
+
return layers.includes(o.layer);
|
|
31570
|
+
});
|
|
31571
|
+
}
|
|
31572
|
+
|
|
31573
|
+
function getDisplayBoundsInLayerCRS(layer, view) {
|
|
31574
|
+
if (!layer.gui?.invertPoint) return view.slice();
|
|
31575
|
+
var bounds = new internal.Bounds();
|
|
31576
|
+
var steps = 16;
|
|
31577
|
+
for (var i = 0; i <= steps; i++) {
|
|
31578
|
+
var t = i / steps;
|
|
31579
|
+
mergeDisplayPoint(bounds, layer, view[0] + getBboxWidth(view) * t, view[1]);
|
|
31580
|
+
mergeDisplayPoint(bounds, layer, view[0] + getBboxWidth(view) * t, view[3]);
|
|
31581
|
+
mergeDisplayPoint(bounds, layer, view[0], view[1] + getBboxHeight(view) * t);
|
|
31582
|
+
mergeDisplayPoint(bounds, layer, view[2], view[1] + getBboxHeight(view) * t);
|
|
31583
|
+
}
|
|
31584
|
+
return bounds.toArray();
|
|
31585
|
+
}
|
|
31586
|
+
|
|
31587
|
+
function mergeDisplayPoint(bounds, layer, x, y) {
|
|
31588
|
+
var p = translateDisplayPoint(layer, [x, y]);
|
|
31589
|
+
if (p && Number.isFinite(p[0]) && Number.isFinite(p[1])) {
|
|
31590
|
+
bounds.mergePoint(p[0], p[1]);
|
|
31591
|
+
}
|
|
31592
|
+
}
|
|
31593
|
+
|
|
31594
|
+
function getPixelWidth(bbox) {
|
|
31595
|
+
var a = ext.translateCoords(bbox[0], bbox[1]);
|
|
31596
|
+
var b = ext.translateCoords(bbox[2], bbox[1]);
|
|
31597
|
+
return Math.abs(b[0] - a[0]);
|
|
31598
|
+
}
|
|
31599
|
+
}
|
|
31600
|
+
|
|
31601
|
+
function addButton(parent, label, action) {
|
|
31602
|
+
El('div').addClass('btn dialog-btn').appendTo(parent)
|
|
31603
|
+
.text(label).on('click', action);
|
|
31604
|
+
}
|
|
31605
|
+
|
|
31606
|
+
// The "?" the rest of the app uses for field help (see .tip-button in
|
|
31607
|
+
// elements.css and the static ones in index.html). The bubble is white-space:
|
|
31608
|
+
// pre, so the line breaks in the text are the ones it gets.
|
|
31609
|
+
function makeFieldTip(parent, text) {
|
|
31610
|
+
var btn = El('div').addClass('tip-button').appendTo(parent).text('?');
|
|
31611
|
+
var anchor = El('div').addClass('tip-anchor').appendTo(btn);
|
|
31612
|
+
El('div').addClass('tip').appendTo(anchor).text(text);
|
|
31613
|
+
return btn;
|
|
31614
|
+
}
|
|
31615
|
+
|
|
31616
|
+
function getUnitFactor$1(units) {
|
|
31617
|
+
return units == 'in' ? 72 : units == 'cm' ? 28.3465 : 1;
|
|
31618
|
+
}
|
|
31619
|
+
|
|
31620
|
+
function formatNumber$1(value) {
|
|
31621
|
+
return String(Math.round(value * 100) / 100);
|
|
31622
|
+
}
|
|
31623
|
+
|
|
31624
|
+
function getBboxWidth(bbox) {
|
|
31625
|
+
return bbox[2] - bbox[0];
|
|
31626
|
+
}
|
|
31627
|
+
|
|
31628
|
+
function getBboxHeight(bbox) {
|
|
31629
|
+
return bbox[3] - bbox[1];
|
|
31630
|
+
}
|
|
31631
|
+
|
|
31632
|
+
function FrameProperties(gui) {
|
|
31633
|
+
var target, form, widthInput, heightInput, unitsSelect, aspectValue;
|
|
31634
|
+
var backgroundControl, neatlineControl, neatlineWidthInput;
|
|
31635
|
+
var boundsValue, crsValue;
|
|
31636
|
+
|
|
31637
|
+
gui.frameProperties = this;
|
|
31638
|
+
|
|
31639
|
+
this.open = function(targetArg) {
|
|
31640
|
+
target = targetArg || internal.getActiveFrame(gui.model);
|
|
31641
|
+
if (!target) return;
|
|
31642
|
+
var popup = showPopupAlert('', 'Frame properties');
|
|
31643
|
+
popup.container().addClass('frame-properties-popup');
|
|
31644
|
+
form = El('div')
|
|
31645
|
+
.addClass('label-style-panel frame-properties-form')
|
|
31646
|
+
.appendTo(popup.container());
|
|
31647
|
+
initForm();
|
|
31648
|
+
updateControls();
|
|
31649
|
+
};
|
|
31650
|
+
|
|
31651
|
+
function initForm() {
|
|
31652
|
+
// Width and height are two ways of writing one thing: either one rescales
|
|
31653
|
+
// the frame and the other follows, because the frame's extent is not what
|
|
31654
|
+
// this panel changes. Shown side by side so that they read as a pair.
|
|
31655
|
+
var sizeRow = El('div').addClass('label-style-row frame-size-row').appendTo(form);
|
|
31656
|
+
widthInput = makeLabeledInput(sizeRow, 'Width')
|
|
31657
|
+
.addClass('frame-width-input').on('change', updateWidth);
|
|
31658
|
+
heightInput = makeLabeledInput(sizeRow, 'Height')
|
|
31659
|
+
.addClass('frame-height-input').on('change', updateHeight);
|
|
31660
|
+
unitsSelect = El('select').addClass('frame-units-select').appendTo(sizeRow)
|
|
31661
|
+
.on('change', updateUnits);
|
|
31662
|
+
['px', 'pt', 'in', 'cm'].forEach(function(unit) {
|
|
31663
|
+
El('option').attr('value', unit).appendTo(unitsSelect).text(unit);
|
|
31664
|
+
});
|
|
31665
|
+
|
|
31666
|
+
// Read-only: setting a ratio reshapes the frame's extent, which is what the
|
|
31667
|
+
// resize tool is for. What is worth saying here is whether the ratio is
|
|
31668
|
+
// fixed or follows the extent, since that is what decides how the pair
|
|
31669
|
+
// above behaves.
|
|
31670
|
+
aspectValue = makeReadOnlyRow(form, 'Aspect ratio');
|
|
31671
|
+
|
|
31672
|
+
var appearance = makePanelSection(form, 'Appearance');
|
|
31673
|
+
backgroundControl = makeColorRow(appearance, {
|
|
31674
|
+
label: 'Background',
|
|
31675
|
+
onColor: function(color) {
|
|
31676
|
+
if (color) applyFrameStyle([['fill', color]]);
|
|
31677
|
+
},
|
|
31678
|
+
onOpacity: function(value) {
|
|
31679
|
+
applyFrameStyle([['fill-opacity', value]]);
|
|
31680
|
+
},
|
|
31681
|
+
revert: updateControls
|
|
31682
|
+
});
|
|
31683
|
+
neatlineControl = makeColorRow(appearance, {
|
|
31684
|
+
label: 'Neatline',
|
|
31685
|
+
onColor: function(color) {
|
|
31686
|
+
if (!color) return;
|
|
31687
|
+
var styles = [['stroke', color]];
|
|
31688
|
+
if (!getStyleValue('stroke-width')) styles.push(['stroke-width', 1]);
|
|
31689
|
+
applyFrameStyle(styles);
|
|
31690
|
+
},
|
|
31691
|
+
onOpacity: function(value) {
|
|
31692
|
+
applyFrameStyle([['stroke-opacity', value]]);
|
|
31693
|
+
},
|
|
31694
|
+
revert: updateControls
|
|
31695
|
+
});
|
|
31696
|
+
// The width belongs to the same property as the colour and opacity beside
|
|
31697
|
+
// it, and all three fit on the row. The background row leaves the column
|
|
31698
|
+
// empty rather than putting the width on a line of its own.
|
|
31699
|
+
neatlineWidthInput = makeRowField(neatlineControl.row, 'Width')
|
|
31700
|
+
.addClass('frame-neatline-width')
|
|
31701
|
+
.on('change', updateNeatlineWidth);
|
|
31702
|
+
var clearRow = El('div')
|
|
31703
|
+
.addClass('label-style-row label-panel-button-row')
|
|
31704
|
+
.appendTo(appearance);
|
|
31705
|
+
makeActionButton(clearRow, 'Clear appearance', clearFrameStyle);
|
|
31706
|
+
|
|
31707
|
+
var details = makePanelSection(form, 'Details');
|
|
31708
|
+
boundsValue = makeReadOnlyRow(details, 'Bounds');
|
|
31709
|
+
crsValue = makeReadOnlyRow(details, 'CRS');
|
|
31710
|
+
}
|
|
31711
|
+
|
|
31712
|
+
function updateControls() {
|
|
31713
|
+
if (!target || !internal.isFrameLayer(target.layer, target.dataset.arcs)) return;
|
|
31714
|
+
var frame = internal.getFrameLayerData(target.layer, target.dataset.arcs);
|
|
31715
|
+
var rec = target.layer.data.getReadOnlyRecordAt(0);
|
|
31716
|
+
var units = frame.units || 'px';
|
|
31717
|
+
var factor = getUnitFactor(units);
|
|
31718
|
+
var info = internal.getLayerInfo(target.layer, target.dataset);
|
|
31719
|
+
widthInput.node().value = formatNumber(frame.width / factor);
|
|
31720
|
+
heightInput.node().value = formatNumber(frame.height / factor);
|
|
31721
|
+
unitsSelect.node().value = units;
|
|
31722
|
+
aspectValue.text(getAspectText(frame));
|
|
31723
|
+
backgroundControl.showColor(rec.fill || '');
|
|
31724
|
+
backgroundControl.opacity.node().value = formatOpacity(rec['fill-opacity']);
|
|
31725
|
+
neatlineControl.showColor(rec.stroke || '');
|
|
31726
|
+
neatlineControl.opacity.node().value = formatOpacity(rec['stroke-opacity']);
|
|
31727
|
+
neatlineWidthInput.node().value =
|
|
31728
|
+
rec['stroke-width'] === undefined ? '' : rec['stroke-width'];
|
|
31729
|
+
boundsValue.text(frame.bbox.map(formatCoordinate).join(', '));
|
|
31730
|
+
crsValue.text(info.proj4 || '[unknown]');
|
|
31731
|
+
}
|
|
31732
|
+
|
|
31733
|
+
function updateWidth() {
|
|
31734
|
+
updateDimension('width', widthInput);
|
|
31735
|
+
}
|
|
31736
|
+
|
|
31737
|
+
function updateHeight() {
|
|
31738
|
+
updateDimension('height', heightInput);
|
|
31739
|
+
}
|
|
31740
|
+
|
|
31741
|
+
// One dimension per command, never both: -update-frame takes a lone width= or
|
|
31742
|
+
// height= as a rescale and derives the other from the extent, but takes the
|
|
31743
|
+
// two together as a new page shape and stretches the extent to fit it.
|
|
31744
|
+
function updateDimension(name, input) {
|
|
31745
|
+
var value = Number(input.node().value);
|
|
31746
|
+
if (!(value > 0)) {
|
|
31747
|
+
updateControls();
|
|
31748
|
+
return;
|
|
31749
|
+
}
|
|
31750
|
+
runCommand(
|
|
31751
|
+
'-update-frame ' + name + '=' +
|
|
31752
|
+
quoteCommandValue(value + unitsSelect.node().value) + ' ' +
|
|
31753
|
+
getTargetOption(),
|
|
31754
|
+
'Update frame size'
|
|
31755
|
+
);
|
|
31756
|
+
}
|
|
31757
|
+
|
|
31758
|
+
function updateUnits() {
|
|
31759
|
+
var frame = internal.getFrameLayerData(target.layer, target.dataset.arcs);
|
|
31760
|
+
var value = frame.width / getUnitFactor(unitsSelect.node().value);
|
|
31761
|
+
runCommand(
|
|
31762
|
+
'-update-frame width=' +
|
|
31763
|
+
quoteCommandValue(formatNumber(value) + unitsSelect.node().value) + ' ' +
|
|
31764
|
+
getTargetOption(),
|
|
31765
|
+
'Update frame units'
|
|
31766
|
+
);
|
|
31767
|
+
}
|
|
31768
|
+
|
|
31769
|
+
function updateNeatlineWidth() {
|
|
31770
|
+
var value = Number(neatlineWidthInput.node().value);
|
|
31771
|
+
if (!(value >= 0)) {
|
|
31772
|
+
updateControls();
|
|
31773
|
+
return;
|
|
31774
|
+
}
|
|
31775
|
+
var styles = [['stroke-width', value]];
|
|
31776
|
+
if (value > 0 && !getStyleValue('stroke')) {
|
|
31777
|
+
styles.push(['stroke', '#000000']);
|
|
31778
|
+
}
|
|
31779
|
+
applyFrameStyle(styles);
|
|
31780
|
+
}
|
|
31781
|
+
|
|
31782
|
+
function getStyleValue(field) {
|
|
31783
|
+
var rec = target.layer.data.getReadOnlyRecordAt(0);
|
|
31784
|
+
return rec && rec[field];
|
|
31785
|
+
}
|
|
31786
|
+
|
|
31787
|
+
function applyFrameStyle(styles) {
|
|
31788
|
+
var parts = ['-style'];
|
|
31789
|
+
styles.forEach(function(item) {
|
|
31790
|
+
parts.push(item[0] + '=' + quoteCommandValue(item[1]));
|
|
31791
|
+
});
|
|
31792
|
+
parts.push(getTargetOption());
|
|
31793
|
+
runCommand(parts.join(' '), 'Style map frame');
|
|
31794
|
+
}
|
|
31795
|
+
|
|
31796
|
+
function clearFrameStyle() {
|
|
31797
|
+
runCommand('-style clear ' + getTargetOption(), 'Clear frame appearance');
|
|
31798
|
+
}
|
|
31799
|
+
|
|
31800
|
+
function getTargetOption() {
|
|
31801
|
+
return 'target=' +
|
|
31802
|
+
internal.formatOptionValue(internal.getLayerTargetId(gui.model, target.layer));
|
|
31803
|
+
}
|
|
31804
|
+
|
|
31805
|
+
function runCommand(cmd, title) {
|
|
31806
|
+
runGuiEditCommand(gui, cmd, {
|
|
31807
|
+
title: title,
|
|
31808
|
+
onDone: updateControls
|
|
31809
|
+
});
|
|
31810
|
+
}
|
|
31811
|
+
}
|
|
31812
|
+
|
|
31813
|
+
// An extra captioned cell on an existing colour row.
|
|
31814
|
+
function makeRowField(row, label) {
|
|
31815
|
+
var cell = El('div').addClass('label-split-cell').appendTo(row);
|
|
31816
|
+
El('span').appendTo(cell).text(label);
|
|
31817
|
+
return makeTextInput(cell);
|
|
31818
|
+
}
|
|
31819
|
+
|
|
31820
|
+
function makeTextInput(parent) {
|
|
31821
|
+
return El('input').attr('type', 'text').appendTo(parent);
|
|
31822
|
+
}
|
|
31823
|
+
|
|
31824
|
+
function makeLabeledInput(parent, label) {
|
|
31825
|
+
var cell = El('label').addClass('frame-input-cell').appendTo(parent);
|
|
31826
|
+
El('span').appendTo(cell).text(label);
|
|
31827
|
+
return makeTextInput(cell);
|
|
31828
|
+
}
|
|
31829
|
+
|
|
31830
|
+
function makeActionButton(parent, label, action) {
|
|
31831
|
+
return El('div').addClass('label-panel-action-btn')
|
|
31832
|
+
.attr('role', 'button').appendTo(parent).text(label).on('click', action);
|
|
31833
|
+
}
|
|
31834
|
+
|
|
31835
|
+
function makeReadOnlyRow(parent, label) {
|
|
31836
|
+
var row = El('div').addClass('frame-property-detail-row').appendTo(parent);
|
|
31837
|
+
El('span').appendTo(row).text(label);
|
|
31838
|
+
return El('span').addClass('frame-property-detail-value').appendTo(row);
|
|
31839
|
+
}
|
|
31840
|
+
|
|
31841
|
+
function getUnitFactor(units) {
|
|
31842
|
+
return units == 'in' ? 72 : units == 'cm' ? 28.3465 : 1;
|
|
31843
|
+
}
|
|
31844
|
+
|
|
31845
|
+
function formatNumber(value) {
|
|
31846
|
+
return String(Math.round(value * 100) / 100);
|
|
31847
|
+
}
|
|
31848
|
+
|
|
31849
|
+
function formatCoordinate(value) {
|
|
31850
|
+
return String(Number(value.toPrecision(12)));
|
|
31851
|
+
}
|
|
31852
|
+
|
|
31853
|
+
function formatOpacity(value) {
|
|
31854
|
+
return value === undefined ? '' :
|
|
31855
|
+
String(Math.round(Number(value) * 100)) + '%';
|
|
31856
|
+
}
|
|
31857
|
+
|
|
31858
|
+
// A fixed ratio holds when the frame is rescaled; one taken from the extent
|
|
31859
|
+
// changes whenever the extent does. That difference is the reason to show the
|
|
31860
|
+
// ratio at all, so it is said rather than implied.
|
|
31861
|
+
function getAspectText(frame) {
|
|
31862
|
+
if (frame.aspect_ratio > 0) {
|
|
31863
|
+
return formatFrameAspectRatio(frame.aspect_ratio) + ' (fixed)';
|
|
31864
|
+
}
|
|
31865
|
+
var bbox = frame.bbox;
|
|
31866
|
+
var ratio = (bbox[2] - bbox[0]) / (bbox[3] - bbox[1]);
|
|
31867
|
+
if (!(ratio > 0) || !Number.isFinite(ratio)) return 'unavailable';
|
|
31868
|
+
return formatFrameAspectRatio(ratio) + ' (from extent)';
|
|
31869
|
+
}
|
|
31870
|
+
|
|
31871
|
+
// This is a new way to handle compatibility problems between
|
|
31872
|
+
// interactive editing modes and other interface modes
|
|
31873
|
+
// (by default, interactive modes stay on when, e.g., the user clicks
|
|
31874
|
+
// "Export" or "Console").
|
|
31875
|
+
//
|
|
31876
|
+
function initModeRules(gui) {
|
|
31877
|
+
|
|
31878
|
+
gui.on('interaction_mode_change', function(e) {
|
|
31879
|
+
var imode = e.mode;
|
|
31880
|
+
var mode = gui.getMode();
|
|
31881
|
+
|
|
31882
|
+
// simplify and vertex editing are not compatible
|
|
31883
|
+
if (imode == 'vertices') {
|
|
31884
|
+
flattenArcs(gui.map.getActiveLayer());
|
|
31885
|
+
|
|
31886
|
+
if (mode == 'simplify') {
|
|
31887
|
+
gui.clearMode(); // exit simplification
|
|
31888
|
+
}
|
|
31889
|
+
|
|
31890
|
+
}
|
|
31891
|
+
|
|
31892
|
+
});
|
|
31893
|
+
|
|
31894
|
+
gui.on('mode', function(e) {
|
|
31895
|
+
var mode = e.name;
|
|
31896
|
+
var imode = gui.interaction.getMode();
|
|
30246
31897
|
|
|
30247
31898
|
// simplify and vertex editing are not compatible
|
|
30248
31899
|
if (mode == 'simplify' && imode == 'vertices') {
|
|
@@ -31016,6 +32667,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
31016
32667
|
new SessionSnapshots(gui);
|
|
31017
32668
|
}
|
|
31018
32669
|
gui.interaction = new InteractionMode(gui);
|
|
32670
|
+
new PreviewMode(gui);
|
|
32671
|
+
new FrameResizeTool(gui);
|
|
32672
|
+
new FrameProperties(gui);
|
|
31019
32673
|
gui.editToolbar = new EditToolbar(gui);
|
|
31020
32674
|
gui.labelTool = new LabelTool(gui);
|
|
31021
32675
|
gui.layerStyleTool = new LayerStyleTool(gui);
|
|
@@ -31301,6 +32955,13 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
31301
32955
|
})
|
|
31302
32956
|
};
|
|
31303
32957
|
},
|
|
32958
|
+
selectLayer: function(name) {
|
|
32959
|
+
var target = gui.model.getLayers().filter(function(o) {
|
|
32960
|
+
return getLayerName(o.layer) === name;
|
|
32961
|
+
})[0];
|
|
32962
|
+
if (!target) throw new Error('Missing layer: ' + name);
|
|
32963
|
+
gui.model.selectLayer(target.layer, target.dataset);
|
|
32964
|
+
},
|
|
31304
32965
|
// What the label path guide drew, as last rendered.
|
|
31305
32966
|
getLabelPathGuideInfo: function() {
|
|
31306
32967
|
return getLabelPathGuideLayers(gui).map(function(lyr) {
|
|
@@ -31334,6 +32995,34 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
31334
32995
|
zoomByPct: function(pct) {
|
|
31335
32996
|
gui.map.getExtent().zoomByPct(pct);
|
|
31336
32997
|
},
|
|
32998
|
+
setPreviewMode: function(on) {
|
|
32999
|
+
if (gui.previewMode) gui.previewMode.setOn(on);
|
|
33000
|
+
},
|
|
33001
|
+
getPreviewMode: function() {
|
|
33002
|
+
return gui.previewMode ? gui.previewMode.isOn() : false;
|
|
33003
|
+
},
|
|
33004
|
+
getPreviewReadout: function() {
|
|
33005
|
+
return gui.previewMode ? gui.previewMode.getReadoutText() : '';
|
|
33006
|
+
},
|
|
33007
|
+
getSymbolScale: function() {
|
|
33008
|
+
return gui.map.getExtent().getSymbolScale();
|
|
33009
|
+
},
|
|
33010
|
+
zoomToFrameMagnification: function(scale) {
|
|
33011
|
+
gui.map.getExtent().zoomToFrameMagnification(scale);
|
|
33012
|
+
},
|
|
33013
|
+
openFrameTool: function() {
|
|
33014
|
+
if (gui.frameTool) gui.frameTool.open();
|
|
33015
|
+
},
|
|
33016
|
+
getFrameInfo: function() {
|
|
33017
|
+
var target = internal.getActiveFrame(gui.model);
|
|
33018
|
+
if (!target) return null;
|
|
33019
|
+
return Object.assign({
|
|
33020
|
+
name: target.layer.name || ''
|
|
33021
|
+
}, internal.getFrameLayerData(target.layer, target.dataset.arcs));
|
|
33022
|
+
},
|
|
33023
|
+
setFrameLockSize: function(on) {
|
|
33024
|
+
if (gui.frameTool) gui.frameTool.setLockSize(on);
|
|
33025
|
+
},
|
|
31337
33026
|
// The map's current view, in display CRS coordinates, for a test that an
|
|
31338
33027
|
// edit leaves the view where the user put it.
|
|
31339
33028
|
getViewBounds: function() {
|