mapshaper 0.7.67 → 0.7.69
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 +1200 -462
- package/package.json +1 -1
- package/www/index.html +10 -3
- package/www/mapshaper-gui.js +1258 -369
- package/www/mapshaper.js +1200 -462
- package/www/page.css +360 -113
package/www/mapshaper-gui.js
CHANGED
|
@@ -3324,7 +3324,7 @@
|
|
|
3324
3324
|
return str;
|
|
3325
3325
|
}
|
|
3326
3326
|
|
|
3327
|
-
function formatValue(val, matches) {
|
|
3327
|
+
function formatValue$2(val, matches) {
|
|
3328
3328
|
var flags = matches[1];
|
|
3329
3329
|
var padding = matches[2];
|
|
3330
3330
|
var decimals = matches[3] ? parseInt(matches[3].substr(1)) : void 0;
|
|
@@ -3420,7 +3420,7 @@
|
|
|
3420
3420
|
error$1("[format()] Data does not match format string; format:", fmt, "data:", arguments);
|
|
3421
3421
|
}
|
|
3422
3422
|
for (var i=0; i<n; i++) {
|
|
3423
|
-
str += formatValue(arguments[i], formatCodes[i]) + literals[i+1];
|
|
3423
|
+
str += formatValue$2(arguments[i], formatCodes[i]) + literals[i+1];
|
|
3424
3424
|
}
|
|
3425
3425
|
return str;
|
|
3426
3426
|
};
|
|
@@ -8623,10 +8623,10 @@
|
|
|
8623
8623
|
}
|
|
8624
8624
|
|
|
8625
8625
|
// Whether a layer can be shown on the map alongside the active layer, and so
|
|
8626
|
-
// has an eye icon in the layers panel. A frame
|
|
8627
|
-
// data-only layer has nothing to draw.
|
|
8626
|
+
// has an eye icon in the layers panel. A frame and its furniture are drawn by
|
|
8627
|
+
// preview mode, and a data-only layer has nothing to draw.
|
|
8628
8628
|
function layerIsPinnable(lyr, dataset) {
|
|
8629
|
-
if (
|
|
8629
|
+
if (internal.isFrameComponentLayer(lyr, dataset)) return false;
|
|
8630
8630
|
return internal.layerIsGeometric(lyr) || internal.layerHasRaster(lyr) ||
|
|
8631
8631
|
internal.layerHasFurniture(lyr);
|
|
8632
8632
|
}
|
|
@@ -9531,7 +9531,7 @@
|
|
|
9531
9531
|
|
|
9532
9532
|
function Console(gui) {
|
|
9533
9533
|
var model = gui.model;
|
|
9534
|
-
var CURSOR = '
|
|
9534
|
+
var CURSOR = '> ';
|
|
9535
9535
|
var PROMPT = 'Enter mapshaper commands or type "tips" for console help.';
|
|
9536
9536
|
var el = gui.container.findChild('.console').hide();
|
|
9537
9537
|
var content = el.findChild('.console-buffer');
|
|
@@ -9548,15 +9548,6 @@
|
|
|
9548
9548
|
var history = [];
|
|
9549
9549
|
var historyId = 0;
|
|
9550
9550
|
var _isOpen = false;
|
|
9551
|
-
var btn = gui.container.findChild('.console-btn')
|
|
9552
|
-
.on('click', toggle)
|
|
9553
|
-
.on('keydown', function(e) {
|
|
9554
|
-
if (e.key == 'Enter' || e.key == ' ') {
|
|
9555
|
-
e.preventDefault();
|
|
9556
|
-
e.stopPropagation();
|
|
9557
|
-
toggle();
|
|
9558
|
-
}
|
|
9559
|
-
});
|
|
9560
9551
|
var globals = {}; // share user-defined globals (job.defs) between runs
|
|
9561
9552
|
var sharedVars = {}; // share -vars / -defaults templating scope between runs
|
|
9562
9553
|
|
|
@@ -9588,10 +9579,6 @@
|
|
|
9588
9579
|
}
|
|
9589
9580
|
});
|
|
9590
9581
|
|
|
9591
|
-
function toggle() {
|
|
9592
|
-
gui.toggleSidebarPanel('console');
|
|
9593
|
-
}
|
|
9594
|
-
|
|
9595
9582
|
gui.on('sidebar', function(e) {
|
|
9596
9583
|
if (e.panels.includes('console')) {
|
|
9597
9584
|
turnOn();
|
|
@@ -9642,7 +9629,6 @@
|
|
|
9642
9629
|
function turnOn() {
|
|
9643
9630
|
// if (!_isOpen && !model.isEmpty()) {
|
|
9644
9631
|
if (!_isOpen) {
|
|
9645
|
-
btn.addClass('active').attr('aria-expanded', 'true');
|
|
9646
9632
|
_isOpen = true;
|
|
9647
9633
|
// Route logging output to the in-app console while it's open, so a
|
|
9648
9634
|
// user typing CLI commands here sees the results inline -- the same
|
|
@@ -9663,7 +9649,6 @@
|
|
|
9663
9649
|
|
|
9664
9650
|
function turnOff() {
|
|
9665
9651
|
if (_isOpen) {
|
|
9666
|
-
btn.removeClass('active').attr('aria-expanded', 'false');
|
|
9667
9652
|
_isOpen = false;
|
|
9668
9653
|
if (GUI.isActiveInstance(gui)) {
|
|
9669
9654
|
setLoggingForGUI(gui); // reset stop, message and error functions
|
|
@@ -10237,14 +10222,14 @@
|
|
|
10237
10222
|
}
|
|
10238
10223
|
|
|
10239
10224
|
function printExamples() {
|
|
10240
|
-
printExample("See a list of all console commands", "
|
|
10241
|
-
printExample("Get help using a single command", "
|
|
10242
|
-
printExample("Get information about imported datasets", "
|
|
10243
|
-
printExample("Print bot/debug runtime context as JSON", "
|
|
10244
|
-
printExample("Display browser session as shell commands", "
|
|
10245
|
-
printExample("Delete one state from a national dataset","
|
|
10246
|
-
printExample("Aggregate counties to states by dissolving shared edges" ,"
|
|
10247
|
-
printExample("Clear the console", "
|
|
10225
|
+
printExample("See a list of all console commands", CURSOR + "help");
|
|
10226
|
+
printExample("Get help using a single command", CURSOR + "help innerlines");
|
|
10227
|
+
printExample("Get information about imported datasets", CURSOR + "info");
|
|
10228
|
+
printExample("Print bot/debug runtime context as JSON", CURSOR + "context");
|
|
10229
|
+
printExample("Display browser session as shell commands", CURSOR + "history");
|
|
10230
|
+
printExample("Delete one state from a national dataset", CURSOR + "filter 'STATE != \"Alaska\"'");
|
|
10231
|
+
printExample("Aggregate counties to states by dissolving shared edges" , CURSOR + "dissolve 'STATE'");
|
|
10232
|
+
printExample("Clear the console", CURSOR + "clear");
|
|
10248
10233
|
}
|
|
10249
10234
|
|
|
10250
10235
|
function renderStructuredConsoleMessage(msg) {
|
|
@@ -10903,6 +10888,9 @@
|
|
|
10903
10888
|
if (opts.format == 'svg' || opts.format == 'topojson') {
|
|
10904
10889
|
opts.gui_frame = getGuiFrameContext();
|
|
10905
10890
|
}
|
|
10891
|
+
if (opts.format == 'svg') {
|
|
10892
|
+
targets = addFrameFurnitureTargets(targets, opts.gui_frame);
|
|
10893
|
+
}
|
|
10906
10894
|
try {
|
|
10907
10895
|
var files = await internal.exportTargetLayers(model, targets, opts);
|
|
10908
10896
|
} catch(e) {
|
|
@@ -10968,7 +10956,7 @@
|
|
|
10968
10956
|
function initLayerMenu() {
|
|
10969
10957
|
var list = menu.findChild('.export-layer-list').empty();
|
|
10970
10958
|
var layers = model.getLayers().filter(function(o) {
|
|
10971
|
-
return !internal.
|
|
10959
|
+
return !internal.isFrameComponentLayer(o.layer, o.dataset);
|
|
10972
10960
|
});
|
|
10973
10961
|
sortLayersForMenuDisplay(layers);
|
|
10974
10962
|
|
|
@@ -11162,21 +11150,42 @@
|
|
|
11162
11150
|
};
|
|
11163
11151
|
}
|
|
11164
11152
|
|
|
11153
|
+
// Snapshots keep the frame and its furniture
|
|
11165
11154
|
function addFrameTarget(targets) {
|
|
11166
11155
|
var frame = internal.getActiveFrame(model);
|
|
11167
|
-
var group;
|
|
11168
11156
|
if (!frame) return targets;
|
|
11157
|
+
return addDatasetTargets(targets, frame.dataset,
|
|
11158
|
+
[frame.layer].concat(internal.getFrameFurnitureLayers(frame.dataset)));
|
|
11159
|
+
}
|
|
11160
|
+
|
|
11161
|
+
// The frame's furniture is drawn in SVG output, the way it is in preview.
|
|
11162
|
+
// An unprojected frame can't show a scalebar, so it doesn't block the export.
|
|
11163
|
+
function addFrameFurnitureTargets(targets, frameContext) {
|
|
11164
|
+
var frame = internal.getActiveFrame(model);
|
|
11165
|
+
var furniture = frame ? internal.getFrameFurnitureLayers(frame.dataset) : [];
|
|
11166
|
+
if (furniture.length === 0) return targets;
|
|
11167
|
+
if (internal.getFurnitureFrameProblem(frameContext && frameContext.data)) {
|
|
11168
|
+
console.warn('Map furniture was not exported: the map frame is unprojected');
|
|
11169
|
+
return targets;
|
|
11170
|
+
}
|
|
11171
|
+
return addDatasetTargets(targets, frame.dataset, furniture);
|
|
11172
|
+
}
|
|
11173
|
+
|
|
11174
|
+
function addDatasetTargets(targets, dataset, layers) {
|
|
11175
|
+
var group;
|
|
11169
11176
|
targets = targets.map(function(target) {
|
|
11170
11177
|
return Object.assign({}, target, {layers: target.layers.slice()});
|
|
11171
11178
|
});
|
|
11172
11179
|
group = targets.find(function(target) {
|
|
11173
|
-
return target.dataset ==
|
|
11180
|
+
return target.dataset == dataset;
|
|
11174
11181
|
});
|
|
11175
|
-
if (group) {
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
targets.push({dataset: frame.dataset, layers: [frame.layer]});
|
|
11182
|
+
if (!group) {
|
|
11183
|
+
group = {dataset: dataset, layers: []};
|
|
11184
|
+
targets.push(group);
|
|
11179
11185
|
}
|
|
11186
|
+
layers.forEach(function(lyr) {
|
|
11187
|
+
if (!group.layers.includes(lyr)) group.layers.push(lyr);
|
|
11188
|
+
});
|
|
11180
11189
|
return targets;
|
|
11181
11190
|
}
|
|
11182
11191
|
|
|
@@ -11219,6 +11228,92 @@
|
|
|
11219
11228
|
};
|
|
11220
11229
|
}
|
|
11221
11230
|
|
|
11231
|
+
// Short descriptions of what a layer holds, for the contents column of the
|
|
11232
|
+
// layer panel: "6 polygons", "1 data record", "12 labels".
|
|
11233
|
+
//
|
|
11234
|
+
// A point layer is described by how its features are drawn, in the terms of
|
|
11235
|
+
// the point style panel: labels, symbols, circles and plain points. A layer
|
|
11236
|
+
// that mixes them lists each kind it has, e.g. "12 labels, 30 points".
|
|
11237
|
+
//
|
|
11238
|
+
// Features with null geometry are left out of the counts, which say what is on
|
|
11239
|
+
// the map. The column is too narrow to list them as well, so they go in a
|
|
11240
|
+
// tooltip with the full description.
|
|
11241
|
+
|
|
11242
|
+
var POINT_KINDS = ['label', 'symbol', 'circle', 'point'];
|
|
11243
|
+
|
|
11244
|
+
// How a point feature is drawn, given its data record.
|
|
11245
|
+
// A label wins over any symbol or circle drawn with it.
|
|
11246
|
+
function getPointKind(d) {
|
|
11247
|
+
if (!d) return 'point';
|
|
11248
|
+
if ('label-text' in d) return 'label';
|
|
11249
|
+
if (d['svg-symbol'] || d.icon) return 'symbol';
|
|
11250
|
+
if (d.r > 0) return 'circle';
|
|
11251
|
+
return 'point';
|
|
11252
|
+
}
|
|
11253
|
+
|
|
11254
|
+
// The kind of feature a point layer is set up for, from its field names:
|
|
11255
|
+
// names the features of an empty layer, e.g. "0 labels".
|
|
11256
|
+
function getPointLayerKind(fields) {
|
|
11257
|
+
if (fields.includes('label-text')) return 'label';
|
|
11258
|
+
if (fields.includes('svg-symbol') || fields.includes('icon')) return 'symbol';
|
|
11259
|
+
if (fields.includes('r')) return 'circle';
|
|
11260
|
+
return 'point';
|
|
11261
|
+
}
|
|
11262
|
+
|
|
11263
|
+
// Same test as the -info command's null shape count
|
|
11264
|
+
function isNullShape(shp) {
|
|
11265
|
+
return !shp || shp.length === 0;
|
|
11266
|
+
}
|
|
11267
|
+
|
|
11268
|
+
// @geometryType: 'point', 'polygon' or 'polyline'
|
|
11269
|
+
// @shapes: the layer's shapes
|
|
11270
|
+
// @records: data records, or null for a layer without attribute data
|
|
11271
|
+
// @fields: names of the layer's data fields
|
|
11272
|
+
// Returns {text, nulls, title}; title is the tooltip, or null if there are no nulls.
|
|
11273
|
+
function describeFeatureContents(geometryType, shapes, records, fields) {
|
|
11274
|
+
var nulls = shapes.length - countNonNull(shapes);
|
|
11275
|
+
var text = geometryType == 'point' ?
|
|
11276
|
+
describePoints(shapes, records, fields || []) :
|
|
11277
|
+
formatCount(shapes.length - nulls, geometryType);
|
|
11278
|
+
return {
|
|
11279
|
+
text: text,
|
|
11280
|
+
nulls: nulls,
|
|
11281
|
+
title: nulls > 0 ? text + ', ' + formatInteger(nulls) + ' without geometry' : null
|
|
11282
|
+
};
|
|
11283
|
+
}
|
|
11284
|
+
|
|
11285
|
+
function describePoints(shapes, records, fields) {
|
|
11286
|
+
var counts = {label: 0, symbol: 0, circle: 0, point: 0};
|
|
11287
|
+
var parts, i;
|
|
11288
|
+
for (i=0; i<shapes.length; i++) {
|
|
11289
|
+
if (isNullShape(shapes[i])) continue;
|
|
11290
|
+
counts[getPointKind(records ? records[i] : null)]++;
|
|
11291
|
+
}
|
|
11292
|
+
parts = POINT_KINDS.filter(function(kind) {
|
|
11293
|
+
return counts[kind] > 0;
|
|
11294
|
+
}).map(function(kind) {
|
|
11295
|
+
return formatCount(counts[kind], kind);
|
|
11296
|
+
});
|
|
11297
|
+
return parts.length > 0 ? parts.join(', ') :
|
|
11298
|
+
formatCount(0, getPointLayerKind(fields));
|
|
11299
|
+
}
|
|
11300
|
+
|
|
11301
|
+
function countNonNull(shapes) {
|
|
11302
|
+
var n = 0;
|
|
11303
|
+
for (var i=0; i<shapes.length; i++) {
|
|
11304
|
+
if (!isNullShape(shapes[i])) n++;
|
|
11305
|
+
}
|
|
11306
|
+
return n;
|
|
11307
|
+
}
|
|
11308
|
+
|
|
11309
|
+
function formatCount(n, noun) {
|
|
11310
|
+
return formatInteger(n) + ' ' + noun + (n == 1 ? '' : 's');
|
|
11311
|
+
}
|
|
11312
|
+
|
|
11313
|
+
function formatInteger(n) {
|
|
11314
|
+
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
11315
|
+
}
|
|
11316
|
+
|
|
11222
11317
|
var openMenu$1;
|
|
11223
11318
|
var openMenuId;
|
|
11224
11319
|
// One menu element per parent element, created on demand.
|
|
@@ -11385,6 +11480,9 @@
|
|
|
11385
11480
|
if (e.frameProperties) {
|
|
11386
11481
|
addMenuItem('frame properties', e.frameProperties, '');
|
|
11387
11482
|
}
|
|
11483
|
+
if (e.scalebarProperties) {
|
|
11484
|
+
addMenuItem(e.scalebarLabel || 'scale bar', e.scalebarProperties, '');
|
|
11485
|
+
}
|
|
11388
11486
|
if (e.resizeFrame) {
|
|
11389
11487
|
addMenuItem('resize frame', e.resizeFrame, '');
|
|
11390
11488
|
}
|
|
@@ -11612,7 +11710,6 @@
|
|
|
11612
11710
|
var el = gui.container.findChild(".layer-control").hide();
|
|
11613
11711
|
var btn = gui.container.findChild('.layer-control-btn');
|
|
11614
11712
|
var headerBtn = btn.findChild('.active-layer-label');
|
|
11615
|
-
var tab = gui.container.findChild('.layer-tab');
|
|
11616
11713
|
var isOpen = false;
|
|
11617
11714
|
var cache = new DomCache();
|
|
11618
11715
|
var pinAll = el.findChild('.pin-all'); // button for toggling layer visibility
|
|
@@ -11647,17 +11744,6 @@
|
|
|
11647
11744
|
gui.toggleSidebarPanel('console');
|
|
11648
11745
|
}
|
|
11649
11746
|
});
|
|
11650
|
-
tab.on('click', toggle).on('keydown', function(e) {
|
|
11651
|
-
if (e.key == 'Enter') {
|
|
11652
|
-
e.preventDefault();
|
|
11653
|
-
e.stopPropagation();
|
|
11654
|
-
toggle();
|
|
11655
|
-
} else if (e.key == ' ') {
|
|
11656
|
-
e.preventDefault();
|
|
11657
|
-
e.stopPropagation();
|
|
11658
|
-
gui.toggleSidebarPanel('console');
|
|
11659
|
-
}
|
|
11660
|
-
});
|
|
11661
11747
|
|
|
11662
11748
|
function toggle() {
|
|
11663
11749
|
gui.toggleSidebarPanel('layers');
|
|
@@ -11763,7 +11849,6 @@
|
|
|
11763
11849
|
function turnOn() {
|
|
11764
11850
|
if (isOpen) return;
|
|
11765
11851
|
isOpen = true;
|
|
11766
|
-
tab.addClass('active').attr('aria-expanded', 'true');
|
|
11767
11852
|
render();
|
|
11768
11853
|
el.addClass('open');
|
|
11769
11854
|
el.show();
|
|
@@ -11773,7 +11858,6 @@
|
|
|
11773
11858
|
if (!isOpen) return;
|
|
11774
11859
|
stopDragging();
|
|
11775
11860
|
isOpen = false;
|
|
11776
|
-
tab.removeClass('active').attr('aria-expanded', 'false');
|
|
11777
11861
|
el.removeClass('open');
|
|
11778
11862
|
el.hide();
|
|
11779
11863
|
}
|
|
@@ -11826,6 +11910,8 @@
|
|
|
11826
11910
|
sortLayersForMenuDisplay(model.getLayers()).forEach(function(o) {
|
|
11827
11911
|
var lyr = o.layer;
|
|
11828
11912
|
var isFrame = internal.isFrameLayer(lyr, o.dataset.arcs);
|
|
11913
|
+
// furniture is shown as part of its frame's entry
|
|
11914
|
+
if (!isFrame && internal.isFrameComponentLayer(lyr, o.dataset)) return;
|
|
11829
11915
|
var opts = {
|
|
11830
11916
|
show_source: layerCount < 5,
|
|
11831
11917
|
pinnable: pinnableCount > 0 && isPinnable(lyr, o.dataset)
|
|
@@ -11854,8 +11940,7 @@
|
|
|
11854
11940
|
|
|
11855
11941
|
function renderLayer(lyr, dataset, opts) {
|
|
11856
11942
|
var classes = 'layer-item';
|
|
11857
|
-
var
|
|
11858
|
-
var entry, html;
|
|
11943
|
+
var html;
|
|
11859
11944
|
|
|
11860
11945
|
if (opts.pinnable) classes += ' pinnable';
|
|
11861
11946
|
if (map.isActiveLayer(lyr)) classes += ' active';
|
|
@@ -11863,8 +11948,9 @@
|
|
|
11863
11948
|
if (lyr.pinned) classes += ' pinned';
|
|
11864
11949
|
|
|
11865
11950
|
html = '<!-- ' + lyr.menu_id + '--><div class="' + classes + '">';
|
|
11866
|
-
html +=
|
|
11867
|
-
|
|
11951
|
+
html += '<div class="layer-row"><div class="layer-name-col">' +
|
|
11952
|
+
'<span class="layer-name colored-text dot-underline">' + formatLayerNameForDisplay(lyr.name) + '</span></div>' +
|
|
11953
|
+
renderContents(lyr, dataset) + '</div>';
|
|
11868
11954
|
html += '<span class="more-btn layer-btn" role="button" aria-label="More layer options"></span>';
|
|
11869
11955
|
if (opts.pinnable) {
|
|
11870
11956
|
html += '<img class="eye-btn black-eye layer-btn" draggable="false" src="images/eye.png">';
|
|
@@ -11925,25 +12011,39 @@
|
|
|
11925
12011
|
function deleteLayer() {
|
|
11926
12012
|
var target = findLayerById(id);
|
|
11927
12013
|
var undoTransaction;
|
|
12014
|
+
var furniture;
|
|
11928
12015
|
if (!target) return;
|
|
12016
|
+
// a frame's furniture goes with it, rather than becoming a stray layer
|
|
12017
|
+
furniture = internal.isFrameLayer(target.layer, target.dataset.arcs) ?
|
|
12018
|
+
internal.getFrameFurnitureLayers(target.dataset) : [];
|
|
11929
12019
|
undoTransaction = createUndoTransaction(gui, 'delete layer');
|
|
11930
12020
|
if (map.isVisibleLayer(target.layer)) {
|
|
11931
12021
|
// TODO: check for double map refresh after model.deleteLayer() below
|
|
11932
12022
|
setLayerPinning(target.layer, false);
|
|
11933
12023
|
}
|
|
11934
12024
|
if (undoTransaction) {
|
|
11935
|
-
undoTransaction.run(
|
|
11936
|
-
model.deleteLayer(target.layer, target.dataset);
|
|
11937
|
-
});
|
|
12025
|
+
undoTransaction.run(deleteLayers);
|
|
11938
12026
|
addUndoTransactionToHistory(gui, undoTransaction, {
|
|
11939
12027
|
flags: {select: true, arc_count: true},
|
|
11940
12028
|
entryPrefix: 'delete-layer'
|
|
11941
12029
|
});
|
|
11942
12030
|
} else {
|
|
12031
|
+
deleteLayers();
|
|
12032
|
+
}
|
|
12033
|
+
|
|
12034
|
+
function deleteLayers() {
|
|
12035
|
+
furniture.forEach(function(lyr) {
|
|
12036
|
+
model.deleteLayer(lyr, target.dataset);
|
|
12037
|
+
});
|
|
11943
12038
|
model.deleteLayer(target.layer, target.dataset);
|
|
11944
12039
|
}
|
|
11945
12040
|
}
|
|
11946
12041
|
|
|
12042
|
+
function openScalebarProperties() {
|
|
12043
|
+
var target = findLayerById(id);
|
|
12044
|
+
if (target && gui.scalebarProperties) gui.scalebarProperties.open(target);
|
|
12045
|
+
}
|
|
12046
|
+
|
|
11947
12047
|
// Duplicate a layer by running an equivalent command, so the copy is
|
|
11948
12048
|
// recorded in session history and can be undone. Uses '-filter true +'
|
|
11949
12049
|
// to append a copy without replacing the source, keeps the same name
|
|
@@ -12031,6 +12131,10 @@
|
|
|
12031
12131
|
internal.isFrameLayer(target.layer, target.dataset.arcs);
|
|
12032
12132
|
if (isFrame) {
|
|
12033
12133
|
menuEvent.frameProperties = openFrameProperties;
|
|
12134
|
+
menuEvent.scalebarProperties = openScalebarProperties;
|
|
12135
|
+
menuEvent.scalebarLabel =
|
|
12136
|
+
internal.findFrameFurnitureLayer(target.dataset, 'scalebar') ?
|
|
12137
|
+
'scale bar' : 'add scale bar';
|
|
12034
12138
|
menuEvent.resizeFrame = resizeFrame;
|
|
12035
12139
|
menuEvent.deleteFrame = deleteLayer;
|
|
12036
12140
|
} else {
|
|
@@ -12114,29 +12218,34 @@
|
|
|
12114
12218
|
|
|
12115
12219
|
}
|
|
12116
12220
|
|
|
12221
|
+
// Returns {text, title}; title is a tooltip, or null
|
|
12117
12222
|
function describeLyr(lyr, dataset) {
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
str, type;
|
|
12121
|
-
if (lyr.data && !lyr.shapes) {
|
|
12122
|
-
type = 'data record';
|
|
12123
|
-
} else if (lyr.geometry_type) {
|
|
12124
|
-
type = lyr.geometry_type + ' feature';
|
|
12125
|
-
} else if (internal.layerHasRaster(lyr)) {
|
|
12126
|
-
type = 'raster layer';
|
|
12127
|
-
}
|
|
12128
|
-
if (isFrame) {
|
|
12129
|
-
str = internal.formatFrameSizeForDisplay(
|
|
12223
|
+
if (internal.isFrameLayer(lyr, dataset.arcs)) {
|
|
12224
|
+
return {text: internal.formatFrameSizeForDisplay(
|
|
12130
12225
|
internal.getFrameLayerData(lyr, dataset.arcs)
|
|
12131
|
-
);
|
|
12132
|
-
} else if (internal.layerHasRaster(lyr)) {
|
|
12133
|
-
str = utils$1.format('%,d x %,d %s', internal.getRasterWidth(lyr.raster), internal.getRasterHeight(lyr.raster), type);
|
|
12134
|
-
} else if (type) {
|
|
12135
|
-
str = utils$1.format('%,d %s%s', n, type, utils$1.pluralSuffix(n));
|
|
12136
|
-
} else {
|
|
12137
|
-
str = "[empty]";
|
|
12226
|
+
), title: null};
|
|
12138
12227
|
}
|
|
12139
|
-
|
|
12228
|
+
if (internal.layerHasRaster(lyr)) {
|
|
12229
|
+
return {text: utils$1.format('%,d x %,d raster', internal.getRasterWidth(lyr.raster),
|
|
12230
|
+
internal.getRasterHeight(lyr.raster)), title: null};
|
|
12231
|
+
}
|
|
12232
|
+
if (lyr.data && !lyr.shapes) {
|
|
12233
|
+
return {text: formatCount(internal.getFeatureCount(lyr), 'data record'), title: null};
|
|
12234
|
+
}
|
|
12235
|
+
if (lyr.geometry_type) {
|
|
12236
|
+
return describeFeatureContents(lyr.geometry_type, lyr.shapes,
|
|
12237
|
+
lyr.data ? lyr.data.getRecords() : null, lyr.data ? lyr.data.getFields() : []);
|
|
12238
|
+
}
|
|
12239
|
+
return {text: '[empty]', title: null};
|
|
12240
|
+
}
|
|
12241
|
+
|
|
12242
|
+
function renderContents(lyr, dataset) {
|
|
12243
|
+
var o = describeLyr(lyr, dataset);
|
|
12244
|
+
if (!o.title) {
|
|
12245
|
+
return '<div class="layer-contents">' + o.text + '</div>';
|
|
12246
|
+
}
|
|
12247
|
+
return '<div class="layer-contents has-nulls"><span title="' + o.title + '">' +
|
|
12248
|
+
o.text + '</span></div>';
|
|
12140
12249
|
}
|
|
12141
12250
|
|
|
12142
12251
|
function renderLayerInfo(info) {
|
|
@@ -12265,9 +12374,48 @@
|
|
|
12265
12374
|
return !!(lyr && (lyr.geometry_type == 'point' || lyr.geometry_type == 'polyline' || lyr.geometry_type == 'polygon'));
|
|
12266
12375
|
}
|
|
12267
12376
|
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12377
|
+
}
|
|
12378
|
+
|
|
12379
|
+
// The buttons that open and close the sidebar panels: an icon strip on the
|
|
12380
|
+
// map while the sidebar is closed, and a strip of toggles at the top of the
|
|
12381
|
+
// sidebar while it is open. Each button toggles its own panel, so both panels
|
|
12382
|
+
// can be open at once.
|
|
12383
|
+
function SidebarTabs(gui) {
|
|
12384
|
+
var buttons = gui.container.findChildren('.sidebar-tab, .sidebar-strip-btn');
|
|
12385
|
+
var hideBtn = gui.container.findChild('.sidebar-hide-btn');
|
|
12386
|
+
|
|
12387
|
+
buttons.forEach(function(btn) {
|
|
12388
|
+
var panel = btn.attr('data-panel');
|
|
12389
|
+
btn.on('click', function() {
|
|
12390
|
+
gui.toggleSidebarPanel(panel);
|
|
12391
|
+
});
|
|
12392
|
+
onKeyboardActivate(btn, function() {
|
|
12393
|
+
gui.toggleSidebarPanel(panel);
|
|
12394
|
+
});
|
|
12395
|
+
});
|
|
12396
|
+
|
|
12397
|
+
hideBtn.on('click', hideSidebar);
|
|
12398
|
+
onKeyboardActivate(hideBtn, hideSidebar);
|
|
12399
|
+
|
|
12400
|
+
gui.on('sidebar', function(e) {
|
|
12401
|
+
buttons.forEach(function(btn) {
|
|
12402
|
+
var open = e.panels.includes(btn.attr('data-panel'));
|
|
12403
|
+
btn.classed('active', open).attr('aria-expanded', String(open));
|
|
12404
|
+
});
|
|
12405
|
+
});
|
|
12406
|
+
|
|
12407
|
+
function hideSidebar() {
|
|
12408
|
+
gui.setSidebarPanels(null);
|
|
12409
|
+
}
|
|
12410
|
+
|
|
12411
|
+
function onKeyboardActivate(btn, action) {
|
|
12412
|
+
btn.on('keydown', function(e) {
|
|
12413
|
+
if (e.key == 'Enter' || e.key == ' ') {
|
|
12414
|
+
e.preventDefault();
|
|
12415
|
+
e.stopPropagation();
|
|
12416
|
+
action();
|
|
12417
|
+
}
|
|
12418
|
+
});
|
|
12271
12419
|
}
|
|
12272
12420
|
}
|
|
12273
12421
|
|
|
@@ -12400,7 +12548,9 @@
|
|
|
12400
12548
|
if (!container.node()) return;
|
|
12401
12549
|
DRAW_KINDS.forEach(function(kind, i) {
|
|
12402
12550
|
if (i > 0) {
|
|
12403
|
-
|
|
12551
|
+
// A breaking space after the dot, so that a sidebar too narrow for the
|
|
12552
|
+
// whole list wraps it between links, and never inside one.
|
|
12553
|
+
El('span').addClass('layer-menu-link-separator').html(' · ')
|
|
12404
12554
|
.appendTo(container);
|
|
12405
12555
|
}
|
|
12406
12556
|
El('span').addClass('layer-menu-link').attr('data-kind', kind.kind)
|
|
@@ -14234,25 +14384,25 @@
|
|
|
14234
14384
|
// labelModeIsAvailable().
|
|
14235
14385
|
var menus = {
|
|
14236
14386
|
standard: ['info', 'selection', 'box', 'ruler'],
|
|
14237
|
-
empty: ['
|
|
14238
|
-
polygons: ['info', 'selection', '
|
|
14239
|
-
rectangles: ['info', 'selection', '
|
|
14240
|
-
lines: ['info', 'selection', '
|
|
14387
|
+
empty: [ 'label','edit_points', 'edit_lines', 'edit_polygons','box', 'ruler'],
|
|
14388
|
+
polygons: ['info', 'selection', 'polygon_style', 'edit_polygons', 'box', 'ruler'],
|
|
14389
|
+
rectangles: ['info', 'selection', 'polygon_style', 'edit_polygons', 'rectangles', 'box', 'ruler'],
|
|
14390
|
+
lines: ['info', 'selection', 'line_style', 'edit_lines', 'snip_lines', 'box', 'ruler'],
|
|
14241
14391
|
table: ['info', 'selection'],
|
|
14242
|
-
raster: ['
|
|
14243
|
-
|
|
14244
|
-
|
|
14245
|
-
// with creating and retyping labels, so each of them offered a subset of
|
|
14246
|
-
// what sat next to it in the menu.
|
|
14247
|
-
labels: ['info', 'selection', 'box', 'label', 'ruler'],
|
|
14248
|
-
points: ['info', 'selection', 'box', 'point_style', 'edit_points', 'ruler'], // , 'add-points'
|
|
14392
|
+
raster: ['box', 'ruler'],
|
|
14393
|
+
labels: ['info', 'selection', 'label', 'box', 'ruler'],
|
|
14394
|
+
points: ['info', 'selection', 'edit_points', 'point_style', 'box', 'ruler'], // , 'add-points'
|
|
14249
14395
|
// An empty point layer is the layer the "Draw: labels" link creates,
|
|
14250
14396
|
// and a label goes into it rather than beside it (see labelWouldJoin), so
|
|
14251
14397
|
// the label tool belongs in its menu as well as the point tools: it is the
|
|
14252
14398
|
// way back into a labels layer that has no label in it yet.
|
|
14253
|
-
emptyPoints: ['info', 'selection', '
|
|
14399
|
+
emptyPoints: ['info', 'selection', 'label', 'point_style', 'edit_points', 'box', 'ruler']
|
|
14254
14400
|
};
|
|
14255
14401
|
|
|
14402
|
+
// Tools that work the same whatever the active layer is. They go below a
|
|
14403
|
+
// divider, so the layer-name heading visibly covers only the tools above it.
|
|
14404
|
+
var layerIndependentModes = ['box', 'ruler'];
|
|
14405
|
+
|
|
14256
14406
|
var prompts = {
|
|
14257
14407
|
box: 'Shift-drag to draw a box',
|
|
14258
14408
|
data: 'Click-select features to edit their attributes',
|
|
@@ -14348,7 +14498,7 @@
|
|
|
14348
14498
|
};
|
|
14349
14499
|
|
|
14350
14500
|
this.modeUsesPopup = function(mode) {
|
|
14351
|
-
return ['info', 'selection', 'data', 'box', 'edit_points'
|
|
14501
|
+
return ['info', 'selection', 'data', 'box', 'edit_points'].includes(mode);
|
|
14352
14502
|
};
|
|
14353
14503
|
|
|
14354
14504
|
this.modeSupportsUndo = function(mode) {
|
|
@@ -14419,27 +14569,54 @@
|
|
|
14419
14569
|
|
|
14420
14570
|
function renderMenu() {
|
|
14421
14571
|
if (!menu) return;
|
|
14422
|
-
var modes = getAvailableModes()
|
|
14423
|
-
menu.empty();
|
|
14424
|
-
modes.forEach(function(mode) {
|
|
14572
|
+
var modes = getAvailableModes().filter(function(mode) {
|
|
14425
14573
|
// don't show "turn off" link if not currently editing
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
setMode(mode);
|
|
14434
|
-
if (mode == 'off') closeMenu(120); // only close if turning off
|
|
14435
|
-
// closeMenu(mode == 'off' ? 120 : 400); // close after selecting
|
|
14436
|
-
}
|
|
14437
|
-
e.stopPropagation();
|
|
14438
|
-
});
|
|
14574
|
+
return !(_editMode == 'off' && mode == 'off');
|
|
14575
|
+
});
|
|
14576
|
+
var layerModes = modes.filter(function(mode) {
|
|
14577
|
+
return !layerIndependentModes.includes(mode);
|
|
14578
|
+
});
|
|
14579
|
+
var otherModes = modes.filter(function(mode) {
|
|
14580
|
+
return layerIndependentModes.includes(mode);
|
|
14439
14581
|
});
|
|
14582
|
+
var lyr = gui.model.getActiveLayer()?.layer;
|
|
14583
|
+
menu.empty();
|
|
14584
|
+
if (lyr && layerModes.length > 0) {
|
|
14585
|
+
renderLayerHeading(lyr);
|
|
14586
|
+
}
|
|
14587
|
+
layerModes.forEach(renderMenuItem);
|
|
14588
|
+
if (layerModes.length > 0 && otherModes.length > 0) {
|
|
14589
|
+
El('div').addClass('nav-menu-divider').appendTo(menu);
|
|
14590
|
+
}
|
|
14591
|
+
otherModes.forEach(renderMenuItem);
|
|
14440
14592
|
updateSelectionHighlight();
|
|
14441
14593
|
}
|
|
14442
14594
|
|
|
14595
|
+
// Names the layer the tools above the divider act on.
|
|
14596
|
+
function renderLayerHeading(lyr) {
|
|
14597
|
+
var name = formatLayerNameForDisplay(lyr.name);
|
|
14598
|
+
var heading = El('div').addClass('nav-menu-heading').attr('title', name).text(name).appendTo(menu);
|
|
14599
|
+
// A click on the heading would reach the button, which picks a tool.
|
|
14600
|
+
heading.on('click', function(e) {
|
|
14601
|
+
e.stopPropagation();
|
|
14602
|
+
});
|
|
14603
|
+
}
|
|
14604
|
+
|
|
14605
|
+
function renderMenuItem(mode) {
|
|
14606
|
+
var link = El('div').addClass('nav-menu-item').attr('data-name', mode).text(getModeLabel(mode)).appendTo(menu);
|
|
14607
|
+
link.on('click', function(e) {
|
|
14608
|
+
if (_editMode == mode) {
|
|
14609
|
+
// closeMenu();
|
|
14610
|
+
setMode('off');
|
|
14611
|
+
} else if (_editMode != mode) {
|
|
14612
|
+
setMode(mode);
|
|
14613
|
+
if (mode == 'off') closeMenu(120); // only close if turning off
|
|
14614
|
+
// closeMenu(mode == 'off' ? 120 : 400); // close after selecting
|
|
14615
|
+
}
|
|
14616
|
+
e.stopPropagation();
|
|
14617
|
+
});
|
|
14618
|
+
}
|
|
14619
|
+
|
|
14443
14620
|
function getModeLabel(mode) {
|
|
14444
14621
|
return labels[mode];
|
|
14445
14622
|
}
|
|
@@ -16482,20 +16659,43 @@
|
|
|
16482
16659
|
|
|
16483
16660
|
// The fraction an opacity control's contents mean ("50%", " 50 " -> 0.5), or
|
|
16484
16661
|
// null if it is not holding a number. Out-of-range values are clamped rather
|
|
16485
|
-
// than refused: a pasted 150% is an intent, not a mistake.
|
|
16662
|
+
// than refused: a pasted 150% is an intent, not a mistake. A blank field is
|
|
16663
|
+
// null, not zero: it is what a colour with no value shows beside it.
|
|
16486
16664
|
function parseOpacityValue(str) {
|
|
16487
|
-
var
|
|
16488
|
-
|
|
16665
|
+
var txt = String(str).replace('%', '').trim();
|
|
16666
|
+
var pct = Number(txt);
|
|
16667
|
+
if (txt === '' || !isFinite(pct)) return null;
|
|
16489
16668
|
return Math.max(0, Math.min(100, pct)) / 100;
|
|
16490
16669
|
}
|
|
16491
16670
|
|
|
16671
|
+
// A typed dash pattern in the form -style stroke-dasharray= takes: lengths
|
|
16672
|
+
// separated by single spaces. Commas and runs of whitespace are accepted
|
|
16673
|
+
// because SVG accepts them, and a pasted "4, 2" means the same as "4 2".
|
|
16674
|
+
// Returns '' for a blank field. Does not check that the lengths are numbers.
|
|
16675
|
+
function normalizeDashArrayInput(str) {
|
|
16676
|
+
return String(str).trim().split(/[\s,]+/).filter(Boolean).join(' ');
|
|
16677
|
+
}
|
|
16678
|
+
|
|
16492
16679
|
// A stored fraction as the percentage a control shows, or '' for no value --
|
|
16493
16680
|
// which is how a control over a selection that does not agree shows.
|
|
16494
16681
|
function formatOpacityPct(val) {
|
|
16682
|
+
if (isUnsetValue(val)) return '';
|
|
16495
16683
|
val = Number(val);
|
|
16496
16684
|
return isFinite(val) ? Math.round(Math.max(0, Math.min(1, val)) * 100) + '%' : '';
|
|
16497
16685
|
}
|
|
16498
16686
|
|
|
16687
|
+
// The opacity shown beside a colour. A colour with no opacity of its own is
|
|
16688
|
+
// drawn opaque, and says so; with no colour either, there is nothing for an
|
|
16689
|
+
// opacity to apply to, and the field is blank like the colour.
|
|
16690
|
+
function formatColorOpacityPct(opacity, hasColor) {
|
|
16691
|
+
if (isUnsetValue(opacity)) return hasColor ? '100%' : '';
|
|
16692
|
+
return formatOpacityPct(opacity);
|
|
16693
|
+
}
|
|
16694
|
+
|
|
16695
|
+
function isUnsetValue(val) {
|
|
16696
|
+
return val === undefined || val === null || val === '';
|
|
16697
|
+
}
|
|
16698
|
+
|
|
16499
16699
|
// The parts the style panels are built from. They were the label panel's, and
|
|
16500
16700
|
// three panels drawing the same control three ways is how they came to look
|
|
16501
16701
|
// like three programs: a colour was a swatch inside a field in one and a
|
|
@@ -16555,6 +16755,16 @@
|
|
|
16555
16755
|
return btn;
|
|
16556
16756
|
}
|
|
16557
16757
|
|
|
16758
|
+
// The "?" the rest of the app uses for field help (see .tip-button in
|
|
16759
|
+
// elements.css and the static ones in index.html). The bubble is white-space:
|
|
16760
|
+
// pre, so the line breaks in the text are the ones it gets.
|
|
16761
|
+
function makeFieldTip(parent, text) {
|
|
16762
|
+
var btn = El('div').addClass('tip-button').appendTo(parent).text('?');
|
|
16763
|
+
var anchor = El('div').addClass('tip-anchor').appendTo(btn);
|
|
16764
|
+
El('div').addClass('tip').appendTo(anchor).text(text);
|
|
16765
|
+
return btn;
|
|
16766
|
+
}
|
|
16767
|
+
|
|
16558
16768
|
function setPanelButtonDisabled(el, disabled) {
|
|
16559
16769
|
el.classed('disabled', !!disabled)
|
|
16560
16770
|
.attr('aria-disabled', disabled ? 'true' : 'false');
|
|
@@ -16565,23 +16775,38 @@
|
|
|
16565
16775
|
function makeColorField(parent, chit, input) {
|
|
16566
16776
|
var box = El('div').addClass('label-color-field').appendTo(parent);
|
|
16567
16777
|
chit.appendTo(box);
|
|
16568
|
-
input.appendTo(box);
|
|
16778
|
+
input.addClass('label-color-input').appendTo(box);
|
|
16569
16779
|
return box;
|
|
16570
16780
|
}
|
|
16571
16781
|
|
|
16572
|
-
// A colour
|
|
16573
|
-
//
|
|
16574
|
-
//
|
|
16782
|
+
// A colour field with its opacity at the right-hand end, behind a divider:
|
|
16783
|
+
// opacity qualifies the colour, and one border says so where two fields side
|
|
16784
|
+
// by side said they were separate settings. It also leaves the narrow column
|
|
16785
|
+
// of the row free for a field that needs it. The opacity has no caption; its
|
|
16786
|
+
// percent sign and its tooltip say what it is.
|
|
16787
|
+
//
|
|
16788
|
+
// Returns the field's box; the opacity input is made by makeOpacityInput().
|
|
16789
|
+
function makeColorOpacityField(parent, chit, input, opacityOpts) {
|
|
16790
|
+
var box = makeColorField(parent, chit, input).addClass('label-color-opacity-field');
|
|
16791
|
+
var opacity = makeOpacityInput(box, opacityOpts);
|
|
16792
|
+
return {box: box, opacity: opacity};
|
|
16793
|
+
}
|
|
16794
|
+
|
|
16795
|
+
// A colour and its opacity in one field, captioned, in the wide column of a
|
|
16796
|
+
// split row. Fill and Stroke are each one of these.
|
|
16575
16797
|
//
|
|
16576
|
-
// opts.label the caption over the
|
|
16798
|
+
// opts.label the caption over the field
|
|
16577
16799
|
// opts.onColor(hex) a colour was typed, picked or previewed to a finish
|
|
16578
16800
|
// opts.onOpacity(frac) a usable percentage was typed
|
|
16579
16801
|
// opts.revert() one that was not, so put the row back as it was
|
|
16802
|
+
//
|
|
16803
|
+
// control.aside is the row's narrow column, empty, for a field that belongs
|
|
16804
|
+
// beside the colour -- a stroke's width, say.
|
|
16580
16805
|
function makeColorRow(parent, opts) {
|
|
16581
16806
|
var row = El('div').addClass('label-style-row label-split-row').appendTo(parent);
|
|
16582
16807
|
var colorCell = El('div').addClass('label-split-cell label-color-row').appendTo(row);
|
|
16583
|
-
var
|
|
16584
|
-
var control = {row: row, chit: null, input: null, opacity: null, picker: null};
|
|
16808
|
+
var aside = El('div').addClass('label-split-cell').appendTo(row);
|
|
16809
|
+
var control = {row: row, aside: aside, chit: null, input: null, opacity: null, picker: null};
|
|
16585
16810
|
|
|
16586
16811
|
control.setColor = function(color) {
|
|
16587
16812
|
control.input.node().value = color || '';
|
|
@@ -16611,7 +16836,10 @@
|
|
|
16611
16836
|
if (isHexColor(color)) control.picker.setColor(color);
|
|
16612
16837
|
opts.onColor(color);
|
|
16613
16838
|
});
|
|
16614
|
-
|
|
16839
|
+
control.opacity = makeColorOpacityField(colorCell, control.chit, control.input, {
|
|
16840
|
+
onSet: opts.onOpacity,
|
|
16841
|
+
revert: opts.revert
|
|
16842
|
+
}).opacity;
|
|
16615
16843
|
control.picker = new ColorPicker(colorCell, {
|
|
16616
16844
|
presetRows: layerColorPresetRows,
|
|
16617
16845
|
// A drag in the picker shows on the map's own terms -- the swatch and the
|
|
@@ -16622,12 +16850,6 @@
|
|
|
16622
16850
|
opts.onColor(hex);
|
|
16623
16851
|
}
|
|
16624
16852
|
});
|
|
16625
|
-
|
|
16626
|
-
El('span').appendTo(opacityCell).text('Opacity');
|
|
16627
|
-
control.opacity = makeOpacityInput(opacityCell, {
|
|
16628
|
-
onSet: opts.onOpacity,
|
|
16629
|
-
revert: opts.revert
|
|
16630
|
-
});
|
|
16631
16853
|
return control;
|
|
16632
16854
|
}
|
|
16633
16855
|
|
|
@@ -17174,11 +17396,11 @@
|
|
|
17174
17396
|
// stroke of twice this.
|
|
17175
17397
|
var defaultHaloWidth = 2;
|
|
17176
17398
|
var defaultHaloColor = internal.svg.DEFAULT_HALO_COLOR;
|
|
17177
|
-
// The line height
|
|
17178
|
-
//
|
|
17179
|
-
//
|
|
17180
|
-
//
|
|
17181
|
-
var lineHeightPlaceholder =
|
|
17399
|
+
// The line height a label without one is drawn with, shown as a placeholder
|
|
17400
|
+
// rather than a value so that a label carries no line-height until one is
|
|
17401
|
+
// chosen. Shown as a bare multiple, which also says what a bare number typed
|
|
17402
|
+
// here means: a multiple of the font size, as in CSS, not px.
|
|
17403
|
+
var lineHeightPlaceholder = String(internal.svg.DEFAULT_LINE_HEIGHT);
|
|
17182
17404
|
// What a control shows for a selection whose labels disagree about it. One
|
|
17183
17405
|
// word, used in every field and menu that can be in that state: the panel
|
|
17184
17406
|
// otherwise says it by showing nothing, which is also what an unset property
|
|
@@ -17451,15 +17673,16 @@
|
|
|
17451
17673
|
clearLink = El('span').addClass('label-editing-clear colored-text').appendTo(selectRow).text('deselect').on('click', clearSelection);
|
|
17452
17674
|
|
|
17453
17675
|
// Three sections rather than one flat stack: the text, the symbol beside
|
|
17454
|
-
// it, and where the two sit relative to each other.
|
|
17455
|
-
//
|
|
17456
|
-
//
|
|
17457
|
-
// first rather than as a different kind of control.
|
|
17676
|
+
// it, and where the two sit relative to each other. Every section's colour
|
|
17677
|
+
// row is the same shape -- the colour with its opacity in one field, and
|
|
17678
|
+
// the value that qualifies it most closely beside it -- so that each reads
|
|
17679
|
+
// as a variation on the first rather than as a different kind of control.
|
|
17458
17680
|
var textSection = addSection('Text');
|
|
17459
17681
|
|
|
17460
|
-
// The controls whose own contents say what they are -- a font name, a
|
|
17461
|
-
//
|
|
17462
|
-
//
|
|
17682
|
+
// The controls whose own contents say what they are -- a font name, a
|
|
17683
|
+
// size beside a font style -- carry no label. The ones that would be a bare
|
|
17684
|
+
// number otherwise keep theirs, and so does a colour field, because the
|
|
17685
|
+
// field beside it has one and "Color" says the percentage is its opacity.
|
|
17463
17686
|
var fontRow = El('div').addClass('label-style-row').appendTo(textSection);
|
|
17464
17687
|
fontSelect = El('select').attr('title', 'Font').appendTo(fontRow).on('change', function() {
|
|
17465
17688
|
if (fontSelect.node().value) {
|
|
@@ -17485,11 +17708,20 @@
|
|
|
17485
17708
|
onDone: releaseFocus
|
|
17486
17709
|
});
|
|
17487
17710
|
|
|
17711
|
+
// Letter spacing beside the colour, above line height: the two spacing
|
|
17712
|
+
// values read as a pair in the narrow column, and the left of the row
|
|
17713
|
+
// below is where the alignment buttons go.
|
|
17714
|
+
//
|
|
17715
|
+
// A text block's width is not here: it is set by dragging the block's
|
|
17716
|
+
// handle, and a field for it would be clutter on every other label.
|
|
17488
17717
|
var colorRow = El('div').addClass('label-style-row label-split-row').appendTo(textSection);
|
|
17489
|
-
var textColorCell = El('div').addClass('label-split-cell label-color-row').appendTo(colorRow);
|
|
17718
|
+
var textColorCell = El('div').addClass('label-split-cell label-color-row label-text-color-row').appendTo(colorRow);
|
|
17719
|
+
El('span').appendTo(textColorCell).text('Color');
|
|
17490
17720
|
colorChit = El('div').addClass('label-color-chit').attr('role', 'button');
|
|
17491
17721
|
colorInput = El('input').attr('type', 'text').attr('title', 'Text color');
|
|
17492
|
-
|
|
17722
|
+
var textColorField = addColorOpacityField(textColorCell, colorChit, colorInput, applyLabelOpacity);
|
|
17723
|
+
colorFieldBox = textColorField.box;
|
|
17724
|
+
opacityInput = textColorField.opacity;
|
|
17493
17725
|
colorChit.on('click', function() {
|
|
17494
17726
|
if (this.classList.contains('disabled')) return;
|
|
17495
17727
|
toggleColorPicker();
|
|
@@ -17505,18 +17737,7 @@
|
|
|
17505
17737
|
});
|
|
17506
17738
|
colorPicker = initColorPicker(textColorCell, colorChit, colorInput, applyLabelColor);
|
|
17507
17739
|
|
|
17508
|
-
var
|
|
17509
|
-
opacityInput = addOpacityInput(opacityCell, applyLabelOpacity);
|
|
17510
|
-
|
|
17511
|
-
// Letter spacing takes the right-hand column on its own, above line
|
|
17512
|
-
// height: the two spacing values read as a pair there, and the left of the
|
|
17513
|
-
// row is where the alignment buttons go.
|
|
17514
|
-
//
|
|
17515
|
-
// A text block's width is not here: it is set by dragging the block's
|
|
17516
|
-
// handle, and a field for it would be clutter on every other label.
|
|
17517
|
-
var letterRow = El('div').addClass('label-style-row label-split-row').appendTo(textSection);
|
|
17518
|
-
El('div').addClass('label-split-cell').appendTo(letterRow);
|
|
17519
|
-
var letterCell = El('div').addClass('label-split-cell label-spacing-row').appendTo(letterRow);
|
|
17740
|
+
var letterCell = El('div').addClass('label-split-cell label-spacing-row').appendTo(colorRow);
|
|
17520
17741
|
El('span').appendTo(letterCell).text('Letter spacing');
|
|
17521
17742
|
letterSpacingInput = makeMeasureInput(letterCell, letterSpacingField, '0');
|
|
17522
17743
|
|
|
@@ -17554,8 +17775,8 @@
|
|
|
17554
17775
|
|
|
17555
17776
|
// A halo is a yes/no that its three values then qualify, as a symbol is, so
|
|
17556
17777
|
// it is switched the same way and its controls are inert while it is off.
|
|
17557
|
-
// Its
|
|
17558
|
-
//
|
|
17778
|
+
// Its row is the shape of the Text section's colour row: a colour and its
|
|
17779
|
+
// opacity, and a width in the narrow column beside them, where letter
|
|
17559
17780
|
// spacing sits above.
|
|
17560
17781
|
haloSection = addSection('Halo');
|
|
17561
17782
|
var haloTitle = haloSection.findChild('.label-style-section-title');
|
|
@@ -17566,10 +17787,13 @@
|
|
|
17566
17787
|
});
|
|
17567
17788
|
|
|
17568
17789
|
var haloColorRow = El('div').addClass('label-style-row label-split-row').appendTo(haloSection);
|
|
17569
|
-
var haloColorCell = El('div').addClass('label-split-cell label-color-row').appendTo(haloColorRow);
|
|
17790
|
+
var haloColorCell = El('div').addClass('label-split-cell label-color-row label-halo-color-row').appendTo(haloColorRow);
|
|
17791
|
+
El('span').appendTo(haloColorCell).text('Color');
|
|
17570
17792
|
haloColorChit = El('div').addClass('label-color-chit').attr('role', 'button');
|
|
17571
17793
|
haloColorInput = El('input').attr('type', 'text').attr('title', 'Halo color');
|
|
17572
|
-
|
|
17794
|
+
var haloColorField = addColorOpacityField(haloColorCell, haloColorChit, haloColorInput, applyHaloOpacity);
|
|
17795
|
+
haloColorFieldBox = haloColorField.box;
|
|
17796
|
+
haloOpacityInput = haloColorField.opacity;
|
|
17573
17797
|
haloColorChit.on('click', function() {
|
|
17574
17798
|
if (this.classList.contains('disabled')) return;
|
|
17575
17799
|
haloColorPicker.toggle();
|
|
@@ -17585,12 +17809,7 @@
|
|
|
17585
17809
|
});
|
|
17586
17810
|
haloColorPicker = initColorPicker(haloColorCell, haloColorChit, haloColorInput, applyHaloColor);
|
|
17587
17811
|
|
|
17588
|
-
var
|
|
17589
|
-
haloOpacityInput = addOpacityInput(haloOpacityCell, applyHaloOpacity);
|
|
17590
|
-
|
|
17591
|
-
var haloWidthRow = El('div').addClass('label-style-row label-split-row').appendTo(haloSection);
|
|
17592
|
-
El('div').addClass('label-split-cell').appendTo(haloWidthRow);
|
|
17593
|
-
var haloWidthCell = El('div').addClass('label-split-cell label-spacing-row').appendTo(haloWidthRow);
|
|
17812
|
+
var haloWidthCell = El('div').addClass('label-split-cell label-spacing-row').appendTo(haloColorRow);
|
|
17594
17813
|
El('span').appendTo(haloWidthCell).text('Width');
|
|
17595
17814
|
// Halves as well as whole pixels: a halo is usually 1 to 2px, and the
|
|
17596
17815
|
// step between those two is most of the range anyone uses.
|
|
@@ -17617,14 +17836,9 @@
|
|
|
17617
17836
|
className: 'label-icon-toggle',
|
|
17618
17837
|
onChange: setIconOn
|
|
17619
17838
|
});
|
|
17620
|
-
// The
|
|
17621
|
-
//
|
|
17622
|
-
|
|
17623
|
-
// pushes it out of line with the other.
|
|
17624
|
-
El('div').addClass('label-icon-size-caption').appendTo(iconTitle).text('Size');
|
|
17625
|
-
|
|
17626
|
-
var iconSizeRow = El('div').addClass('label-style-row label-split-row label-icon-shapes-row').appendTo(iconSection);
|
|
17627
|
-
var iconRow = El('div').addClass('label-split-cell').appendTo(iconSizeRow);
|
|
17839
|
+
// The shapes have the whole row, so there is room for more of them, and
|
|
17840
|
+
// the size sits beside the colour below, as the halo's width does.
|
|
17841
|
+
var iconRow = El('div').addClass('label-style-row label-icon-shapes-row').appendTo(iconSection);
|
|
17628
17842
|
var iconGroup = iconGroupEl = El('div').addClass('label-btn-group label-icon-buttons').appendTo(iconRow);
|
|
17629
17843
|
iconBtns = {};
|
|
17630
17844
|
iconTypes.forEach(function(icon) {
|
|
@@ -17637,21 +17851,14 @@
|
|
|
17637
17851
|
appendIconButtonSymbol(btn, icon.name);
|
|
17638
17852
|
});
|
|
17639
17853
|
|
|
17640
|
-
var sizeRow = El('div').addClass('label-split-cell label-icon-size-row').appendTo(iconSizeRow);
|
|
17641
|
-
iconSizeInput = new SizeField(sizeRow, {
|
|
17642
|
-
title: 'Symbol size in px',
|
|
17643
|
-
onSet: function(value) {
|
|
17644
|
-
applyIconSize(value);
|
|
17645
|
-
},
|
|
17646
|
-
onStep: nudgeIconSize,
|
|
17647
|
-
onDone: releaseFocus
|
|
17648
|
-
});
|
|
17649
|
-
|
|
17650
17854
|
var iconColorRow = El('div').addClass('label-style-row label-split-row').appendTo(iconSection);
|
|
17651
17855
|
var iconColorCell = El('div').addClass('label-split-cell label-color-row label-icon-color-row').appendTo(iconColorRow);
|
|
17856
|
+
El('span').appendTo(iconColorCell).text('Color');
|
|
17652
17857
|
iconColorChit = El('div').addClass('label-color-chit').attr('role', 'button');
|
|
17653
17858
|
iconColorInput = El('input').attr('type', 'text').attr('title', 'Symbol color');
|
|
17654
|
-
|
|
17859
|
+
var iconColorField = addColorOpacityField(iconColorCell, iconColorChit, iconColorInput, applyIconOpacity);
|
|
17860
|
+
iconColorFieldBox = iconColorField.box;
|
|
17861
|
+
iconOpacityInput = iconColorField.opacity;
|
|
17655
17862
|
iconColorChit.on('click', function() {
|
|
17656
17863
|
if (this.classList.contains('disabled')) return;
|
|
17657
17864
|
iconColorPicker.toggle();
|
|
@@ -17667,8 +17874,16 @@
|
|
|
17667
17874
|
});
|
|
17668
17875
|
iconColorPicker = initColorPicker(iconColorCell, iconColorChit, iconColorInput, applyIconColor);
|
|
17669
17876
|
|
|
17670
|
-
var
|
|
17671
|
-
|
|
17877
|
+
var sizeRow = El('div').addClass('label-split-cell label-icon-size-row').appendTo(iconColorRow);
|
|
17878
|
+
El('span').appendTo(sizeRow).text('Size');
|
|
17879
|
+
iconSizeInput = new SizeField(sizeRow, {
|
|
17880
|
+
title: 'Symbol size in px',
|
|
17881
|
+
onSet: function(value) {
|
|
17882
|
+
applyIconSize(value);
|
|
17883
|
+
},
|
|
17884
|
+
onStep: nudgeIconSize,
|
|
17885
|
+
onDone: releaseFocus
|
|
17886
|
+
});
|
|
17672
17887
|
|
|
17673
17888
|
initCalloutSection();
|
|
17674
17889
|
|
|
@@ -17780,10 +17995,13 @@
|
|
|
17780
17995
|
});
|
|
17781
17996
|
|
|
17782
17997
|
var colorRow = El('div').addClass('label-style-row label-split-row').appendTo(calloutSection);
|
|
17783
|
-
var colorCell = El('div').addClass('label-split-cell label-color-row').appendTo(colorRow);
|
|
17998
|
+
var colorCell = El('div').addClass('label-split-cell label-color-row label-callout-color-row').appendTo(colorRow);
|
|
17999
|
+
El('span').appendTo(colorCell).text('Color');
|
|
17784
18000
|
calloutColorChit = El('div').addClass('label-color-chit').attr('role', 'button');
|
|
17785
18001
|
calloutColorInput = El('input').attr('type', 'text').attr('title', 'Callout color');
|
|
17786
|
-
|
|
18002
|
+
var calloutColorField = addColorOpacityField(colorCell, calloutColorChit, calloutColorInput, applyCalloutOpacity);
|
|
18003
|
+
calloutColorFieldBox = calloutColorField.box;
|
|
18004
|
+
calloutOpacityInput = calloutColorField.opacity;
|
|
17787
18005
|
calloutColorChit.on('click', function() {
|
|
17788
18006
|
if (this.classList.contains('disabled')) return;
|
|
17789
18007
|
calloutColorPicker.toggle();
|
|
@@ -17798,14 +18016,10 @@
|
|
|
17798
18016
|
}
|
|
17799
18017
|
});
|
|
17800
18018
|
calloutColorPicker = initColorPicker(colorCell, calloutColorChit, calloutColorInput, applyCalloutColor);
|
|
17801
|
-
var opacityCell = El('div').addClass('label-split-cell label-opacity-row').appendTo(colorRow);
|
|
17802
|
-
calloutOpacityInput = addOpacityInput(opacityCell, applyCalloutOpacity);
|
|
17803
18019
|
|
|
17804
18020
|
// The gap in the narrow column, under the sizes: it has a blank state,
|
|
17805
18021
|
// "auto", which clears the anchor's symbol, so it is not a size field.
|
|
17806
|
-
var
|
|
17807
|
-
El('div').addClass('label-split-cell').appendTo(gapRow);
|
|
17808
|
-
var gapCell = El('div').addClass('label-split-cell label-spacing-row label-callout-gap-row').appendTo(gapRow);
|
|
18022
|
+
var gapCell = El('div').addClass('label-split-cell label-spacing-row label-callout-gap-row').appendTo(colorRow);
|
|
17809
18023
|
El('span').appendTo(gapCell).text('Gap');
|
|
17810
18024
|
calloutGapInput = El('input').attr('type', 'text').addClass('label-measure-input')
|
|
17811
18025
|
.attr('title', 'Space between the callout and the anchor, in px')
|
|
@@ -17887,9 +18101,9 @@
|
|
|
17887
18101
|
};
|
|
17888
18102
|
}
|
|
17889
18103
|
|
|
17890
|
-
function
|
|
17891
|
-
return
|
|
17892
|
-
onSet:
|
|
18104
|
+
function addColorOpacityField(parent, chit, input, onOpacity) {
|
|
18105
|
+
return makeColorOpacityField(parent, chit, input, {
|
|
18106
|
+
onSet: onOpacity,
|
|
17893
18107
|
revert: updateControls
|
|
17894
18108
|
});
|
|
17895
18109
|
}
|
|
@@ -19421,12 +19635,12 @@
|
|
|
19421
19635
|
}
|
|
19422
19636
|
|
|
19423
19637
|
var savedStylesKey = 'layer_style_presets';
|
|
19424
|
-
var styleFields = ['stroke', 'stroke-width', 'stroke-opacity', 'fill', 'fill-opacity'];
|
|
19638
|
+
var styleFields = ['stroke', 'stroke-width', 'stroke-opacity', 'stroke-dasharray', 'fill', 'fill-opacity'];
|
|
19425
19639
|
|
|
19426
19640
|
function LayerStyleTool(gui) {
|
|
19427
19641
|
var parent = gui.container.findChild('.mshp-main-map');
|
|
19428
19642
|
var panel = El('div').addClass('label-style-panel layer-style-panel rollover').appendTo(parent).hide();
|
|
19429
|
-
var title, editingStatus, clearLink, strokeControl, fillControl, strokeWidthField, randomFillBtn, presetControl, hit;
|
|
19643
|
+
var title, editingStatus, clearLink, strokeControl, fillControl, strokeWidthField, dashControl, randomFillBtn, presetControl, hit;
|
|
19430
19644
|
var targetLayer = null;
|
|
19431
19645
|
|
|
19432
19646
|
initPanel();
|
|
@@ -19511,7 +19725,8 @@
|
|
|
19511
19725
|
|
|
19512
19726
|
fillControl = addColorControl(panel, 'Fill', 'fill', '');
|
|
19513
19727
|
strokeControl = addColorControl(panel, 'Stroke', 'stroke', '#000000');
|
|
19514
|
-
strokeWidthField = addStrokeWidthControl(
|
|
19728
|
+
strokeWidthField = addStrokeWidthControl(strokeControl.aside);
|
|
19729
|
+
dashControl = addDashArrayControl(panel);
|
|
19515
19730
|
|
|
19516
19731
|
var buttonRow = El('div').addClass('label-style-row label-panel-button-row').appendTo(panel);
|
|
19517
19732
|
randomFillBtn = makePanelActionButton(buttonRow, 'Random fills', applyRandomFillColors);
|
|
@@ -19553,14 +19768,11 @@
|
|
|
19553
19768
|
return control;
|
|
19554
19769
|
}
|
|
19555
19770
|
|
|
19556
|
-
// In the narrow column
|
|
19771
|
+
// In the narrow column beside the stroke's colour. Stepping runs up a ladder
|
|
19557
19772
|
// of widths rather than by a fixed amount, because the useful ones are close
|
|
19558
19773
|
// together at the hairline end and far apart above 2px.
|
|
19559
|
-
function addStrokeWidthControl(
|
|
19560
|
-
|
|
19561
|
-
El('div').addClass('label-split-cell').appendTo(row);
|
|
19562
|
-
var cell = El('div').addClass('label-split-cell').appendTo(row);
|
|
19563
|
-
El('span').appendTo(cell).text('Stroke width');
|
|
19774
|
+
function addStrokeWidthControl(cell) {
|
|
19775
|
+
El('span').appendTo(cell).text('Width');
|
|
19564
19776
|
return new SizeField(cell, {
|
|
19565
19777
|
min: 0,
|
|
19566
19778
|
// Quarter-pixel widths are the useful hairlines, and the ladder below
|
|
@@ -19575,6 +19787,29 @@
|
|
|
19575
19787
|
});
|
|
19576
19788
|
}
|
|
19577
19789
|
|
|
19790
|
+
// A literal stroke-dasharray, in the wide column so that it lines up with
|
|
19791
|
+
// the stroke's colour above it. A blank field makes the line solid again.
|
|
19792
|
+
function addDashArrayControl(parent) {
|
|
19793
|
+
var row = El('div').addClass('label-style-row label-split-row layer-dash-row').appendTo(parent);
|
|
19794
|
+
var cell = El('div').addClass('label-split-cell').appendTo(row);
|
|
19795
|
+
El('div').addClass('label-split-cell').appendTo(row);
|
|
19796
|
+
var caption = El('div').addClass('label-style-row-label').appendTo(cell).text('Dashes');
|
|
19797
|
+
makeFieldTip(caption,
|
|
19798
|
+
'Dash and gap lengths in pixels, separated\n' +
|
|
19799
|
+
'by spaces. "4" gives 4px dashes and 4px gaps.\n' +
|
|
19800
|
+
'"6 3" gives 6px dashes and 3px gaps.');
|
|
19801
|
+
var input = El('input').attr('type', 'text').appendTo(cell)
|
|
19802
|
+
.on('change', function() {
|
|
19803
|
+
var value = normalizeDashArrayInput(input.node().value);
|
|
19804
|
+
if (value && internal.parseStyleLiteral('stroke-dasharray', value) === undefined) {
|
|
19805
|
+
updateControls();
|
|
19806
|
+
return;
|
|
19807
|
+
}
|
|
19808
|
+
applyDashArrayStyle(value);
|
|
19809
|
+
});
|
|
19810
|
+
return {row: row, input: input};
|
|
19811
|
+
}
|
|
19812
|
+
|
|
19578
19813
|
function releaseFocus() {
|
|
19579
19814
|
releasePanelFocus(panel.node());
|
|
19580
19815
|
}
|
|
@@ -19588,9 +19823,11 @@
|
|
|
19588
19823
|
updateEditingStatus(manualIds.length);
|
|
19589
19824
|
strokeControl.row.show();
|
|
19590
19825
|
fillControl.row.classed('hidden', geom != 'polygon');
|
|
19826
|
+
dashControl.row.classed('hidden', geom != 'polyline');
|
|
19591
19827
|
updateColorControl(strokeControl);
|
|
19592
19828
|
updateColorControl(fillControl);
|
|
19593
19829
|
updateStrokeWidthControl();
|
|
19830
|
+
updateDashArrayControl();
|
|
19594
19831
|
randomFillBtn.classed('hidden', geom != 'polygon');
|
|
19595
19832
|
presetControl.render();
|
|
19596
19833
|
updateSavedStyleControls();
|
|
@@ -19604,10 +19841,13 @@
|
|
|
19604
19841
|
if (!isHexColor(value)) control.picker.hide();
|
|
19605
19842
|
}
|
|
19606
19843
|
|
|
19844
|
+
// A selection whose colours disagree still has colours, so an opacity unset
|
|
19845
|
+
// on all of it shows as full rather than blank.
|
|
19607
19846
|
function updateOpacityControl(control) {
|
|
19608
|
-
var
|
|
19609
|
-
control.opacity.node().value =
|
|
19610
|
-
|
|
19847
|
+
var field = control.field + '-opacity';
|
|
19848
|
+
control.opacity.node().value = styleFieldIsUnsetForTargets(field) ?
|
|
19849
|
+
formatColorOpacityPct(null, !styleFieldIsUnsetForTargets(control.field)) :
|
|
19850
|
+
formatOpacityPct(getCommonStyleValue(field));
|
|
19611
19851
|
}
|
|
19612
19852
|
|
|
19613
19853
|
function updateStrokeWidthControl() {
|
|
@@ -19617,6 +19857,11 @@
|
|
|
19617
19857
|
getDefaultStrokeWidth() : value));
|
|
19618
19858
|
}
|
|
19619
19859
|
|
|
19860
|
+
function updateDashArrayControl() {
|
|
19861
|
+
var value = getCommonStyleValue('stroke-dasharray');
|
|
19862
|
+
dashControl.input.node().value = value === undefined || value === null ? '' : String(value);
|
|
19863
|
+
}
|
|
19864
|
+
|
|
19620
19865
|
function nudgeStrokeWidth(direction) {
|
|
19621
19866
|
var value = Number(getCommonStyleValue('stroke-width'));
|
|
19622
19867
|
if (!isFinite(value)) value = getDefaultStrokeWidth();
|
|
@@ -19658,6 +19903,14 @@
|
|
|
19658
19903
|
runStyleCommand(styles);
|
|
19659
19904
|
}
|
|
19660
19905
|
|
|
19906
|
+
function applyDashArrayStyle(value) {
|
|
19907
|
+
var styles = [['stroke-dasharray', value]];
|
|
19908
|
+
if (value && styleFieldIsUnsetForTargets('stroke')) {
|
|
19909
|
+
styles.push(['stroke', strokeControl.defaultColor]);
|
|
19910
|
+
}
|
|
19911
|
+
runStyleCommand(styles);
|
|
19912
|
+
}
|
|
19913
|
+
|
|
19661
19914
|
function runStyleCommand(styles) {
|
|
19662
19915
|
var parts = ['-style'];
|
|
19663
19916
|
releaseFocus();
|
|
@@ -19719,6 +19972,9 @@
|
|
|
19719
19972
|
addStyleValue(style, 'stroke', getControlValue(strokeControl.input));
|
|
19720
19973
|
addStyleValue(style, 'stroke-width', strokeWidthField.getValue());
|
|
19721
19974
|
addStyleValue(style, 'stroke-opacity', parseOpacityValue(strokeControl.opacity.node().value));
|
|
19975
|
+
if (targetLayer && targetLayer.geometry_type == 'polyline') {
|
|
19976
|
+
addStyleValue(style, 'stroke-dasharray', getControlValue(dashControl.input));
|
|
19977
|
+
}
|
|
19722
19978
|
if (targetLayer && targetLayer.geometry_type == 'polygon') {
|
|
19723
19979
|
addStyleValue(style, 'fill', getControlValue(fillControl.input));
|
|
19724
19980
|
addStyleValue(style, 'fill-opacity', parseOpacityValue(fillControl.opacity.node().value));
|
|
@@ -20007,14 +20263,11 @@
|
|
|
20007
20263
|
circleStrokeControl = addCircleColorControl('Stroke', 'stroke');
|
|
20008
20264
|
circleControlRows.push(circleFillControl.row, circleStrokeControl.row);
|
|
20009
20265
|
|
|
20010
|
-
//
|
|
20011
|
-
//
|
|
20012
|
-
var
|
|
20013
|
-
|
|
20014
|
-
|
|
20015
|
-
var radiusCell = El('div').addClass('label-split-cell').appendTo(sizeRow);
|
|
20016
|
-
var widthCell = El('div').addClass('label-split-cell').appendTo(sizeRow);
|
|
20017
|
-
El('span').appendTo(widthCell).text('Stroke width');
|
|
20266
|
+
// Each size beside the colour it goes with: the radius beside the fill,
|
|
20267
|
+
// the stroke's width beside its colour, as in the line and polygon panels.
|
|
20268
|
+
var radiusCell = circleFillControl.aside;
|
|
20269
|
+
var widthCell = circleStrokeControl.aside;
|
|
20270
|
+
El('span').appendTo(widthCell).text('Width');
|
|
20018
20271
|
circleStrokeWidthField = new SizeField(widthCell, {
|
|
20019
20272
|
min: 0,
|
|
20020
20273
|
decimals: 2, // quarter-pixel hairlines, which the ladder below steps through
|
|
@@ -20045,7 +20298,6 @@
|
|
|
20045
20298
|
},
|
|
20046
20299
|
onDone: releaseFocus
|
|
20047
20300
|
});
|
|
20048
|
-
circleControlRows.push(sizeRow);
|
|
20049
20301
|
}
|
|
20050
20302
|
|
|
20051
20303
|
function initSymbolsSection() {
|
|
@@ -20133,27 +20385,42 @@
|
|
|
20133
20385
|
var radius = getCommonValue('r');
|
|
20134
20386
|
var fill = getCommonValue('fill');
|
|
20135
20387
|
var stroke = getCommonValue('stroke');
|
|
20136
|
-
var fillOpacity = getCommonValue('fill-opacity');
|
|
20137
|
-
var strokeOpacity = getCommonValue('stroke-opacity');
|
|
20138
20388
|
var strokeWidth = getCommonValue('stroke-width');
|
|
20139
20389
|
setCircleRadius(radius);
|
|
20140
20390
|
setCircleColor(circleFillControl, fill);
|
|
20141
20391
|
setCircleColor(circleStrokeControl, stroke);
|
|
20142
|
-
|
|
20143
|
-
|
|
20392
|
+
updateCircleOpacity(circleFillControl);
|
|
20393
|
+
updateCircleOpacity(circleStrokeControl);
|
|
20144
20394
|
setCircleStrokeWidth(strokeWidth === '' ? 0 : strokeWidth);
|
|
20145
20395
|
if (representation != 'circle' && representation != 'unstyled') {
|
|
20146
20396
|
setCircleRadius(defaultCircleRadius);
|
|
20147
20397
|
setCircleColor(circleFillControl, '');
|
|
20148
20398
|
setCircleColor(circleStrokeControl, '');
|
|
20149
|
-
|
|
20150
|
-
|
|
20399
|
+
circleFillControl.opacity.node().value = '';
|
|
20400
|
+
circleStrokeControl.opacity.node().value = '';
|
|
20151
20401
|
setCircleStrokeWidth(0);
|
|
20152
20402
|
}
|
|
20153
20403
|
}
|
|
20154
20404
|
|
|
20155
|
-
|
|
20156
|
-
|
|
20405
|
+
// The opacity to put in the style command: undefined to leave the property
|
|
20406
|
+
// as it is, null for a field holding something that is not an opacity.
|
|
20407
|
+
// A blank field has nothing to say, and 100% over an unset opacity is the
|
|
20408
|
+
// default being shown rather than a value to store.
|
|
20409
|
+
function getCircleOpacityToWrite(control) {
|
|
20410
|
+
var str = control.opacity.node().value.trim();
|
|
20411
|
+
var val = parseOpacityValue(str);
|
|
20412
|
+
if (str === '') return undefined;
|
|
20413
|
+
if (val === 1 && styleFieldIsUnset(control.field + '-opacity')) return undefined;
|
|
20414
|
+
return val;
|
|
20415
|
+
}
|
|
20416
|
+
|
|
20417
|
+
// A selection whose colours disagree still has colours, so an opacity unset
|
|
20418
|
+
// on all of it shows as full rather than blank.
|
|
20419
|
+
function updateCircleOpacity(control) {
|
|
20420
|
+
var field = control.field + '-opacity';
|
|
20421
|
+
control.opacity.node().value = styleFieldIsUnset(field) ?
|
|
20422
|
+
formatColorOpacityPct(null, !styleFieldIsUnset(control.field)) :
|
|
20423
|
+
formatOpacityPct(getCommonValue(field));
|
|
20157
20424
|
}
|
|
20158
20425
|
|
|
20159
20426
|
function renderCreateFields() {
|
|
@@ -20204,16 +20471,16 @@
|
|
|
20204
20471
|
function createSimpleCircles() {
|
|
20205
20472
|
runCommand('-style r=' + defaultCreatedCircleRadius +
|
|
20206
20473
|
' fill=' + quoteCommandValue(defaultCircleFill) +
|
|
20207
|
-
'
|
|
20474
|
+
' stroke-width=0', 'Create circles');
|
|
20208
20475
|
}
|
|
20209
20476
|
|
|
20210
20477
|
function applyCircleStyles() {
|
|
20211
20478
|
var representation = getPointRepresentation();
|
|
20212
20479
|
var radius = getCircleRadius();
|
|
20213
20480
|
var fill = circleFillControl.input.node().value.trim();
|
|
20214
|
-
var fillOpacity =
|
|
20481
|
+
var fillOpacity = getCircleOpacityToWrite(circleFillControl);
|
|
20215
20482
|
var stroke = circleStrokeControl.input.node().value.trim();
|
|
20216
|
-
var strokeOpacity =
|
|
20483
|
+
var strokeOpacity = getCircleOpacityToWrite(circleStrokeControl);
|
|
20217
20484
|
var strokeWidth = getCircleStrokeWidth();
|
|
20218
20485
|
var args;
|
|
20219
20486
|
if (!gui.console || !(representation == 'unstyled' || representation == 'circle') ||
|
|
@@ -20222,11 +20489,9 @@
|
|
|
20222
20489
|
fill = defaultCircleFill;
|
|
20223
20490
|
setCircleColor(circleFillControl, fill);
|
|
20224
20491
|
}
|
|
20225
|
-
args = [
|
|
20226
|
-
|
|
20227
|
-
|
|
20228
|
-
'stroke-width=' + strokeWidth
|
|
20229
|
-
];
|
|
20492
|
+
args = ['stroke-width=' + strokeWidth];
|
|
20493
|
+
if (fillOpacity !== undefined) args.push('fill-opacity=' + fillOpacity);
|
|
20494
|
+
if (strokeOpacity !== undefined) args.push('stroke-opacity=' + strokeOpacity);
|
|
20230
20495
|
if (radius !== null) args.unshift('r=' + radius);
|
|
20231
20496
|
if (fill) args.push('fill=' + quoteCommandValue(fill));
|
|
20232
20497
|
if (stroke) args.push('stroke=' + quoteCommandValue(stroke));
|
|
@@ -20453,7 +20718,7 @@
|
|
|
20453
20718
|
var setDefaultTargets = self.setDefaultTargets;
|
|
20454
20719
|
utils$1.extend(self, EventDispatcher.prototype);
|
|
20455
20720
|
|
|
20456
|
-
// A map frame is composition state, not editable content. Commands may
|
|
20721
|
+
// A map frame (and its furniture) is composition state, not editable content. Commands may
|
|
20457
20722
|
// explicitly target it, but it must not replace the GUI's active content
|
|
20458
20723
|
// layer or become the implicit target of the next console command.
|
|
20459
20724
|
self.setDefaultTargets = function(targets, opts) {
|
|
@@ -20520,15 +20785,25 @@
|
|
|
20520
20785
|
};
|
|
20521
20786
|
|
|
20522
20787
|
self.selectNextLayer = function() {
|
|
20523
|
-
var next = self.findNextLayer
|
|
20788
|
+
var next = findContentLayer(self.findNextLayer);
|
|
20524
20789
|
if (next) self.selectLayer(next.layer, next.dataset);
|
|
20525
20790
|
};
|
|
20526
20791
|
|
|
20527
20792
|
self.selectPrevLayer = function() {
|
|
20528
|
-
var prev = self.findPrevLayer
|
|
20793
|
+
var prev = findContentLayer(self.findPrevLayer);
|
|
20529
20794
|
if (prev) self.selectLayer(prev.layer, prev.dataset);
|
|
20530
20795
|
};
|
|
20531
20796
|
|
|
20797
|
+
// step past the frame and its furniture, which can't be selected
|
|
20798
|
+
function findContentLayer(step) {
|
|
20799
|
+
var start = self.getActiveLayer().layer;
|
|
20800
|
+
var o = step.call(self, start);
|
|
20801
|
+
while (o && o.layer != start && internal.isFrameComponentLayer(o.layer, o.dataset)) {
|
|
20802
|
+
o = step.call(self, o.layer);
|
|
20803
|
+
}
|
|
20804
|
+
return o;
|
|
20805
|
+
}
|
|
20806
|
+
|
|
20532
20807
|
return self;
|
|
20533
20808
|
|
|
20534
20809
|
function getContentTargets(targets) {
|
|
@@ -20536,7 +20811,7 @@
|
|
|
20536
20811
|
return {
|
|
20537
20812
|
dataset: target.dataset,
|
|
20538
20813
|
layers: target.layers.filter(function(lyr) {
|
|
20539
|
-
return !internal.
|
|
20814
|
+
return !internal.isFrameComponentLayer(lyr, target.dataset);
|
|
20540
20815
|
})
|
|
20541
20816
|
};
|
|
20542
20817
|
}).filter(function(target) {
|
|
@@ -20546,7 +20821,7 @@
|
|
|
20546
20821
|
|
|
20547
20822
|
function getFirstContentTarget() {
|
|
20548
20823
|
var target = self.getLayers().find(function(o) {
|
|
20549
|
-
return !internal.
|
|
20824
|
+
return !internal.isFrameComponentLayer(o.layer, o.dataset);
|
|
20550
20825
|
});
|
|
20551
20826
|
return target ? [{dataset: target.dataset, layers: [target.layer]}] : [];
|
|
20552
20827
|
}
|
|
@@ -22929,15 +23204,22 @@
|
|
|
22929
23204
|
this.zoomToBbox = zoomToBbox;
|
|
22930
23205
|
|
|
22931
23206
|
if (gui.options.homeControl) {
|
|
22932
|
-
gui.buttons.addButton("#home-icon")
|
|
22933
|
-
|
|
22934
|
-
|
|
22935
|
-
|
|
23207
|
+
gui.buttons.addButton("#home-icon")
|
|
23208
|
+
.attr('data-tooltip', 'Zoom to fit')
|
|
23209
|
+
.attr('aria-label', 'Zoom to fit')
|
|
23210
|
+
.on('click', function() {
|
|
23211
|
+
if (disabled()) return;
|
|
23212
|
+
gui.dispatchEvent('map_reset');
|
|
23213
|
+
});
|
|
22936
23214
|
}
|
|
22937
23215
|
|
|
22938
23216
|
if (gui.options.zoomControl) {
|
|
22939
|
-
inBtn = gui.buttons.addButton("#zoom-in-icon")
|
|
22940
|
-
|
|
23217
|
+
inBtn = gui.buttons.addButton("#zoom-in-icon")
|
|
23218
|
+
.attr('data-tooltip', 'Zoom in')
|
|
23219
|
+
.attr('aria-label', 'Zoom in');
|
|
23220
|
+
outBtn = gui.buttons.addButton("#zoom-out-icon")
|
|
23221
|
+
.attr('data-tooltip', 'Zoom out')
|
|
23222
|
+
.attr('aria-label', 'Zoom out');
|
|
22941
23223
|
initVariableClick(inBtn.node(), zoomIn);
|
|
22942
23224
|
initVariableClick(outBtn.node(), zoomOut);
|
|
22943
23225
|
ext.on('change', function() {
|
|
@@ -23102,8 +23384,11 @@
|
|
|
23102
23384
|
gui.on('interaction_mode_change', function(e) {
|
|
23103
23385
|
if (e.mode === 'selection') {
|
|
23104
23386
|
gui.enterMode('selection_tool');
|
|
23105
|
-
} else if (
|
|
23106
|
-
turnOff()
|
|
23387
|
+
} else if (gui.getMode() == 'selection_tool') {
|
|
23388
|
+
// Leave the gui mode rather than just calling turnOff(): a mode left
|
|
23389
|
+
// behind makes the next enterMode('selection_tool') a no-op, so the tool
|
|
23390
|
+
// would never turn back on.
|
|
23391
|
+
gui.clearMode();
|
|
23107
23392
|
}
|
|
23108
23393
|
});
|
|
23109
23394
|
|
|
@@ -26361,7 +26646,7 @@
|
|
|
26361
26646
|
|
|
26362
26647
|
function getLineHeight(o) {
|
|
26363
26648
|
var rec = o.pending ? o.pending.getStyle() : getRecord(o.target, o.id);
|
|
26364
|
-
return rec && rec['line-height']
|
|
26649
|
+
return internal.svg.getLineHeightDy(rec && rec['line-height']);
|
|
26365
26650
|
}
|
|
26366
26651
|
|
|
26367
26652
|
function getProvider(nodes) {
|
|
@@ -29211,6 +29496,7 @@
|
|
|
29211
29496
|
referenceStyle = { // outline style for reference layers
|
|
29212
29497
|
type: 'outline',
|
|
29213
29498
|
strokeColors: [null, '#87b73b'], // was 78c110
|
|
29499
|
+
// strokeColors: [null, 'rgba(79,140,0,0.67)'],
|
|
29214
29500
|
strokeWidth: 0.85,
|
|
29215
29501
|
dotColor: "#73ba20",
|
|
29216
29502
|
dotSize: 1
|
|
@@ -30090,37 +30376,103 @@
|
|
|
30090
30376
|
}
|
|
30091
30377
|
|
|
30092
30378
|
|
|
30093
|
-
|
|
30094
|
-
|
|
30095
|
-
|
|
30096
|
-
|
|
30097
|
-
|
|
30379
|
+
// symbolScale: the preview symbol scale (1 outside preview). Pattern sizes are
|
|
30380
|
+
// in output pixels, like stroke widths, so the tile grows with the page.
|
|
30381
|
+
// anchor: canvas-pixel position the pattern origin is pinned to, so the tile
|
|
30382
|
+
// travels with the map instead of sticking to the viewport. Panning only
|
|
30383
|
+
// updates this matrix. The tile image is rebuilt when the scale changes,
|
|
30384
|
+
// which is at most once per pattern per render.
|
|
30385
|
+
function getCanvasFillPattern(style, symbolScale, anchor) {
|
|
30386
|
+
var scale = symbolScale > 0 ? symbolScale : 1;
|
|
30387
|
+
var entry = hatches[style.fillPattern];
|
|
30388
|
+
if (!entry || entry.scale != scale) {
|
|
30389
|
+
entry = makePatternEntry(style, scale);
|
|
30390
|
+
hatches[style.fillPattern] = entry;
|
|
30098
30391
|
}
|
|
30099
|
-
|
|
30392
|
+
if (!entry || !entry.pattern) return style.fill || '#000';
|
|
30393
|
+
if (anchor) applyPatternAnchor(entry, anchor);
|
|
30394
|
+
return entry.pattern;
|
|
30100
30395
|
}
|
|
30101
30396
|
|
|
30102
|
-
function
|
|
30397
|
+
function makePatternEntry(style, scale) {
|
|
30103
30398
|
var o = internal.parsePattern(style.fillPattern);
|
|
30104
30399
|
if (!o) return null;
|
|
30105
30400
|
var canv = document.createElement('canvas');
|
|
30106
30401
|
var ctx = canv.getContext('2d');
|
|
30107
|
-
var
|
|
30108
|
-
var
|
|
30109
|
-
|
|
30402
|
+
var k = GUI.getPixelRatio() * scale;
|
|
30403
|
+
var tw = o.tileSize[0], th = o.tileSize[1];
|
|
30404
|
+
// A canvas has whole-pixel dimensions, but the scaled tile generally does
|
|
30405
|
+
// not. The tile is drawn into the nearest whole-pixel canvas, and the
|
|
30406
|
+
// pattern transform stretches it back to its exact size so that the
|
|
30407
|
+
// repeat period doesn't drift across a large polygon.
|
|
30408
|
+
var w = Math.max(1, Math.round(tw * k));
|
|
30409
|
+
var h = Math.max(1, Math.round(th * k));
|
|
30110
30410
|
canv.setAttribute('width', w);
|
|
30111
30411
|
canv.setAttribute('height', h);
|
|
30412
|
+
ctx.scale(w / tw, h / th);
|
|
30112
30413
|
if (o.background) {
|
|
30113
30414
|
ctx.fillStyle = o.background;
|
|
30114
|
-
ctx.fillRect(0, 0,
|
|
30115
|
-
}
|
|
30116
|
-
if (o.type == 'dots' || o.type == 'squares') makeDotFill(o, ctx, res);
|
|
30117
|
-
if (o.type == 'dashes') makeDashFill(o, ctx, res);
|
|
30118
|
-
if (o.type == 'hatches') makeHatchFill(o, ctx, res);
|
|
30119
|
-
var pattern = ctx.createPattern(canv, 'repeat');
|
|
30120
|
-
if (o.rotation) {
|
|
30121
|
-
pattern.setTransform(new DOMMatrix('rotate(' + o.rotation + 'deg)'));
|
|
30415
|
+
ctx.fillRect(0, 0, tw, th);
|
|
30122
30416
|
}
|
|
30123
|
-
|
|
30417
|
+
if (o.type == 'dots' || o.type == 'squares') makeDotFill(o, ctx, 1);
|
|
30418
|
+
if (o.type == 'dashes') makeDashFill(o, ctx, 1);
|
|
30419
|
+
if (o.type == 'hatches') makeHatchFill(o, ctx, 1);
|
|
30420
|
+
return {
|
|
30421
|
+
scale: scale,
|
|
30422
|
+
pattern: ctx.createPattern(canv, 'repeat'),
|
|
30423
|
+
rotation: o.rotation || 0,
|
|
30424
|
+
sx: tw * k / w,
|
|
30425
|
+
sy: th * k / h,
|
|
30426
|
+
w: w,
|
|
30427
|
+
h: h
|
|
30428
|
+
};
|
|
30429
|
+
}
|
|
30430
|
+
|
|
30431
|
+
// Pins the cached pattern to @anchor. Skipped when the view has not moved,
|
|
30432
|
+
// so a hover redraw does not touch the pattern.
|
|
30433
|
+
function applyPatternAnchor(entry, anchor) {
|
|
30434
|
+
if (entry.ax == anchor.x && entry.ay == anchor.y) return;
|
|
30435
|
+
entry.ax = anchor.x;
|
|
30436
|
+
entry.ay = anchor.y;
|
|
30437
|
+
var t = getPatternTransform(entry, anchor);
|
|
30438
|
+
entry.pattern.setTransform(new DOMMatrix([t.a, t.b, t.c, t.d, t.e, t.f]));
|
|
30439
|
+
}
|
|
30440
|
+
|
|
30441
|
+
// Matrix taking pattern-image pixels to canvas pixels: translate to the
|
|
30442
|
+
// anchor, then rotate, then scale. The anchor is folded into one tile so a
|
|
30443
|
+
// map origin far outside the viewport does not blow the matrix precision.
|
|
30444
|
+
function getPatternTransform(entry, anchor) {
|
|
30445
|
+
var p = wrapPatternAnchor(anchor.x, anchor.y, entry);
|
|
30446
|
+
var rad = (entry.rotation || 0) * Math.PI / 180;
|
|
30447
|
+
var c = Math.cos(rad);
|
|
30448
|
+
var s = Math.sin(rad);
|
|
30449
|
+
return {
|
|
30450
|
+
a: c * entry.sx,
|
|
30451
|
+
b: s * entry.sx,
|
|
30452
|
+
c: -s * entry.sy,
|
|
30453
|
+
d: c * entry.sy,
|
|
30454
|
+
e: p.x,
|
|
30455
|
+
f: p.y
|
|
30456
|
+
};
|
|
30457
|
+
}
|
|
30458
|
+
|
|
30459
|
+
// @entry.sx/sy scale image pixels to canvas pixels; the image repeats every
|
|
30460
|
+
// @entry.w by @entry.h pixels. Returns a point in the same place on the
|
|
30461
|
+
// pattern, within one tile of the origin.
|
|
30462
|
+
function wrapPatternAnchor(x, y, entry) {
|
|
30463
|
+
var rad = (entry.rotation || 0) * Math.PI / 180;
|
|
30464
|
+
var c = Math.cos(rad);
|
|
30465
|
+
var s = Math.sin(rad);
|
|
30466
|
+
var rx = c * x + s * y;
|
|
30467
|
+
var ry = -s * x + c * y;
|
|
30468
|
+
var qx = rx / entry.sx - Math.floor(rx / entry.sx / entry.w) * entry.w;
|
|
30469
|
+
var qy = ry / entry.sy - Math.floor(ry / entry.sy / entry.h) * entry.h;
|
|
30470
|
+
var lx = qx * entry.sx;
|
|
30471
|
+
var ly = qy * entry.sy;
|
|
30472
|
+
return {
|
|
30473
|
+
x: c * lx - s * ly,
|
|
30474
|
+
y: s * lx + c * ly
|
|
30475
|
+
};
|
|
30124
30476
|
}
|
|
30125
30477
|
|
|
30126
30478
|
function makeDashFill(o, ctx, res) {
|
|
@@ -30811,27 +31163,66 @@
|
|
|
30811
31163
|
return iter;
|
|
30812
31164
|
}
|
|
30813
31165
|
|
|
31166
|
+
// Same multiplier as ctx.lineWidth. Retina canvases are pixRatio times the
|
|
31167
|
+
// CSS pixel; preview mode multiplies styled strokes by the symbol scale.
|
|
31168
|
+
function getCanvasStrokeScale(pixRatio, lineScale) {
|
|
31169
|
+
return (pixRatio > 1 ? pixRatio : 1) * (lineScale || 1);
|
|
31170
|
+
}
|
|
31171
|
+
|
|
31172
|
+
// Scale a stroke-dasharray into canvas pixels. Scale 1 returns the authored
|
|
31173
|
+
// tokens, so a 100% preview and a non-preview view keep the pattern as stored.
|
|
31174
|
+
function scaleCanvasLineDash(lineDash, scale) {
|
|
31175
|
+
var parts = String(lineDash).split(' ');
|
|
31176
|
+
var out, i;
|
|
31177
|
+
if (!(scale > 0) || scale === 1) return parts;
|
|
31178
|
+
out = new Array(parts.length);
|
|
31179
|
+
for (i = 0; i < parts.length; i++) {
|
|
31180
|
+
out[i] = +parts[i] * scale;
|
|
31181
|
+
}
|
|
31182
|
+
return out;
|
|
31183
|
+
}
|
|
31184
|
+
|
|
31185
|
+
// Where a fill pattern's origin sits, in canvas pixels. In preview that is
|
|
31186
|
+
// the top-left of the page; otherwise the geographic origin, so a pan (which
|
|
31187
|
+
// only changes the translation) carries the pattern with the shapes.
|
|
31188
|
+
function getPatternAnchor(ext) {
|
|
31189
|
+
var t = ext.getTransform(GUI.getPixelRatio());
|
|
31190
|
+
var frame = ext.getFrameData();
|
|
31191
|
+
if (frame && frame.bbox) {
|
|
31192
|
+
return {
|
|
31193
|
+
x: frame.bbox[0] * t.mx + t.bx,
|
|
31194
|
+
y: frame.bbox[3] * t.my + t.by
|
|
31195
|
+
};
|
|
31196
|
+
}
|
|
31197
|
+
return {x: t.bx, y: t.by};
|
|
31198
|
+
}
|
|
31199
|
+
|
|
30814
31200
|
function getPathStart(ext, lineScale) {
|
|
30815
31201
|
var pixRatio = GUI.getPixelRatio();
|
|
30816
31202
|
if (!lineScale) lineScale = 1;
|
|
31203
|
+
// Same factor as lineWidth, so dash length and gap stay in proportion to
|
|
31204
|
+
// the stroke. In preview that factor includes the symbol scale; without it
|
|
31205
|
+
// the pattern stays a fixed screen size while the line grows.
|
|
31206
|
+
var strokeScale = getCanvasStrokeScale(pixRatio, lineScale);
|
|
31207
|
+
// The symbol scale itself, not lineScale: lineScale adds a bump to widen
|
|
31208
|
+
// hover outlines, which a fill has no reason to follow.
|
|
31209
|
+
var patternScale = ext.getSymbolScale() || 1;
|
|
31210
|
+
// Page corner in preview, so the pattern is fixed to the page the way the
|
|
31211
|
+
// exported SVG is. With no frame, the geographic origin, which is enough
|
|
31212
|
+
// for panning: the pattern is not scaled by the geographic zoom then.
|
|
31213
|
+
var patternAnchor = getPatternAnchor(ext);
|
|
30817
31214
|
return function(ctx, style) {
|
|
30818
31215
|
var strokeWidth;
|
|
30819
31216
|
ctx.beginPath();
|
|
30820
31217
|
if (style.strokeWidth > 0) {
|
|
30821
31218
|
strokeWidth = style.strokeWidth;
|
|
30822
|
-
if (pixRatio > 1) {
|
|
30823
|
-
// bump up thin lines on retina, but not to more than 1px
|
|
30824
|
-
// (tests on Chrome showed much faster rendering of 1px lines)
|
|
30825
|
-
// strokeWidth = strokeWidth < 1 ? 1 : strokeWidth * pixRatio;
|
|
30826
|
-
strokeWidth = strokeWidth * pixRatio;
|
|
30827
|
-
}
|
|
30828
31219
|
ctx.lineCap = style.lineCap || 'round';
|
|
30829
31220
|
ctx.lineJoin = style.lineJoin || 'round';
|
|
30830
|
-
ctx.lineWidth = strokeWidth *
|
|
31221
|
+
ctx.lineWidth = strokeWidth * strokeScale;
|
|
30831
31222
|
ctx.strokeStyle = style.strokeColor;
|
|
30832
31223
|
if (style.lineDash){
|
|
30833
31224
|
ctx.lineCap = 'butt';
|
|
30834
|
-
ctx.setLineDash(style.lineDash
|
|
31225
|
+
ctx.setLineDash(scaleCanvasLineDash(style.lineDash, strokeScale));
|
|
30835
31226
|
}
|
|
30836
31227
|
if (style.miterLimit) {
|
|
30837
31228
|
ctx.miterLimit = style.miterLimit;
|
|
@@ -30839,7 +31230,7 @@
|
|
|
30839
31230
|
}
|
|
30840
31231
|
|
|
30841
31232
|
if (style.fillPattern) {
|
|
30842
|
-
ctx.fillStyle = getCanvasFillPattern(style);
|
|
31233
|
+
ctx.fillStyle = getCanvasFillPattern(style, patternScale, patternAnchor);
|
|
30843
31234
|
} else if (style.fillColor) {
|
|
30844
31235
|
ctx.fillStyle = style.fillColor;
|
|
30845
31236
|
}
|
|
@@ -30878,20 +31269,28 @@
|
|
|
30878
31269
|
return internal.svg.getTransform(p, scale);
|
|
30879
31270
|
}
|
|
30880
31271
|
|
|
30881
|
-
function repositionFurniture(container,
|
|
30882
|
-
|
|
30883
|
-
|
|
31272
|
+
function repositionFurniture(container, ext) {
|
|
31273
|
+
if (!ext.getFrameData()) return;
|
|
31274
|
+
El.findAll('.mapshaper-svg-furniture', container).forEach(function(g) {
|
|
31275
|
+
g.setAttribute('transform', getSvgFurnitureTransform(ext));
|
|
31276
|
+
});
|
|
30884
31277
|
}
|
|
30885
31278
|
|
|
31279
|
+
// Returns an SVG string, or '' if the furniture can't be drawn in the current
|
|
31280
|
+
// frame (e.g. the map is unprojected). The scalebar panel explains why.
|
|
30886
31281
|
function renderFurniture(lyr, ext) {
|
|
30887
|
-
var frame = ext.getFrameData();
|
|
30888
|
-
var obj
|
|
30889
|
-
if (
|
|
30890
|
-
|
|
30891
|
-
}
|
|
31282
|
+
var frame = ext.getFrameData();
|
|
31283
|
+
var obj;
|
|
31284
|
+
if (internal.getFurnitureFrameProblem(frame)) return '';
|
|
31285
|
+
obj = internal.getEmptyLayerForSVG(lyr, {});
|
|
30892
31286
|
obj.properties.transform = getSvgFurnitureTransform(ext);
|
|
30893
31287
|
obj.properties.class = 'mapshaper-svg-furniture';
|
|
30894
|
-
|
|
31288
|
+
try {
|
|
31289
|
+
obj.children = internal.renderFurnitureLayer(lyr, frame);
|
|
31290
|
+
} catch(e) {
|
|
31291
|
+
console.error(e);
|
|
31292
|
+
return '';
|
|
31293
|
+
}
|
|
30895
31294
|
return internal.svg.stringify(obj);
|
|
30896
31295
|
}
|
|
30897
31296
|
|
|
@@ -30933,8 +31332,6 @@
|
|
|
30933
31332
|
resize(ext);
|
|
30934
31333
|
if (type == 'label' || type == 'symbol') {
|
|
30935
31334
|
html = renderSymbols(lyr.gui.displayLayer, ext, id);
|
|
30936
|
-
} else if (type == 'furniture') {
|
|
30937
|
-
html = renderFurniture(lyr.gui.displayLayer, ext);
|
|
30938
31335
|
}
|
|
30939
31336
|
g.innerHTML = html;
|
|
30940
31337
|
if (type == 'label' || type == 'symbol') {
|
|
@@ -30948,6 +31345,26 @@
|
|
|
30948
31345
|
}
|
|
30949
31346
|
};
|
|
30950
31347
|
|
|
31348
|
+
// Furniture layers are dataset layers (they have no display layer)
|
|
31349
|
+
el.drawFurniture = function(layers) {
|
|
31350
|
+
el.clear();
|
|
31351
|
+
resize(ext);
|
|
31352
|
+
layers.forEach(function(lyr) {
|
|
31353
|
+
var html = renderFurniture(lyr, ext);
|
|
31354
|
+
var g;
|
|
31355
|
+
if (!html) return;
|
|
31356
|
+
g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
31357
|
+
g.setAttribute('class', 'mapshaper-svg-layer mapshaper-furniture-layer');
|
|
31358
|
+
g.innerHTML = html;
|
|
31359
|
+
svg.append(g);
|
|
31360
|
+
});
|
|
31361
|
+
};
|
|
31362
|
+
|
|
31363
|
+
el.repositionFurniture = function() {
|
|
31364
|
+
resize(ext);
|
|
31365
|
+
repositionFurniture(svg, ext);
|
|
31366
|
+
};
|
|
31367
|
+
|
|
30951
31368
|
function reposition(lyr, type, ext) {
|
|
30952
31369
|
var container = el.findChild('.' + lyr.gui.svg_id);
|
|
30953
31370
|
if (!container || !container.node()) {
|
|
@@ -30961,8 +31378,6 @@
|
|
|
30961
31378
|
// a symbol group's transform absorbs panning, and zooming too when a
|
|
30962
31379
|
// frame is defined; anything it can't absorb needs the baselines rebuilt
|
|
30963
31380
|
updateLabelPaths(container.node(), lyr.gui.displayLayer, ext);
|
|
30964
|
-
} else if (type == 'furniture') {
|
|
30965
|
-
repositionFurniture(container.node(), lyr.gui.displayLayer, ext);
|
|
30966
31381
|
} else {
|
|
30967
31382
|
// container.getElementsByTagName('text')
|
|
30968
31383
|
error('Unsupported symbol type:', type);
|
|
@@ -31118,19 +31533,14 @@
|
|
|
31118
31533
|
});
|
|
31119
31534
|
};
|
|
31120
31535
|
|
|
31536
|
+
// Furniture is sized in frame pixels, so panning and zooming only need to
|
|
31537
|
+
// update its transform; its content changes only with a full redraw.
|
|
31121
31538
|
this.drawFurnitureLayers = function(layers, action) {
|
|
31122
|
-
|
|
31123
|
-
|
|
31124
|
-
|
|
31125
|
-
_furniture.
|
|
31539
|
+
if (action == 'nav' || action == 'hover') {
|
|
31540
|
+
_furniture.repositionFurniture();
|
|
31541
|
+
} else {
|
|
31542
|
+
_furniture.drawFurniture(layers);
|
|
31126
31543
|
}
|
|
31127
|
-
layers.forEach(function(lyr) {
|
|
31128
|
-
if (noRedraw) {
|
|
31129
|
-
_furniture.reposition(lyr, 'furniture');
|
|
31130
|
-
} else {
|
|
31131
|
-
_furniture.drawLayer(lyr, 'furniture');
|
|
31132
|
-
}
|
|
31133
|
-
});
|
|
31134
31544
|
};
|
|
31135
31545
|
|
|
31136
31546
|
// kludge: skip rendering base layers if hovering, except on first hover
|
|
@@ -31489,50 +31899,73 @@
|
|
|
31489
31899
|
gui.on('interaction_mode_change', function(e) {
|
|
31490
31900
|
if (e.mode === 'rectangles') {
|
|
31491
31901
|
gui.enterMode('rectangle_tool');
|
|
31492
|
-
} else if (
|
|
31493
|
-
turnOff()
|
|
31902
|
+
} else if (gui.getMode() == 'rectangle_tool') {
|
|
31903
|
+
// Leave the gui mode rather than just calling turnOff(): a mode left
|
|
31904
|
+
// behind makes the next enterMode('rectangle_tool') a no-op, so the tool
|
|
31905
|
+
// would never turn back on.
|
|
31906
|
+
gui.clearMode();
|
|
31494
31907
|
}
|
|
31495
31908
|
});
|
|
31496
31909
|
|
|
31497
31910
|
hit.on('change', function(e) {
|
|
31498
31911
|
if (!_on) return;
|
|
31499
31912
|
// TODO: handle multiple hits (see gui-inspection-control)
|
|
31500
|
-
var id = e.id;
|
|
31501
31913
|
if (e.id > -1 && e.pinned) {
|
|
31502
|
-
|
|
31503
|
-
|
|
31504
|
-
var bbox = target.gui.displayArcs.getSimpleShapeBounds(path).toArray();
|
|
31505
|
-
box.setDataCoords(bbox);
|
|
31506
|
-
dragInfo = {
|
|
31507
|
-
id: e.id,
|
|
31508
|
-
target: target,
|
|
31509
|
-
ids: [],
|
|
31510
|
-
points: []
|
|
31511
|
-
};
|
|
31512
|
-
var iter = target.gui.displayArcs.getShapeIter(path);
|
|
31513
|
-
while (iter.hasNext()) {
|
|
31514
|
-
dragInfo.points.push([iter.x, iter.y]);
|
|
31515
|
-
dragInfo.ids.push(iter._arc.i);
|
|
31914
|
+
if (!dragInfo || dragInfo.id != e.id) {
|
|
31915
|
+
selectRectangle(e.id);
|
|
31516
31916
|
}
|
|
31517
|
-
gui.container.findChild('.map-layers').classed('dragging', true);
|
|
31518
|
-
|
|
31519
|
-
} else if (dragInfo) {
|
|
31520
|
-
gui.dispatchEvent('rectangle_dragend', dragInfo); // save undo state
|
|
31521
|
-
gui.container.findChild('.map-layers').classed('dragging', false);
|
|
31522
|
-
reset();
|
|
31523
31917
|
} else {
|
|
31524
|
-
|
|
31918
|
+
reset();
|
|
31525
31919
|
}
|
|
31526
|
-
|
|
31527
31920
|
});
|
|
31528
31921
|
|
|
31529
31922
|
box.on('handle_drag', function(e) {
|
|
31530
31923
|
if (!_on || !dragInfo) return;
|
|
31531
31924
|
var coords = internal.bboxToCoords(box.getDataCoords());
|
|
31532
31925
|
setRectangleCoords(dragInfo.target, dragInfo.ids, coords);
|
|
31926
|
+
dragInfo.moved = true;
|
|
31533
31927
|
gui.dispatchEvent('map-needs-refresh');
|
|
31534
31928
|
});
|
|
31535
31929
|
|
|
31930
|
+
// Each handle drag is one undo step, saved when it ends -- waiting until the
|
|
31931
|
+
// rectangle is deselected would lose the edit if the mode changes first.
|
|
31932
|
+
box.on('handle_up', function() {
|
|
31933
|
+
if (!_on || !dragInfo || !dragInfo.moved) return;
|
|
31934
|
+
gui.dispatchEvent('rectangle_dragend', {
|
|
31935
|
+
target: dragInfo.target,
|
|
31936
|
+
ids: dragInfo.ids,
|
|
31937
|
+
points: dragInfo.points
|
|
31938
|
+
});
|
|
31939
|
+
dragInfo.points = getDataPoints(dragInfo.target, dragInfo.ids);
|
|
31940
|
+
dragInfo.moved = false;
|
|
31941
|
+
});
|
|
31942
|
+
|
|
31943
|
+
function selectRectangle(id) {
|
|
31944
|
+
var target = hit.getHitTarget();
|
|
31945
|
+
var path = target.shapes[id][0];
|
|
31946
|
+
var bbox = target.gui.displayArcs.getSimpleShapeBounds(path).toArray();
|
|
31947
|
+
var ids = [];
|
|
31948
|
+
var iter = target.gui.displayArcs.getShapeIter(path);
|
|
31949
|
+
while (iter.hasNext()) {
|
|
31950
|
+
ids.push(iter._arc.i);
|
|
31951
|
+
}
|
|
31952
|
+
box.setDataCoords(bbox);
|
|
31953
|
+
dragInfo = {
|
|
31954
|
+
id: id,
|
|
31955
|
+
target: target,
|
|
31956
|
+
ids: ids,
|
|
31957
|
+
points: getDataPoints(target, ids), // for undo
|
|
31958
|
+
moved: false
|
|
31959
|
+
};
|
|
31960
|
+
gui.container.findChild('.map-layers').classed('dragging', true);
|
|
31961
|
+
}
|
|
31962
|
+
|
|
31963
|
+
function getDataPoints(target, ids) {
|
|
31964
|
+
return ids.map(function(id) {
|
|
31965
|
+
return getVertexCoords(target, id);
|
|
31966
|
+
});
|
|
31967
|
+
}
|
|
31968
|
+
|
|
31536
31969
|
function turnOn() {
|
|
31537
31970
|
box.turnOn();
|
|
31538
31971
|
_on = true;
|
|
@@ -31550,6 +31983,9 @@
|
|
|
31550
31983
|
|
|
31551
31984
|
function reset() {
|
|
31552
31985
|
box.hide();
|
|
31986
|
+
if (dragInfo) {
|
|
31987
|
+
gui.container.findChild('.map-layers').classed('dragging', false);
|
|
31988
|
+
}
|
|
31553
31989
|
dragInfo = null;
|
|
31554
31990
|
}
|
|
31555
31991
|
}
|
|
@@ -32955,7 +33391,7 @@
|
|
|
32955
33391
|
|
|
32956
33392
|
function isFrameMapLayer(lyr) {
|
|
32957
33393
|
var dataset = lyr && lyr.gui && lyr.gui.source && lyr.gui.source.dataset;
|
|
32958
|
-
return !!dataset && internal.
|
|
33394
|
+
return !!dataset && internal.isFrameComponentLayer(lyr, dataset);
|
|
32959
33395
|
}
|
|
32960
33396
|
|
|
32961
33397
|
function getDrawableContentLayers() {
|
|
@@ -32965,11 +33401,18 @@
|
|
|
32965
33401
|
});
|
|
32966
33402
|
}
|
|
32967
33403
|
|
|
32968
|
-
|
|
32969
|
-
|
|
32970
|
-
|
|
32971
|
-
|
|
33404
|
+
// The active frame's furniture, plus any visible standalone furniture layers
|
|
33405
|
+
// (e.g. from -scalebar run before a frame was created)
|
|
33406
|
+
function getDrawableFurnitureLayers() {
|
|
33407
|
+
var frame = isPreviewView() ? internal.getActiveFrame(model) : null;
|
|
33408
|
+
if (!frame) return [];
|
|
33409
|
+
var layers = internal.getFrameFurnitureLayers(frame.dataset);
|
|
33410
|
+
getVisibleMapLayers().forEach(function(lyr) {
|
|
33411
|
+
if (internal.layerIsFurniture(lyr) && !layers.includes(lyr) && !lyr.hidden) {
|
|
33412
|
+
layers.push(lyr);
|
|
33413
|
+
}
|
|
32972
33414
|
});
|
|
33415
|
+
return layers;
|
|
32973
33416
|
}
|
|
32974
33417
|
|
|
32975
33418
|
function updateLayerStyles(layers) {
|
|
@@ -33021,7 +33464,6 @@
|
|
|
33021
33464
|
var layersMayHaveChanged = action != 'nav'; // !action;
|
|
33022
33465
|
var fullBounds;
|
|
33023
33466
|
var contentLayers = getDrawableContentLayers();
|
|
33024
|
-
// var furnitureLayers = getDrawableFurnitureLayers();
|
|
33025
33467
|
if (!(_ext.width() > 0 && _ext.height() > 0)) {
|
|
33026
33468
|
// TODO: track down source of these errors
|
|
33027
33469
|
console.error("Collapsed map container, unable to draw.");
|
|
@@ -33062,8 +33504,7 @@
|
|
|
33062
33504
|
}
|
|
33063
33505
|
_renderer.drawOverlayLayers(_overlayLayers, action);
|
|
33064
33506
|
|
|
33065
|
-
|
|
33066
|
-
// _renderer.drawFurnitureLayers(furnitureLayers, action);
|
|
33507
|
+
_renderer.drawFurnitureLayers(getDrawableFurnitureLayers(), action);
|
|
33067
33508
|
// The action says how much was redrawn, which a listener rebuilding its own
|
|
33068
33509
|
// DOM overlays needs: a 'hover' draw leaves the SVG markup and its
|
|
33069
33510
|
// transforms alone, so anything anchored to them is still good.
|
|
@@ -33613,22 +34054,20 @@
|
|
|
33613
34054
|
|
|
33614
34055
|
// Fitting obeys the same mode as a handle drag: holding the scale grows the
|
|
33615
34056
|
// output to cover the new extent, holding the output rescales onto it. The
|
|
33616
|
-
// command works out
|
|
33617
|
-
//
|
|
34057
|
+
// command works out the extent, which has to leave room for symbols and
|
|
34058
|
+
// labels at the scale it ends up at, along with the padding and the
|
|
34059
|
+
// held-scale width, so the margin's unit handling lives in one place.
|
|
33618
34060
|
function fitVisibleLayers() {
|
|
33619
34061
|
var target = getFrameTarget();
|
|
33620
34062
|
var entries = getCompositionEntries();
|
|
33621
34063
|
var margin = getMargin();
|
|
33622
|
-
var parts
|
|
34064
|
+
var parts;
|
|
33623
34065
|
if (!target || !entries.length) return;
|
|
33624
|
-
bounds = entries.reduce(function(memo, o) {
|
|
33625
|
-
return memo.mergeBounds(
|
|
33626
|
-
internal.getLayerBounds(o.layer, o.dataset.arcs)
|
|
33627
|
-
);
|
|
33628
|
-
}, new internal.Bounds());
|
|
33629
34066
|
parts = [
|
|
33630
34067
|
'-update-frame',
|
|
33631
|
-
'
|
|
34068
|
+
'fit=' + internal.formatOptionValue(entries.map(function(o) {
|
|
34069
|
+
return internal.getLayerTargetId(gui.model, o.layer);
|
|
34070
|
+
}).join(','))
|
|
33632
34071
|
];
|
|
33633
34072
|
if (margin) parts.push('offset=' + quoteCommandValue(margin));
|
|
33634
34073
|
if (!lockSize) parts.push('fix-scale');
|
|
@@ -33654,10 +34093,7 @@
|
|
|
33654
34093
|
return;
|
|
33655
34094
|
}
|
|
33656
34095
|
if (!getSourceLayer()) {
|
|
33657
|
-
showPopupAlert(
|
|
33658
|
-
'Add one or more layers before creating a map frame.',
|
|
33659
|
-
'Map frame'
|
|
33660
|
-
);
|
|
34096
|
+
showPopupAlert(getNoSourceMessage(), 'Map frame');
|
|
33661
34097
|
return;
|
|
33662
34098
|
}
|
|
33663
34099
|
var popup = showPopupAlert('', 'Add map frame', {classname: 'frame-create-box'});
|
|
@@ -33802,7 +34238,7 @@
|
|
|
33802
34238
|
function createFromVisibleLayers(width, aspect, margin) {
|
|
33803
34239
|
var entries = getCompositionEntries();
|
|
33804
34240
|
if (!entries.length) {
|
|
33805
|
-
showPopupAlert(
|
|
34241
|
+
showPopupAlert(getNoSourceMessage(), 'Map frame');
|
|
33806
34242
|
return;
|
|
33807
34243
|
}
|
|
33808
34244
|
var ids = entries.map(function(o) {
|
|
@@ -33825,6 +34261,15 @@
|
|
|
33825
34261
|
});
|
|
33826
34262
|
}
|
|
33827
34263
|
|
|
34264
|
+
function getNoSourceMessage() {
|
|
34265
|
+
var active = gui.model.getActiveLayer();
|
|
34266
|
+
if (active && active.layer && !internal.layerHasGeometry(active.layer)) {
|
|
34267
|
+
return 'A map frame is fitted to layers with shapes, and this layer ' +
|
|
34268
|
+
'contains only attribute data. Select or show a map layer to create a frame.';
|
|
34269
|
+
}
|
|
34270
|
+
return 'Add one or more layers before creating a map frame.';
|
|
34271
|
+
}
|
|
34272
|
+
|
|
33828
34273
|
function getFrameTarget() {
|
|
33829
34274
|
return internal.getActiveFrame(gui.model);
|
|
33830
34275
|
}
|
|
@@ -33844,10 +34289,11 @@
|
|
|
33844
34289
|
return entries.length ? entries[0].layer : null;
|
|
33845
34290
|
}
|
|
33846
34291
|
|
|
34292
|
+
// A frame is fitted to shapes, so a data-only layer on show doesn't count
|
|
33847
34293
|
function getCompositionEntries() {
|
|
33848
34294
|
var layers = gui.map.getCompositionLayers();
|
|
33849
34295
|
return gui.model.getLayers().filter(function(o) {
|
|
33850
|
-
return layers.includes(o.layer);
|
|
34296
|
+
return layers.includes(o.layer) && internal.layerHasGeometry(o.layer);
|
|
33851
34297
|
});
|
|
33852
34298
|
}
|
|
33853
34299
|
|
|
@@ -33884,16 +34330,6 @@
|
|
|
33884
34330
|
.text(label).on('click', action);
|
|
33885
34331
|
}
|
|
33886
34332
|
|
|
33887
|
-
// The "?" the rest of the app uses for field help (see .tip-button in
|
|
33888
|
-
// elements.css and the static ones in index.html). The bubble is white-space:
|
|
33889
|
-
// pre, so the line breaks in the text are the ones it gets.
|
|
33890
|
-
function makeFieldTip(parent, text) {
|
|
33891
|
-
var btn = El('div').addClass('tip-button').appendTo(parent).text('?');
|
|
33892
|
-
var anchor = El('div').addClass('tip-anchor').appendTo(btn);
|
|
33893
|
-
El('div').addClass('tip').appendTo(anchor).text(text);
|
|
33894
|
-
return btn;
|
|
33895
|
-
}
|
|
33896
|
-
|
|
33897
34333
|
function getUnitFactor$1(units) {
|
|
33898
34334
|
return units == 'in' ? 72 : units == 'cm' ? 28.3465 : 1;
|
|
33899
34335
|
}
|
|
@@ -33913,7 +34349,7 @@
|
|
|
33913
34349
|
function FrameProperties(gui) {
|
|
33914
34350
|
var target, form, widthInput, heightInput, unitsSelect, aspectValue;
|
|
33915
34351
|
var backgroundControl, neatlineControl, neatlineWidthInput;
|
|
33916
|
-
var boundsValue, crsValue;
|
|
34352
|
+
var boundsValue, crsValue, scalebarButton;
|
|
33917
34353
|
|
|
33918
34354
|
gui.frameProperties = this;
|
|
33919
34355
|
|
|
@@ -33975,9 +34411,9 @@
|
|
|
33975
34411
|
revert: updateControls
|
|
33976
34412
|
});
|
|
33977
34413
|
// The width belongs to the same property as the colour and opacity beside
|
|
33978
|
-
// it
|
|
33979
|
-
//
|
|
33980
|
-
neatlineWidthInput =
|
|
34414
|
+
// it. The background row leaves the column empty rather than putting the
|
|
34415
|
+
// width on a line of its own.
|
|
34416
|
+
neatlineWidthInput = makeCellField(neatlineControl.aside, 'Width')
|
|
33981
34417
|
.addClass('frame-neatline-width')
|
|
33982
34418
|
.on('change', updateNeatlineWidth);
|
|
33983
34419
|
var clearRow = El('div')
|
|
@@ -33985,6 +34421,14 @@
|
|
|
33985
34421
|
.appendTo(appearance);
|
|
33986
34422
|
makeActionButton(clearRow, 'Clear appearance', clearFrameStyle);
|
|
33987
34423
|
|
|
34424
|
+
var elements = makePanelSection(form, 'Map elements');
|
|
34425
|
+
var elementsRow = El('div')
|
|
34426
|
+
.addClass('label-style-row label-panel-button-row')
|
|
34427
|
+
.appendTo(elements);
|
|
34428
|
+
scalebarButton = makeActionButton(elementsRow, '', function() {
|
|
34429
|
+
if (gui.scalebarProperties) gui.scalebarProperties.open(target);
|
|
34430
|
+
}).addClass('frame-scalebar-btn');
|
|
34431
|
+
|
|
33988
34432
|
var details = makePanelSection(form, 'Details');
|
|
33989
34433
|
boundsValue = makeReadOnlyRow(details, 'Bounds');
|
|
33990
34434
|
crsValue = makeReadOnlyRow(details, 'CRS');
|
|
@@ -34002,11 +34446,13 @@
|
|
|
34002
34446
|
unitsSelect.node().value = units;
|
|
34003
34447
|
aspectValue.text(getAspectText(frame));
|
|
34004
34448
|
backgroundControl.showColor(rec.fill || '');
|
|
34005
|
-
backgroundControl.opacity.node().value =
|
|
34449
|
+
backgroundControl.opacity.node().value = formatColorOpacityPct(rec['fill-opacity'], !!rec.fill);
|
|
34006
34450
|
neatlineControl.showColor(rec.stroke || '');
|
|
34007
|
-
neatlineControl.opacity.node().value =
|
|
34451
|
+
neatlineControl.opacity.node().value = formatColorOpacityPct(rec['stroke-opacity'], !!rec.stroke);
|
|
34008
34452
|
neatlineWidthInput.node().value =
|
|
34009
34453
|
rec['stroke-width'] === undefined ? '' : rec['stroke-width'];
|
|
34454
|
+
scalebarButton.text(internal.findFrameFurnitureLayer(target.dataset, 'scalebar') ?
|
|
34455
|
+
'Edit scale bar' : 'Add scale bar');
|
|
34010
34456
|
boundsValue.text(frame.bbox.map(formatCoordinate).join(', '));
|
|
34011
34457
|
crsValue.text(info.proj4 || '[unknown]');
|
|
34012
34458
|
}
|
|
@@ -34092,8 +34538,7 @@
|
|
|
34092
34538
|
}
|
|
34093
34539
|
|
|
34094
34540
|
// An extra captioned cell on an existing colour row.
|
|
34095
|
-
function
|
|
34096
|
-
var cell = El('div').addClass('label-split-cell').appendTo(row);
|
|
34541
|
+
function makeCellField(cell, label) {
|
|
34097
34542
|
El('span').appendTo(cell).text(label);
|
|
34098
34543
|
return makeTextInput(cell);
|
|
34099
34544
|
}
|
|
@@ -34131,11 +34576,6 @@
|
|
|
34131
34576
|
return String(Number(value.toPrecision(12)));
|
|
34132
34577
|
}
|
|
34133
34578
|
|
|
34134
|
-
function formatOpacity(value) {
|
|
34135
|
-
return value === undefined ? '' :
|
|
34136
|
-
String(Math.round(Number(value) * 100)) + '%';
|
|
34137
|
-
}
|
|
34138
|
-
|
|
34139
34579
|
// A fixed ratio holds when the frame is rescaled; one taken from the extent
|
|
34140
34580
|
// changes whenever the extent does. That difference is the reason to show the
|
|
34141
34581
|
// ratio at all, so it is said rather than implied.
|
|
@@ -34149,6 +34589,434 @@
|
|
|
34149
34589
|
return formatFrameAspectRatio(ratio) + ' (from extent)';
|
|
34150
34590
|
}
|
|
34151
34591
|
|
|
34592
|
+
// Record fields of a scalebar, in the order they are written as options
|
|
34593
|
+
var scalebarOptions = ['units', 'style', 'position', 'label_position',
|
|
34594
|
+
'font_size', 'font_family', 'font_style', 'font_weight', 'color', 'bar_width',
|
|
34595
|
+
'tic_length', 'label_offset', 'margin'];
|
|
34596
|
+
|
|
34597
|
+
// Returns a -scalebar command that recreates a scalebar with the given
|
|
34598
|
+
// settings (a scalebar data record, with any edits applied). -scalebar
|
|
34599
|
+
// replaces the frame's scalebar, so every setting has to be written.
|
|
34600
|
+
function getScalebarCommand(settings) {
|
|
34601
|
+
var parts = ['-scalebar'];
|
|
34602
|
+
var d = settings || {};
|
|
34603
|
+
if (isSet(d.label)) {
|
|
34604
|
+
parts.push(quoteCommandValue(d.label));
|
|
34605
|
+
}
|
|
34606
|
+
scalebarOptions.forEach(function(k) {
|
|
34607
|
+
if (isSet(d[k])) {
|
|
34608
|
+
parts.push(k.replace(/_/g, '-') + '=' + formatValue$1(d[k]));
|
|
34609
|
+
}
|
|
34610
|
+
});
|
|
34611
|
+
if (d.dual_units) {
|
|
34612
|
+
parts.push('dual-units');
|
|
34613
|
+
}
|
|
34614
|
+
return parts.join(' ');
|
|
34615
|
+
}
|
|
34616
|
+
|
|
34617
|
+
function isSet(val) {
|
|
34618
|
+
return val !== undefined && val !== null && val !== '';
|
|
34619
|
+
}
|
|
34620
|
+
|
|
34621
|
+
function formatValue$1(val) {
|
|
34622
|
+
var str = String(val);
|
|
34623
|
+
return /^[\w.#-]+$/.test(str) ? str : quoteCommandValue(str);
|
|
34624
|
+
}
|
|
34625
|
+
|
|
34626
|
+
var positionOptions = [
|
|
34627
|
+
['top-left', 'Top left'],
|
|
34628
|
+
['top-right', 'Top right'],
|
|
34629
|
+
['bottom-left', 'Bottom left'],
|
|
34630
|
+
['bottom-right', 'Bottom right']
|
|
34631
|
+
];
|
|
34632
|
+
|
|
34633
|
+
// What the scalebar is drawn in when it has no color of its own
|
|
34634
|
+
var defaultColor = '#000000';
|
|
34635
|
+
|
|
34636
|
+
// Settings of the frame's scalebar. Every edit sends a complete -scalebar
|
|
34637
|
+
// command, which replaces the scalebar, so edits are undoable and replayable
|
|
34638
|
+
// like any other command.
|
|
34639
|
+
function ScalebarProperties(gui) {
|
|
34640
|
+
var popup, form, note;
|
|
34641
|
+
var labelInput, unitsSelect, styleSelect, dualUnitsBox, positionSelect,
|
|
34642
|
+
labelPositionSelect, fontSizeInput, barWidthInput, marginInput,
|
|
34643
|
+
fontSelect, fontStyleSelect, colorChit, colorInput, colorPicker;
|
|
34644
|
+
|
|
34645
|
+
gui.scalebarProperties = this;
|
|
34646
|
+
|
|
34647
|
+
// The panel doesn't block the map, so anything can change the scalebar or
|
|
34648
|
+
// the frame while it is open: an undo, a frame edit, a console command.
|
|
34649
|
+
gui.model.on('update', function(e) {
|
|
34650
|
+
var flags = e.flags || {};
|
|
34651
|
+
if (!popup || flags.simplify_amount || flags.redraw_only) return;
|
|
34652
|
+
if (getScalebarRecord()) {
|
|
34653
|
+
updateControls();
|
|
34654
|
+
} else {
|
|
34655
|
+
closePanel();
|
|
34656
|
+
}
|
|
34657
|
+
});
|
|
34658
|
+
|
|
34659
|
+
// Adds a scalebar to the frame if it doesn't have one
|
|
34660
|
+
this.open = function(frameTarget) {
|
|
34661
|
+
var frame = frameTarget || internal.getActiveFrame(gui.model);
|
|
34662
|
+
if (!frame) return;
|
|
34663
|
+
if (getScalebarRecord()) {
|
|
34664
|
+
showPanel();
|
|
34665
|
+
} else {
|
|
34666
|
+
runCommand('-scalebar', 'Add scale bar', showPanel);
|
|
34667
|
+
}
|
|
34668
|
+
};
|
|
34669
|
+
|
|
34670
|
+
this.isOpen = function() {
|
|
34671
|
+
return !!popup;
|
|
34672
|
+
};
|
|
34673
|
+
|
|
34674
|
+
// The map stays live, so the panel is placed where it is least likely to
|
|
34675
|
+
// hide the scalebar: at the top of the map, on the side away from it
|
|
34676
|
+
function showPanel() {
|
|
34677
|
+
var d = getScalebarRecord();
|
|
34678
|
+
if (!d) return;
|
|
34679
|
+
closePanel();
|
|
34680
|
+
popup = showPopupAlert('', 'Scale bar', {
|
|
34681
|
+
non_blocking: true,
|
|
34682
|
+
classname: 'scalebar-properties-box'
|
|
34683
|
+
});
|
|
34684
|
+
popup.onClose(function() {
|
|
34685
|
+
popup = form = null;
|
|
34686
|
+
});
|
|
34687
|
+
popup.container().addClass('scalebar-properties-popup');
|
|
34688
|
+
form = El('div')
|
|
34689
|
+
.addClass('label-style-panel scalebar-properties-form')
|
|
34690
|
+
.appendTo(popup.container());
|
|
34691
|
+
initForm();
|
|
34692
|
+
updateControls();
|
|
34693
|
+
placePanel(/right/.test(d.position || '') ? 'left' : 'right');
|
|
34694
|
+
}
|
|
34695
|
+
|
|
34696
|
+
function closePanel() {
|
|
34697
|
+
if (popup) popup.close();
|
|
34698
|
+
}
|
|
34699
|
+
|
|
34700
|
+
function placePanel(side) {
|
|
34701
|
+
var box = popup.container().node().parentNode;
|
|
34702
|
+
var map = gui.container.findChild('.map-layers').node().getBoundingClientRect();
|
|
34703
|
+
// clears the zoom buttons on the right edge of the map
|
|
34704
|
+
var rightInset = 58;
|
|
34705
|
+
var leftInset = 16;
|
|
34706
|
+
box.style.position = 'absolute';
|
|
34707
|
+
box.style.marginTop = '0';
|
|
34708
|
+
box.style.top = Math.round(map.top + 12) + 'px';
|
|
34709
|
+
if (side == 'left') {
|
|
34710
|
+
box.style.left = Math.round(map.left + leftInset) + 'px';
|
|
34711
|
+
} else {
|
|
34712
|
+
box.style.right = Math.round(window.innerWidth - map.right + rightInset) + 'px';
|
|
34713
|
+
}
|
|
34714
|
+
}
|
|
34715
|
+
|
|
34716
|
+
function initForm() {
|
|
34717
|
+
var row;
|
|
34718
|
+
note = El('div').addClass('scalebar-properties-note').appendTo(form).hide();
|
|
34719
|
+
|
|
34720
|
+
row = makeRow(form);
|
|
34721
|
+
labelInput = makeInput(makeCell(row, 'Distance'), 'scalebar-label-input')
|
|
34722
|
+
.attr('placeholder', 'Automatic')
|
|
34723
|
+
.on('change', function() {
|
|
34724
|
+
update({label: labelInput.node().value.trim()});
|
|
34725
|
+
});
|
|
34726
|
+
unitsSelect = makeSelect(makeCell(row, 'Units'), 'scalebar-units-select',
|
|
34727
|
+
[['miles', 'Miles'], ['km', 'Kilometers']])
|
|
34728
|
+
.on('change', function() {
|
|
34729
|
+
var changes = {units: unitsSelect.node().value};
|
|
34730
|
+
var bare = getBareDistance(getScalebarRecord().label);
|
|
34731
|
+
if (bare) changes.label = bare;
|
|
34732
|
+
update(changes);
|
|
34733
|
+
});
|
|
34734
|
+
|
|
34735
|
+
row = makeRow(form);
|
|
34736
|
+
styleSelect = makeSelect(makeCell(row, 'Style'), 'scalebar-style-select',
|
|
34737
|
+
[['a', 'Bar'], ['b', 'Ticks']])
|
|
34738
|
+
.on('change', function() {
|
|
34739
|
+
update({style: styleSelect.node().value});
|
|
34740
|
+
});
|
|
34741
|
+
dualUnitsBox = makeCheckbox(row, 'Metric + imperial', 'scalebar-dual-units')
|
|
34742
|
+
.on('change', function() {
|
|
34743
|
+
update({dual_units: dualUnitsBox.node().checked});
|
|
34744
|
+
});
|
|
34745
|
+
|
|
34746
|
+
row = makeRow(form);
|
|
34747
|
+
positionSelect = makeSelect(makeCell(row, 'Position'), 'scalebar-position-select',
|
|
34748
|
+
positionOptions)
|
|
34749
|
+
.on('change', function() {
|
|
34750
|
+
update({position: positionSelect.node().value});
|
|
34751
|
+
});
|
|
34752
|
+
labelPositionSelect = makeSelect(makeCell(row, 'Labels'),
|
|
34753
|
+
'scalebar-label-position-select', [['top', 'Above'], ['bottom', 'Below']])
|
|
34754
|
+
.on('change', function() {
|
|
34755
|
+
update({label_position: labelPositionSelect.node().value});
|
|
34756
|
+
});
|
|
34757
|
+
|
|
34758
|
+
var appearance = makePanelSection(form, 'Appearance');
|
|
34759
|
+
|
|
34760
|
+
// Font and face menus, as in the label panel
|
|
34761
|
+
row = makeRow(appearance);
|
|
34762
|
+
fontSelect = El('select').attr('title', 'Font').addClass('scalebar-font-select')
|
|
34763
|
+
.appendTo(makeCell(row, 'Font'))
|
|
34764
|
+
.on('change', function() {
|
|
34765
|
+
if (fontSelect.node().value) applyFont(fontSelect.node().value);
|
|
34766
|
+
});
|
|
34767
|
+
renderFontOptions();
|
|
34768
|
+
fontStyleSelect = El('select').attr('title', 'Font style')
|
|
34769
|
+
.addClass('scalebar-font-style-select')
|
|
34770
|
+
.appendTo(makeCell(row, 'Font style'))
|
|
34771
|
+
.on('change', function() {
|
|
34772
|
+
if (fontStyleSelect.node().value) applyFontStyle(fontStyleSelect.node().value);
|
|
34773
|
+
});
|
|
34774
|
+
|
|
34775
|
+
// The picker lines up with the right edge of its parent, so the row is its
|
|
34776
|
+
// parent rather than the narrow color cell
|
|
34777
|
+
row = makeRow(appearance);
|
|
34778
|
+
var colorCell = El('div').addClass('frame-input-cell scalebar-color-cell').appendTo(row);
|
|
34779
|
+
El('span').appendTo(colorCell).text('Color');
|
|
34780
|
+
colorChit = El('div').addClass('label-color-chit').attr('role', 'button')
|
|
34781
|
+
.on('click', function() { colorPicker.toggle(); });
|
|
34782
|
+
colorInput = El('input').attr('type', 'text').attr('title', 'Scale bar color')
|
|
34783
|
+
.on('change', function() {
|
|
34784
|
+
var color = colorInput.node().value.trim();
|
|
34785
|
+
if (!color) {
|
|
34786
|
+
updateControls();
|
|
34787
|
+
return;
|
|
34788
|
+
}
|
|
34789
|
+
if (isHexColor(color)) colorPicker.setColor(color);
|
|
34790
|
+
update({color: color});
|
|
34791
|
+
});
|
|
34792
|
+
makeColorField(colorCell, colorChit, colorInput);
|
|
34793
|
+
colorPicker = new ColorPicker(row, {
|
|
34794
|
+
onPreview: showColor,
|
|
34795
|
+
onChange: function(hex) {
|
|
34796
|
+
showColor(hex);
|
|
34797
|
+
update({color: hex});
|
|
34798
|
+
}
|
|
34799
|
+
});
|
|
34800
|
+
fontSizeInput = makeNumberInput(makeCell(row, 'Font size'), 'font_size',
|
|
34801
|
+
'scalebar-font-size');
|
|
34802
|
+
|
|
34803
|
+
row = makeRow(appearance);
|
|
34804
|
+
barWidthInput = makeNumberInput(makeCell(row, 'Bar width'), 'bar_width',
|
|
34805
|
+
'scalebar-bar-width');
|
|
34806
|
+
marginInput = makeNumberInput(makeCell(row, 'Inset'), 'margin',
|
|
34807
|
+
'scalebar-margin')
|
|
34808
|
+
.attr('title', 'Distance from the edges of the frame, in pixels');
|
|
34809
|
+
|
|
34810
|
+
row = El('div').addClass('label-style-row label-panel-button-row').appendTo(form);
|
|
34811
|
+
makePanelActionButton(row, 'Remove scale bar', function() {
|
|
34812
|
+
runCommand('-scalebar remove', 'Remove scale bar', function() {
|
|
34813
|
+
if (popup) popup.close();
|
|
34814
|
+
});
|
|
34815
|
+
});
|
|
34816
|
+
}
|
|
34817
|
+
|
|
34818
|
+
function updateControls() {
|
|
34819
|
+
var d = getScalebarRecord();
|
|
34820
|
+
var problem;
|
|
34821
|
+
if (!d || !form) return;
|
|
34822
|
+
labelInput.node().value = d.label || '';
|
|
34823
|
+
setSelectValue(unitsSelect, getUnitsMenuValue(d.units ||
|
|
34824
|
+
internal.parseScalebarUnits(d.label || '')));
|
|
34825
|
+
setSelectValue(styleSelect, d.style == 'b' || d.style == 'B' ||
|
|
34826
|
+
!d.style && d.dual_units ? 'b' : 'a');
|
|
34827
|
+
dualUnitsBox.node().checked = !!d.dual_units;
|
|
34828
|
+
setSelectValue(positionSelect, d.position || 'top-left');
|
|
34829
|
+
setSelectValue(labelPositionSelect, d.label_position || 'top');
|
|
34830
|
+
fontSizeInput.node().value = formatValue(d.font_size);
|
|
34831
|
+
barWidthInput.node().value = formatValue(d.bar_width);
|
|
34832
|
+
marginInput.node().value = formatValue(d.margin);
|
|
34833
|
+
updateFontControls(d);
|
|
34834
|
+
showColor(d.color || defaultColor);
|
|
34835
|
+
if (isHexColor(d.color || defaultColor)) colorPicker.setColor(d.color || defaultColor);
|
|
34836
|
+
problem = getFrameProblem();
|
|
34837
|
+
note.text(problem ? 'The scale bar is hidden: ' + problem + '.' : '');
|
|
34838
|
+
if (problem) note.show(); else note.hide();
|
|
34839
|
+
}
|
|
34840
|
+
|
|
34841
|
+
function showColor(color) {
|
|
34842
|
+
colorInput.node().value = color || '';
|
|
34843
|
+
colorChit.css('background-color', color || 'transparent');
|
|
34844
|
+
}
|
|
34845
|
+
|
|
34846
|
+
function renderFontOptions() {
|
|
34847
|
+
getInstalledFonts().forEach(function(group) {
|
|
34848
|
+
var optgroup = El('optgroup').attr('label', group.name).appendTo(fontSelect);
|
|
34849
|
+
group.fonts.forEach(function(fontName) {
|
|
34850
|
+
El('option').attr('value', fontName).appendTo(optgroup).text(fontName);
|
|
34851
|
+
});
|
|
34852
|
+
});
|
|
34853
|
+
}
|
|
34854
|
+
|
|
34855
|
+
// A scalebar with no font of its own is shown in the font it is drawn in
|
|
34856
|
+
function getShownFont(d) {
|
|
34857
|
+
return d.font_family || getDefaultFontName();
|
|
34858
|
+
}
|
|
34859
|
+
|
|
34860
|
+
function updateFontControls(d) {
|
|
34861
|
+
var font = getShownFont(d);
|
|
34862
|
+
var shown = font ? getNearestFontStyleVariant(font, d.font_style, d.font_weight) : null;
|
|
34863
|
+
if (font) {
|
|
34864
|
+
setSelectValue(fontSelect, font);
|
|
34865
|
+
} else {
|
|
34866
|
+
fontSelect.node().selectedIndex = -1;
|
|
34867
|
+
}
|
|
34868
|
+
fontStyleSelect.empty();
|
|
34869
|
+
getFontStyleVariants(font).forEach(function(variant) {
|
|
34870
|
+
El('option').attr('value', variant.value).appendTo(fontStyleSelect).text(variant.label);
|
|
34871
|
+
});
|
|
34872
|
+
fontStyleSelect.node().disabled = !font;
|
|
34873
|
+
if (shown) {
|
|
34874
|
+
fontStyleSelect.node().value = shown.value;
|
|
34875
|
+
} else {
|
|
34876
|
+
fontStyleSelect.node().selectedIndex = -1;
|
|
34877
|
+
}
|
|
34878
|
+
}
|
|
34879
|
+
|
|
34880
|
+
// Keeps the current face, or the nearest one the new font has
|
|
34881
|
+
function applyFont(fontName) {
|
|
34882
|
+
var d = getScalebarRecord();
|
|
34883
|
+
var changes = {font_family: fontName};
|
|
34884
|
+
var variant = getNearestFontStyleVariant(fontName, d.font_style, d.font_weight);
|
|
34885
|
+
if (variant) Object.assign(changes, getFontStyleValues(variant));
|
|
34886
|
+
update(changes);
|
|
34887
|
+
}
|
|
34888
|
+
|
|
34889
|
+
// A face belongs to a font, so a scalebar given one names the font it is
|
|
34890
|
+
// drawn in
|
|
34891
|
+
function applyFontStyle(value) {
|
|
34892
|
+
var parts = value.split('|');
|
|
34893
|
+
var changes = getFontStyleValues({style: parts[0], weight: parts[1]});
|
|
34894
|
+
var d = getScalebarRecord();
|
|
34895
|
+
if (!d.font_family && getDefaultFontName()) {
|
|
34896
|
+
changes.font_family = getDefaultFontName();
|
|
34897
|
+
}
|
|
34898
|
+
update(changes);
|
|
34899
|
+
}
|
|
34900
|
+
|
|
34901
|
+
// Regular is stored as no face at all
|
|
34902
|
+
function getFontStyleValues(variant) {
|
|
34903
|
+
if (variantIsRegular(variant)) {
|
|
34904
|
+
return {font_style: '', font_weight: ''};
|
|
34905
|
+
}
|
|
34906
|
+
return {font_style: variant.style, font_weight: variant.weight};
|
|
34907
|
+
}
|
|
34908
|
+
|
|
34909
|
+
// Applies edits to the current settings and replaces the scalebar. The
|
|
34910
|
+
// units menu always applies, so it is written even when it shows the default.
|
|
34911
|
+
function update(changes) {
|
|
34912
|
+
var d = Object.assign({}, getScalebarRecord(), changes);
|
|
34913
|
+
if (changes.style == 'a') d.dual_units = false;
|
|
34914
|
+
if (changes.dual_units) d.style = 'b';
|
|
34915
|
+
if (!d.units) d.units = unitsSelect.node().value;
|
|
34916
|
+
runCommand(getScalebarCommand(d), 'Update scale bar', updateControls);
|
|
34917
|
+
}
|
|
34918
|
+
|
|
34919
|
+
function makeNumberInput(cell, field, className) {
|
|
34920
|
+
var input = makeInput(cell, className).attr('placeholder', 'auto');
|
|
34921
|
+
input.on('change', function() {
|
|
34922
|
+
var str = input.node().value.trim();
|
|
34923
|
+
var o = {};
|
|
34924
|
+
if (str !== '' && !(Number(str) >= 0)) {
|
|
34925
|
+
updateControls();
|
|
34926
|
+
return;
|
|
34927
|
+
}
|
|
34928
|
+
o[field] = str === '' ? '' : Number(str);
|
|
34929
|
+
update(o);
|
|
34930
|
+
});
|
|
34931
|
+
return input;
|
|
34932
|
+
}
|
|
34933
|
+
|
|
34934
|
+
function getFrameProblem() {
|
|
34935
|
+
var frame = internal.getActiveFrame(gui.model);
|
|
34936
|
+
var crs, data;
|
|
34937
|
+
if (!frame) return 'there is no map frame';
|
|
34938
|
+
crs = frame.layer.gui && frame.layer.gui.dynamic_crs ||
|
|
34939
|
+
internal.getDatasetCRS(frame.dataset);
|
|
34940
|
+
data = internal.getFrameLayerData(frame.layer, frame.dataset.arcs, crs);
|
|
34941
|
+
return internal.getFurnitureFrameProblem(data);
|
|
34942
|
+
}
|
|
34943
|
+
|
|
34944
|
+
function getScalebarRecord() {
|
|
34945
|
+
var frame = internal.getActiveFrame(gui.model);
|
|
34946
|
+
var lyr = frame && internal.findFrameFurnitureLayer(frame.dataset, 'scalebar');
|
|
34947
|
+
return lyr ? lyr.data.getReadOnlyRecordAt(0) : null;
|
|
34948
|
+
}
|
|
34949
|
+
|
|
34950
|
+
function runCommand(cmd, title, done) {
|
|
34951
|
+
runGuiEditCommand(gui, cmd, {
|
|
34952
|
+
title: title,
|
|
34953
|
+
onSuccess: done,
|
|
34954
|
+
onError: updateControls
|
|
34955
|
+
});
|
|
34956
|
+
}
|
|
34957
|
+
}
|
|
34958
|
+
|
|
34959
|
+
function makeRow(parent, className) {
|
|
34960
|
+
return El('div').addClass('label-style-row scalebar-row')
|
|
34961
|
+
.addClass(className || 'scalebar-two-col').appendTo(parent);
|
|
34962
|
+
}
|
|
34963
|
+
|
|
34964
|
+
function makeCell(row, label) {
|
|
34965
|
+
var cell = El('label').addClass('frame-input-cell').appendTo(row);
|
|
34966
|
+
El('span').appendTo(cell).text(label);
|
|
34967
|
+
return cell;
|
|
34968
|
+
}
|
|
34969
|
+
|
|
34970
|
+
function makeInput(parent, className) {
|
|
34971
|
+
return El('input').attr('type', 'text').addClass(className).appendTo(parent);
|
|
34972
|
+
}
|
|
34973
|
+
|
|
34974
|
+
function makeSelect(parent, className, options) {
|
|
34975
|
+
var select = El('select').addClass(className).appendTo(parent);
|
|
34976
|
+
options.forEach(function(o) {
|
|
34977
|
+
El('option').attr('value', o[0]).appendTo(select).text(o[1]);
|
|
34978
|
+
});
|
|
34979
|
+
return select;
|
|
34980
|
+
}
|
|
34981
|
+
|
|
34982
|
+
function makeCheckbox(parent, label, className) {
|
|
34983
|
+
var box = El('input').attr('type', 'checkbox').addClass(className);
|
|
34984
|
+
var wrapper = El('label').addClass('scalebar-checkbox').appendTo(parent);
|
|
34985
|
+
box.appendTo(wrapper);
|
|
34986
|
+
El('span').appendTo(wrapper).text(label);
|
|
34987
|
+
return box;
|
|
34988
|
+
}
|
|
34989
|
+
|
|
34990
|
+
// Shows a value that came from the command line even if the menu lacks it
|
|
34991
|
+
function setSelectValue(select, value) {
|
|
34992
|
+
var node = select.node();
|
|
34993
|
+
var found = Array.prototype.some.call(node.options, function(o) {
|
|
34994
|
+
return o.value == value;
|
|
34995
|
+
});
|
|
34996
|
+
if (!found) {
|
|
34997
|
+
El('option').attr('value', value).appendTo(select).text(value);
|
|
34998
|
+
}
|
|
34999
|
+
node.value = value;
|
|
35000
|
+
}
|
|
35001
|
+
|
|
35002
|
+
// The number of a single-distance label, e.g. "100" from "100 km", so the
|
|
35003
|
+
// units menu can apply to it
|
|
35004
|
+
function getBareDistance(label) {
|
|
35005
|
+
var match = /^([\d\s.,/]*\d)\s*[^\d\s.,/]+$/.exec(String(label || '').trim());
|
|
35006
|
+
return match ? match[1].trim() : '';
|
|
35007
|
+
}
|
|
35008
|
+
|
|
35009
|
+
function getUnitsMenuValue(units) {
|
|
35010
|
+
var parsed = internal.parseLabelUnits(units);
|
|
35011
|
+
return parsed == 'mile' || !parsed ? 'miles' :
|
|
35012
|
+
parsed == 'm' ? 'meters' :
|
|
35013
|
+
parsed == 'ft' ? 'feet' : 'km';
|
|
35014
|
+
}
|
|
35015
|
+
|
|
35016
|
+
function formatValue(val) {
|
|
35017
|
+
return val === undefined || val === null ? '' : String(val);
|
|
35018
|
+
}
|
|
35019
|
+
|
|
34152
35020
|
// This is a new way to handle compatibility problems between
|
|
34153
35021
|
// interactive editing modes and other interface modes
|
|
34154
35022
|
// (by default, interactive modes stay on when, e.g., the user clicks
|
|
@@ -34951,6 +35819,7 @@
|
|
|
34951
35819
|
new PreviewMode(gui);
|
|
34952
35820
|
new FrameResizeTool(gui);
|
|
34953
35821
|
new FrameProperties(gui);
|
|
35822
|
+
new ScalebarProperties(gui);
|
|
34954
35823
|
gui.editToolbar = new EditToolbar(gui);
|
|
34955
35824
|
gui.labelTool = new LabelTool(gui);
|
|
34956
35825
|
gui.layerStyleTool = new LayerStyleTool(gui);
|
|
@@ -35151,7 +36020,8 @@
|
|
|
35151
36020
|
}
|
|
35152
36021
|
|
|
35153
36022
|
function clampSidebarPanelsSeparatorPosition(pageY) {
|
|
35154
|
-
var
|
|
36023
|
+
var rect = gui.container.findChild('.sidebar-panels').node().getBoundingClientRect();
|
|
36024
|
+
var pct = 100 * (pageY - rect.top) / rect.height;
|
|
35155
36025
|
return Math.max(15, Math.min(85, pct));
|
|
35156
36026
|
}
|
|
35157
36027
|
}
|
|
@@ -35582,6 +36452,7 @@
|
|
|
35582
36452
|
new ImportControl(gui, importOpts);
|
|
35583
36453
|
new ExportControl(gui);
|
|
35584
36454
|
new LayerControl(gui);
|
|
36455
|
+
new SidebarTabs(gui);
|
|
35585
36456
|
new AddLayerLinks(gui);
|
|
35586
36457
|
HeaderMenu();
|
|
35587
36458
|
gui.console = new Console(gui);
|
|
@@ -35644,6 +36515,24 @@
|
|
|
35644
36515
|
El('body').addClass('map-view');
|
|
35645
36516
|
gui.console.runInitialCommands(getInitialConsoleCommands());
|
|
35646
36517
|
});
|
|
36518
|
+
|
|
36519
|
+
// The layer list, opened beside the first data so that the user sees what
|
|
36520
|
+
// was imported and where to go next -- once, and only for data: dismissing
|
|
36521
|
+
// the splash popup without importing anything leaves the sidebar closed.
|
|
36522
|
+
// Waits for any menu mode (the import dialog, while files are still loading)
|
|
36523
|
+
// to close, since opening a panel would close it.
|
|
36524
|
+
var layersShownForData = false;
|
|
36525
|
+
gui.model.on('update', showLayersForFirstData);
|
|
36526
|
+
gui.on('mode', showLayersForFirstData);
|
|
36527
|
+
function showLayersForFirstData() {
|
|
36528
|
+
var panels;
|
|
36529
|
+
if (layersShownForData || gui.model.isEmpty() || gui.getMode()) return;
|
|
36530
|
+
layersShownForData = true;
|
|
36531
|
+
panels = gui.getSidebarPanels();
|
|
36532
|
+
if (!panels.includes('layers')) {
|
|
36533
|
+
gui.setSidebarPanels(panels.concat('layers').sort());
|
|
36534
|
+
}
|
|
36535
|
+
}
|
|
35647
36536
|
};
|
|
35648
36537
|
|
|
35649
36538
|
})();
|