mapshaper 0.7.15 → 0.7.17
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 +953 -86
- package/package.json +1 -1
- package/www/index.html +8 -32
- package/www/mapshaper-gui.js +2803 -577
- package/www/mapshaper.js +953 -86
- package/www/page.css +406 -18
package/www/mapshaper-gui.js
CHANGED
|
@@ -1476,29 +1476,27 @@
|
|
|
1476
1476
|
}
|
|
1477
1477
|
|
|
1478
1478
|
function SessionSnapshots(gui) {
|
|
1479
|
-
var _menuOpen = false;
|
|
1480
|
-
var _menuTimeout;
|
|
1481
|
-
var btn, menu;
|
|
1482
|
-
|
|
1483
1479
|
gui.sessionSnapshots = {
|
|
1480
|
+
enabled: false,
|
|
1484
1481
|
saveSnapshot: function() {
|
|
1485
1482
|
return saveSnapshot(gui);
|
|
1486
1483
|
},
|
|
1487
1484
|
restoreLatestSnapshot: function() {
|
|
1488
1485
|
return restoreLatestSnapshot(gui);
|
|
1486
|
+
},
|
|
1487
|
+
renderSnapshotList: function(el) {
|
|
1488
|
+
return renderSnapshotList(el, gui);
|
|
1489
1489
|
}
|
|
1490
1490
|
};
|
|
1491
1491
|
|
|
1492
1492
|
init();
|
|
1493
1493
|
|
|
1494
1494
|
async function init() {
|
|
1495
|
-
btn = gui.buttons.addButton('#ribbon-icon').addClass('menu-btn save-btn');
|
|
1496
1495
|
var enabled = await isStorageEnabled();
|
|
1497
1496
|
if (!enabled) {
|
|
1498
|
-
btn.remove();
|
|
1499
1497
|
return;
|
|
1500
1498
|
}
|
|
1501
|
-
|
|
1499
|
+
gui.sessionSnapshots.enabled = true;
|
|
1502
1500
|
startLifecycle();
|
|
1503
1501
|
await initialCleanup();
|
|
1504
1502
|
|
|
@@ -1514,98 +1512,6 @@
|
|
|
1514
1512
|
announceLeaving();
|
|
1515
1513
|
attemptOwnDataDeletion();
|
|
1516
1514
|
});
|
|
1517
|
-
|
|
1518
|
-
btn.on('mouseenter', function() {
|
|
1519
|
-
btn.addClass('hover');
|
|
1520
|
-
clearTimeout(_menuTimeout); // prevent timed closing
|
|
1521
|
-
if (!_menuOpen) {
|
|
1522
|
-
openMenu();
|
|
1523
|
-
}
|
|
1524
|
-
});
|
|
1525
|
-
|
|
1526
|
-
btn.on('mouseleave', function() {
|
|
1527
|
-
if (!_menuOpen) {
|
|
1528
|
-
btn.removeClass('hover');
|
|
1529
|
-
} else {
|
|
1530
|
-
closeMenu(200);
|
|
1531
|
-
}
|
|
1532
|
-
});
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
async function renderMenu() {
|
|
1536
|
-
var snapshots = await fetchSnapshotList();
|
|
1537
|
-
|
|
1538
|
-
menu.empty();
|
|
1539
|
-
|
|
1540
|
-
addMenuLink({
|
|
1541
|
-
slug: 'stash',
|
|
1542
|
-
// label: 'save data snapshot',
|
|
1543
|
-
label: 'create a snapshot',
|
|
1544
|
-
action: saveSnapshot
|
|
1545
|
-
});
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
// var available = await getAvailableStorage();
|
|
1550
|
-
// if (available) {
|
|
1551
|
-
// El('div').addClass('save-menu-entry').text(available + ' available').appendTo(menu);
|
|
1552
|
-
// }
|
|
1553
|
-
|
|
1554
|
-
// if (snapshots.length > 0) {
|
|
1555
|
-
// El('div').addClass('save-menu-entry').text('snapshots').appendTo(menu);
|
|
1556
|
-
// }
|
|
1557
|
-
|
|
1558
|
-
snapshots.forEach(function(item, i) {
|
|
1559
|
-
var line = El('div').appendTo(menu).addClass('save-menu-item');
|
|
1560
|
-
El('span').appendTo(line).html(`<span class="save-item-label">#${item.number}</span> `);
|
|
1561
|
-
// show snapshot size
|
|
1562
|
-
El('span').appendTo(line).html(` <span class="save-item-size">${item.display_size}</span>`);
|
|
1563
|
-
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1564
|
-
await restoreSnapshotById(item.id, gui);
|
|
1565
|
-
closeMenu(100);
|
|
1566
|
-
}).text('restore');
|
|
1567
|
-
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1568
|
-
var obj = await idb$2.get(item.id);
|
|
1569
|
-
await internal.compressSnapshotForExport(obj);
|
|
1570
|
-
var buf = internal.pack(obj);
|
|
1571
|
-
var fileName = `snapshot-${String(item.number).padStart(2, '0')}.msx`;
|
|
1572
|
-
// choose output filename and directory every time, if supported
|
|
1573
|
-
saveBlobToLocalFile2(fileName, new Blob([buf]));
|
|
1574
|
-
}).text('export');
|
|
1575
|
-
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1576
|
-
await removeSnapshotById(item.id);
|
|
1577
|
-
closeMenu(300);
|
|
1578
|
-
renderMenu();
|
|
1579
|
-
}).text('remove');
|
|
1580
|
-
});
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
function addMenuLink(item) {
|
|
1584
|
-
var line = El('div').appendTo(menu);
|
|
1585
|
-
var link = El('div').addClass('save-menu-link save-menu-entry').attr('data-name', item.slug).text(item.label).appendTo(line);
|
|
1586
|
-
link.on('click', async function(e) {
|
|
1587
|
-
await item.action(gui);
|
|
1588
|
-
e.stopPropagation();
|
|
1589
|
-
});
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
function openMenu() {
|
|
1593
|
-
clearTimeout(_menuTimeout);
|
|
1594
|
-
if (!_menuOpen) {
|
|
1595
|
-
btn.addClass('open');
|
|
1596
|
-
_menuOpen = true;
|
|
1597
|
-
renderMenu();
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
function closeMenu(delay) {
|
|
1602
|
-
if (!_menuOpen) return;
|
|
1603
|
-
clearTimeout(_menuTimeout);
|
|
1604
|
-
_menuTimeout = setTimeout(function() {
|
|
1605
|
-
_menuOpen = false;
|
|
1606
|
-
btn.removeClass('open');
|
|
1607
|
-
btn.removeClass('hover');
|
|
1608
|
-
}, delay || 0);
|
|
1609
1515
|
}
|
|
1610
1516
|
|
|
1611
1517
|
async function saveSnapshot(gui) {
|
|
@@ -1631,10 +1537,37 @@
|
|
|
1631
1537
|
await idb$2.set(entry.id, obj);
|
|
1632
1538
|
ownSnapshotIds.add(entry.id);
|
|
1633
1539
|
await addToIndex(entry);
|
|
1634
|
-
renderMenu();
|
|
1635
1540
|
}
|
|
1636
1541
|
}
|
|
1637
1542
|
|
|
1543
|
+
async function renderSnapshotList(el, gui) {
|
|
1544
|
+
var snapshots = await fetchSnapshotList();
|
|
1545
|
+
el.empty();
|
|
1546
|
+
snapshots.forEach(function(item) {
|
|
1547
|
+
var line = El('div').appendTo(el).addClass('save-menu-item');
|
|
1548
|
+
El('span').appendTo(line).html(`<span class="save-item-label">#${item.number}</span> `);
|
|
1549
|
+
El('span').appendTo(line).html(` <span class="save-item-size">${item.display_size}</span>`);
|
|
1550
|
+
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1551
|
+
e.stopPropagation();
|
|
1552
|
+
await restoreSnapshotById(item.id, gui);
|
|
1553
|
+
}).text('restore');
|
|
1554
|
+
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1555
|
+
var obj;
|
|
1556
|
+
e.stopPropagation();
|
|
1557
|
+
obj = await idb$2.get(item.id);
|
|
1558
|
+
await internal.compressSnapshotForExport(obj);
|
|
1559
|
+
var buf = internal.pack(obj);
|
|
1560
|
+
var fileName = `snapshot-${String(item.number).padStart(2, '0')}.msx`;
|
|
1561
|
+
saveBlobToLocalFile2(fileName, new Blob([buf]));
|
|
1562
|
+
}).text('export');
|
|
1563
|
+
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1564
|
+
e.stopPropagation();
|
|
1565
|
+
await removeSnapshotById(item.id);
|
|
1566
|
+
renderSnapshotList(el, gui);
|
|
1567
|
+
}).text('remove');
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1638
1571
|
async function fetchSnapshotList() {
|
|
1639
1572
|
await pruneIndexAgainstKeys();
|
|
1640
1573
|
var index = await fetchIndex();
|
|
@@ -1708,6 +1641,7 @@
|
|
|
1708
1641
|
}
|
|
1709
1642
|
|
|
1710
1643
|
function importDatasets(datasets, gui) {
|
|
1644
|
+
regenerateRasterPreviews(datasets);
|
|
1711
1645
|
gui.model.addDatasets(datasets);
|
|
1712
1646
|
var target = findTargetLayer(datasets);
|
|
1713
1647
|
delete target.layers[0].active; // kludge, active flag only used in snapshots now
|
|
@@ -1715,6 +1649,17 @@
|
|
|
1715
1649
|
gui.model.updated({select: true, arc_count: true}); // arc_count to refresh display shapes
|
|
1716
1650
|
}
|
|
1717
1651
|
|
|
1652
|
+
function regenerateRasterPreviews(datasets) {
|
|
1653
|
+
datasets.forEach(function(dataset) {
|
|
1654
|
+
dataset.layers.forEach(function(lyr) {
|
|
1655
|
+
var raster = lyr.raster;
|
|
1656
|
+
if (!raster || !raster.grid || raster.view && raster.view.preview) return;
|
|
1657
|
+
raster.view = raster.view || {};
|
|
1658
|
+
raster.view.preview = internal.createRasterPreview(raster);
|
|
1659
|
+
});
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1718
1663
|
async function captureSnapshot(gui) {
|
|
1719
1664
|
var lyr = gui.model.getActiveLayer()?.layer;
|
|
1720
1665
|
if (!lyr) return null; // no data -- no snapshot
|
|
@@ -2477,77 +2422,303 @@
|
|
|
2477
2422
|
return bytes;
|
|
2478
2423
|
}
|
|
2479
2424
|
|
|
2480
|
-
|
|
2481
|
-
|
|
2425
|
+
var PAYLOAD_FIELDS = {
|
|
2426
|
+
table: ['records'],
|
|
2427
|
+
'table-records': ['records'],
|
|
2428
|
+
'table-fields': ['columns'],
|
|
2429
|
+
'table-schema': ['fields'],
|
|
2430
|
+
arcs: ['nn', 'xx', 'yy', 'zz', 'zlimit'],
|
|
2431
|
+
'arcs-simplification': ['zz'],
|
|
2432
|
+
layer: ['shapes', 'raster']
|
|
2433
|
+
};
|
|
2482
2434
|
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2435
|
+
async function storeUndoUnits(units, store, entryId, role) {
|
|
2436
|
+
var stored = [];
|
|
2437
|
+
try {
|
|
2438
|
+
for (var i = 0; i < units.length; i++) {
|
|
2439
|
+
stored.push(await storeUndoUnit(units[i], store, entryId, role));
|
|
2440
|
+
}
|
|
2441
|
+
} catch(e) {
|
|
2442
|
+
await store.delMany(getStoredUndoPayloadRefs(stored));
|
|
2443
|
+
throw e;
|
|
2444
|
+
}
|
|
2445
|
+
return stored;
|
|
2486
2446
|
}
|
|
2487
2447
|
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2448
|
+
async function restoreStoredUndoUnits(units, store) {
|
|
2449
|
+
var hydrated = await hydrateStoredUndoUnits(units, store);
|
|
2450
|
+
internal.restoreCapturedUnits(hydrated);
|
|
2491
2451
|
}
|
|
2492
2452
|
|
|
2493
|
-
function
|
|
2494
|
-
|
|
2453
|
+
async function hydrateStoredUndoUnits(units, store) {
|
|
2454
|
+
var hydrated = [];
|
|
2455
|
+
for (var i = 0; i < units.length; i++) {
|
|
2456
|
+
hydrated.push(await hydrateStoredUndoUnit(units[i], store));
|
|
2457
|
+
}
|
|
2458
|
+
return hydrated;
|
|
2495
2459
|
}
|
|
2496
2460
|
|
|
2497
|
-
function
|
|
2498
|
-
|
|
2461
|
+
function getStoredUndoPayloadRefs(units) {
|
|
2462
|
+
var refs = [];
|
|
2463
|
+
units.forEach(function(unit) {
|
|
2464
|
+
if (unit.payloadRef) refs.push(unit.payloadRef);
|
|
2465
|
+
});
|
|
2466
|
+
return refs;
|
|
2499
2467
|
}
|
|
2500
2468
|
|
|
2501
|
-
function
|
|
2502
|
-
|
|
2469
|
+
function getUndoRestoreFlags(units, baseFlags) {
|
|
2470
|
+
var flags = Object.assign({}, baseFlags || {}, {undo_restore: true});
|
|
2471
|
+
units.forEach(function(unit) {
|
|
2472
|
+
if (unit.type == 'changed') return;
|
|
2473
|
+
if (unit.type == 'arcs') {
|
|
2474
|
+
flags.arc_count = true;
|
|
2475
|
+
} else if (unit.type == 'arcs-simplification') {
|
|
2476
|
+
flags.simplify = true;
|
|
2477
|
+
} else if (unit.type == 'table' ||
|
|
2478
|
+
unit.type == 'table-records' ||
|
|
2479
|
+
unit.type == 'table-fields' ||
|
|
2480
|
+
unit.type == 'table-order' ||
|
|
2481
|
+
unit.type == 'table-schema') {
|
|
2482
|
+
flags.same_table = false;
|
|
2483
|
+
} else if (unit.type == 'catalog' || unit.type == 'dataset') {
|
|
2484
|
+
flags.select = true;
|
|
2485
|
+
flags.arc_count = true;
|
|
2486
|
+
} else if (unit.type == 'dataset-info') {
|
|
2487
|
+
flags.info = true;
|
|
2488
|
+
} else if (unit.type == 'layer' ||
|
|
2489
|
+
unit.type == 'layer-metadata' ||
|
|
2490
|
+
unit.type == 'layer-order') {
|
|
2491
|
+
flags.select = true;
|
|
2492
|
+
}
|
|
2493
|
+
});
|
|
2494
|
+
return flags;
|
|
2503
2495
|
}
|
|
2504
2496
|
|
|
2505
|
-
function
|
|
2506
|
-
|
|
2497
|
+
async function storeUndoUnit(unit, store, entryId, role) {
|
|
2498
|
+
var payload = await getUnitPayload(unit);
|
|
2499
|
+
var stored = stripPayload(unit);
|
|
2500
|
+
if (payload) {
|
|
2501
|
+
stored.payloadRef = await store.put(payload, {
|
|
2502
|
+
entryId: entryId,
|
|
2503
|
+
role: role,
|
|
2504
|
+
unitType: unit.type
|
|
2505
|
+
});
|
|
2506
|
+
}
|
|
2507
|
+
return stored;
|
|
2507
2508
|
}
|
|
2508
2509
|
|
|
2509
|
-
function
|
|
2510
|
-
|
|
2510
|
+
async function hydrateStoredUndoUnit(unit, store) {
|
|
2511
|
+
var hydrated = Object.assign({}, unit);
|
|
2512
|
+
var payload;
|
|
2513
|
+
if (unit.payloadRef) {
|
|
2514
|
+
payload = await store.get(unit.payloadRef);
|
|
2515
|
+
if (!payload) {
|
|
2516
|
+
throw new Error('Missing undo payload: ' + unit.payloadRef.key);
|
|
2517
|
+
}
|
|
2518
|
+
payload = unpackPayload(unit, payload);
|
|
2519
|
+
Object.assign(hydrated, payload);
|
|
2520
|
+
}
|
|
2521
|
+
delete hydrated.payloadRef;
|
|
2522
|
+
return hydrated;
|
|
2511
2523
|
}
|
|
2512
2524
|
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2525
|
+
async function getUnitPayload(unit) {
|
|
2526
|
+
var fields = PAYLOAD_FIELDS[unit.type];
|
|
2527
|
+
var payload, hasPayload;
|
|
2528
|
+
if (!fields) return null;
|
|
2529
|
+
payload = {};
|
|
2530
|
+
fields.forEach(function(field) {
|
|
2531
|
+
if (field in unit) {
|
|
2532
|
+
payload[field] = unit[field];
|
|
2533
|
+
hasPayload = true;
|
|
2534
|
+
}
|
|
2535
|
+
});
|
|
2536
|
+
if (!hasPayload) return null;
|
|
2537
|
+
if (unit.type == 'layer') return packLayerPayload(payload);
|
|
2538
|
+
return unit.type == 'table' ? packTablePayload(payload) : payload;
|
|
2516
2539
|
}
|
|
2517
2540
|
|
|
2518
|
-
function
|
|
2519
|
-
|
|
2541
|
+
async function packLayerPayload(payload) {
|
|
2542
|
+
if (payload.raster) {
|
|
2543
|
+
payload = Object.assign({}, payload, {
|
|
2544
|
+
raster: packRasterUndoPayload(payload.raster)
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2547
|
+
return payload;
|
|
2520
2548
|
}
|
|
2521
2549
|
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2550
|
+
async function packTablePayload(payload) {
|
|
2551
|
+
return {
|
|
2552
|
+
packedRecords: packRecordsAsColumns(payload.records)
|
|
2553
|
+
};
|
|
2525
2554
|
}
|
|
2526
2555
|
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2556
|
+
function unpackPayload(unit, payload) {
|
|
2557
|
+
if (unit.type == 'table' && payload.packedRecords) {
|
|
2558
|
+
return {
|
|
2559
|
+
records: unpackRecordsFromColumns(payload.packedRecords)
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
if (unit.type == 'layer' && payload.raster) {
|
|
2563
|
+
return Object.assign({}, payload, {
|
|
2564
|
+
raster: unpackRasterUndoPayload(payload.raster)
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
return payload;
|
|
2534
2568
|
}
|
|
2535
2569
|
|
|
2536
|
-
function
|
|
2537
|
-
|
|
2570
|
+
function packRasterUndoPayload(raster) {
|
|
2571
|
+
var copy = Object.assign({}, raster);
|
|
2572
|
+
if (raster.view) {
|
|
2573
|
+
copy.view = Object.assign({}, raster.view);
|
|
2574
|
+
if (raster.view.preview) {
|
|
2575
|
+
copy.view.preview = stripPreviewPixels(raster.view.preview);
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
if (raster.preview) {
|
|
2579
|
+
copy.preview = stripPreviewPixels(raster.preview);
|
|
2580
|
+
}
|
|
2581
|
+
return copy;
|
|
2538
2582
|
}
|
|
2539
2583
|
|
|
2540
|
-
function
|
|
2541
|
-
|
|
2584
|
+
function unpackRasterUndoPayload(raster) {
|
|
2585
|
+
var copy = Object.assign({}, raster);
|
|
2586
|
+
if (raster.view) {
|
|
2587
|
+
copy.view = Object.assign({}, raster.view);
|
|
2588
|
+
if (raster.view.preview && !raster.view.preview.pixels && raster.grid && raster.grid.samples) {
|
|
2589
|
+
copy.view.preview = internal.renderRasterPreview(raster.grid, raster.view.recipe, raster.view.preview.width, raster.view.preview.height);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
return copy;
|
|
2542
2593
|
}
|
|
2543
2594
|
|
|
2544
|
-
function
|
|
2545
|
-
|
|
2595
|
+
function stripPreviewPixels(preview) {
|
|
2596
|
+
var copy = Object.assign({}, preview);
|
|
2597
|
+
delete copy.canvas;
|
|
2598
|
+
delete copy.pixels;
|
|
2599
|
+
return copy;
|
|
2546
2600
|
}
|
|
2547
2601
|
|
|
2548
|
-
function
|
|
2549
|
-
|
|
2550
|
-
|
|
2602
|
+
function packRecordsAsColumns(records) {
|
|
2603
|
+
var fields = getRecordFields(records);
|
|
2604
|
+
return {
|
|
2605
|
+
fields: fields,
|
|
2606
|
+
types: fields.map(function() { return null; }),
|
|
2607
|
+
data: fields.map(function(field) {
|
|
2608
|
+
return records.map(function(rec) {
|
|
2609
|
+
return rec ? rec[field] : undefined;
|
|
2610
|
+
});
|
|
2611
|
+
}),
|
|
2612
|
+
size: records.length
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
function getRecordFields(records) {
|
|
2617
|
+
var index = {};
|
|
2618
|
+
records.forEach(function(rec) {
|
|
2619
|
+
Object.keys(rec || {}).forEach(function(field) {
|
|
2620
|
+
index[field] = true;
|
|
2621
|
+
});
|
|
2622
|
+
});
|
|
2623
|
+
return Object.keys(index);
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
function unpackRecordsFromColumns(data) {
|
|
2627
|
+
var records = [];
|
|
2628
|
+
for (var i = 0; i < data.size; i++) {
|
|
2629
|
+
records[i] = {};
|
|
2630
|
+
}
|
|
2631
|
+
data.fields.forEach(function(field, j) {
|
|
2632
|
+
var values = data.data[j];
|
|
2633
|
+
values.forEach(function(val, i) {
|
|
2634
|
+
records[i][field] = val;
|
|
2635
|
+
});
|
|
2636
|
+
});
|
|
2637
|
+
return records;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
function stripPayload(unit) {
|
|
2641
|
+
var fields = PAYLOAD_FIELDS[unit.type];
|
|
2642
|
+
var stripped = Object.assign({}, unit);
|
|
2643
|
+
if (fields) {
|
|
2644
|
+
fields.forEach(function(field) {
|
|
2645
|
+
delete stripped[field];
|
|
2646
|
+
});
|
|
2647
|
+
}
|
|
2648
|
+
return stripped;
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
// Fall back to browserify's Buffer polyfill
|
|
2652
|
+
var B = typeof Buffer != 'undefined' ? Buffer : require$1('buffer').Buffer;
|
|
2653
|
+
|
|
2654
|
+
// We do NOT import from mapshaper-logging here to avoid a circular dependency
|
|
2655
|
+
function error$1() {
|
|
2656
|
+
throw new Error(Array.prototype.slice.call(arguments).join(' '));
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
var uniqCount = 0;
|
|
2660
|
+
function getUniqueName(prefix) {
|
|
2661
|
+
return (prefix || "__id_") + (++uniqCount);
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
function isFunction(obj) {
|
|
2665
|
+
return typeof obj == 'function';
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
function isPromise(arg) {
|
|
2669
|
+
return arg ? isFunction(arg.then) : false;
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
function isObject(obj) {
|
|
2673
|
+
return obj === Object(obj); // via underscore
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
function clamp(val, min, max) {
|
|
2677
|
+
return val < min ? min : (val > max ? max : val);
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
function isArray(obj) {
|
|
2681
|
+
return Array.isArray(obj);
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
// Is obj a valid number or NaN? (test if obj is type number)
|
|
2685
|
+
function isNumber(obj) {
|
|
2686
|
+
return obj != null && obj.constructor == Number;
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
function isValidNumber(val) {
|
|
2690
|
+
return isNumber(val) && !isNaN(val);
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
// Similar to isFinite() but does not coerce strings or other types
|
|
2694
|
+
function isFiniteNumber(val) {
|
|
2695
|
+
return isValidNumber(val) && val !== Infinity && val !== -Infinity;
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
// This uses type conversion
|
|
2699
|
+
// export function isFiniteNumber(val) {
|
|
2700
|
+
// return val > -Infinity && val < Infinity;
|
|
2701
|
+
// }
|
|
2702
|
+
|
|
2703
|
+
function isNonNegNumber(val) {
|
|
2704
|
+
return isNumber(val) && val >= 0;
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
function isInteger(obj) {
|
|
2708
|
+
return isNumber(obj) && ((obj | 0) === obj);
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
function isEven(obj) {
|
|
2712
|
+
return (obj % 2) === 0;
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
function isOdd(obj) {
|
|
2716
|
+
return (obj % 2) === 1;
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
function isString(obj) {
|
|
2720
|
+
return obj != null && obj.toString === String.prototype.toString;
|
|
2721
|
+
// TODO: replace w/ something better.
|
|
2551
2722
|
}
|
|
2552
2723
|
|
|
2553
2724
|
function isDate(obj) {
|
|
@@ -4629,6 +4800,10 @@
|
|
|
4629
4800
|
return this;
|
|
4630
4801
|
};
|
|
4631
4802
|
|
|
4803
|
+
function runningInBrowser() {
|
|
4804
|
+
return typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
4805
|
+
}
|
|
4806
|
+
|
|
4632
4807
|
// Lightweight hooks that make in-place mutations observable to GUI undo.
|
|
4633
4808
|
// Normal CLI runs leave activeTransaction unset, so capture hooks are no-ops.
|
|
4634
4809
|
|
|
@@ -4833,6 +5008,8 @@
|
|
|
4833
5008
|
}
|
|
4834
5009
|
|
|
4835
5010
|
var DEFAULT_MAX_PREVIEW_PIXELS = 4e6;
|
|
5011
|
+
var MAX_EXACT_AVERAGE_PIXELS = 256;
|
|
5012
|
+
var MAX_APPROX_AVERAGE_STEPS = 16;
|
|
4836
5013
|
|
|
4837
5014
|
function getRasterGrid(raster) {
|
|
4838
5015
|
return raster && (raster.grid || raster);
|
|
@@ -4978,6 +5155,13 @@
|
|
|
4978
5155
|
});
|
|
4979
5156
|
}
|
|
4980
5157
|
|
|
5158
|
+
function renderRasterExportPreview(raster, bbox, width, height, opts) {
|
|
5159
|
+
var grid = getRasterGrid(raster);
|
|
5160
|
+
var recipe = getRasterViewRecipe(grid, raster.view && raster.view.recipe, opts);
|
|
5161
|
+
var stats = getRasterViewScalingStats(raster, recipe);
|
|
5162
|
+
return renderRasterGridPreview(grid, recipe, width, height, stats, bbox, getRasterExportResamplingMethod(grid, bbox, width, height));
|
|
5163
|
+
}
|
|
5164
|
+
|
|
4981
5165
|
function getRasterScalingStats(grid, recipe) {
|
|
4982
5166
|
recipe = getRasterViewRecipe(grid, recipe);
|
|
4983
5167
|
return getScalingStats(grid.samples, grid.bands, grid.nodata, recipe);
|
|
@@ -5002,7 +5186,7 @@
|
|
|
5002
5186
|
return stats;
|
|
5003
5187
|
}
|
|
5004
5188
|
|
|
5005
|
-
function renderRasterGridPreview(grid, recipe, width, height, statsArg, sourceBbox) {
|
|
5189
|
+
function renderRasterGridPreview(grid, recipe, width, height, statsArg, sourceBbox, resamplingMethod) {
|
|
5006
5190
|
var samples = grid.samples;
|
|
5007
5191
|
var bands = grid.bands;
|
|
5008
5192
|
var noData = grid.nodata;
|
|
@@ -5010,23 +5194,29 @@
|
|
|
5010
5194
|
var stats = statsArg || getScalingStats(samples, bands, noData, recipe);
|
|
5011
5195
|
var sourceRange = recipe.scaling == 'none' ? getPixelTypeRange(grid.pixelType) : null;
|
|
5012
5196
|
var displayRange = getDisplayRange(recipe.scaleRange);
|
|
5013
|
-
var src, dest, val, isNoData, j;
|
|
5197
|
+
var src, dest, val, isNoData, j, sample;
|
|
5014
5198
|
for (var y = 0; y < height; y++) {
|
|
5015
5199
|
for (var x = 0; x < width; x++) {
|
|
5016
|
-
src = getPreviewSourceOffset(grid, sourceBbox, x, y, width, height, bands);
|
|
5017
5200
|
dest = (y * width + x) * 4;
|
|
5018
|
-
|
|
5201
|
+
if (resamplingMethod) {
|
|
5202
|
+
sample = getResampledRasterSample(grid, sourceBbox, x, y, width, height, resamplingMethod);
|
|
5203
|
+
isNoData = !sample.valid;
|
|
5204
|
+
} else {
|
|
5205
|
+
src = getPreviewSourceOffset(grid, sourceBbox, x, y, width, height, bands);
|
|
5206
|
+
sample = null;
|
|
5207
|
+
isNoData = noData !== null && noData !== undefined && allSamplesAreNoData(samples, src, bands, noData);
|
|
5208
|
+
}
|
|
5019
5209
|
if (bands == 1) {
|
|
5020
|
-
val = scaleSample(samples[src], stats && stats[0], sourceRange, displayRange);
|
|
5210
|
+
val = scaleSample(sample ? sample.values[0] : samples[src], stats && stats[0], sourceRange, displayRange);
|
|
5021
5211
|
pixels[dest] = val;
|
|
5022
5212
|
pixels[dest + 1] = val;
|
|
5023
5213
|
pixels[dest + 2] = val;
|
|
5024
5214
|
pixels[dest + 3] = isNoData ? 0 : 255;
|
|
5025
5215
|
} else {
|
|
5026
5216
|
for (j = 0; j < 3; j++) {
|
|
5027
|
-
pixels[dest + j] = scaleSample(samples[src + j], stats && stats[j], sourceRange, displayRange);
|
|
5217
|
+
pixels[dest + j] = scaleSample(sample ? sample.values[j] : samples[src + j], stats && stats[j], sourceRange, displayRange);
|
|
5028
5218
|
}
|
|
5029
|
-
pixels[dest + 3] = isNoData ? 0 : bands >= 4 ? scaleSample(samples[src + 3], stats && stats[3], sourceRange, [0, 255]) : 255;
|
|
5219
|
+
pixels[dest + 3] = isNoData ? 0 : bands >= 4 ? scaleSample(sample ? sample.values[3] : samples[src + 3], stats && stats[3], sourceRange, [0, 255]) : 255;
|
|
5030
5220
|
}
|
|
5031
5221
|
}
|
|
5032
5222
|
}
|
|
@@ -5040,6 +5230,156 @@
|
|
|
5040
5230
|
};
|
|
5041
5231
|
}
|
|
5042
5232
|
|
|
5233
|
+
function getRasterExportResamplingMethod(grid, bbox, width, height) {
|
|
5234
|
+
var srcSize = getRasterSourcePixelSize$1(grid, bbox);
|
|
5235
|
+
return width < srcSize.width || height < srcSize.height ? 'average' : 'bilinear';
|
|
5236
|
+
}
|
|
5237
|
+
|
|
5238
|
+
function getRasterSourcePixelSize$1(grid, bbox) {
|
|
5239
|
+
var rb = grid.bbox;
|
|
5240
|
+
return {
|
|
5241
|
+
width: Math.abs((bbox[2] - bbox[0]) / (rb[2] - rb[0]) * grid.width),
|
|
5242
|
+
height: Math.abs((bbox[3] - bbox[1]) / (rb[3] - rb[1]) * grid.height)
|
|
5243
|
+
};
|
|
5244
|
+
}
|
|
5245
|
+
|
|
5246
|
+
function getResampledRasterSample(grid, bbox, x, y, width, height, method) {
|
|
5247
|
+
return method == 'average' ?
|
|
5248
|
+
getAverageRasterSample(grid, bbox, x, y, width, height) :
|
|
5249
|
+
getBilinearRasterSample(grid, bbox, x, y, width, height);
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5252
|
+
function getAverageRasterSample(grid, bbox, x, y, width, height) {
|
|
5253
|
+
var bounds = getRasterSourcePixelBounds(grid, bbox, x, y, width, height);
|
|
5254
|
+
if (getSourcePixelBoundsArea(bounds) > MAX_EXACT_AVERAGE_PIXELS) {
|
|
5255
|
+
return getApproxAverageRasterSample(grid, bounds);
|
|
5256
|
+
}
|
|
5257
|
+
var samples = grid.samples;
|
|
5258
|
+
var bands = grid.bands;
|
|
5259
|
+
var values = new Array(bands).fill(0);
|
|
5260
|
+
var total = 0;
|
|
5261
|
+
var src, weight;
|
|
5262
|
+
var x0 = Math.max(0, Math.floor(bounds.x0));
|
|
5263
|
+
var x1 = Math.min(grid.width, Math.ceil(bounds.x1));
|
|
5264
|
+
var y0 = Math.max(0, Math.floor(bounds.y0));
|
|
5265
|
+
var y1 = Math.min(grid.height, Math.ceil(bounds.y1));
|
|
5266
|
+
for (var sy = y0; sy < y1; sy++) {
|
|
5267
|
+
for (var sx = x0; sx < x1; sx++) {
|
|
5268
|
+
weight = getIntervalOverlap(bounds.x0, bounds.x1, sx, sx + 1) *
|
|
5269
|
+
getIntervalOverlap(bounds.y0, bounds.y1, sy, sy + 1);
|
|
5270
|
+
if (weight <= 0) continue;
|
|
5271
|
+
src = (sy * grid.width + sx) * bands;
|
|
5272
|
+
if (sampleIsNoData(samples, src, bands, grid.nodata)) continue;
|
|
5273
|
+
for (var band = 0; band < bands; band++) {
|
|
5274
|
+
values[band] += samples[src + band] * weight;
|
|
5275
|
+
}
|
|
5276
|
+
total += weight;
|
|
5277
|
+
}
|
|
5278
|
+
}
|
|
5279
|
+
if (total <= 0) return {valid: false, values: values};
|
|
5280
|
+
for (var i = 0; i < bands; i++) values[i] /= total;
|
|
5281
|
+
return {valid: true, values: values};
|
|
5282
|
+
}
|
|
5283
|
+
|
|
5284
|
+
function getApproxAverageRasterSample(grid, bounds) {
|
|
5285
|
+
var samples = grid.samples;
|
|
5286
|
+
var bands = grid.bands;
|
|
5287
|
+
var values = new Array(bands).fill(0);
|
|
5288
|
+
var xSteps = Math.min(MAX_APPROX_AVERAGE_STEPS, Math.max(1, Math.ceil(Math.abs(bounds.x1 - bounds.x0))));
|
|
5289
|
+
var ySteps = Math.min(MAX_APPROX_AVERAGE_STEPS, Math.max(1, Math.ceil(Math.abs(bounds.y1 - bounds.y0))));
|
|
5290
|
+
var count = 0;
|
|
5291
|
+
var sx, sy, src;
|
|
5292
|
+
for (var y = 0; y < ySteps; y++) {
|
|
5293
|
+
for (var x = 0; x < xSteps; x++) {
|
|
5294
|
+
sx = Math.max(0, Math.min(grid.width - 1, Math.floor(bounds.x0 + (x + 0.5) / xSteps * (bounds.x1 - bounds.x0))));
|
|
5295
|
+
sy = Math.max(0, Math.min(grid.height - 1, Math.floor(bounds.y0 + (y + 0.5) / ySteps * (bounds.y1 - bounds.y0))));
|
|
5296
|
+
src = (sy * grid.width + sx) * bands;
|
|
5297
|
+
if (sampleIsNoData(samples, src, bands, grid.nodata)) continue;
|
|
5298
|
+
for (var band = 0; band < bands; band++) {
|
|
5299
|
+
values[band] += samples[src + band];
|
|
5300
|
+
}
|
|
5301
|
+
count++;
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
if (count === 0) return {valid: false, values: values};
|
|
5305
|
+
for (var i = 0; i < bands; i++) values[i] /= count;
|
|
5306
|
+
return {valid: true, values: values};
|
|
5307
|
+
}
|
|
5308
|
+
|
|
5309
|
+
function getBilinearRasterSample(grid, bbox, x, y, width, height) {
|
|
5310
|
+
var p = getRasterSourcePixelCenter(grid, bbox, x, y, width, height);
|
|
5311
|
+
var x0 = Math.max(0, Math.min(grid.width - 1, Math.floor(p.x)));
|
|
5312
|
+
var y0 = Math.max(0, Math.min(grid.height - 1, Math.floor(p.y)));
|
|
5313
|
+
var x1 = Math.max(0, Math.min(grid.width - 1, x0 + 1));
|
|
5314
|
+
var y1 = Math.max(0, Math.min(grid.height - 1, y0 + 1));
|
|
5315
|
+
var tx = Math.max(0, Math.min(1, p.x - x0));
|
|
5316
|
+
var ty = Math.max(0, Math.min(1, p.y - y0));
|
|
5317
|
+
var samples = grid.samples;
|
|
5318
|
+
var bands = grid.bands;
|
|
5319
|
+
var values = new Array(bands).fill(0);
|
|
5320
|
+
var offsets = [
|
|
5321
|
+
(y0 * grid.width + x0) * bands,
|
|
5322
|
+
(y0 * grid.width + x1) * bands,
|
|
5323
|
+
(y1 * grid.width + x0) * bands,
|
|
5324
|
+
(y1 * grid.width + x1) * bands
|
|
5325
|
+
];
|
|
5326
|
+
var weights = [
|
|
5327
|
+
(1 - tx) * (1 - ty),
|
|
5328
|
+
tx * (1 - ty),
|
|
5329
|
+
(1 - tx) * ty,
|
|
5330
|
+
tx * ty
|
|
5331
|
+
];
|
|
5332
|
+
var total = 0;
|
|
5333
|
+
offsets.forEach(function(src, i) {
|
|
5334
|
+
if (weights[i] <= 0 || sampleIsNoData(samples, src, bands, grid.nodata)) return;
|
|
5335
|
+
for (var band = 0; band < bands; band++) {
|
|
5336
|
+
values[band] += samples[src + band] * weights[i];
|
|
5337
|
+
}
|
|
5338
|
+
total += weights[i];
|
|
5339
|
+
});
|
|
5340
|
+
if (total <= 0) return {valid: false, values: values};
|
|
5341
|
+
for (var j = 0; j < bands; j++) values[j] /= total;
|
|
5342
|
+
return {valid: true, values: values};
|
|
5343
|
+
}
|
|
5344
|
+
|
|
5345
|
+
function getRasterSourcePixelBounds(grid, bbox, x, y, width, height) {
|
|
5346
|
+
return {
|
|
5347
|
+
x0: mapXToRasterPixel(grid, bbox[0] + x / width * (bbox[2] - bbox[0])),
|
|
5348
|
+
x1: mapXToRasterPixel(grid, bbox[0] + (x + 1) / width * (bbox[2] - bbox[0])),
|
|
5349
|
+
y0: mapYToRasterPixel(grid, bbox[3] - y / height * (bbox[3] - bbox[1])),
|
|
5350
|
+
y1: mapYToRasterPixel(grid, bbox[3] - (y + 1) / height * (bbox[3] - bbox[1]))
|
|
5351
|
+
};
|
|
5352
|
+
}
|
|
5353
|
+
|
|
5354
|
+
function getRasterSourcePixelCenter(grid, bbox, x, y, width, height) {
|
|
5355
|
+
var mapX = bbox[0] + (x + 0.5) / width * (bbox[2] - bbox[0]);
|
|
5356
|
+
var mapY = bbox[3] - (y + 0.5) / height * (bbox[3] - bbox[1]);
|
|
5357
|
+
return {
|
|
5358
|
+
x: mapXToRasterPixel(grid, mapX) - 0.5,
|
|
5359
|
+
y: mapYToRasterPixel(grid, mapY) - 0.5
|
|
5360
|
+
};
|
|
5361
|
+
}
|
|
5362
|
+
|
|
5363
|
+
function mapXToRasterPixel(grid, x) {
|
|
5364
|
+
return (x - grid.bbox[0]) / (grid.bbox[2] - grid.bbox[0]) * grid.width;
|
|
5365
|
+
}
|
|
5366
|
+
|
|
5367
|
+
function mapYToRasterPixel(grid, y) {
|
|
5368
|
+
return (grid.bbox[3] - y) / (grid.bbox[3] - grid.bbox[1]) * grid.height;
|
|
5369
|
+
}
|
|
5370
|
+
|
|
5371
|
+
function getIntervalOverlap(a0, a1, b0, b1) {
|
|
5372
|
+
return Math.max(0, Math.min(Math.max(a0, a1), b1) - Math.max(Math.min(a0, a1), b0));
|
|
5373
|
+
}
|
|
5374
|
+
|
|
5375
|
+
function getSourcePixelBoundsArea(bounds) {
|
|
5376
|
+
return Math.abs((bounds.x1 - bounds.x0) * (bounds.y1 - bounds.y0));
|
|
5377
|
+
}
|
|
5378
|
+
|
|
5379
|
+
function sampleIsNoData(samples, offset, bands, noData) {
|
|
5380
|
+
return noData !== null && noData !== undefined && allSamplesAreNoData(samples, offset, bands, noData);
|
|
5381
|
+
}
|
|
5382
|
+
|
|
5043
5383
|
function renderRawEightBitPreview(grid, width, height, sourceBbox) {
|
|
5044
5384
|
var samples = grid.samples;
|
|
5045
5385
|
var bands = grid.bands;
|
|
@@ -5104,7 +5444,12 @@
|
|
|
5104
5444
|
noteLayerWillChange(lyr, {operation: 'clipRasterToBBox', unit: 'raster'});
|
|
5105
5445
|
raster.grid = cropRasterGrid(grid, crop, clipBbox);
|
|
5106
5446
|
raster.view = raster.view || {};
|
|
5107
|
-
raster.view.
|
|
5447
|
+
delete raster.view.scalingStats;
|
|
5448
|
+
if (runningInBrowser()) {
|
|
5449
|
+
raster.view.preview = createRasterPreview(raster, opts || {});
|
|
5450
|
+
} else {
|
|
5451
|
+
delete raster.view.preview;
|
|
5452
|
+
}
|
|
5108
5453
|
clearLegacyRasterFields(raster);
|
|
5109
5454
|
markLayerChanged(lyr, {operation: 'clipRasterToBBox', unit: 'raster'});
|
|
5110
5455
|
return true;
|
|
@@ -6159,239 +6504,13 @@
|
|
|
6159
6504
|
return Object.keys(index);
|
|
6160
6505
|
}
|
|
6161
6506
|
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
'table-records': ['records'],
|
|
6165
|
-
'table-fields': ['columns'],
|
|
6166
|
-
'table-schema': ['fields'],
|
|
6167
|
-
arcs: ['nn', 'xx', 'yy', 'zz', 'zlimit'],
|
|
6168
|
-
'arcs-simplification': ['zz'],
|
|
6169
|
-
layer: ['shapes', 'raster']
|
|
6170
|
-
};
|
|
6171
|
-
|
|
6172
|
-
async function storeUndoUnits(units, store, entryId, role) {
|
|
6173
|
-
var stored = [];
|
|
6174
|
-
try {
|
|
6175
|
-
for (var i = 0; i < units.length; i++) {
|
|
6176
|
-
stored.push(await storeUndoUnit(units[i], store, entryId, role));
|
|
6177
|
-
}
|
|
6178
|
-
} catch(e) {
|
|
6179
|
-
await store.delMany(getStoredUndoPayloadRefs(stored));
|
|
6180
|
-
throw e;
|
|
6181
|
-
}
|
|
6182
|
-
return stored;
|
|
6183
|
-
}
|
|
6184
|
-
|
|
6185
|
-
async function restoreStoredUndoUnits(units, store) {
|
|
6186
|
-
var hydrated = await hydrateStoredUndoUnits(units, store);
|
|
6187
|
-
restoreCapturedUnits(hydrated);
|
|
6188
|
-
}
|
|
6189
|
-
|
|
6190
|
-
async function hydrateStoredUndoUnits(units, store) {
|
|
6191
|
-
var hydrated = [];
|
|
6192
|
-
for (var i = 0; i < units.length; i++) {
|
|
6193
|
-
hydrated.push(await hydrateStoredUndoUnit(units[i], store));
|
|
6194
|
-
}
|
|
6195
|
-
return hydrated;
|
|
6196
|
-
}
|
|
6507
|
+
function createStoredUndoHistory(gui) {
|
|
6508
|
+
var undoEntryId = 0;
|
|
6197
6509
|
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
});
|
|
6203
|
-
return refs;
|
|
6204
|
-
}
|
|
6205
|
-
|
|
6206
|
-
function getUndoRestoreFlags(units, baseFlags) {
|
|
6207
|
-
var flags = Object.assign({}, baseFlags || {}, {undo_restore: true});
|
|
6208
|
-
units.forEach(function(unit) {
|
|
6209
|
-
if (unit.type == 'changed') return;
|
|
6210
|
-
if (unit.type == 'arcs') {
|
|
6211
|
-
flags.arc_count = true;
|
|
6212
|
-
} else if (unit.type == 'arcs-simplification') {
|
|
6213
|
-
flags.simplify = true;
|
|
6214
|
-
} else if (unit.type == 'table' ||
|
|
6215
|
-
unit.type == 'table-records' ||
|
|
6216
|
-
unit.type == 'table-fields' ||
|
|
6217
|
-
unit.type == 'table-order' ||
|
|
6218
|
-
unit.type == 'table-schema') {
|
|
6219
|
-
flags.same_table = false;
|
|
6220
|
-
} else if (unit.type == 'catalog' || unit.type == 'dataset') {
|
|
6221
|
-
flags.select = true;
|
|
6222
|
-
flags.arc_count = true;
|
|
6223
|
-
} else if (unit.type == 'dataset-info') {
|
|
6224
|
-
flags.info = true;
|
|
6225
|
-
} else if (unit.type == 'layer' ||
|
|
6226
|
-
unit.type == 'layer-metadata' ||
|
|
6227
|
-
unit.type == 'layer-order') {
|
|
6228
|
-
flags.select = true;
|
|
6229
|
-
}
|
|
6230
|
-
});
|
|
6231
|
-
return flags;
|
|
6232
|
-
}
|
|
6233
|
-
|
|
6234
|
-
async function storeUndoUnit(unit, store, entryId, role) {
|
|
6235
|
-
var payload = await getUnitPayload(unit);
|
|
6236
|
-
var stored = stripPayload(unit);
|
|
6237
|
-
if (payload) {
|
|
6238
|
-
stored.payloadRef = await store.put(payload, {
|
|
6239
|
-
entryId: entryId,
|
|
6240
|
-
role: role,
|
|
6241
|
-
unitType: unit.type
|
|
6242
|
-
});
|
|
6243
|
-
}
|
|
6244
|
-
return stored;
|
|
6245
|
-
}
|
|
6246
|
-
|
|
6247
|
-
async function hydrateStoredUndoUnit(unit, store) {
|
|
6248
|
-
var hydrated = Object.assign({}, unit);
|
|
6249
|
-
var payload;
|
|
6250
|
-
if (unit.payloadRef) {
|
|
6251
|
-
payload = await store.get(unit.payloadRef);
|
|
6252
|
-
if (!payload) {
|
|
6253
|
-
throw new Error('Missing undo payload: ' + unit.payloadRef.key);
|
|
6254
|
-
}
|
|
6255
|
-
payload = unpackPayload(unit, payload);
|
|
6256
|
-
Object.assign(hydrated, payload);
|
|
6257
|
-
}
|
|
6258
|
-
delete hydrated.payloadRef;
|
|
6259
|
-
return hydrated;
|
|
6260
|
-
}
|
|
6261
|
-
|
|
6262
|
-
async function getUnitPayload(unit) {
|
|
6263
|
-
var fields = PAYLOAD_FIELDS[unit.type];
|
|
6264
|
-
var payload, hasPayload;
|
|
6265
|
-
if (!fields) return null;
|
|
6266
|
-
payload = {};
|
|
6267
|
-
fields.forEach(function(field) {
|
|
6268
|
-
if (field in unit) {
|
|
6269
|
-
payload[field] = unit[field];
|
|
6270
|
-
hasPayload = true;
|
|
6271
|
-
}
|
|
6272
|
-
});
|
|
6273
|
-
if (!hasPayload) return null;
|
|
6274
|
-
if (unit.type == 'layer') return packLayerPayload(payload);
|
|
6275
|
-
return unit.type == 'table' ? packTablePayload(payload) : payload;
|
|
6276
|
-
}
|
|
6277
|
-
|
|
6278
|
-
async function packLayerPayload(payload) {
|
|
6279
|
-
if (payload.raster) {
|
|
6280
|
-
payload = Object.assign({}, payload, {
|
|
6281
|
-
raster: packRasterUndoPayload(payload.raster)
|
|
6282
|
-
});
|
|
6283
|
-
}
|
|
6284
|
-
return payload;
|
|
6285
|
-
}
|
|
6286
|
-
|
|
6287
|
-
async function packTablePayload(payload) {
|
|
6288
|
-
return {
|
|
6289
|
-
packedRecords: packRecordsAsColumns(payload.records)
|
|
6290
|
-
};
|
|
6291
|
-
}
|
|
6292
|
-
|
|
6293
|
-
function unpackPayload(unit, payload) {
|
|
6294
|
-
if (unit.type == 'table' && payload.packedRecords) {
|
|
6295
|
-
return {
|
|
6296
|
-
records: unpackRecordsFromColumns(payload.packedRecords)
|
|
6297
|
-
};
|
|
6298
|
-
}
|
|
6299
|
-
if (unit.type == 'layer' && payload.raster) {
|
|
6300
|
-
return Object.assign({}, payload, {
|
|
6301
|
-
raster: unpackRasterUndoPayload(payload.raster)
|
|
6302
|
-
});
|
|
6303
|
-
}
|
|
6304
|
-
return payload;
|
|
6305
|
-
}
|
|
6306
|
-
|
|
6307
|
-
function packRasterUndoPayload(raster) {
|
|
6308
|
-
var copy = Object.assign({}, raster);
|
|
6309
|
-
if (raster.view) {
|
|
6310
|
-
copy.view = Object.assign({}, raster.view);
|
|
6311
|
-
if (raster.view.preview) {
|
|
6312
|
-
copy.view.preview = stripPreviewPixels(raster.view.preview);
|
|
6313
|
-
}
|
|
6314
|
-
}
|
|
6315
|
-
if (raster.preview) {
|
|
6316
|
-
copy.preview = stripPreviewPixels(raster.preview);
|
|
6317
|
-
}
|
|
6318
|
-
return copy;
|
|
6319
|
-
}
|
|
6320
|
-
|
|
6321
|
-
function unpackRasterUndoPayload(raster) {
|
|
6322
|
-
var copy = Object.assign({}, raster);
|
|
6323
|
-
if (raster.view) {
|
|
6324
|
-
copy.view = Object.assign({}, raster.view);
|
|
6325
|
-
if (raster.view.preview && !raster.view.preview.pixels && raster.grid && raster.grid.samples) {
|
|
6326
|
-
copy.view.preview = renderRasterPreview(raster.grid, raster.view.recipe, raster.view.preview.width, raster.view.preview.height);
|
|
6327
|
-
}
|
|
6328
|
-
}
|
|
6329
|
-
return copy;
|
|
6330
|
-
}
|
|
6331
|
-
|
|
6332
|
-
function stripPreviewPixels(preview) {
|
|
6333
|
-
var copy = Object.assign({}, preview);
|
|
6334
|
-
delete copy.canvas;
|
|
6335
|
-
delete copy.pixels;
|
|
6336
|
-
return copy;
|
|
6337
|
-
}
|
|
6338
|
-
|
|
6339
|
-
function packRecordsAsColumns(records) {
|
|
6340
|
-
var fields = getRecordFields(records);
|
|
6341
|
-
return {
|
|
6342
|
-
fields: fields,
|
|
6343
|
-
types: fields.map(function() { return null; }),
|
|
6344
|
-
data: fields.map(function(field) {
|
|
6345
|
-
return records.map(function(rec) {
|
|
6346
|
-
return rec ? rec[field] : undefined;
|
|
6347
|
-
});
|
|
6348
|
-
}),
|
|
6349
|
-
size: records.length
|
|
6350
|
-
};
|
|
6351
|
-
}
|
|
6352
|
-
|
|
6353
|
-
function getRecordFields(records) {
|
|
6354
|
-
var index = {};
|
|
6355
|
-
records.forEach(function(rec) {
|
|
6356
|
-
Object.keys(rec || {}).forEach(function(field) {
|
|
6357
|
-
index[field] = true;
|
|
6358
|
-
});
|
|
6359
|
-
});
|
|
6360
|
-
return Object.keys(index);
|
|
6361
|
-
}
|
|
6362
|
-
|
|
6363
|
-
function unpackRecordsFromColumns(data) {
|
|
6364
|
-
var records = [];
|
|
6365
|
-
for (var i = 0; i < data.size; i++) {
|
|
6366
|
-
records[i] = {};
|
|
6367
|
-
}
|
|
6368
|
-
data.fields.forEach(function(field, j) {
|
|
6369
|
-
var values = data.data[j];
|
|
6370
|
-
values.forEach(function(val, i) {
|
|
6371
|
-
records[i][field] = val;
|
|
6372
|
-
});
|
|
6373
|
-
});
|
|
6374
|
-
return records;
|
|
6375
|
-
}
|
|
6376
|
-
|
|
6377
|
-
function stripPayload(unit) {
|
|
6378
|
-
var fields = PAYLOAD_FIELDS[unit.type];
|
|
6379
|
-
var stripped = Object.assign({}, unit);
|
|
6380
|
-
if (fields) {
|
|
6381
|
-
fields.forEach(function(field) {
|
|
6382
|
-
delete stripped[field];
|
|
6383
|
-
});
|
|
6384
|
-
}
|
|
6385
|
-
return stripped;
|
|
6386
|
-
}
|
|
6387
|
-
|
|
6388
|
-
function createStoredUndoHistory(gui) {
|
|
6389
|
-
var undoEntryId = 0;
|
|
6390
|
-
|
|
6391
|
-
return {
|
|
6392
|
-
addTransaction: addTransaction,
|
|
6393
|
-
getPayloadStore: getPayloadStore
|
|
6394
|
-
};
|
|
6510
|
+
return {
|
|
6511
|
+
addTransaction: addTransaction,
|
|
6512
|
+
getPayloadStore: getPayloadStore
|
|
6513
|
+
};
|
|
6395
6514
|
|
|
6396
6515
|
async function addTransaction(tx, opts) {
|
|
6397
6516
|
var undoUnits, redoCaptureUnits, updateFlags, store, entryId, storedUndoUnits,
|
|
@@ -6752,14 +6871,38 @@
|
|
|
6752
6871
|
async function considerReprojecting(gui, dataset, opts) {
|
|
6753
6872
|
var mapCRS = gui.map.getActiveLayerCRS();
|
|
6754
6873
|
var dataCRS = internal.getDatasetCRS(dataset);
|
|
6874
|
+
var msg, reproject;
|
|
6755
6875
|
if (!dataCRS || !mapCRS || internal.crsAreEqual(mapCRS, dataCRS)) return;
|
|
6756
|
-
|
|
6757
|
-
|
|
6876
|
+
if (!datasetCanBeReprojected(dataset, dataCRS, mapCRS)) {
|
|
6877
|
+
notifyProjectionMismatch(gui, dataset);
|
|
6878
|
+
return;
|
|
6879
|
+
}
|
|
6880
|
+
msg = `The input file ${dataset?.info?.input_files[0] || ''} has a different projection from the current selected layer. Would you like to reproject it to match?`;
|
|
6881
|
+
reproject = await showPrompt(msg, 'Reproject file?');
|
|
6758
6882
|
if (reproject) {
|
|
6759
6883
|
internal.projectDataset(dataset, dataCRS, mapCRS, {densify: true});
|
|
6760
6884
|
}
|
|
6761
6885
|
}
|
|
6762
6886
|
|
|
6887
|
+
function datasetCanBeReprojected(dataset, srcCRS, destCRS) {
|
|
6888
|
+
var bounds = internal.getDatasetBounds(dataset);
|
|
6889
|
+
var transform, p;
|
|
6890
|
+
if (!bounds || !bounds.hasBounds()) return false;
|
|
6891
|
+
transform = internal.getProjTransform2(srcCRS, destCRS);
|
|
6892
|
+
p = transform(bounds.centerX(), bounds.centerY());
|
|
6893
|
+
return !!(p && isFinite(p[0]) && isFinite(p[1]));
|
|
6894
|
+
}
|
|
6895
|
+
|
|
6896
|
+
function notifyProjectionMismatch(gui, dataset) {
|
|
6897
|
+
if (!gui.notify) return;
|
|
6898
|
+
gui.notify({
|
|
6899
|
+
severity: 'warn',
|
|
6900
|
+
body: `The input file ${dataset?.info?.input_files[0] || ''} has a different projection from the current selected layer, but Mapshaper cannot transform it to the current projection.`,
|
|
6901
|
+
dedupKey: 'projection-mismatch:' + (dataset?.info?.input_files?.[0] || '')
|
|
6902
|
+
});
|
|
6903
|
+
}
|
|
6904
|
+
|
|
6905
|
+
|
|
6763
6906
|
var geopackagePromise = null;
|
|
6764
6907
|
var geoParquetPromise = null;
|
|
6765
6908
|
var geoTIFFPromise = null;
|
|
@@ -6903,7 +7046,7 @@
|
|
|
6903
7046
|
async function persistRasterLayerSamples(dataset) {
|
|
6904
7047
|
var promises = [];
|
|
6905
7048
|
dataset.layers.forEach(function(lyr) {
|
|
6906
|
-
var grid = lyr.raster && getRasterGrid(lyr.raster);
|
|
7049
|
+
var grid = lyr.raster && internal.getRasterGrid(lyr.raster);
|
|
6907
7050
|
var key;
|
|
6908
7051
|
if (!grid || !grid.samples) return;
|
|
6909
7052
|
key = makeRasterSamplesKey(lyr.name);
|
|
@@ -8459,12 +8602,6 @@
|
|
|
8459
8602
|
} else if (internal.layerHasRaster(layer)) {
|
|
8460
8603
|
gui.geographic = true;
|
|
8461
8604
|
gui.displayLayer = layer;
|
|
8462
|
-
if (needReprojectionForDisplay(sourceCRS, displayCRS)) {
|
|
8463
|
-
// Raster warping is not supported yet; hide rather than misplace pixels.
|
|
8464
|
-
gui.unprojectable = true;
|
|
8465
|
-
gui.displayLayer = {shapes: []};
|
|
8466
|
-
notifyRasterReprojectionBlocked(layer, opts);
|
|
8467
|
-
}
|
|
8468
8605
|
} else if (layer.geometry_type) {
|
|
8469
8606
|
gui.geographic = true;
|
|
8470
8607
|
gui.displayLayer = layer;
|
|
@@ -8489,22 +8626,18 @@
|
|
|
8489
8626
|
// Don't try to draw paths containing coordinates that failed to project
|
|
8490
8627
|
gui.displayLayer = internal.filterPathLayerByArcIds(gui.displayLayer, emptyArcs);
|
|
8491
8628
|
}
|
|
8629
|
+
} else if (internal.layerHasRaster(layer)) {
|
|
8630
|
+
gui.bounds = getProjectedRasterDisplayBounds(layer, sourceCRS, displayCRS);
|
|
8492
8631
|
}
|
|
8493
8632
|
}
|
|
8494
8633
|
|
|
8495
|
-
gui.bounds = getDisplayBounds(gui.displayLayer, gui.displayArcs);
|
|
8634
|
+
gui.bounds = gui.bounds || getDisplayBounds(gui.displayLayer, gui.displayArcs);
|
|
8496
8635
|
layer.gui = gui;
|
|
8497
8636
|
}
|
|
8498
8637
|
|
|
8499
|
-
function
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
severity: 'warn',
|
|
8503
|
-
title: 'Raster layer hidden',
|
|
8504
|
-
body: 'The raster layer "' + (layer.name || '[unnamed]') +
|
|
8505
|
-
'" cannot be displayed in the current map projection because raster reprojection is not supported yet.',
|
|
8506
|
-
dedupKey: 'raster-reprojection-blocked:' + (layer.menu_id || layer.name || '')
|
|
8507
|
-
});
|
|
8638
|
+
function getProjectedRasterDisplayBounds(layer, sourceCRS, displayCRS) {
|
|
8639
|
+
var bbox = internal.getProjectedRasterGridBBox(layer.raster, sourceCRS, displayCRS, {raster_mesh_interval: 32});
|
|
8640
|
+
return bbox ? new Bounds$1(bbox) : new Bounds$1();
|
|
8508
8641
|
}
|
|
8509
8642
|
|
|
8510
8643
|
function getDisplayBounds(lyr, arcs) {
|
|
@@ -10655,8 +10788,8 @@
|
|
|
10655
10788
|
if (e.deleteLayer) {
|
|
10656
10789
|
addMenuItem('delete layer', e.deleteLayer, '');
|
|
10657
10790
|
}
|
|
10658
|
-
if (e.
|
|
10659
|
-
addMenuItem('
|
|
10791
|
+
if (e.showLayerInfo) {
|
|
10792
|
+
addMenuItem('show info', e.showLayerInfo, '');
|
|
10660
10793
|
}
|
|
10661
10794
|
|
|
10662
10795
|
if (lyr && lyr.gui.geographic) {
|
|
@@ -11083,6 +11216,15 @@
|
|
|
11083
11216
|
}
|
|
11084
11217
|
}
|
|
11085
11218
|
|
|
11219
|
+
function showLayerInfo() {
|
|
11220
|
+
var target = findLayerById(id);
|
|
11221
|
+
var popup, content;
|
|
11222
|
+
if (!target) return;
|
|
11223
|
+
popup = showPopupAlert('', 'Layer info');
|
|
11224
|
+
content = popup.container().addClass('layer-info-popup');
|
|
11225
|
+
content.node().appendChild(renderLayerInfo(internal.getLayerInfo(target.layer, target.dataset)));
|
|
11226
|
+
}
|
|
11227
|
+
|
|
11086
11228
|
function openLayerMenu(e) {
|
|
11087
11229
|
var menuEvent = e;
|
|
11088
11230
|
e.stopPropagation();
|
|
@@ -11094,7 +11236,7 @@
|
|
|
11094
11236
|
};
|
|
11095
11237
|
}
|
|
11096
11238
|
menuEvent.deleteLayer = deleteLayer;
|
|
11097
|
-
menuEvent.
|
|
11239
|
+
menuEvent.showLayerInfo = showLayerInfo;
|
|
11098
11240
|
menuEvent.contextMenuId = 'layer-' + id;
|
|
11099
11241
|
openContextMenu(menuEvent, null, null);
|
|
11100
11242
|
}
|
|
@@ -11173,7 +11315,7 @@
|
|
|
11173
11315
|
if (isFrame) {
|
|
11174
11316
|
str = 'map frame';
|
|
11175
11317
|
} else if (internal.layerHasRaster(lyr)) {
|
|
11176
|
-
str = utils$1.format('%,d x %,d %s', getRasterWidth(lyr.raster), getRasterHeight(lyr.raster), type);
|
|
11318
|
+
str = utils$1.format('%,d x %,d %s', internal.getRasterWidth(lyr.raster), internal.getRasterHeight(lyr.raster), type);
|
|
11177
11319
|
} else if (type) {
|
|
11178
11320
|
str = utils$1.format('%,d %s%s', n, type, utils$1.pluralSuffix(n));
|
|
11179
11321
|
} else {
|
|
@@ -11182,44 +11324,137 @@
|
|
|
11182
11324
|
return str;
|
|
11183
11325
|
}
|
|
11184
11326
|
|
|
11185
|
-
function
|
|
11186
|
-
var
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
entryPrefix: 'rename-layer'
|
|
11196
|
-
});
|
|
11197
|
-
} else {
|
|
11198
|
-
target.layer.name = name;
|
|
11327
|
+
function renderLayerInfo(info) {
|
|
11328
|
+
var container = document.createElement('div');
|
|
11329
|
+
var title = document.createElement('div');
|
|
11330
|
+
container.className = 'console-info';
|
|
11331
|
+
title.className = 'console-info-title';
|
|
11332
|
+
title.textContent = 'Layer: ' + (info.layer_name || '[unnamed layer]');
|
|
11333
|
+
container.appendChild(title);
|
|
11334
|
+
container.appendChild(renderKeyValueTable(getInfoRows(info), 'console-info-table'));
|
|
11335
|
+
if (!info.raster_type) {
|
|
11336
|
+
container.appendChild(renderAttributeInfoTable(info.attribute_data));
|
|
11199
11337
|
}
|
|
11200
|
-
|
|
11201
|
-
updateMenuBtn();
|
|
11338
|
+
return container;
|
|
11202
11339
|
}
|
|
11203
11340
|
|
|
11204
|
-
function
|
|
11205
|
-
|
|
11206
|
-
|
|
11341
|
+
function getInfoRows(info) {
|
|
11342
|
+
var rows = [
|
|
11343
|
+
['Type', info.raster_type ? 'raster data' : info.geometry_type || 'tabular data']
|
|
11344
|
+
];
|
|
11345
|
+
if (info.raster_type) {
|
|
11346
|
+
rows.push(['Size', utils$1.format('%,d x %,d x %,d', info.raster_width, info.raster_height, info.raster_bands)]);
|
|
11347
|
+
rows.push(['Data', info.raster_pixel_type || 'unknown']);
|
|
11348
|
+
} else {
|
|
11349
|
+
rows.push(['Records', utils$1.format('%,d', info.feature_count)]);
|
|
11350
|
+
}
|
|
11351
|
+
if (info.null_shape_count > 0) {
|
|
11352
|
+
rows.push(['Nulls', utils$1.format("%'d", info.null_shape_count)]);
|
|
11353
|
+
}
|
|
11354
|
+
if (info.raster_type || info.geometry_type && info.feature_count > info.null_shape_count) {
|
|
11355
|
+
rows.push(['Bounds', info.bbox.join(',')]);
|
|
11356
|
+
rows.push(['CRS', info.proj4]);
|
|
11207
11357
|
}
|
|
11358
|
+
rows.push(['Source', info.source_file || 'n/a']);
|
|
11359
|
+
return rows;
|
|
11208
11360
|
}
|
|
11209
11361
|
|
|
11210
|
-
function
|
|
11211
|
-
|
|
11362
|
+
function renderAttributeInfoTable(fields) {
|
|
11363
|
+
var wrapper = document.createElement('div');
|
|
11364
|
+
var title = document.createElement('div');
|
|
11365
|
+
wrapper.className = 'console-attribute-info';
|
|
11366
|
+
title.className = 'console-info-subtitle';
|
|
11367
|
+
title.textContent = 'Attribute data';
|
|
11368
|
+
wrapper.appendChild(title);
|
|
11369
|
+
if (!fields) {
|
|
11370
|
+
var none = document.createElement('div');
|
|
11371
|
+
none.className = 'console-info-empty';
|
|
11372
|
+
none.textContent = '[none]';
|
|
11373
|
+
wrapper.appendChild(none);
|
|
11374
|
+
return wrapper;
|
|
11375
|
+
}
|
|
11376
|
+
wrapper.appendChild(renderDataTable(
|
|
11377
|
+
[['Field', 'First value']].concat(fields.map(function(o) {
|
|
11378
|
+
var valKey = 'first_value' in o ? 'first_value' : 'value';
|
|
11379
|
+
return [o.field, formatInfoValue(o[valKey])];
|
|
11380
|
+
})),
|
|
11381
|
+
'console-attribute-table'
|
|
11382
|
+
));
|
|
11383
|
+
return wrapper;
|
|
11212
11384
|
}
|
|
11213
11385
|
|
|
11214
|
-
function
|
|
11215
|
-
return
|
|
11216
|
-
'<div class="col2">%s</div></div>', cname ? ' ' + cname : '', c1, c2);
|
|
11386
|
+
function renderKeyValueTable(rows, className) {
|
|
11387
|
+
return renderDataTable(rows, className + ' console-key-value-table');
|
|
11217
11388
|
}
|
|
11218
|
-
}
|
|
11219
11389
|
|
|
11220
|
-
|
|
11221
|
-
|
|
11222
|
-
|
|
11390
|
+
function renderDataTable(rows, className) {
|
|
11391
|
+
var table = document.createElement('table');
|
|
11392
|
+
var tbody = document.createElement('tbody');
|
|
11393
|
+
table.className = className;
|
|
11394
|
+
rows.forEach(function(row, i) {
|
|
11395
|
+
var tr = document.createElement('tr');
|
|
11396
|
+
row.forEach(function(val) {
|
|
11397
|
+
var cell = document.createElement(i === 0 && rows.length > 1 && className == 'console-attribute-table' ? 'th' : 'td');
|
|
11398
|
+
cell.textContent = val == null ? '' : String(val);
|
|
11399
|
+
tr.appendChild(cell);
|
|
11400
|
+
});
|
|
11401
|
+
tbody.appendChild(tr);
|
|
11402
|
+
});
|
|
11403
|
+
table.appendChild(tbody);
|
|
11404
|
+
return table;
|
|
11405
|
+
}
|
|
11406
|
+
|
|
11407
|
+
function formatInfoValue(val) {
|
|
11408
|
+
if (val === null || val === undefined) return '';
|
|
11409
|
+
if (utils$1.isString(val)) {
|
|
11410
|
+
return "'" + val.replace(/[\r\t\n]/g, function(c) {
|
|
11411
|
+
return c == '\n' ? '\\n' : c == '\r' ? '\\r' : '\\t';
|
|
11412
|
+
}) + "'";
|
|
11413
|
+
}
|
|
11414
|
+
if (utils$1.isNumber(val) || val === true || val === false) {
|
|
11415
|
+
return String(val);
|
|
11416
|
+
}
|
|
11417
|
+
return JSON.stringify(val);
|
|
11418
|
+
}
|
|
11419
|
+
|
|
11420
|
+
function renameLayer(target, name) {
|
|
11421
|
+
var undoTransaction;
|
|
11422
|
+
if (!target || target.layer.name == name) return;
|
|
11423
|
+
undoTransaction = createUndoTransaction(gui, 'rename layer');
|
|
11424
|
+
if (undoTransaction) {
|
|
11425
|
+
undoTransaction.captureLayerMetadataBefore(target.layer, {operation: 'renameLayer', unit: 'name'});
|
|
11426
|
+
target.layer.name = name;
|
|
11427
|
+
markLayerChanged(target.layer, {operation: 'renameLayer', unit: 'name'});
|
|
11428
|
+
addUndoTransactionToHistory(gui, undoTransaction, {
|
|
11429
|
+
flags: {select: true},
|
|
11430
|
+
entryPrefix: 'rename-layer'
|
|
11431
|
+
});
|
|
11432
|
+
} else {
|
|
11433
|
+
target.layer.name = name;
|
|
11434
|
+
}
|
|
11435
|
+
gui.session.layerRenamed(target.layer, name);
|
|
11436
|
+
updateMenuBtn();
|
|
11437
|
+
}
|
|
11438
|
+
|
|
11439
|
+
function markLayerChanged(layer, detail) {
|
|
11440
|
+
if (internal.UndoTracking && internal.UndoTracking.markLayerChanged) {
|
|
11441
|
+
internal.UndoTracking.markLayerChanged(layer, detail);
|
|
11442
|
+
}
|
|
11443
|
+
}
|
|
11444
|
+
|
|
11445
|
+
function isPinnable(lyr) {
|
|
11446
|
+
return internal.layerIsGeometric(lyr) || internal.layerHasRaster(lyr) || internal.layerHasFurniture(lyr);
|
|
11447
|
+
}
|
|
11448
|
+
|
|
11449
|
+
function rowHTML(c1, c2, cname) {
|
|
11450
|
+
return utils$1.format('<div class="row%s"><div class="col1">%s</div>' +
|
|
11451
|
+
'<div class="col2">%s</div></div>', cname ? ' ' + cname : '', c1, c2);
|
|
11452
|
+
}
|
|
11453
|
+
}
|
|
11454
|
+
|
|
11455
|
+
// Hamburger menu in the top-right of the header. After the user starts
|
|
11456
|
+
// editing, the splash bar (Docs / GitHub / Survey / Sponsor) is hidden by
|
|
11457
|
+
// gui.mjs; this menu is the post-edit replacement, exposing the same links
|
|
11223
11458
|
// from a single button so the editing controls don't have to share the row.
|
|
11224
11459
|
|
|
11225
11460
|
|
|
@@ -11282,6 +11517,8 @@
|
|
|
11282
11517
|
var note = menu.findChild('.history-menu-note');
|
|
11283
11518
|
var clearBtn = menu.findChild('.history-clear-btn');
|
|
11284
11519
|
var commandLogBtn = menu.findChild('.history-command-log-btn');
|
|
11520
|
+
var createSnapshotBtn = menu.findChild('.history-create-snapshot-btn');
|
|
11521
|
+
var snapshotList = menu.findChild('.history-snapshot-list');
|
|
11285
11522
|
|
|
11286
11523
|
gui.appUndoIsEnabled = isAppUndoEnabled;
|
|
11287
11524
|
|
|
@@ -11334,6 +11571,13 @@
|
|
|
11334
11571
|
gui.clearMode();
|
|
11335
11572
|
});
|
|
11336
11573
|
|
|
11574
|
+
createSnapshotBtn.on('click', function(e) {
|
|
11575
|
+
e.stopPropagation();
|
|
11576
|
+
if (!createSnapshotBtn.hasClass('disabled') && gui.sessionSnapshots) {
|
|
11577
|
+
gui.sessionSnapshots.saveSnapshot().then(renderSnapshotList);
|
|
11578
|
+
}
|
|
11579
|
+
});
|
|
11580
|
+
|
|
11337
11581
|
document.addEventListener('keydown', function(e) {
|
|
11338
11582
|
if (gui.getMode() == 'history_menu' && e.key == 'Escape') {
|
|
11339
11583
|
gui.clearMode();
|
|
@@ -11347,6 +11591,7 @@
|
|
|
11347
11591
|
function turnOn() {
|
|
11348
11592
|
btn.attr('aria-expanded', 'true');
|
|
11349
11593
|
updateMenuState();
|
|
11594
|
+
renderSnapshotList();
|
|
11350
11595
|
menu.show();
|
|
11351
11596
|
}
|
|
11352
11597
|
|
|
@@ -11359,6 +11604,7 @@
|
|
|
11359
11604
|
// setItemEnabled(undoBtn, gui.undo.canUndo());
|
|
11360
11605
|
// setItemEnabled(redoBtn, gui.undo.canRedo());
|
|
11361
11606
|
setItemEnabled(clearBtn, gui.undo.canUndo() || gui.undo.canRedo());
|
|
11607
|
+
setItemEnabled(createSnapshotBtn, !!(gui.sessionSnapshots && gui.sessionSnapshots.enabled));
|
|
11362
11608
|
updateToggle();
|
|
11363
11609
|
}
|
|
11364
11610
|
|
|
@@ -11381,6 +11627,14 @@
|
|
|
11381
11627
|
}
|
|
11382
11628
|
return Promise.resolve();
|
|
11383
11629
|
}
|
|
11630
|
+
|
|
11631
|
+
function renderSnapshotList() {
|
|
11632
|
+
if (gui.sessionSnapshots && gui.sessionSnapshots.enabled && snapshotList) {
|
|
11633
|
+
gui.sessionSnapshots.renderSnapshotList(snapshotList);
|
|
11634
|
+
} else if (snapshotList) {
|
|
11635
|
+
snapshotList.empty();
|
|
11636
|
+
}
|
|
11637
|
+
}
|
|
11384
11638
|
}
|
|
11385
11639
|
|
|
11386
11640
|
function isAppUndoEnabled() {
|
|
@@ -12317,6 +12571,7 @@
|
|
|
12317
12571
|
info: 'inspect features',
|
|
12318
12572
|
box: 'rectangle tool',
|
|
12319
12573
|
data: 'edit attributes',
|
|
12574
|
+
label_style: 'style labels',
|
|
12320
12575
|
labels: 'position labels',
|
|
12321
12576
|
edit_points: 'add/drag points',
|
|
12322
12577
|
edit_lines: 'draw/edit polylines',
|
|
@@ -12351,6 +12606,7 @@
|
|
|
12351
12606
|
});
|
|
12352
12607
|
|
|
12353
12608
|
btn.on('mouseenter', function() {
|
|
12609
|
+
if (gui.state.label_style_panel_open) return;
|
|
12354
12610
|
btn.addClass('hover');
|
|
12355
12611
|
if (_menuOpen) {
|
|
12356
12612
|
clearTimeout(_menuTimeout); // prevent timed closing
|
|
@@ -12363,7 +12619,10 @@
|
|
|
12363
12619
|
});
|
|
12364
12620
|
|
|
12365
12621
|
btn.on('click', function(e) {
|
|
12366
|
-
if (
|
|
12622
|
+
if (_editMode == 'label_style') {
|
|
12623
|
+
setMode('info');
|
|
12624
|
+
closeMenu();
|
|
12625
|
+
} else if (active()) {
|
|
12367
12626
|
setMode('off');
|
|
12368
12627
|
closeMenu();
|
|
12369
12628
|
} else if (_menuOpen) {
|
|
@@ -12374,6 +12633,7 @@
|
|
|
12374
12633
|
}
|
|
12375
12634
|
e.stopPropagation();
|
|
12376
12635
|
});
|
|
12636
|
+
|
|
12377
12637
|
}
|
|
12378
12638
|
|
|
12379
12639
|
this.turnOff = function() {
|
|
@@ -12385,7 +12645,7 @@
|
|
|
12385
12645
|
};
|
|
12386
12646
|
|
|
12387
12647
|
this.modeUsesHitDetection = function(mode) {
|
|
12388
|
-
return ['info', 'selection', 'data', 'labels', 'edit_points', 'vertices', 'rectangles', 'edit_lines', 'edit_polygons', 'snip_lines'].includes(mode);
|
|
12648
|
+
return ['info', 'selection', 'data', 'label_style', 'labels', 'edit_points', 'vertices', 'rectangles', 'edit_lines', 'edit_polygons', 'snip_lines'].includes(mode);
|
|
12389
12649
|
};
|
|
12390
12650
|
|
|
12391
12651
|
this.modeUsesPopup = function(mode) {
|
|
@@ -12393,7 +12653,7 @@
|
|
|
12393
12653
|
};
|
|
12394
12654
|
|
|
12395
12655
|
this.modeSupportsUndo = function(mode) {
|
|
12396
|
-
return ['data', 'labels', 'edit_points', 'edit_lines', 'edit_polygons', 'vertices', 'rectangles'].includes(mode);
|
|
12656
|
+
return ['data', 'label_style', 'labels', 'edit_points', 'edit_lines', 'edit_polygons', 'vertices', 'rectangles'].includes(mode);
|
|
12397
12657
|
};
|
|
12398
12658
|
|
|
12399
12659
|
this.getMode = getInteractionMode;
|
|
@@ -12475,11 +12735,16 @@
|
|
|
12475
12735
|
// if current editing mode is not available, turn off the tool
|
|
12476
12736
|
function updateCurrentMode() {
|
|
12477
12737
|
var modes = getAvailableModes();
|
|
12478
|
-
if (modes.indexOf(_editMode) == -1) {
|
|
12738
|
+
if (modes.indexOf(_editMode) == -1 && !labelStyleModeIsAvailable()) {
|
|
12479
12739
|
setMode('off');
|
|
12480
12740
|
}
|
|
12481
12741
|
}
|
|
12482
12742
|
|
|
12743
|
+
function labelStyleModeIsAvailable() {
|
|
12744
|
+
var o = gui.model.getActiveLayer();
|
|
12745
|
+
return _editMode == 'label_style' && o && o.layer && internal.layerHasLabels(o.layer);
|
|
12746
|
+
}
|
|
12747
|
+
|
|
12483
12748
|
function openMenu() {
|
|
12484
12749
|
clearTimeout(_menuTimeout);
|
|
12485
12750
|
if (!_menuOpen) {
|
|
@@ -12530,7 +12795,7 @@
|
|
|
12530
12795
|
}
|
|
12531
12796
|
btn.classed('hover', _menuOpen);
|
|
12532
12797
|
// btn.classed('selected', active() && !_menuOpen);
|
|
12533
|
-
btn.classed('selected', active());
|
|
12798
|
+
btn.classed('selected', active() && _editMode != 'label_style');
|
|
12534
12799
|
}
|
|
12535
12800
|
|
|
12536
12801
|
function updateSelectionHighlight() {
|
|
@@ -12611,142 +12876,1924 @@
|
|
|
12611
12876
|
return el.node();
|
|
12612
12877
|
};
|
|
12613
12878
|
|
|
12614
|
-
function updateVisibility() {
|
|
12615
|
-
var shouldShow = visible && GUI.isActiveInstance(gui);
|
|
12616
|
-
if (shouldShow) {
|
|
12617
|
-
clearTimeout(hideTimer);
|
|
12618
|
-
hideTimer = null;
|
|
12619
|
-
el.css('display', 'flex');
|
|
12620
|
-
// wait one frame so the browser registers the initial state before
|
|
12621
|
-
// the transition kicks in
|
|
12622
|
-
requestAnimationFrame(function() {
|
|
12623
|
-
el.addClass('visible');
|
|
12879
|
+
function updateVisibility() {
|
|
12880
|
+
var shouldShow = visible && GUI.isActiveInstance(gui);
|
|
12881
|
+
if (shouldShow) {
|
|
12882
|
+
clearTimeout(hideTimer);
|
|
12883
|
+
hideTimer = null;
|
|
12884
|
+
el.css('display', 'flex');
|
|
12885
|
+
// wait one frame so the browser registers the initial state before
|
|
12886
|
+
// the transition kicks in
|
|
12887
|
+
requestAnimationFrame(function() {
|
|
12888
|
+
el.addClass('visible');
|
|
12889
|
+
});
|
|
12890
|
+
} else {
|
|
12891
|
+
el.removeClass('visible');
|
|
12892
|
+
// wait for the transition to finish before hiding completely
|
|
12893
|
+
clearTimeout(hideTimer);
|
|
12894
|
+
hideTimer = setTimeout(function() {
|
|
12895
|
+
if (!(visible && GUI.isActiveInstance(gui))) {
|
|
12896
|
+
el.css('display', 'none');
|
|
12897
|
+
}
|
|
12898
|
+
}, transitionMs);
|
|
12899
|
+
}
|
|
12900
|
+
}
|
|
12901
|
+
}
|
|
12902
|
+
|
|
12903
|
+
function ToolbarButton(parent, iconRef, opts) {
|
|
12904
|
+
var btn = El('div').addClass('floating-toolbar-btn').appendTo(parent);
|
|
12905
|
+
if (iconRef) {
|
|
12906
|
+
var iconNode = El('body').findChild(iconRef);
|
|
12907
|
+
if (iconNode) {
|
|
12908
|
+
var icon = iconNode.node().cloneNode(true);
|
|
12909
|
+
if (icon.hasAttribute('id')) icon.removeAttribute('id');
|
|
12910
|
+
btn.node().appendChild(icon);
|
|
12911
|
+
}
|
|
12912
|
+
}
|
|
12913
|
+
var enabled = true;
|
|
12914
|
+
var clickHandlers = [];
|
|
12915
|
+
|
|
12916
|
+
if (opts.tooltip) setTooltip(opts.tooltip);
|
|
12917
|
+
|
|
12918
|
+
// Block native dblclick to avoid the map's double-click zoom
|
|
12919
|
+
btn.on('dblclick', function(e) { e.stopPropagation(); });
|
|
12920
|
+
|
|
12921
|
+
btn.on('click', function(e) {
|
|
12922
|
+
if (!enabled) return;
|
|
12923
|
+
for (var i = 0; i < clickHandlers.length; i++) {
|
|
12924
|
+
clickHandlers[i](e);
|
|
12925
|
+
}
|
|
12926
|
+
});
|
|
12927
|
+
|
|
12928
|
+
this.on = function(event, fn) {
|
|
12929
|
+
if (event == 'click') {
|
|
12930
|
+
clickHandlers.push(fn);
|
|
12931
|
+
} else {
|
|
12932
|
+
btn.on(event, fn);
|
|
12933
|
+
}
|
|
12934
|
+
return this;
|
|
12935
|
+
};
|
|
12936
|
+
|
|
12937
|
+
this.enable = function() { return this.setEnabled(true); };
|
|
12938
|
+
this.disable = function() { return this.setEnabled(false); };
|
|
12939
|
+
|
|
12940
|
+
this.setEnabled = function(b) {
|
|
12941
|
+
enabled = !!b;
|
|
12942
|
+
btn.classed('disabled', !enabled);
|
|
12943
|
+
return this;
|
|
12944
|
+
};
|
|
12945
|
+
|
|
12946
|
+
this.setTooltip = setTooltip;
|
|
12947
|
+
|
|
12948
|
+
this.node = function() {
|
|
12949
|
+
return btn.node();
|
|
12950
|
+
};
|
|
12951
|
+
|
|
12952
|
+
function setTooltip(text) {
|
|
12953
|
+
btn.attr('data-tooltip', text);
|
|
12954
|
+
}
|
|
12955
|
+
}
|
|
12956
|
+
|
|
12957
|
+
// Floating toolbar that exposes undo/redo while the user is in an editing
|
|
12958
|
+
// interaction mode. The toolbar is the first consumer of FloatingToolbar;
|
|
12959
|
+
// future per-mode toolbars (e.g. feature styling) can follow the same pattern.
|
|
12960
|
+
|
|
12961
|
+
function EditToolbar(gui) {
|
|
12962
|
+
var toolbar = new FloatingToolbar(gui, { name: 'edit-toolbar' });
|
|
12963
|
+
var isMac = navigator.userAgent.includes('Mac');
|
|
12964
|
+
var modKey = isMac ? '\u2318' : 'Ctrl+';
|
|
12965
|
+
var shiftKey = isMac ? '\u21e7' : 'Shift+';
|
|
12966
|
+
|
|
12967
|
+
var undoBtn = toolbar.addButton('#undo-icon', {
|
|
12968
|
+
tooltip: 'Undo (' + modKey + 'Z)'
|
|
12969
|
+
}).on('click', function() {
|
|
12970
|
+
gui.undo.undo();
|
|
12971
|
+
});
|
|
12972
|
+
|
|
12973
|
+
var redoBtn = toolbar.addButton('#redo-icon', {
|
|
12974
|
+
tooltip: 'Redo (' + shiftKey + modKey + 'Z)'
|
|
12975
|
+
}).on('click', function() {
|
|
12976
|
+
gui.undo.redo();
|
|
12977
|
+
});
|
|
12978
|
+
|
|
12979
|
+
updateButtons();
|
|
12980
|
+
|
|
12981
|
+
gui.on('interaction_mode_change', function() {
|
|
12982
|
+
updateVisibility();
|
|
12983
|
+
// history is cleared on mode change; refresh button states next tick
|
|
12984
|
+
updateButtons();
|
|
12985
|
+
});
|
|
12986
|
+
|
|
12987
|
+
gui.on('history_change', function(e) {
|
|
12988
|
+
undoBtn.setEnabled(!!e.canUndo);
|
|
12989
|
+
redoBtn.setEnabled(!!e.canRedo);
|
|
12990
|
+
// Visibility may also depend on history (e.g. attribute edits via popup
|
|
12991
|
+
// happen in modes that don't otherwise support undo).
|
|
12992
|
+
updateVisibility();
|
|
12993
|
+
});
|
|
12994
|
+
|
|
12995
|
+
updateVisibility();
|
|
12996
|
+
|
|
12997
|
+
function updateVisibility() {
|
|
12998
|
+
if (!gui.interaction) {
|
|
12999
|
+
toolbar.hide();
|
|
13000
|
+
return;
|
|
13001
|
+
}
|
|
13002
|
+
var mode = gui.interaction.getMode();
|
|
13003
|
+
var hasHistory = gui.undo.canUndo() || gui.undo.canRedo();
|
|
13004
|
+
if (gui.interaction.modeSupportsUndo(mode) || hasHistory) {
|
|
13005
|
+
toolbar.show();
|
|
13006
|
+
} else {
|
|
13007
|
+
toolbar.hide();
|
|
13008
|
+
}
|
|
13009
|
+
}
|
|
13010
|
+
|
|
13011
|
+
function updateButtons() {
|
|
13012
|
+
undoBtn.setEnabled(gui.undo.canUndo());
|
|
13013
|
+
redoBtn.setEnabled(gui.undo.canRedo());
|
|
13014
|
+
}
|
|
13015
|
+
}
|
|
13016
|
+
|
|
13017
|
+
var sans = [{
|
|
13018
|
+
name: 'NYTFranklin'
|
|
13019
|
+
}, {
|
|
13020
|
+
name: 'Acumin Pro'
|
|
13021
|
+
}, {
|
|
13022
|
+
name: 'Akzidenz-Grotesk'
|
|
13023
|
+
}, {
|
|
13024
|
+
name: 'Arial'
|
|
13025
|
+
}, {
|
|
13026
|
+
name: 'Avenir',
|
|
13027
|
+
alternates: ['Avenir Next']
|
|
13028
|
+
}, {
|
|
13029
|
+
name: 'Bahnschrift'
|
|
13030
|
+
}, {
|
|
13031
|
+
name: 'Brandon Grotesque'
|
|
13032
|
+
}, {
|
|
13033
|
+
name: 'Cabin'
|
|
13034
|
+
}, {
|
|
13035
|
+
name: 'Calibri'
|
|
13036
|
+
}, {
|
|
13037
|
+
name: 'Candara'
|
|
13038
|
+
}, {
|
|
13039
|
+
name: 'Cantarell'
|
|
13040
|
+
}, {
|
|
13041
|
+
name: 'Century Gothic'
|
|
13042
|
+
}, {
|
|
13043
|
+
name: 'Clearview'
|
|
13044
|
+
}, {
|
|
13045
|
+
name: 'Corbel'
|
|
13046
|
+
}, {
|
|
13047
|
+
name: 'DejaVu Sans'
|
|
13048
|
+
}, {
|
|
13049
|
+
name: 'DIN 1451',
|
|
13050
|
+
alternates: ['DIN']
|
|
13051
|
+
}, {
|
|
13052
|
+
name: 'DM Sans'
|
|
13053
|
+
}, {
|
|
13054
|
+
name: 'FF Meta'
|
|
13055
|
+
}, {
|
|
13056
|
+
name: 'Fira Sans'
|
|
13057
|
+
}, {
|
|
13058
|
+
name: 'Frutiger',
|
|
13059
|
+
alternates: ['Frutiger Next']
|
|
13060
|
+
}, {
|
|
13061
|
+
name: 'Futura'
|
|
13062
|
+
}, {
|
|
13063
|
+
name: 'Gill Sans',
|
|
13064
|
+
alternates: ['Gill Sans MT']
|
|
13065
|
+
}, {
|
|
13066
|
+
name: 'Gotham'
|
|
13067
|
+
}, {
|
|
13068
|
+
name: 'Gudea'
|
|
13069
|
+
}, {
|
|
13070
|
+
name: 'Helvetica',
|
|
13071
|
+
alternates: ['Helvetica Neue']
|
|
13072
|
+
}, {
|
|
13073
|
+
name: 'IBM Plex Sans'
|
|
13074
|
+
}, {
|
|
13075
|
+
name: 'Inter'
|
|
13076
|
+
}, {
|
|
13077
|
+
name: 'Interstate'
|
|
13078
|
+
}, {
|
|
13079
|
+
name: 'ITC Stone Sans'
|
|
13080
|
+
}, {
|
|
13081
|
+
name: 'Josefin Sans'
|
|
13082
|
+
}, {
|
|
13083
|
+
name: 'Karla'
|
|
13084
|
+
}, {
|
|
13085
|
+
name: 'Lato'
|
|
13086
|
+
}, {
|
|
13087
|
+
name: 'Liberation Sans'
|
|
13088
|
+
}, {
|
|
13089
|
+
name: 'Lucida Grande',
|
|
13090
|
+
alternates: ['Lucida Sans', 'Lucida Sans Unicode']
|
|
13091
|
+
}, {
|
|
13092
|
+
name: 'Montserrat'
|
|
13093
|
+
}, {
|
|
13094
|
+
name: 'Museo Sans'
|
|
13095
|
+
}, {
|
|
13096
|
+
name: 'Myriad Pro'
|
|
13097
|
+
}, {
|
|
13098
|
+
name: 'Neue Haas Grotesk',
|
|
13099
|
+
alternates: ['Neue Haas Grotesk Text Pro']
|
|
13100
|
+
}, {
|
|
13101
|
+
name: 'Nunito Sans',
|
|
13102
|
+
alternates: ['Nunito']
|
|
13103
|
+
}, {
|
|
13104
|
+
name: 'Open Sans'
|
|
13105
|
+
}, {
|
|
13106
|
+
name: 'Optima'
|
|
13107
|
+
}, {
|
|
13108
|
+
name: 'Overpass'
|
|
13109
|
+
}, {
|
|
13110
|
+
name: 'Poppins'
|
|
13111
|
+
}, {
|
|
13112
|
+
name: 'Proxima Nova'
|
|
13113
|
+
}, {
|
|
13114
|
+
name: 'PT Sans'
|
|
13115
|
+
}, {
|
|
13116
|
+
name: 'Raleway'
|
|
13117
|
+
}, {
|
|
13118
|
+
name: 'Roboto',
|
|
13119
|
+
alternates: ['Noto Sans']
|
|
13120
|
+
}, {
|
|
13121
|
+
name: 'Rotis Sans Serif'
|
|
13122
|
+
}, {
|
|
13123
|
+
name: 'Rubik'
|
|
13124
|
+
}, {
|
|
13125
|
+
name: 'San Francisco',
|
|
13126
|
+
alternates: ['SF Pro']
|
|
13127
|
+
}, {
|
|
13128
|
+
name: 'Scala Sans'
|
|
13129
|
+
}, {
|
|
13130
|
+
name: 'Segoe UI'
|
|
13131
|
+
}, {
|
|
13132
|
+
name: 'Source Sans Pro',
|
|
13133
|
+
alternates: ['Source Sans 3']
|
|
13134
|
+
}, {
|
|
13135
|
+
name: 'Tahoma'
|
|
13136
|
+
}, {
|
|
13137
|
+
name: 'Trebuchet MS'
|
|
13138
|
+
}, {
|
|
13139
|
+
name: 'Tw Cen MT'
|
|
13140
|
+
}, {
|
|
13141
|
+
name: 'Ubuntu'
|
|
13142
|
+
}, {
|
|
13143
|
+
name: 'Univers'
|
|
13144
|
+
}, {
|
|
13145
|
+
name: 'Verdana'
|
|
13146
|
+
}, {
|
|
13147
|
+
name: 'Work Sans'
|
|
13148
|
+
}
|
|
13149
|
+
];
|
|
13150
|
+
|
|
13151
|
+
var condensed = [{
|
|
13152
|
+
name: 'Arial Narrow'
|
|
13153
|
+
}, {
|
|
13154
|
+
name: 'Barlow Condensed'
|
|
13155
|
+
}, {
|
|
13156
|
+
name: 'Bebas Neue'
|
|
13157
|
+
}, {
|
|
13158
|
+
name: 'DIN Condensed'
|
|
13159
|
+
}, {
|
|
13160
|
+
name: 'Franklin Gothic',
|
|
13161
|
+
alternates: ['Franklin Gothic Medium']
|
|
13162
|
+
}, {
|
|
13163
|
+
name: 'Fira Sans Condensed'
|
|
13164
|
+
}, {
|
|
13165
|
+
name: 'Helvetica Condensed'
|
|
13166
|
+
}, {
|
|
13167
|
+
name: 'Impact'
|
|
13168
|
+
}, {
|
|
13169
|
+
name: 'News Gothic MT'
|
|
13170
|
+
}, {
|
|
13171
|
+
name: 'Oswald'
|
|
13172
|
+
}, {
|
|
13173
|
+
name: 'PT Sans Narrow'
|
|
13174
|
+
}, {
|
|
13175
|
+
name: 'Roboto Condensed'
|
|
13176
|
+
}, {
|
|
13177
|
+
name: 'Trade Gothic',
|
|
13178
|
+
alternates: ['Trade Gothic Next']
|
|
13179
|
+
}, {
|
|
13180
|
+
name: 'Univers Condensed'
|
|
13181
|
+
}];
|
|
13182
|
+
|
|
13183
|
+
var mono = [{
|
|
13184
|
+
name: 'Andale Mono'
|
|
13185
|
+
}, {
|
|
13186
|
+
name: 'Cascadia Code',
|
|
13187
|
+
alternates: ['Cascadia Mono']
|
|
13188
|
+
}, {
|
|
13189
|
+
name: 'Consolas'
|
|
13190
|
+
}, {
|
|
13191
|
+
name: 'Courier New'
|
|
13192
|
+
}, {
|
|
13193
|
+
name: 'DejaVu Sans Mono'
|
|
13194
|
+
}, {
|
|
13195
|
+
name: 'Fira Mono',
|
|
13196
|
+
alternates: ['Fira Code']
|
|
13197
|
+
}, {
|
|
13198
|
+
name: 'IBM Plex Mono'
|
|
13199
|
+
}, {
|
|
13200
|
+
name: 'Inconsolata'
|
|
13201
|
+
}, {
|
|
13202
|
+
name: 'JetBrains Mono'
|
|
13203
|
+
}, {
|
|
13204
|
+
name: 'Liberation Mono'
|
|
13205
|
+
}, {
|
|
13206
|
+
name: 'Lucida Console'
|
|
13207
|
+
}, {
|
|
13208
|
+
name: 'Menlo'
|
|
13209
|
+
}, {
|
|
13210
|
+
name: 'Monaco'
|
|
13211
|
+
}, {
|
|
13212
|
+
name: 'Roboto Mono'
|
|
13213
|
+
}, {
|
|
13214
|
+
name: 'SF Mono'
|
|
13215
|
+
}, {
|
|
13216
|
+
name: 'Source Code Pro'
|
|
13217
|
+
}, {
|
|
13218
|
+
name: 'Ubuntu Mono'
|
|
13219
|
+
}];
|
|
13220
|
+
|
|
13221
|
+
var serif = [{
|
|
13222
|
+
name: 'NYTCheltenham'
|
|
13223
|
+
}, {
|
|
13224
|
+
name: 'Adobe Caslon Pro',
|
|
13225
|
+
alternates: ['Caslon']
|
|
13226
|
+
}, {
|
|
13227
|
+
name: 'Adobe Garamond Pro',
|
|
13228
|
+
alternates: ['Garamond']
|
|
13229
|
+
}, {
|
|
13230
|
+
name: 'Baskerville'
|
|
13231
|
+
}, {
|
|
13232
|
+
name: 'Bodoni',
|
|
13233
|
+
alternates: ['Bodoni MT', 'Bodoni 72']
|
|
13234
|
+
}, {
|
|
13235
|
+
name: 'Book Antiqua'
|
|
13236
|
+
}, {
|
|
13237
|
+
name: 'Bookman Old Style'
|
|
13238
|
+
}, {
|
|
13239
|
+
name: 'Cambria'
|
|
13240
|
+
}, {
|
|
13241
|
+
name: 'Century Schoolbook'
|
|
13242
|
+
}, {
|
|
13243
|
+
name: 'Clarendon'
|
|
13244
|
+
}, {
|
|
13245
|
+
name: 'Cochin'
|
|
13246
|
+
}, {
|
|
13247
|
+
name: 'Constantia'
|
|
13248
|
+
}, {
|
|
13249
|
+
name: 'Crimson Pro',
|
|
13250
|
+
alternates: ['Crimson Text']
|
|
13251
|
+
}, {
|
|
13252
|
+
name: 'DejaVu Serif'
|
|
13253
|
+
}, {
|
|
13254
|
+
name: 'Didot'
|
|
13255
|
+
}, {
|
|
13256
|
+
name: 'EB Garamond'
|
|
13257
|
+
}, {
|
|
13258
|
+
name: 'Georgia'
|
|
13259
|
+
}, {
|
|
13260
|
+
name: 'Goudy Old Style'
|
|
13261
|
+
}, {
|
|
13262
|
+
name: 'Hoefler Text'
|
|
13263
|
+
}, {
|
|
13264
|
+
name: 'IBM Plex Serif'
|
|
13265
|
+
}, {
|
|
13266
|
+
name: 'IM Fell'
|
|
13267
|
+
}, {
|
|
13268
|
+
name: 'Liberation Serif'
|
|
13269
|
+
}, {
|
|
13270
|
+
name: 'Libre Baskerville'
|
|
13271
|
+
}, {
|
|
13272
|
+
name: 'Lora'
|
|
13273
|
+
}, {
|
|
13274
|
+
name: 'Merriweather'
|
|
13275
|
+
}, {
|
|
13276
|
+
name: 'Minion Pro'
|
|
13277
|
+
}, {
|
|
13278
|
+
name: 'Noto Serif'
|
|
13279
|
+
}, {
|
|
13280
|
+
name: 'Palatino',
|
|
13281
|
+
alternates: ['Palatino Linotype']
|
|
13282
|
+
}, {
|
|
13283
|
+
name: 'Perpetua'
|
|
13284
|
+
}, {
|
|
13285
|
+
name: 'Playfair Display'
|
|
13286
|
+
}, {
|
|
13287
|
+
name: 'PT Serif'
|
|
13288
|
+
}, {
|
|
13289
|
+
name: 'Roboto Slab'
|
|
13290
|
+
}, {
|
|
13291
|
+
name: 'Rockwell'
|
|
13292
|
+
}, {
|
|
13293
|
+
name: 'Sabon',
|
|
13294
|
+
alternates: ['Sabon Next']
|
|
13295
|
+
}, {
|
|
13296
|
+
name: 'Sitka'
|
|
13297
|
+
}, {
|
|
13298
|
+
name: 'Source Serif Pro',
|
|
13299
|
+
alternates: ['Source Serif 4']
|
|
13300
|
+
}, {
|
|
13301
|
+
name: 'Times New Roman'
|
|
13302
|
+
}, {
|
|
13303
|
+
name: 'Zilla Slab'
|
|
13304
|
+
}];
|
|
13305
|
+
|
|
13306
|
+
var fontCategories = [{
|
|
13307
|
+
name: 'Sans serif',
|
|
13308
|
+
fonts: sans
|
|
13309
|
+
}, {
|
|
13310
|
+
name: 'Condensed',
|
|
13311
|
+
fonts: condensed
|
|
13312
|
+
}, {
|
|
13313
|
+
name: 'Serif',
|
|
13314
|
+
fonts: serif
|
|
13315
|
+
}, {
|
|
13316
|
+
name: 'Monospace',
|
|
13317
|
+
fonts: mono
|
|
13318
|
+
}];
|
|
13319
|
+
|
|
13320
|
+
var cachedFonts = null;
|
|
13321
|
+
var cachedFontStyles = {};
|
|
13322
|
+
var fontWeights = [100, 200, 300, 400, 500, 600, 700, 800, 900];
|
|
13323
|
+
var fontStyles = ['normal', 'italic'];
|
|
13324
|
+
var preferredWeights = [400, 700, 900, 500, 300, 600, 800, 200, 100];
|
|
13325
|
+
var weightNames = {
|
|
13326
|
+
100: 'Thin',
|
|
13327
|
+
200: 'Extra Light',
|
|
13328
|
+
300: 'Light',
|
|
13329
|
+
400: 'Regular',
|
|
13330
|
+
500: 'Medium',
|
|
13331
|
+
600: 'Semi Bold',
|
|
13332
|
+
700: 'Bold',
|
|
13333
|
+
800: 'Extra Bold',
|
|
13334
|
+
900: 'Black'
|
|
13335
|
+
};
|
|
13336
|
+
|
|
13337
|
+
// Return a list of the above fonts that are installed on the user's computer
|
|
13338
|
+
// sorted by category, alphabetically within each category.
|
|
13339
|
+
function getInstalledFonts() {
|
|
13340
|
+
if (!cachedFonts) {
|
|
13341
|
+
cachedFonts = detectInstalledFonts();
|
|
13342
|
+
}
|
|
13343
|
+
return cachedFonts;
|
|
13344
|
+
}
|
|
13345
|
+
|
|
13346
|
+
function getFontStyleVariants(fontName) {
|
|
13347
|
+
if (!fontName) return [];
|
|
13348
|
+
if (!cachedFontStyles[fontName]) {
|
|
13349
|
+
cachedFontStyles[fontName] = detectFontStyleVariants(fontName);
|
|
13350
|
+
}
|
|
13351
|
+
return cachedFontStyles[fontName];
|
|
13352
|
+
}
|
|
13353
|
+
|
|
13354
|
+
function detectInstalledFonts() {
|
|
13355
|
+
var detector = getCanvasFontDetector();
|
|
13356
|
+
return fontCategories.map(function(category) {
|
|
13357
|
+
var fonts = category.fonts.filter(function(font) {
|
|
13358
|
+
return detector && fontIsInstalled(font, detector);
|
|
13359
|
+
});
|
|
13360
|
+
return {
|
|
13361
|
+
name: category.name,
|
|
13362
|
+
fonts: fonts.map(function(font) {
|
|
13363
|
+
return font.name;
|
|
13364
|
+
}).sort(sortNames)
|
|
13365
|
+
};
|
|
13366
|
+
}).filter(function(category) {
|
|
13367
|
+
return category.fonts.length > 0;
|
|
13368
|
+
});
|
|
13369
|
+
}
|
|
13370
|
+
|
|
13371
|
+
function fontIsInstalled(font, detector) {
|
|
13372
|
+
return getFontNames(font).some(function(name) {
|
|
13373
|
+
return detector(name);
|
|
13374
|
+
});
|
|
13375
|
+
}
|
|
13376
|
+
|
|
13377
|
+
function getFontNames(font) {
|
|
13378
|
+
var alternates = font.alternates || [];
|
|
13379
|
+
if (!Array.isArray(alternates)) {
|
|
13380
|
+
alternates = [alternates];
|
|
13381
|
+
}
|
|
13382
|
+
return [font.name].concat(alternates);
|
|
13383
|
+
}
|
|
13384
|
+
|
|
13385
|
+
function sortNames(a, b) {
|
|
13386
|
+
return a.toLowerCase() < b.toLowerCase() ? -1 :
|
|
13387
|
+
a.toLowerCase() > b.toLowerCase() ? 1 : 0;
|
|
13388
|
+
}
|
|
13389
|
+
|
|
13390
|
+
function getCanvasFontDetector() {
|
|
13391
|
+
var canvas, ctx, baselines;
|
|
13392
|
+
if (typeof document == 'undefined') return null;
|
|
13393
|
+
canvas = document.createElement('canvas');
|
|
13394
|
+
ctx = canvas && canvas.getContext && canvas.getContext('2d');
|
|
13395
|
+
if (!ctx) return null;
|
|
13396
|
+
baselines = getBaselineWidths(ctx);
|
|
13397
|
+
return function(fontName) {
|
|
13398
|
+
return fontIsDetectable(ctx, baselines, fontName);
|
|
13399
|
+
};
|
|
13400
|
+
}
|
|
13401
|
+
|
|
13402
|
+
function getBaselineWidths(ctx) {
|
|
13403
|
+
return {
|
|
13404
|
+
monospace: measureFont(ctx, 'monospace'),
|
|
13405
|
+
'sans-serif': measureFont(ctx, 'sans-serif'),
|
|
13406
|
+
serif: measureFont(ctx, 'serif')
|
|
13407
|
+
};
|
|
13408
|
+
}
|
|
13409
|
+
|
|
13410
|
+
function fontIsDetectable(ctx, baselines, fontName) {
|
|
13411
|
+
return Object.keys(baselines).some(function(generic) {
|
|
13412
|
+
return measureFont(ctx, quoteFontName(fontName) + ',' + generic) != baselines[generic];
|
|
13413
|
+
});
|
|
13414
|
+
}
|
|
13415
|
+
|
|
13416
|
+
function measureFont(ctx, fontFamily) {
|
|
13417
|
+
ctx.font = '72px ' + fontFamily;
|
|
13418
|
+
return ctx.measureText('mmmmmmmmmmlliMW@#0123456789').width;
|
|
13419
|
+
}
|
|
13420
|
+
|
|
13421
|
+
function quoteFontName(name) {
|
|
13422
|
+
return '"' + String(name).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
|
|
13423
|
+
}
|
|
13424
|
+
|
|
13425
|
+
function detectFontStyleVariants(fontName) {
|
|
13426
|
+
var canvas, ctx, groups;
|
|
13427
|
+
if (typeof document == 'undefined') return getDefaultFontStyleVariants();
|
|
13428
|
+
canvas = document.createElement('canvas');
|
|
13429
|
+
ctx = canvas && canvas.getContext && canvas.getContext('2d');
|
|
13430
|
+
if (!ctx) return getDefaultFontStyleVariants();
|
|
13431
|
+
groups = {};
|
|
13432
|
+
fontStyles.forEach(function(style) {
|
|
13433
|
+
fontWeights.forEach(function(weight) {
|
|
13434
|
+
var sig = measureFontVariant(ctx, fontName, weight, style);
|
|
13435
|
+
var key = style + '|' + sig;
|
|
13436
|
+
if (!groups[key]) {
|
|
13437
|
+
groups[key] = {
|
|
13438
|
+
style: style,
|
|
13439
|
+
weights: []
|
|
13440
|
+
};
|
|
13441
|
+
}
|
|
13442
|
+
groups[key].weights.push(weight);
|
|
13443
|
+
});
|
|
13444
|
+
});
|
|
13445
|
+
var variants = Object.keys(groups).map(function(key) {
|
|
13446
|
+
var group = groups[key];
|
|
13447
|
+
return getFontStyleVariant(getPreferredWeight(group.weights), group.style);
|
|
13448
|
+
}).sort(sortFontStyleVariants);
|
|
13449
|
+
return variants.length > 0 ? variants : getDefaultFontStyleVariants();
|
|
13450
|
+
}
|
|
13451
|
+
|
|
13452
|
+
function getDefaultFontStyleVariants() {
|
|
13453
|
+
return [getFontStyleVariant(400, 'normal')];
|
|
13454
|
+
}
|
|
13455
|
+
|
|
13456
|
+
function getFontStyleVariant(weight, style) {
|
|
13457
|
+
return {
|
|
13458
|
+
label: getFontStyleLabel(weight, style),
|
|
13459
|
+
value: style + '|' + weight,
|
|
13460
|
+
style: style,
|
|
13461
|
+
weight: String(weight)
|
|
13462
|
+
};
|
|
13463
|
+
}
|
|
13464
|
+
|
|
13465
|
+
function getPreferredWeight(weights) {
|
|
13466
|
+
for (var i=0; i<preferredWeights.length; i++) {
|
|
13467
|
+
if (weights.indexOf(preferredWeights[i]) > -1) {
|
|
13468
|
+
return preferredWeights[i];
|
|
13469
|
+
}
|
|
13470
|
+
}
|
|
13471
|
+
return weights[0];
|
|
13472
|
+
}
|
|
13473
|
+
|
|
13474
|
+
function sortFontStyleVariants(a, b) {
|
|
13475
|
+
if (a.style != b.style) {
|
|
13476
|
+
return a.style == 'normal' ? -1 : 1;
|
|
13477
|
+
}
|
|
13478
|
+
return Number(a.weight) - Number(b.weight);
|
|
13479
|
+
}
|
|
13480
|
+
|
|
13481
|
+
function getFontStyleLabel(weight, style) {
|
|
13482
|
+
var name = weightNames[weight] || String(weight);
|
|
13483
|
+
if (style == 'italic') {
|
|
13484
|
+
return weight == 400 ? 'Italic' : name + ' Italic';
|
|
13485
|
+
}
|
|
13486
|
+
return name;
|
|
13487
|
+
}
|
|
13488
|
+
|
|
13489
|
+
function measureFontVariant(ctx, fontName, weight, style) {
|
|
13490
|
+
return [
|
|
13491
|
+
measureFontSample(ctx, fontName, weight, style, 'Hamburgefonts 0123456789'),
|
|
13492
|
+
measureFontSample(ctx, fontName, weight, style, 'mmmmmmmmmmlliMW@#'),
|
|
13493
|
+
measureFontSample(ctx, fontName, weight, style, 'Quick brown fox')
|
|
13494
|
+
].join('|');
|
|
13495
|
+
}
|
|
13496
|
+
|
|
13497
|
+
function measureFontSample(ctx, fontName, weight, style, text) {
|
|
13498
|
+
var metrics;
|
|
13499
|
+
ctx.font = style + ' ' + weight + ' 72px ' + quoteFontName(fontName) + ',sans-serif';
|
|
13500
|
+
metrics = ctx.measureText(text);
|
|
13501
|
+
return [
|
|
13502
|
+
metrics.width,
|
|
13503
|
+
metrics.actualBoundingBoxLeft || 0,
|
|
13504
|
+
metrics.actualBoundingBoxRight || 0,
|
|
13505
|
+
metrics.actualBoundingBoxAscent || 0,
|
|
13506
|
+
metrics.actualBoundingBoxDescent || 0
|
|
13507
|
+
].map(roundMetric).join(',');
|
|
13508
|
+
}
|
|
13509
|
+
|
|
13510
|
+
function roundMetric(val) {
|
|
13511
|
+
return Math.round(val * 100) / 100;
|
|
13512
|
+
}
|
|
13513
|
+
|
|
13514
|
+
var labelTextField = 'label-text';
|
|
13515
|
+
var fontField = 'font-family';
|
|
13516
|
+
var fontSizeField = 'font-size';
|
|
13517
|
+
var fontStyleField = 'font-style';
|
|
13518
|
+
var fontWeightField = 'font-weight';
|
|
13519
|
+
var fillField = 'fill';
|
|
13520
|
+
var cssField = 'css';
|
|
13521
|
+
var iconField = 'icon';
|
|
13522
|
+
var iconSizeField = 'icon-size';
|
|
13523
|
+
var defaultFontSize = 12;
|
|
13524
|
+
var defaultFontStyle = 'normal';
|
|
13525
|
+
var defaultFontWeight = '400';
|
|
13526
|
+
var defaultLabelColor = '#000000';
|
|
13527
|
+
var defaultIconSize = 5;
|
|
13528
|
+
var labelStyleMode = 'label_style';
|
|
13529
|
+
var labelStylePanelMode = 'label_style_tool';
|
|
13530
|
+
var savedStylesKey = 'label_styles';
|
|
13531
|
+
var labelPositionFields = internal.labelPositionFields;
|
|
13532
|
+
var savedStyleFields = [
|
|
13533
|
+
fontField,
|
|
13534
|
+
fontSizeField,
|
|
13535
|
+
fontStyleField,
|
|
13536
|
+
fontWeightField,
|
|
13537
|
+
fillField,
|
|
13538
|
+
cssField,
|
|
13539
|
+
'label-pos',
|
|
13540
|
+
iconField,
|
|
13541
|
+
iconSizeField
|
|
13542
|
+
];
|
|
13543
|
+
var labelPositions = ['nw', 'n', 'ne', 'w', 'c', 'e', 'sw', 's', 'se'];
|
|
13544
|
+
var iconTypes = [{
|
|
13545
|
+
name: ''
|
|
13546
|
+
}, {
|
|
13547
|
+
name: 'circle'
|
|
13548
|
+
}, {
|
|
13549
|
+
name: 'square'
|
|
13550
|
+
}, {
|
|
13551
|
+
name: 'ring'
|
|
13552
|
+
}, {
|
|
13553
|
+
name: 'star'
|
|
13554
|
+
}];
|
|
13555
|
+
var iconButtonSymbols = {
|
|
13556
|
+
'': '<line x1="4.25" y1="4.25" x2="11.75" y2="11.75"></line><line x1="11.75" y1="4.25" x2="4.25" y2="11.75"></line>',
|
|
13557
|
+
circle: '<circle cx="8" cy="8" r="4.25"></circle>',
|
|
13558
|
+
square: '<rect x="4" y="4" width="8" height="8"></rect>',
|
|
13559
|
+
ring: '<circle cx="8" cy="8" r="3.8"></circle>',
|
|
13560
|
+
star: '<path d="M8 3.2l1.18 2.92 3.14.22-2.42 2 .76 3.06L8 9.75 5.34 11.4l.76-3.06-2.42-2 3.14-.22L8 3.2z"></path>'
|
|
13561
|
+
};
|
|
13562
|
+
|
|
13563
|
+
function LabelTool(gui) {
|
|
13564
|
+
// button is only visible when label editing is available
|
|
13565
|
+
// clicking the button opens a panel for applying styles to labels
|
|
13566
|
+
var textBtn = gui.buttons.addButton('#text-tool-icon').addClass('menu-btn pointer-btn');
|
|
13567
|
+
var parent = gui.container.findChild('.mshp-main-map');
|
|
13568
|
+
var panel = El('div').addClass('label-style-panel rollover').appendTo(parent).hide();
|
|
13569
|
+
var createPanel = El('div').addClass('label-style-panel label-create-panel rollover').appendTo(parent).hide();
|
|
13570
|
+
var createFieldSelect, createExprInput, createCopyCheckbox, createBtn;
|
|
13571
|
+
var styleSelect, fontSelect, fontStyleSelect, fontSizeText, colorChit, colorInput, colorPicker, sbCanvas, hueCanvas, sbMarker, hueMarker, pickerHsbInputs, cssInput, posBtns, iconBtns, iconSizeText, hit;
|
|
13572
|
+
var fontOptionsRendered = false;
|
|
13573
|
+
var pickerColor = {h: 0, s: 0, b: 0};
|
|
13574
|
+
var pendingStyleHistory = null;
|
|
13575
|
+
|
|
13576
|
+
initCreatePanel();
|
|
13577
|
+
initPanel();
|
|
13578
|
+
gui.addMode(labelStylePanelMode, turnOn, turnOff, textBtn);
|
|
13579
|
+
gui.model.on('update', updateVisibility);
|
|
13580
|
+
gui.model.on('update', function() {
|
|
13581
|
+
setTimeout(updateSelectionDisplay, 0);
|
|
13582
|
+
});
|
|
13583
|
+
gui.on('interaction_mode_change', function(e) {
|
|
13584
|
+
if (panel.visible() && e.mode != labelStyleMode && gui.getMode() == labelStylePanelMode) {
|
|
13585
|
+
gui.clearMode();
|
|
13586
|
+
}
|
|
13587
|
+
});
|
|
13588
|
+
|
|
13589
|
+
hit = gui.map.getHitControl && gui.map.getHitControl();
|
|
13590
|
+
if (hit) {
|
|
13591
|
+
hit.on('change', function(e) {
|
|
13592
|
+
if (e.mode == labelStyleMode) {
|
|
13593
|
+
updateSelectionDisplay();
|
|
13594
|
+
updateControls();
|
|
13595
|
+
}
|
|
13596
|
+
});
|
|
13597
|
+
}
|
|
13598
|
+
|
|
13599
|
+
updateVisibility();
|
|
13600
|
+
|
|
13601
|
+
function initPanel() {
|
|
13602
|
+
var header = El('div').addClass('label-style-panel-title').appendTo(panel).text('Label styles');
|
|
13603
|
+
El('button').addClass('label-style-close').appendTo(header).text('×').on('click', function() {
|
|
13604
|
+
gui.clearMode();
|
|
13605
|
+
});
|
|
13606
|
+
|
|
13607
|
+
var selectRow = El('div').addClass('label-style-row label-style-selection-row').appendTo(panel);
|
|
13608
|
+
El('button').appendTo(selectRow).text('Select all').on('click', selectAllLabels);
|
|
13609
|
+
El('button').appendTo(selectRow).text('Clear').on('click', clearSelection);
|
|
13610
|
+
|
|
13611
|
+
var fontRow = El('label').addClass('label-style-row').appendTo(panel);
|
|
13612
|
+
El('span').appendTo(fontRow).text('Font');
|
|
13613
|
+
fontSelect = El('select').appendTo(fontRow).on('change', function() {
|
|
13614
|
+
if (fontSelect.node().value) {
|
|
13615
|
+
applyFont(fontSelect.node().value);
|
|
13616
|
+
}
|
|
13617
|
+
});
|
|
13618
|
+
|
|
13619
|
+
var fontStyleRow = El('label').addClass('label-style-row').appendTo(panel);
|
|
13620
|
+
El('span').appendTo(fontStyleRow).text('Font style');
|
|
13621
|
+
fontStyleSelect = El('select').appendTo(fontStyleRow).on('change', function() {
|
|
13622
|
+
if (fontStyleSelect.node().value) {
|
|
13623
|
+
applyFontStyleVariant(fontStyleSelect.node().value);
|
|
13624
|
+
}
|
|
13625
|
+
});
|
|
13626
|
+
|
|
13627
|
+
var colorSizeRow = El('div').addClass('label-style-row label-split-row').appendTo(panel);
|
|
13628
|
+
var colorRow = El('div').addClass('label-split-cell label-color-row').appendTo(colorSizeRow);
|
|
13629
|
+
El('span').appendTo(colorRow).text('Color');
|
|
13630
|
+
colorChit = makePanelButton(colorRow, '', toggleColorPicker).addClass('label-color-chit');
|
|
13631
|
+
colorInput = El('input').attr('type', 'text').appendTo(colorRow).on('change', function() {
|
|
13632
|
+
var color = colorInput.node().value.trim();
|
|
13633
|
+
if (color) {
|
|
13634
|
+
if (isHexColor(color)) {
|
|
13635
|
+
setPickerColor(hexToHsb(color));
|
|
13636
|
+
}
|
|
13637
|
+
applyLabelColor(color);
|
|
13638
|
+
}
|
|
13639
|
+
});
|
|
13640
|
+
initColorPicker(colorRow);
|
|
13641
|
+
|
|
13642
|
+
var fontSizeRow = El('div').addClass('label-split-cell label-size-row').appendTo(colorSizeRow);
|
|
13643
|
+
El('span').appendTo(fontSizeRow).text('Font size');
|
|
13644
|
+
makePanelButton(fontSizeRow, '−', function() {
|
|
13645
|
+
nudgeFontSize(-1);
|
|
13646
|
+
});
|
|
13647
|
+
fontSizeText = El('span').addClass('label-size-value').appendTo(fontSizeRow);
|
|
13648
|
+
makePanelButton(fontSizeRow, '+', function() {
|
|
13649
|
+
nudgeFontSize(1);
|
|
13650
|
+
});
|
|
13651
|
+
|
|
13652
|
+
var cssRow = El('label').addClass('label-style-row label-css-row').appendTo(panel);
|
|
13653
|
+
El('span').appendTo(cssRow).text('Inline CSS');
|
|
13654
|
+
cssInput = El('input').attr('type', 'text').appendTo(cssRow).on('change', function() {
|
|
13655
|
+
applyInlineCss(cssInput.node().value.trim());
|
|
13656
|
+
});
|
|
13657
|
+
|
|
13658
|
+
var iconSizeRow = El('div').addClass('label-style-row label-split-row').appendTo(panel);
|
|
13659
|
+
var iconRow = El('div').addClass('label-split-cell').appendTo(iconSizeRow);
|
|
13660
|
+
El('div').addClass('label-style-row-label').appendTo(iconRow).text('Icon');
|
|
13661
|
+
var iconGroup = El('div').addClass('label-icon-buttons').appendTo(iconRow);
|
|
13662
|
+
iconBtns = {};
|
|
13663
|
+
iconTypes.forEach(function(icon) {
|
|
13664
|
+
var btn = makePanelButton(iconGroup, '', function() {
|
|
13665
|
+
applyIcon(icon.name);
|
|
13666
|
+
})
|
|
13667
|
+
.attr('data-icon', icon.name || 'none')
|
|
13668
|
+
.attr('title', icon.name || 'no icon');
|
|
13669
|
+
iconBtns[icon.name] = btn;
|
|
13670
|
+
appendIconButtonSymbol(btn, icon.name);
|
|
13671
|
+
});
|
|
13672
|
+
|
|
13673
|
+
var sizeRow = El('div').addClass('label-split-cell label-icon-size-row').appendTo(iconSizeRow);
|
|
13674
|
+
El('span').appendTo(sizeRow).text('Icon size');
|
|
13675
|
+
makePanelButton(sizeRow, '−', function() {
|
|
13676
|
+
nudgeIconSize(-1);
|
|
13677
|
+
});
|
|
13678
|
+
iconSizeText = El('span').addClass('label-icon-size-value').appendTo(sizeRow);
|
|
13679
|
+
makePanelButton(sizeRow, '+', function() {
|
|
13680
|
+
nudgeIconSize(1);
|
|
13681
|
+
});
|
|
13682
|
+
|
|
13683
|
+
var posRow = El('div').addClass('label-style-row').appendTo(panel);
|
|
13684
|
+
El('div').addClass('label-style-row-label').appendTo(posRow).text('Position');
|
|
13685
|
+
var grid = El('div').addClass('label-position-grid').appendTo(posRow);
|
|
13686
|
+
posBtns = {};
|
|
13687
|
+
labelPositions.forEach(function(pos) {
|
|
13688
|
+
posBtns[pos] = makePanelButton(grid, '', function() {
|
|
13689
|
+
applyLabelPosition(pos);
|
|
13690
|
+
})
|
|
13691
|
+
.attr('data-position', pos)
|
|
13692
|
+
.attr('title', pos);
|
|
13693
|
+
});
|
|
13694
|
+
|
|
13695
|
+
var savedRow = El('div').addClass('label-style-row label-saved-style-row').appendTo(panel);
|
|
13696
|
+
El('span').appendTo(savedRow).text('Presets');
|
|
13697
|
+
styleSelect = El('select').appendTo(savedRow).on('change', function() {
|
|
13698
|
+
if (styleSelect.node().value) {
|
|
13699
|
+
applySavedStyle(styleSelect.node().value);
|
|
13700
|
+
}
|
|
13701
|
+
updateSavedStyleControls();
|
|
13702
|
+
});
|
|
13703
|
+
El('button').appendTo(savedRow).text('Save preset').on('click', saveCurrentStyle);
|
|
13704
|
+
El('button').appendTo(savedRow).text('Delete').on('click', deleteSelectedStyle);
|
|
13705
|
+
renderSavedStyles();
|
|
13706
|
+
}
|
|
13707
|
+
|
|
13708
|
+
function initCreatePanel() {
|
|
13709
|
+
var header = El('div').addClass('label-style-panel-title').appendTo(createPanel).text('Create labels');
|
|
13710
|
+
El('button').addClass('label-style-close').appendTo(header).text('×').on('click', function() {
|
|
13711
|
+
gui.clearMode();
|
|
13712
|
+
});
|
|
13713
|
+
|
|
13714
|
+
var fieldRow = El('label').addClass('label-style-row').appendTo(createPanel);
|
|
13715
|
+
El('span').appendTo(fieldRow).text('Label field');
|
|
13716
|
+
createFieldSelect = El('select').appendTo(fieldRow).on('change', function() {
|
|
13717
|
+
var field = createFieldSelect.node().value;
|
|
13718
|
+
if (field) {
|
|
13719
|
+
createExprInput.node().value = getFieldExpression(field);
|
|
13720
|
+
}
|
|
13721
|
+
updateCreateButton();
|
|
13722
|
+
});
|
|
13723
|
+
|
|
13724
|
+
var exprRow = El('label').addClass('label-style-row label-create-expression-row').appendTo(createPanel);
|
|
13725
|
+
El('span').appendTo(exprRow).text('or expression');
|
|
13726
|
+
createExprInput = El('input')
|
|
13727
|
+
.attr('type', 'text')
|
|
13728
|
+
.appendTo(exprRow)
|
|
13729
|
+
.on('input', updateCreateButton)
|
|
13730
|
+
.on('change', updateCreateButton);
|
|
13731
|
+
|
|
13732
|
+
var copyRow = El('label').addClass('label-style-row label-create-copy-row').appendTo(createPanel);
|
|
13733
|
+
createCopyCheckbox = El('input').attr('type', 'checkbox').appendTo(copyRow);
|
|
13734
|
+
El('span').appendTo(copyRow).text('Create as new layer');
|
|
13735
|
+
|
|
13736
|
+
var btnRow = El('div').addClass('label-style-row').appendTo(createPanel);
|
|
13737
|
+
createBtn = El('button').appendTo(btnRow).text('Create labels').on('click', createLabels);
|
|
13738
|
+
}
|
|
13739
|
+
|
|
13740
|
+
function appendIconButtonSymbol(btn, iconName) {
|
|
13741
|
+
var svg = '<svg class="label-icon-symbol" viewBox="0 0 16 16" aria-hidden="true">' +
|
|
13742
|
+
iconButtonSymbols[iconName] + '</svg>';
|
|
13743
|
+
El(svg).appendTo(btn);
|
|
13744
|
+
}
|
|
13745
|
+
|
|
13746
|
+
function makePanelButton(parent, label, action) {
|
|
13747
|
+
return El('div')
|
|
13748
|
+
.addClass('label-panel-btn')
|
|
13749
|
+
.attr('role', 'button')
|
|
13750
|
+
.attr('tabindex', '0')
|
|
13751
|
+
.appendTo(parent)
|
|
13752
|
+
.text(label)
|
|
13753
|
+
.on('click', function(e) {
|
|
13754
|
+
if (this.classList.contains('disabled')) return;
|
|
13755
|
+
action(e);
|
|
13756
|
+
})
|
|
13757
|
+
.on('keydown', function(e) {
|
|
13758
|
+
if (e.key == 'Enter' || e.key == ' ') {
|
|
13759
|
+
e.preventDefault();
|
|
13760
|
+
if (!this.classList.contains('disabled')) action(e);
|
|
13761
|
+
}
|
|
13762
|
+
});
|
|
13763
|
+
}
|
|
13764
|
+
|
|
13765
|
+
function setPanelButtonDisabled(el, disabled) {
|
|
13766
|
+
el.classed('disabled', !!disabled)
|
|
13767
|
+
.attr('aria-disabled', disabled ? 'true' : 'false')
|
|
13768
|
+
.attr('tabindex', disabled ? '-1' : '0');
|
|
13769
|
+
}
|
|
13770
|
+
|
|
13771
|
+
function turnOn() {
|
|
13772
|
+
if (!activeLayerIsPointLayer()) {
|
|
13773
|
+
gui.clearMode();
|
|
13774
|
+
return;
|
|
13775
|
+
}
|
|
13776
|
+
if (!activeLayerHasLabels()) {
|
|
13777
|
+
showCreatePanel();
|
|
13778
|
+
return;
|
|
13779
|
+
}
|
|
13780
|
+
showStylePanel();
|
|
13781
|
+
}
|
|
13782
|
+
|
|
13783
|
+
function showStylePanel() {
|
|
13784
|
+
renderFontOptions();
|
|
13785
|
+
createPanel.hide();
|
|
13786
|
+
gui.interaction.setMode(labelStyleMode);
|
|
13787
|
+
gui.state.label_style_panel_open = true;
|
|
13788
|
+
panel.show();
|
|
13789
|
+
textBtn.addClass('selected');
|
|
13790
|
+
updateControls();
|
|
13791
|
+
updateSelectionDisplay();
|
|
13792
|
+
}
|
|
13793
|
+
|
|
13794
|
+
function showCreatePanel() {
|
|
13795
|
+
panel.hide();
|
|
13796
|
+
hideColorPicker();
|
|
13797
|
+
if (gui.interaction.getMode() == labelStyleMode) {
|
|
13798
|
+
gui.interaction.turnOff();
|
|
13799
|
+
}
|
|
13800
|
+
gui.state.label_style_panel_open = true;
|
|
13801
|
+
createPanel.show();
|
|
13802
|
+
textBtn.addClass('selected');
|
|
13803
|
+
renderCreateFields();
|
|
13804
|
+
updateCreateButton();
|
|
13805
|
+
}
|
|
13806
|
+
|
|
13807
|
+
function turnOff() {
|
|
13808
|
+
if (panel.visible()) {
|
|
13809
|
+
flushPendingStyleHistory();
|
|
13810
|
+
}
|
|
13811
|
+
panel.hide();
|
|
13812
|
+
createPanel.hide();
|
|
13813
|
+
hideColorPicker();
|
|
13814
|
+
gui.state.label_style_panel_open = false;
|
|
13815
|
+
textBtn.removeClass('selected');
|
|
13816
|
+
clearSelectionDisplay();
|
|
13817
|
+
if (hit) hit.clearSelection();
|
|
13818
|
+
if (gui.interaction.getMode() == labelStyleMode) {
|
|
13819
|
+
gui.interaction.turnOff();
|
|
13820
|
+
}
|
|
13821
|
+
}
|
|
13822
|
+
|
|
13823
|
+
function updateVisibility() {
|
|
13824
|
+
var enabled = activeLayerIsPointLayer();
|
|
13825
|
+
textBtn.classed('disabled', !enabled);
|
|
13826
|
+
textBtn[enabled ? 'show' : 'hide']();
|
|
13827
|
+
if (!enabled && gui.getMode() == labelStylePanelMode) {
|
|
13828
|
+
gui.clearMode();
|
|
13829
|
+
}
|
|
13830
|
+
}
|
|
13831
|
+
|
|
13832
|
+
function activeLayerHasLabels() {
|
|
13833
|
+
var active = gui.model.getActiveLayer();
|
|
13834
|
+
return !!(active && internal.layerHasLabels(active.layer));
|
|
13835
|
+
}
|
|
13836
|
+
|
|
13837
|
+
function activeLayerIsPointLayer() {
|
|
13838
|
+
var active = gui.model.getActiveLayer();
|
|
13839
|
+
return !!(active && active.layer && active.layer.geometry_type == 'point');
|
|
13840
|
+
}
|
|
13841
|
+
|
|
13842
|
+
function renderFontOptions() {
|
|
13843
|
+
if (fontOptionsRendered) return;
|
|
13844
|
+
fontOptionsRendered = true;
|
|
13845
|
+
fontSelect.empty();
|
|
13846
|
+
El('option').attr('value', '').appendTo(fontSelect).text('');
|
|
13847
|
+
getInstalledFonts().forEach(function(group) {
|
|
13848
|
+
var optgroup = El('optgroup').attr('label', group.name).appendTo(fontSelect);
|
|
13849
|
+
group.fonts.forEach(function(fontName) {
|
|
13850
|
+
El('option').attr('value', fontName).appendTo(optgroup).text(fontName);
|
|
13851
|
+
});
|
|
13852
|
+
});
|
|
13853
|
+
}
|
|
13854
|
+
|
|
13855
|
+
function renderCreateFields() {
|
|
13856
|
+
var lyr = getActiveLayer();
|
|
13857
|
+
var records = lyr && lyr.data ? lyr.data.getRecords() : [];
|
|
13858
|
+
var fields = lyr && lyr.data ? lyr.data.getFields().filter(function(field) {
|
|
13859
|
+
return internal.getColumnType(field, records) == 'string';
|
|
13860
|
+
}) : [];
|
|
13861
|
+
var value = createFieldSelect.node().value;
|
|
13862
|
+
createFieldSelect.empty();
|
|
13863
|
+
El('option').attr('value', '').appendTo(createFieldSelect).text('');
|
|
13864
|
+
fields.forEach(function(field) {
|
|
13865
|
+
El('option').attr('value', field).appendTo(createFieldSelect).text(field);
|
|
13866
|
+
});
|
|
13867
|
+
createFieldSelect.node().disabled = fields.length === 0;
|
|
13868
|
+
createFieldSelect.node().value = fields.indexOf(value) > -1 ? value : '';
|
|
13869
|
+
}
|
|
13870
|
+
|
|
13871
|
+
function updateCreateButton() {
|
|
13872
|
+
createBtn.node().disabled = createExprInput.node().value.trim() === '';
|
|
13873
|
+
}
|
|
13874
|
+
|
|
13875
|
+
function getFieldExpression(field) {
|
|
13876
|
+
return 'd[' + JSON.stringify(field) + ']';
|
|
13877
|
+
}
|
|
13878
|
+
|
|
13879
|
+
function createLabels() {
|
|
13880
|
+
var active = gui.model.getActiveLayer();
|
|
13881
|
+
var srcLyr = active && active.layer;
|
|
13882
|
+
var dataset = active && active.dataset;
|
|
13883
|
+
var expr = createExprInput.node().value.trim();
|
|
13884
|
+
var lyr;
|
|
13885
|
+
if (!srcLyr || srcLyr.geometry_type != 'point' || !expr) return;
|
|
13886
|
+
lyr = createCopyCheckbox.node().checked ? makeLabelLayerCopy(srcLyr, dataset) : srcLyr;
|
|
13887
|
+
if (!applyLabelText(lyr, expr)) return;
|
|
13888
|
+
if (lyr != srcLyr) {
|
|
13889
|
+
addLabelLayerCopy(lyr, srcLyr, dataset);
|
|
13890
|
+
recordCreateLabelsCommand(expr, lyr);
|
|
13891
|
+
selectLabelLayerCopy(lyr, dataset);
|
|
13892
|
+
} else {
|
|
13893
|
+
gui.model.updated({style: true, same_table: true});
|
|
13894
|
+
recordCreateLabelsCommand(expr);
|
|
13895
|
+
}
|
|
13896
|
+
showStylePanel();
|
|
13897
|
+
setTimeout(function() {
|
|
13898
|
+
selectAllLabels();
|
|
13899
|
+
}, 0);
|
|
13900
|
+
}
|
|
13901
|
+
|
|
13902
|
+
function makeLabelLayerCopy(srcLyr, dataset) {
|
|
13903
|
+
var lyr = internal.copyLayer(srcLyr);
|
|
13904
|
+
delete lyr.gui;
|
|
13905
|
+
lyr.name = 'labels';
|
|
13906
|
+
return lyr;
|
|
13907
|
+
}
|
|
13908
|
+
|
|
13909
|
+
function addLabelLayerCopy(lyr, srcLyr, dataset) {
|
|
13910
|
+
var i = dataset.layers.indexOf(srcLyr);
|
|
13911
|
+
internal.noteDatasetWillChange(dataset, {operation: 'createLabelLayer', unit: 'layers'});
|
|
13912
|
+
dataset.layers.splice(i + 1, 0, lyr);
|
|
13913
|
+
internal.markDatasetChanged(dataset, {operation: 'createLabelLayer', unit: 'layers'});
|
|
13914
|
+
}
|
|
13915
|
+
|
|
13916
|
+
function selectLabelLayerCopy(lyr, dataset) {
|
|
13917
|
+
gui.model.setDefaultTarget([lyr], dataset);
|
|
13918
|
+
gui.model.updated({select: true});
|
|
13919
|
+
}
|
|
13920
|
+
|
|
13921
|
+
function applyLabelText(lyr, expr) {
|
|
13922
|
+
var table = internal.getLayerDataTable(lyr);
|
|
13923
|
+
var hasField = table.fieldExists(labelTextField);
|
|
13924
|
+
var records = table.getRecords();
|
|
13925
|
+
var accessor;
|
|
13926
|
+
try {
|
|
13927
|
+
accessor = internal.getSymbolPropertyAccessor(expr, labelTextField, lyr);
|
|
13928
|
+
} catch(e) {
|
|
13929
|
+
showPopupAlert(e.message || 'Invalid label expression', 'Create labels');
|
|
13930
|
+
return false;
|
|
13931
|
+
}
|
|
13932
|
+
if (hasField) {
|
|
13933
|
+
table.captureFieldsBefore([labelTextField], {operation: 'create-labels'});
|
|
13934
|
+
} else {
|
|
13935
|
+
table.captureSchemaBefore({operation: 'create-labels', fields: [labelTextField]});
|
|
13936
|
+
}
|
|
13937
|
+
records.forEach(function(rec, i) {
|
|
13938
|
+
if (!rec) rec = records[i] = {};
|
|
13939
|
+
rec[labelTextField] = accessor(i);
|
|
13940
|
+
});
|
|
13941
|
+
if (hasField) {
|
|
13942
|
+
table.markFieldsChanged([labelTextField], {operation: 'create-labels'});
|
|
13943
|
+
} else {
|
|
13944
|
+
table.markSchemaChanged({operation: 'create-labels', fields: [labelTextField]});
|
|
13945
|
+
}
|
|
13946
|
+
return true;
|
|
13947
|
+
}
|
|
13948
|
+
|
|
13949
|
+
function updatePendingStyleHistory(lyr, ids, fields) {
|
|
13950
|
+
if (!gui.session || !lyr || ids.length === 0) return;
|
|
13951
|
+
if (pendingStyleHistory && (pendingStyleHistory.layer != lyr ||
|
|
13952
|
+
!sameIds(pendingStyleHistory.ids, ids))) {
|
|
13953
|
+
flushPendingStyleHistory();
|
|
13954
|
+
}
|
|
13955
|
+
if (!pendingStyleHistory) {
|
|
13956
|
+
pendingStyleHistory = {
|
|
13957
|
+
layer: lyr,
|
|
13958
|
+
ids: ids.concat(),
|
|
13959
|
+
fields: []
|
|
13960
|
+
};
|
|
13961
|
+
}
|
|
13962
|
+
getHistoryStyleFields(fields).forEach(function(field) {
|
|
13963
|
+
if (pendingStyleHistory.fields.indexOf(field) == -1) {
|
|
13964
|
+
pendingStyleHistory.fields.push(field);
|
|
13965
|
+
}
|
|
13966
|
+
});
|
|
13967
|
+
}
|
|
13968
|
+
|
|
13969
|
+
function flushPendingStyleHistory() {
|
|
13970
|
+
var cmd;
|
|
13971
|
+
if (!pendingStyleHistory || !gui.session) return;
|
|
13972
|
+
cmd = getStyleHistoryCommand(pendingStyleHistory);
|
|
13973
|
+
pendingStyleHistory = null;
|
|
13974
|
+
if (cmd) {
|
|
13975
|
+
gui.session.consoleCommands(cmd);
|
|
13976
|
+
}
|
|
13977
|
+
}
|
|
13978
|
+
|
|
13979
|
+
function getStyleHistoryCommand(entry) {
|
|
13980
|
+
var table = entry.layer.data;
|
|
13981
|
+
var records = table && table.getRecords();
|
|
13982
|
+
var fields = entry.fields.filter(function(field) {
|
|
13983
|
+
return field != labelTextField;
|
|
13984
|
+
});
|
|
13985
|
+
var parts = ['-style'];
|
|
13986
|
+
var values;
|
|
13987
|
+
if (!records || fields.length === 0) return '';
|
|
13988
|
+
values = getHistoryStyleValues(records, entry.ids, fields);
|
|
13989
|
+
Object.keys(values).forEach(function(field) {
|
|
13990
|
+
parts.push(field + '=' + quoteCommandValue(values[field]));
|
|
13991
|
+
});
|
|
13992
|
+
if (parts.length == 1) return '';
|
|
13993
|
+
if (getActiveLayer() != entry.layer) {
|
|
13994
|
+
parts.push('target=' + internal.formatOptionValue(internal.getLayerTargetId(gui.model, entry.layer)));
|
|
13995
|
+
}
|
|
13996
|
+
if (entry.ids.length < internal.getFeatureCount(entry.layer)) {
|
|
13997
|
+
parts.push('where=' + quoteCommandValue(getIdsWhereExpression(entry.ids)));
|
|
13998
|
+
}
|
|
13999
|
+
return parts.join(' ');
|
|
14000
|
+
}
|
|
14001
|
+
|
|
14002
|
+
function getHistoryStyleValues(records, ids, fields) {
|
|
14003
|
+
return fields.reduce(function(memo, field) {
|
|
14004
|
+
var value = getCommonRecordValue(records, ids, field);
|
|
14005
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
14006
|
+
memo[field] = value;
|
|
14007
|
+
}
|
|
14008
|
+
return memo;
|
|
14009
|
+
}, {});
|
|
14010
|
+
}
|
|
14011
|
+
|
|
14012
|
+
function getCommonRecordValue(records, ids, field) {
|
|
14013
|
+
var value, val;
|
|
14014
|
+
for (var i=0; i<ids.length; i++) {
|
|
14015
|
+
val = records[ids[i]] && records[ids[i]][field];
|
|
14016
|
+
if (i === 0) {
|
|
14017
|
+
value = val;
|
|
14018
|
+
} else if (val != value) {
|
|
14019
|
+
return undefined;
|
|
14020
|
+
}
|
|
14021
|
+
}
|
|
14022
|
+
return value;
|
|
14023
|
+
}
|
|
14024
|
+
|
|
14025
|
+
function getHistoryStyleFields(fields) {
|
|
14026
|
+
var out = [];
|
|
14027
|
+
fields.forEach(function(field) {
|
|
14028
|
+
if (field == 'dx' || field == 'dy' || field == 'text-anchor') return;
|
|
14029
|
+
if (out.indexOf(field) == -1) out.push(field);
|
|
14030
|
+
});
|
|
14031
|
+
return out;
|
|
14032
|
+
}
|
|
14033
|
+
|
|
14034
|
+
function getIdsWhereExpression(ids) {
|
|
14035
|
+
return '[' + ids.join(',') + '].indexOf($.id)>-1';
|
|
14036
|
+
}
|
|
14037
|
+
|
|
14038
|
+
function sameIds(a, b) {
|
|
14039
|
+
if (!a || !b || a.length != b.length) return false;
|
|
14040
|
+
for (var i=0; i<a.length; i++) {
|
|
14041
|
+
if (a[i] != b[i]) return false;
|
|
14042
|
+
}
|
|
14043
|
+
return true;
|
|
14044
|
+
}
|
|
14045
|
+
|
|
14046
|
+
function recordCreateLabelsCommand(expr, copyLyr) {
|
|
14047
|
+
var cmd = '';
|
|
14048
|
+
if (!gui.session) return;
|
|
14049
|
+
if (copyLyr) {
|
|
14050
|
+
cmd += '-filter true + name=' + internal.formatOptionValue(copyLyr.name) + ' ';
|
|
14051
|
+
}
|
|
14052
|
+
cmd += '-style label-text=' + quoteCommandValue(expr);
|
|
14053
|
+
gui.session.consoleCommands(cmd);
|
|
14054
|
+
}
|
|
14055
|
+
|
|
14056
|
+
function quoteCommandValue(str) {
|
|
14057
|
+
return "'" + String(str).replace(/'/g, "\\'") + "'";
|
|
14058
|
+
}
|
|
14059
|
+
|
|
14060
|
+
function selectAllLabels() {
|
|
14061
|
+
var lyr = getActiveLayer();
|
|
14062
|
+
var ids = [];
|
|
14063
|
+
if (!hit || !lyr) return;
|
|
14064
|
+
for (var i=0, n=internal.getFeatureCount(lyr); i<n; i++) {
|
|
14065
|
+
ids.push(i);
|
|
14066
|
+
}
|
|
14067
|
+
hit.clearSelection();
|
|
14068
|
+
if (ids.length > 0) {
|
|
14069
|
+
hit.addSelectionIds(ids);
|
|
14070
|
+
}
|
|
14071
|
+
updateSelectionDisplay();
|
|
14072
|
+
updateControls();
|
|
14073
|
+
}
|
|
14074
|
+
|
|
14075
|
+
function clearSelection() {
|
|
14076
|
+
if (hit) hit.clearSelection();
|
|
14077
|
+
updateSelectionDisplay();
|
|
14078
|
+
updateControls();
|
|
14079
|
+
}
|
|
14080
|
+
|
|
14081
|
+
function getActiveLayer() {
|
|
14082
|
+
var active = gui.model.getActiveLayer();
|
|
14083
|
+
return active && active.layer;
|
|
14084
|
+
}
|
|
14085
|
+
|
|
14086
|
+
function getActiveTable() {
|
|
14087
|
+
var lyr = getActiveLayer();
|
|
14088
|
+
return lyr && lyr.data || null;
|
|
14089
|
+
}
|
|
14090
|
+
|
|
14091
|
+
function getSelectionIds() {
|
|
14092
|
+
return hit ? hit.getSelectionIds() : [];
|
|
14093
|
+
}
|
|
14094
|
+
|
|
14095
|
+
function updateControls() {
|
|
14096
|
+
var ids = getSelectionIds();
|
|
14097
|
+
var fontVal = getCommonValue(ids, fontField);
|
|
14098
|
+
var fontSizeVal = getCommonValue(ids, fontSizeField, {useDefault: true, defaultValue: defaultFontSize});
|
|
14099
|
+
var fontStyleVal = getCommonValue(ids, fontStyleField, {useDefault: true, defaultValue: defaultFontStyle});
|
|
14100
|
+
var fontWeightVal = getCommonValue(ids, fontWeightField, {useDefault: true, defaultValue: defaultFontWeight});
|
|
14101
|
+
var fillVal = getCommonValue(ids, fillField, {useDefault: true, defaultValue: defaultLabelColor});
|
|
14102
|
+
var cssVal = getCommonValue(ids, cssField);
|
|
14103
|
+
var posVal = getCommonValue(ids, 'label-pos');
|
|
14104
|
+
var iconVal = getCommonValue(ids, iconField);
|
|
14105
|
+
var iconSizeVal = getCommonValue(ids, iconSizeField, {useDefault: true, defaultValue: defaultIconSize});
|
|
14106
|
+
styleSelect.node().disabled = ids.length === 0;
|
|
14107
|
+
updateSavedStyleControls();
|
|
14108
|
+
fontSelect.node().disabled = ids.length === 0;
|
|
14109
|
+
fontSelect.node().value = fontVal;
|
|
14110
|
+
updateFontStyleControls(fontVal, fontStyleVal, fontWeightVal);
|
|
14111
|
+
updateFontSizeControls(ids.length ? fontSizeVal : '');
|
|
14112
|
+
updateColorControls(ids.length ? fillVal : '');
|
|
14113
|
+
updateCssControl(ids.length ? cssVal : '');
|
|
14114
|
+
updatePositionButtons(ids.length ? posVal : '');
|
|
14115
|
+
updateIconButtons(ids.length ? iconVal : '');
|
|
14116
|
+
updateIconSizeControls(ids.length ? iconSizeVal : '');
|
|
14117
|
+
}
|
|
14118
|
+
|
|
14119
|
+
function updatePositionButtons(pos) {
|
|
14120
|
+
var disabled = getSelectionIds().length === 0;
|
|
14121
|
+
labelPositions.forEach(function(name) {
|
|
14122
|
+
posBtns[name].classed('selected', name == pos);
|
|
14123
|
+
setPanelButtonDisabled(posBtns[name], disabled);
|
|
14124
|
+
});
|
|
14125
|
+
}
|
|
14126
|
+
|
|
14127
|
+
function updateFontSizeControls(fontSizeVal) {
|
|
14128
|
+
var disabled = getSelectionIds().length === 0;
|
|
14129
|
+
fontSizeText.text(fontSizeVal || '');
|
|
14130
|
+
panel.findChildren('.label-size-row .label-panel-btn').forEach(function(btn) {
|
|
14131
|
+
setPanelButtonDisabled(btn, disabled);
|
|
14132
|
+
});
|
|
14133
|
+
}
|
|
14134
|
+
|
|
14135
|
+
function updateFontStyleControls(fontName, fontStyleVal, fontWeightVal) {
|
|
14136
|
+
var disabled = getSelectionIds().length === 0 || !fontName;
|
|
14137
|
+
fontStyleSelect.empty();
|
|
14138
|
+
El('option').attr('value', '').appendTo(fontStyleSelect).text('');
|
|
14139
|
+
if (fontName) {
|
|
14140
|
+
getFontStyleVariants(fontName).forEach(function(variant) {
|
|
14141
|
+
El('option').attr('value', variant.value).appendTo(fontStyleSelect).text(variant.label);
|
|
14142
|
+
});
|
|
14143
|
+
}
|
|
14144
|
+
fontStyleSelect.node().disabled = disabled;
|
|
14145
|
+
fontStyleSelect.node().value = fontStyleVal && fontWeightVal ?
|
|
14146
|
+
fontStyleVal + '|' + fontWeightVal : '';
|
|
14147
|
+
}
|
|
14148
|
+
|
|
14149
|
+
function updateColorControls(colorVal) {
|
|
14150
|
+
var disabled = getSelectionIds().length === 0;
|
|
14151
|
+
colorInput.node().disabled = disabled;
|
|
14152
|
+
colorInput.node().value = colorVal || '';
|
|
14153
|
+
setPanelButtonDisabled(colorChit, disabled || !isHexColor(colorVal));
|
|
14154
|
+
colorChit.css('background-color', isHexColor(colorVal) ? colorVal : 'transparent');
|
|
14155
|
+
if (colorPicker.visible()) {
|
|
14156
|
+
return; // avoid HSB -> RGB -> HSB rounding jumps after picker commits
|
|
14157
|
+
}
|
|
14158
|
+
if (isHexColor(colorVal)) {
|
|
14159
|
+
setPickerColor(hexToHsb(colorVal));
|
|
14160
|
+
} else {
|
|
14161
|
+
hideColorPicker();
|
|
14162
|
+
}
|
|
14163
|
+
}
|
|
14164
|
+
|
|
14165
|
+
function updateCssControl(cssVal) {
|
|
14166
|
+
cssInput.node().disabled = getSelectionIds().length === 0;
|
|
14167
|
+
cssInput.node().value = cssVal || '';
|
|
14168
|
+
}
|
|
14169
|
+
|
|
14170
|
+
function updateIconButtons(iconVal) {
|
|
14171
|
+
var disabled = getSelectionIds().length === 0;
|
|
14172
|
+
iconTypes.forEach(function(icon) {
|
|
14173
|
+
iconBtns[icon.name].classed('selected', !disabled && icon.name == iconVal);
|
|
14174
|
+
setPanelButtonDisabled(iconBtns[icon.name], disabled);
|
|
14175
|
+
});
|
|
14176
|
+
}
|
|
14177
|
+
|
|
14178
|
+
function updateIconSizeControls(iconSizeVal) {
|
|
14179
|
+
var disabled = getSelectionIds().length === 0;
|
|
14180
|
+
iconSizeText.text(iconSizeVal || '');
|
|
14181
|
+
panel.findChildren('.label-icon-size-row .label-panel-btn').forEach(function(btn) {
|
|
14182
|
+
setPanelButtonDisabled(btn, disabled);
|
|
14183
|
+
});
|
|
14184
|
+
}
|
|
14185
|
+
|
|
14186
|
+
function updateSavedStyleControls() {
|
|
14187
|
+
var disabled = getSelectionIds().length === 0;
|
|
14188
|
+
var buttons = panel.findChildren('.label-saved-style-row button');
|
|
14189
|
+
buttons.forEach(function(btn, i) {
|
|
14190
|
+
btn.node().disabled = i === 0 ? disabled : disabled || !styleSelect.node().value;
|
|
14191
|
+
});
|
|
14192
|
+
}
|
|
14193
|
+
|
|
14194
|
+
function getCommonValue(ids, field, opts) {
|
|
14195
|
+
var table = getActiveTable();
|
|
14196
|
+
var records = table && table.getRecords();
|
|
14197
|
+
var value, val, hasValue;
|
|
14198
|
+
if (!records || ids.length === 0) return '';
|
|
14199
|
+
for (var i=0; i<ids.length; i++) {
|
|
14200
|
+
val = records[ids[i]] && records[ids[i]][field];
|
|
14201
|
+
if (!val) {
|
|
14202
|
+
if (opts && opts.useDefault) {
|
|
14203
|
+
val = opts.defaultValue;
|
|
14204
|
+
} else {
|
|
14205
|
+
return '';
|
|
14206
|
+
}
|
|
14207
|
+
} else {
|
|
14208
|
+
hasValue = true;
|
|
14209
|
+
}
|
|
14210
|
+
if (i === 0) {
|
|
14211
|
+
value = val;
|
|
14212
|
+
} else if (val != value) {
|
|
14213
|
+
return '';
|
|
14214
|
+
}
|
|
14215
|
+
}
|
|
14216
|
+
return hasValue || opts && opts.useDefault ? value : '';
|
|
14217
|
+
}
|
|
14218
|
+
|
|
14219
|
+
function applyFont(fontName) {
|
|
14220
|
+
applyStyleFields([fontField], function(rec) {
|
|
14221
|
+
rec[fontField] = fontName;
|
|
14222
|
+
});
|
|
14223
|
+
}
|
|
14224
|
+
|
|
14225
|
+
function nudgeFontSize(delta) {
|
|
14226
|
+
var ids = getSelectionIds();
|
|
14227
|
+
var size = getNumericSize(ids, fontSizeField, defaultFontSize);
|
|
14228
|
+
if (ids.length === 0) return;
|
|
14229
|
+
size = Math.max(1, size + delta);
|
|
14230
|
+
applyStyleFields([fontSizeField], function(rec) {
|
|
14231
|
+
rec[fontSizeField] = size;
|
|
14232
|
+
});
|
|
14233
|
+
}
|
|
14234
|
+
|
|
14235
|
+
function applyFontStyleVariant(value) {
|
|
14236
|
+
var variant = parseFontStyleVariant(value);
|
|
14237
|
+
if (!variant) return;
|
|
14238
|
+
applyStyleFields([fontStyleField, fontWeightField], function(rec) {
|
|
14239
|
+
rec[fontStyleField] = variant.style;
|
|
14240
|
+
rec[fontWeightField] = variant.weight;
|
|
14241
|
+
});
|
|
14242
|
+
}
|
|
14243
|
+
|
|
14244
|
+
function applyLabelColor(color) {
|
|
14245
|
+
applyStyleFields([fillField], function(rec) {
|
|
14246
|
+
rec[fillField] = color;
|
|
14247
|
+
});
|
|
14248
|
+
}
|
|
14249
|
+
|
|
14250
|
+
function applyInlineCss(css) {
|
|
14251
|
+
applyStyleFields([cssField], function(rec) {
|
|
14252
|
+
rec[cssField] = css || undefined;
|
|
14253
|
+
});
|
|
14254
|
+
}
|
|
14255
|
+
|
|
14256
|
+
function saveCurrentStyle() {
|
|
14257
|
+
openSaveStylePopup();
|
|
14258
|
+
}
|
|
14259
|
+
|
|
14260
|
+
function openSaveStylePopup() {
|
|
14261
|
+
var popup = showPopupAlert('', 'Save label style');
|
|
14262
|
+
var el = popup.container();
|
|
14263
|
+
el.addClass('option-menu');
|
|
14264
|
+
el.html(`<div><input type="text" class="style-name text-input" placeholder="style name"></div>
|
|
14265
|
+
<div tabindex="0" class="btn dialog-btn">Save</div>`);
|
|
14266
|
+
var input = el.findChild('.style-name');
|
|
14267
|
+
var btn = el.findChild('.btn');
|
|
14268
|
+
input.node().focus();
|
|
14269
|
+
btn.on('click', function() {
|
|
14270
|
+
var name = input.node().value.trim();
|
|
14271
|
+
if (!name) return;
|
|
14272
|
+
saveStyleWithName(name);
|
|
14273
|
+
popup.close();
|
|
14274
|
+
});
|
|
14275
|
+
input.on('keydown', function(e) {
|
|
14276
|
+
if (e.key == 'Enter') {
|
|
14277
|
+
btn.node().click();
|
|
14278
|
+
}
|
|
14279
|
+
});
|
|
14280
|
+
}
|
|
14281
|
+
|
|
14282
|
+
function saveStyleWithName(name) {
|
|
14283
|
+
var styles = getSavedStyles().filter(function(item) {
|
|
14284
|
+
return item.name != name;
|
|
14285
|
+
});
|
|
14286
|
+
var i;
|
|
14287
|
+
styles.push({
|
|
14288
|
+
name: name,
|
|
14289
|
+
style: getCurrentStyle()
|
|
14290
|
+
});
|
|
14291
|
+
styles.sort(function(a, b) {
|
|
14292
|
+
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 :
|
|
14293
|
+
a.name.toLowerCase() > b.name.toLowerCase() ? 1 : 0;
|
|
14294
|
+
});
|
|
14295
|
+
GUI.setSavedValue(savedStylesKey, styles);
|
|
14296
|
+
renderSavedStyles();
|
|
14297
|
+
for (i=0; i<styleSelect.node().options.length; i++) {
|
|
14298
|
+
if (styleSelect.node().options[i].value == name) {
|
|
14299
|
+
styleSelect.node().selectedIndex = i;
|
|
14300
|
+
break;
|
|
14301
|
+
}
|
|
14302
|
+
}
|
|
14303
|
+
}
|
|
14304
|
+
|
|
14305
|
+
async function deleteSelectedStyle() {
|
|
14306
|
+
var name = styleSelect.node().value;
|
|
14307
|
+
var styles;
|
|
14308
|
+
if (!name) return;
|
|
14309
|
+
if (!await showPrompt('Delete label style "' + name + '"?', 'Delete preset')) return;
|
|
14310
|
+
styles = getSavedStyles().filter(function(item) {
|
|
14311
|
+
return item.name != name;
|
|
14312
|
+
});
|
|
14313
|
+
GUI.setSavedValue(savedStylesKey, styles);
|
|
14314
|
+
renderSavedStyles();
|
|
14315
|
+
updateSavedStyleControls();
|
|
14316
|
+
}
|
|
14317
|
+
|
|
14318
|
+
function applySavedStyle(name) {
|
|
14319
|
+
var item = getSavedStyles().find(function(item) {
|
|
14320
|
+
return item.name == name;
|
|
14321
|
+
});
|
|
14322
|
+
if (!item) return;
|
|
14323
|
+
applyStyleObject(item.style);
|
|
14324
|
+
}
|
|
14325
|
+
|
|
14326
|
+
function getSavedStyles() {
|
|
14327
|
+
var styles = GUI.getSavedValue(savedStylesKey);
|
|
14328
|
+
return Array.isArray(styles) ? styles : [];
|
|
14329
|
+
}
|
|
14330
|
+
|
|
14331
|
+
function renderSavedStyles() {
|
|
14332
|
+
var value = styleSelect && styleSelect.node().value;
|
|
14333
|
+
if (!styleSelect) return;
|
|
14334
|
+
styleSelect.empty();
|
|
14335
|
+
El('option').attr('value', '').appendTo(styleSelect).text('Apply preset...');
|
|
14336
|
+
getSavedStyles().forEach(function(item) {
|
|
14337
|
+
El('option').attr('value', item.name).appendTo(styleSelect).text(item.name);
|
|
14338
|
+
});
|
|
14339
|
+
styleSelect.node().value = value || '';
|
|
14340
|
+
updateSavedStyleControls();
|
|
14341
|
+
}
|
|
14342
|
+
|
|
14343
|
+
function getCurrentStyle() {
|
|
14344
|
+
var style = {};
|
|
14345
|
+
var fontStyle = parseFontStyleVariant(fontStyleSelect.node().value);
|
|
14346
|
+
var icon = getSelectedIcon();
|
|
14347
|
+
addStyleValue(style, fontField, fontSelect.node().value);
|
|
14348
|
+
if (fontStyle) {
|
|
14349
|
+
style[fontStyleField] = fontStyle.style;
|
|
14350
|
+
style[fontWeightField] = fontStyle.weight;
|
|
14351
|
+
}
|
|
14352
|
+
addStyleValue(style, fontSizeField, getNumericControlValue(fontSizeText));
|
|
14353
|
+
addStyleValue(style, fillField, colorInput.node().value.trim());
|
|
14354
|
+
addStyleValue(style, cssField, cssInput.node().value.trim());
|
|
14355
|
+
addStyleValue(style, 'label-pos', getSelectedLabelPosition());
|
|
14356
|
+
addStyleValue(style, iconField, icon);
|
|
14357
|
+
if (icon) {
|
|
14358
|
+
addStyleValue(style, iconSizeField, getNumericControlValue(iconSizeText));
|
|
14359
|
+
}
|
|
14360
|
+
return style;
|
|
14361
|
+
}
|
|
14362
|
+
|
|
14363
|
+
function addStyleValue(style, field, value) {
|
|
14364
|
+
if (value || value === 0) {
|
|
14365
|
+
style[field] = value;
|
|
14366
|
+
}
|
|
14367
|
+
}
|
|
14368
|
+
|
|
14369
|
+
function getNumericControlValue(el) {
|
|
14370
|
+
var value = Number(el.text());
|
|
14371
|
+
return isFinite(value) && value > 0 ? value : null;
|
|
14372
|
+
}
|
|
14373
|
+
|
|
14374
|
+
function getSelectedLabelPosition() {
|
|
14375
|
+
var out = '';
|
|
14376
|
+
labelPositions.forEach(function(pos) {
|
|
14377
|
+
if (posBtns[pos].hasClass('selected')) out = pos;
|
|
14378
|
+
});
|
|
14379
|
+
return out;
|
|
14380
|
+
}
|
|
14381
|
+
|
|
14382
|
+
function getSelectedIcon() {
|
|
14383
|
+
var out = '';
|
|
14384
|
+
iconTypes.forEach(function(icon) {
|
|
14385
|
+
if (iconBtns[icon.name].hasClass('selected')) out = icon.name;
|
|
14386
|
+
});
|
|
14387
|
+
return out;
|
|
14388
|
+
}
|
|
14389
|
+
|
|
14390
|
+
function applyStyleObject(style) {
|
|
14391
|
+
var fields = Object.keys(style || {}).reduce(function(memo, field) {
|
|
14392
|
+
if (field == 'label-pos') {
|
|
14393
|
+
labelPositionFields.forEach(function(field) {
|
|
14394
|
+
if (memo.indexOf(field) == -1) memo.push(field);
|
|
14395
|
+
});
|
|
14396
|
+
} else if (field != 'dx' && field != 'dy') {
|
|
14397
|
+
memo.push(field);
|
|
14398
|
+
}
|
|
14399
|
+
return memo;
|
|
14400
|
+
}, []);
|
|
14401
|
+
applyStyleFields(fields, function(rec) {
|
|
14402
|
+
savedStyleFields.forEach(function(field) {
|
|
14403
|
+
if (field in style && field != 'label-pos') {
|
|
14404
|
+
rec[field] = style[field];
|
|
14405
|
+
}
|
|
14406
|
+
});
|
|
14407
|
+
if (style['label-pos']) {
|
|
14408
|
+
internal.setLabelPositionStyle(rec, style['label-pos']);
|
|
14409
|
+
}
|
|
14410
|
+
});
|
|
14411
|
+
}
|
|
14412
|
+
|
|
14413
|
+
function parseFontStyleVariant(value) {
|
|
14414
|
+
var parts = String(value).split('|');
|
|
14415
|
+
if (parts.length != 2) return null;
|
|
14416
|
+
return {
|
|
14417
|
+
style: parts[0],
|
|
14418
|
+
weight: parts[1]
|
|
14419
|
+
};
|
|
14420
|
+
}
|
|
14421
|
+
|
|
14422
|
+
function applyLabelPosition(pos) {
|
|
14423
|
+
applyStyleFields(labelPositionFields, function(rec) {
|
|
14424
|
+
internal.setLabelPositionStyle(rec, pos);
|
|
14425
|
+
});
|
|
14426
|
+
}
|
|
14427
|
+
|
|
14428
|
+
function applyIcon(iconName) {
|
|
14429
|
+
applyStyleFields([iconField, iconSizeField], function(rec) {
|
|
14430
|
+
if (iconName) {
|
|
14431
|
+
rec[iconField] = iconName;
|
|
14432
|
+
if (!(rec[iconSizeField] > 0)) {
|
|
14433
|
+
rec[iconSizeField] = defaultIconSize;
|
|
14434
|
+
}
|
|
14435
|
+
} else {
|
|
14436
|
+
rec[iconField] = undefined;
|
|
14437
|
+
rec[iconSizeField] = undefined;
|
|
14438
|
+
}
|
|
14439
|
+
});
|
|
14440
|
+
}
|
|
14441
|
+
|
|
14442
|
+
function nudgeIconSize(delta) {
|
|
14443
|
+
var ids = getSelectionIds();
|
|
14444
|
+
var size = getNumericSize(ids, iconSizeField, defaultIconSize);
|
|
14445
|
+
if (ids.length === 0) return;
|
|
14446
|
+
size = Math.max(1, size + delta);
|
|
14447
|
+
applyStyleFields([iconField, iconSizeField], function(rec) {
|
|
14448
|
+
if (!rec[iconField]) {
|
|
14449
|
+
rec[iconField] = 'circle';
|
|
14450
|
+
}
|
|
14451
|
+
rec[iconSizeField] = size;
|
|
14452
|
+
});
|
|
14453
|
+
}
|
|
14454
|
+
|
|
14455
|
+
function getNumericSize(ids, field, defaultValue) {
|
|
14456
|
+
var val = getCommonValue(ids, field);
|
|
14457
|
+
val = val ? Number(val) : defaultValue;
|
|
14458
|
+
return isFinite(val) && val > 0 ? val : defaultValue;
|
|
14459
|
+
}
|
|
14460
|
+
|
|
14461
|
+
function isHexColor(str) {
|
|
14462
|
+
return /^#[0-9a-f]{6}$/i.test(str);
|
|
14463
|
+
}
|
|
14464
|
+
|
|
14465
|
+
function initColorPicker(colorRow) {
|
|
14466
|
+
colorPicker = El('div').addClass('label-color-picker').appendTo(colorRow).hide();
|
|
14467
|
+
var sbWrap = El('div').addClass('label-color-canvas-wrap').appendTo(colorPicker);
|
|
14468
|
+
sbCanvas = El('canvas').attr('width', '256').attr('height', '256').appendTo(sbWrap);
|
|
14469
|
+
sbMarker = makePickerMarker().appendTo(sbWrap);
|
|
14470
|
+
var hueWrap = El('div').addClass('label-color-canvas-wrap').appendTo(colorPicker);
|
|
14471
|
+
hueCanvas = El('canvas').attr('width', '256').attr('height', '18').appendTo(hueWrap);
|
|
14472
|
+
hueMarker = makePickerMarker().appendTo(hueWrap);
|
|
14473
|
+
pickerHsbInputs = {};
|
|
14474
|
+
var hsbRow = El('div').addClass('label-color-picker-fields').appendTo(colorPicker);
|
|
14475
|
+
addPickerNumberInput(hsbRow, 'h', 'H');
|
|
14476
|
+
addPickerNumberInput(hsbRow, 's', 'S');
|
|
14477
|
+
addPickerNumberInput(hsbRow, 'b', 'B');
|
|
14478
|
+
El('button').appendTo(hsbRow).text('Close').on('click', closePickerColor);
|
|
14479
|
+
sbCanvas.on('mousedown', function(e) {
|
|
14480
|
+
startCanvasDrag(e, updateSbFromEvent);
|
|
14481
|
+
});
|
|
14482
|
+
hueCanvas.on('mousedown', function(e) {
|
|
14483
|
+
startCanvasDrag(e, updateHueFromEvent);
|
|
14484
|
+
});
|
|
14485
|
+
setPickerColor(pickerColor);
|
|
14486
|
+
}
|
|
14487
|
+
|
|
14488
|
+
function addPickerNumberInput(row, name, label) {
|
|
14489
|
+
var wrapper = El('label').appendTo(row);
|
|
14490
|
+
El('span').appendTo(wrapper).text(label);
|
|
14491
|
+
pickerHsbInputs[name] = El('input')
|
|
14492
|
+
.attr('type', 'text')
|
|
14493
|
+
.appendTo(wrapper)
|
|
14494
|
+
.on('change', function() {
|
|
14495
|
+
var h = parseNumberField(pickerHsbInputs.h.node().value);
|
|
14496
|
+
var s = parseNumberField(pickerHsbInputs.s.node().value);
|
|
14497
|
+
var b = parseNumberField(pickerHsbInputs.b.node().value);
|
|
14498
|
+
if (!isFinite(h) || !isFinite(s) || !isFinite(b)) return;
|
|
14499
|
+
setPickerColor({
|
|
14500
|
+
h: degreesToByte(h),
|
|
14501
|
+
s: pctToByte(s),
|
|
14502
|
+
b: pctToByte(b)
|
|
14503
|
+
});
|
|
14504
|
+
commitPickerColor();
|
|
12624
14505
|
});
|
|
14506
|
+
}
|
|
14507
|
+
|
|
14508
|
+
function toggleColorPicker() {
|
|
14509
|
+
if (colorPicker.visible()) {
|
|
14510
|
+
hideColorPicker();
|
|
12625
14511
|
} else {
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
clearTimeout(hideTimer);
|
|
12629
|
-
hideTimer = setTimeout(function() {
|
|
12630
|
-
if (!(visible && GUI.isActiveInstance(gui))) {
|
|
12631
|
-
el.css('display', 'none');
|
|
12632
|
-
}
|
|
12633
|
-
}, transitionMs);
|
|
14512
|
+
colorPicker.show();
|
|
14513
|
+
drawColorPicker();
|
|
12634
14514
|
}
|
|
12635
14515
|
}
|
|
12636
|
-
}
|
|
12637
14516
|
|
|
12638
|
-
|
|
12639
|
-
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
|
|
12644
|
-
|
|
12645
|
-
|
|
14517
|
+
function hideColorPicker() {
|
|
14518
|
+
colorPicker.hide();
|
|
14519
|
+
}
|
|
14520
|
+
|
|
14521
|
+
function startCanvasDrag(e, update) {
|
|
14522
|
+
var evt = e.originalEvent || e;
|
|
14523
|
+
colorPicker.addClass('dragging-color');
|
|
14524
|
+
El('body').addClass('dragging-color-picker');
|
|
14525
|
+
update(evt);
|
|
14526
|
+
document.addEventListener('mousemove', onmove);
|
|
14527
|
+
document.addEventListener('mouseup', onup);
|
|
14528
|
+
evt.preventDefault();
|
|
14529
|
+
function onmove(evt) {
|
|
14530
|
+
update(evt);
|
|
14531
|
+
}
|
|
14532
|
+
function onup() {
|
|
14533
|
+
document.removeEventListener('mousemove', onmove);
|
|
14534
|
+
document.removeEventListener('mouseup', onup);
|
|
14535
|
+
colorPicker.removeClass('dragging-color');
|
|
14536
|
+
El('body').removeClass('dragging-color-picker');
|
|
14537
|
+
commitPickerColor();
|
|
12646
14538
|
}
|
|
12647
14539
|
}
|
|
12648
|
-
var enabled = true;
|
|
12649
|
-
var clickHandlers = [];
|
|
12650
14540
|
|
|
12651
|
-
|
|
14541
|
+
function updateSbFromEvent(evt) {
|
|
14542
|
+
var p = getCanvasPoint(sbCanvas.node(), evt);
|
|
14543
|
+
setPickerColor({
|
|
14544
|
+
h: pickerColor.h,
|
|
14545
|
+
s: p.x,
|
|
14546
|
+
b: 255 - p.y
|
|
14547
|
+
});
|
|
14548
|
+
}
|
|
12652
14549
|
|
|
12653
|
-
|
|
12654
|
-
|
|
14550
|
+
function updateHueFromEvent(evt) {
|
|
14551
|
+
var p = getCanvasPoint(hueCanvas.node(), evt);
|
|
14552
|
+
setPickerColor({
|
|
14553
|
+
h: p.x,
|
|
14554
|
+
s: pickerColor.s,
|
|
14555
|
+
b: pickerColor.b
|
|
14556
|
+
});
|
|
14557
|
+
}
|
|
12655
14558
|
|
|
12656
|
-
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
|
|
12660
|
-
|
|
12661
|
-
|
|
14559
|
+
function getCanvasPoint(canvas, evt) {
|
|
14560
|
+
var rect = canvas.getBoundingClientRect();
|
|
14561
|
+
return {
|
|
14562
|
+
x: clamp(Math.round((evt.clientX - rect.left) / rect.width * (canvas.width - 1)), 0, canvas.width - 1),
|
|
14563
|
+
y: clamp(Math.round((evt.clientY - rect.top) / rect.height * (canvas.height - 1)), 0, canvas.height - 1)
|
|
14564
|
+
};
|
|
14565
|
+
}
|
|
12662
14566
|
|
|
12663
|
-
|
|
12664
|
-
|
|
12665
|
-
|
|
12666
|
-
|
|
12667
|
-
|
|
14567
|
+
function setPickerColor(hsb) {
|
|
14568
|
+
pickerColor = {
|
|
14569
|
+
h: clamp(Math.round(hsb.h), 0, 255),
|
|
14570
|
+
s: clamp(Math.round(hsb.s), 0, 255),
|
|
14571
|
+
b: clamp(Math.round(hsb.b), 0, 255)
|
|
14572
|
+
};
|
|
14573
|
+
drawColorPicker();
|
|
14574
|
+
updatePickerFields();
|
|
14575
|
+
}
|
|
14576
|
+
|
|
14577
|
+
function updatePickerFields() {
|
|
14578
|
+
var hex = hsbToHex(pickerColor);
|
|
14579
|
+
pickerHsbInputs.h.node().value = byteToDegrees(pickerColor.h) + '°';
|
|
14580
|
+
pickerHsbInputs.s.node().value = byteToPct(pickerColor.s) + '%';
|
|
14581
|
+
pickerHsbInputs.b.node().value = byteToPct(pickerColor.b) + '%';
|
|
14582
|
+
colorInput.node().value = hex;
|
|
14583
|
+
colorChit.css('background-color', hex);
|
|
14584
|
+
}
|
|
14585
|
+
|
|
14586
|
+
function drawColorPicker() {
|
|
14587
|
+
if (!sbCanvas) return;
|
|
14588
|
+
drawSaturationBrightnessCanvas();
|
|
14589
|
+
drawHueCanvas();
|
|
14590
|
+
}
|
|
14591
|
+
|
|
14592
|
+
function drawSaturationBrightnessCanvas() {
|
|
14593
|
+
var ctx = sbCanvas.node().getContext('2d');
|
|
14594
|
+
var image = ctx.createImageData(256, 256);
|
|
14595
|
+
var data = image.data;
|
|
14596
|
+
var i = 0;
|
|
14597
|
+
for (var y=0; y<256; y++) {
|
|
14598
|
+
for (var x=0; x<256; x++) {
|
|
14599
|
+
var rgb = hsbToRgb({
|
|
14600
|
+
h: pickerColor.h,
|
|
14601
|
+
s: x,
|
|
14602
|
+
b: 255 - y
|
|
14603
|
+
});
|
|
14604
|
+
data[i++] = rgb.r;
|
|
14605
|
+
data[i++] = rgb.g;
|
|
14606
|
+
data[i++] = rgb.b;
|
|
14607
|
+
data[i++] = 255;
|
|
14608
|
+
}
|
|
12668
14609
|
}
|
|
12669
|
-
|
|
12670
|
-
|
|
14610
|
+
ctx.putImageData(image, 0, 0);
|
|
14611
|
+
positionMarker(sbMarker, pickerColor.s, 255 - pickerColor.b, pickerColor);
|
|
14612
|
+
}
|
|
14613
|
+
|
|
14614
|
+
function drawHueCanvas() {
|
|
14615
|
+
var ctx = hueCanvas.node().getContext('2d');
|
|
14616
|
+
var image = ctx.createImageData(256, 18);
|
|
14617
|
+
var data = image.data;
|
|
14618
|
+
var i = 0;
|
|
14619
|
+
for (var y=0; y<18; y++) {
|
|
14620
|
+
for (var x=0; x<256; x++) {
|
|
14621
|
+
var rgb = hsbToRgb({h: x, s: 255, b: 255});
|
|
14622
|
+
data[i++] = rgb.r;
|
|
14623
|
+
data[i++] = rgb.g;
|
|
14624
|
+
data[i++] = rgb.b;
|
|
14625
|
+
data[i++] = 255;
|
|
14626
|
+
}
|
|
14627
|
+
}
|
|
14628
|
+
ctx.putImageData(image, 0, 0);
|
|
14629
|
+
positionMarker(hueMarker, pickerColor.h, 9, {h: pickerColor.h, s: 255, b: 255});
|
|
14630
|
+
}
|
|
12671
14631
|
|
|
12672
|
-
|
|
12673
|
-
|
|
14632
|
+
function commitPickerColor() {
|
|
14633
|
+
applyLabelColor(hsbToHex(pickerColor));
|
|
14634
|
+
}
|
|
12674
14635
|
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
return this;
|
|
12679
|
-
};
|
|
14636
|
+
function closePickerColor() {
|
|
14637
|
+
hideColorPicker();
|
|
14638
|
+
}
|
|
12680
14639
|
|
|
12681
|
-
|
|
14640
|
+
function getMarkerColor(rgb) {
|
|
14641
|
+
var luminance = rgb.r * 0.2126 + rgb.g * 0.7152 + rgb.b * 0.0722;
|
|
14642
|
+
return luminance < 128 ? '#fff' : '#000';
|
|
14643
|
+
}
|
|
12682
14644
|
|
|
12683
|
-
|
|
12684
|
-
return
|
|
12685
|
-
}
|
|
14645
|
+
function makePickerMarker() {
|
|
14646
|
+
return El('<svg class="label-color-marker" width="16" height="16" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6"></circle></svg>');
|
|
14647
|
+
}
|
|
12686
14648
|
|
|
12687
|
-
function
|
|
12688
|
-
|
|
14649
|
+
function positionMarker(marker, x, y, hsb) {
|
|
14650
|
+
var rgb = hsbToRgb(hsb);
|
|
14651
|
+
x = utils$1.clamp(x, 1, 254);
|
|
14652
|
+
y = utils$1.clamp(y, 1, 254) + 0.5;
|
|
14653
|
+
marker.css({
|
|
14654
|
+
left: x - 8,
|
|
14655
|
+
top: y - 8
|
|
14656
|
+
});
|
|
14657
|
+
marker.findChild('circle').attr('stroke', getMarkerColor(rgb));
|
|
14658
|
+
}
|
|
14659
|
+
|
|
14660
|
+
function hexToHsb(hex) {
|
|
14661
|
+
var r = parseInt(hex.substr(1, 2), 16);
|
|
14662
|
+
var g = parseInt(hex.substr(3, 2), 16);
|
|
14663
|
+
var b = parseInt(hex.substr(5, 2), 16);
|
|
14664
|
+
var max = Math.max(r, g, b);
|
|
14665
|
+
var min = Math.min(r, g, b);
|
|
14666
|
+
var d = max - min;
|
|
14667
|
+
var h = 0;
|
|
14668
|
+
if (d) {
|
|
14669
|
+
if (max == r) h = ((g - b) / d) % 6;
|
|
14670
|
+
else if (max == g) h = (b - r) / d + 2;
|
|
14671
|
+
else h = (r - g) / d + 4;
|
|
14672
|
+
h *= 60;
|
|
14673
|
+
if (h < 0) h += 360;
|
|
14674
|
+
}
|
|
14675
|
+
return {
|
|
14676
|
+
h: Math.round(h / 360 * 255),
|
|
14677
|
+
s: Math.round(max === 0 ? 0 : d / max * 255),
|
|
14678
|
+
b: max
|
|
14679
|
+
};
|
|
12689
14680
|
}
|
|
12690
|
-
}
|
|
12691
14681
|
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
|
|
14682
|
+
function hsbToHex(hsb) {
|
|
14683
|
+
var rgb = hsbToRgb(hsb);
|
|
14684
|
+
return '#' + [rgb.r, rgb.g, rgb.b].map(function(val) {
|
|
14685
|
+
return val.toString(16).padStart(2, '0');
|
|
14686
|
+
}).join('');
|
|
14687
|
+
}
|
|
12695
14688
|
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
|
|
12700
|
-
|
|
14689
|
+
function hsbToRgb(hsb) {
|
|
14690
|
+
var h = hsb.h / 255 * 360;
|
|
14691
|
+
var s = hsb.s / 255;
|
|
14692
|
+
var v = hsb.b / 255;
|
|
14693
|
+
var c = v * s;
|
|
14694
|
+
var x = c * (1 - Math.abs((h / 60) % 2 - 1));
|
|
14695
|
+
var m = v - c;
|
|
14696
|
+
var rgb = h < 60 ? [c, x, 0] :
|
|
14697
|
+
h < 120 ? [x, c, 0] :
|
|
14698
|
+
h < 180 ? [0, c, x] :
|
|
14699
|
+
h < 240 ? [0, x, c] :
|
|
14700
|
+
h < 300 ? [x, 0, c] : [c, 0, x];
|
|
14701
|
+
return {
|
|
14702
|
+
r: Math.round((rgb[0] + m) * 255),
|
|
14703
|
+
g: Math.round((rgb[1] + m) * 255),
|
|
14704
|
+
b: Math.round((rgb[2] + m) * 255)
|
|
14705
|
+
};
|
|
14706
|
+
}
|
|
12701
14707
|
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
}
|
|
12705
|
-
gui.undo.undo();
|
|
12706
|
-
});
|
|
14708
|
+
function byteToPct(val) {
|
|
14709
|
+
return Math.round(val / 255 * 100);
|
|
14710
|
+
}
|
|
12707
14711
|
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
}
|
|
12711
|
-
gui.undo.redo();
|
|
12712
|
-
});
|
|
14712
|
+
function pctToByte(val) {
|
|
14713
|
+
return Math.round(clamp(val, 0, 100) / 100 * 255);
|
|
14714
|
+
}
|
|
12713
14715
|
|
|
12714
|
-
|
|
14716
|
+
function byteToDegrees(val) {
|
|
14717
|
+
return Math.round(val / 255 * 360);
|
|
14718
|
+
}
|
|
12715
14719
|
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
updateButtons();
|
|
12720
|
-
});
|
|
14720
|
+
function degreesToByte(val) {
|
|
14721
|
+
return Math.round(clamp(val, 0, 360) / 360 * 255);
|
|
14722
|
+
}
|
|
12721
14723
|
|
|
12722
|
-
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
// Visibility may also depend on history (e.g. attribute edits via popup
|
|
12726
|
-
// happen in modes that don't otherwise support undo).
|
|
12727
|
-
updateVisibility();
|
|
12728
|
-
});
|
|
14724
|
+
function parseNumberField(str) {
|
|
14725
|
+
return Number(String(str).replace(/[°%]/g, '').trim());
|
|
14726
|
+
}
|
|
12729
14727
|
|
|
12730
|
-
|
|
14728
|
+
function clamp(val, min, max) {
|
|
14729
|
+
return isFinite(val) ? Math.max(min, Math.min(max, val)) : min;
|
|
14730
|
+
}
|
|
12731
14731
|
|
|
12732
|
-
function
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
|
|
14732
|
+
function applyStyleFields(fields, updateRecord) {
|
|
14733
|
+
var table = getActiveTable();
|
|
14734
|
+
var ids = getSelectionIds();
|
|
14735
|
+
var lyr = getActiveLayer();
|
|
14736
|
+
var hasNewFields;
|
|
14737
|
+
if (!table || ids.length === 0) return;
|
|
14738
|
+
hasNewFields = fields.some(function(field) {
|
|
14739
|
+
return !table.fieldExists(field);
|
|
14740
|
+
});
|
|
14741
|
+
gui.dispatchEvent('data_preupdate', {ids: ids});
|
|
14742
|
+
if (hasNewFields) {
|
|
14743
|
+
table.captureSchemaBefore({operation: 'label-style', fields: fields});
|
|
14744
|
+
} else {
|
|
14745
|
+
table.captureFieldsBefore(fields, {operation: 'label-style'});
|
|
12736
14746
|
}
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
toolbar.show();
|
|
14747
|
+
updateRecords(ids, table, updateRecord);
|
|
14748
|
+
if (hasNewFields) {
|
|
14749
|
+
table.markSchemaChanged({operation: 'label-style'});
|
|
12741
14750
|
} else {
|
|
12742
|
-
|
|
14751
|
+
table.markFieldsChanged(fields, {operation: 'label-style'});
|
|
12743
14752
|
}
|
|
14753
|
+
gui.dispatchEvent('data_postupdate', {ids: ids});
|
|
14754
|
+
gui.model.updated({style: true, same_table: true});
|
|
14755
|
+
updatePendingStyleHistory(lyr, ids, fields);
|
|
14756
|
+
setTimeout(updateSelectionDisplay, 0);
|
|
14757
|
+
updateControls();
|
|
14758
|
+
updateSelectionDisplay();
|
|
12744
14759
|
}
|
|
12745
14760
|
|
|
12746
|
-
function
|
|
12747
|
-
|
|
12748
|
-
|
|
14761
|
+
function updateRecords(ids, table, updateRecord) {
|
|
14762
|
+
var records = table.getRecords();
|
|
14763
|
+
ids.forEach(function(id) {
|
|
14764
|
+
var rec = records[id] || {};
|
|
14765
|
+
updateRecord(rec);
|
|
14766
|
+
records[id] = rec;
|
|
14767
|
+
});
|
|
14768
|
+
}
|
|
14769
|
+
|
|
14770
|
+
function updateSelectionDisplay() {
|
|
14771
|
+
clearSelectionDisplay();
|
|
14772
|
+
getSelectionIds().forEach(function(id) {
|
|
14773
|
+
var textNode = getTextNodeById(id);
|
|
14774
|
+
if (textNode) {
|
|
14775
|
+
textNode.classList.add('label-style-selected');
|
|
14776
|
+
}
|
|
14777
|
+
});
|
|
14778
|
+
}
|
|
14779
|
+
|
|
14780
|
+
function clearSelectionDisplay() {
|
|
14781
|
+
var lyr = hit && hit.getHitTarget();
|
|
14782
|
+
var container = lyr && lyr.gui && lyr.gui.svg_container;
|
|
14783
|
+
if (!container) return;
|
|
14784
|
+
container.querySelectorAll('.label-style-selected').forEach(function(node) {
|
|
14785
|
+
node.classList.remove('label-style-selected');
|
|
14786
|
+
});
|
|
14787
|
+
}
|
|
14788
|
+
|
|
14789
|
+
function getTextNodeById(id) {
|
|
14790
|
+
var lyr = hit && hit.getHitTarget();
|
|
14791
|
+
var container = lyr && lyr.gui && lyr.gui.svg_container;
|
|
14792
|
+
var symbol = container && container.querySelector('[data-id="' + id + '"]');
|
|
14793
|
+
if (!symbol) return null;
|
|
14794
|
+
return symbol.tagName == 'text' ? symbol : symbol.querySelector('text');
|
|
12749
14795
|
}
|
|
14796
|
+
|
|
12750
14797
|
}
|
|
12751
14798
|
|
|
12752
14799
|
function Model(gui) {
|
|
@@ -13466,7 +15513,8 @@
|
|
|
13466
15513
|
}
|
|
13467
15514
|
|
|
13468
15515
|
function selectable() {
|
|
13469
|
-
|
|
15516
|
+
var mode = interactionMode();
|
|
15517
|
+
return mode == 'selection' || mode == 'label_style';
|
|
13470
15518
|
}
|
|
13471
15519
|
|
|
13472
15520
|
function pinnable() {
|
|
@@ -13484,7 +15532,7 @@
|
|
|
13484
15532
|
var mode = interactionMode();
|
|
13485
15533
|
// click used to pin popup and select features
|
|
13486
15534
|
return mode == 'data' || mode == 'info' || mode == 'selection' ||
|
|
13487
|
-
mode == 'rectangles' || mode == 'edit_points';
|
|
15535
|
+
mode == 'label_style' || mode == 'rectangles' || mode == 'edit_points';
|
|
13488
15536
|
}
|
|
13489
15537
|
|
|
13490
15538
|
self.getHitId = function() {
|
|
@@ -16817,6 +18865,8 @@
|
|
|
16817
18865
|
var style;
|
|
16818
18866
|
if (layerHasDrawableStyle(lyr) && !opts.outlineMode) {
|
|
16819
18867
|
style = getCanvasDisplayStyle(lyr);
|
|
18868
|
+
} else if (internal.layerHasLabels(lyr) && opts.interactionMode == 'label_style') {
|
|
18869
|
+
style = {dotSize: 0};
|
|
16820
18870
|
} else if (internal.layerHasLabels(lyr) && !opts.outlineMode) {
|
|
16821
18871
|
style = copyBaseStyle(activeStyleForLabels);
|
|
16822
18872
|
} else if (opts.darkMode) {
|
|
@@ -17171,11 +19221,11 @@
|
|
|
17171
19221
|
utils$1.inherit(MapExtent, EventDispatcher);
|
|
17172
19222
|
|
|
17173
19223
|
var MAX_VIEWPORT_PREVIEW_PIXELS = 6e6;
|
|
17174
|
-
var cache = new WeakMap();
|
|
17175
|
-
var requestId = 0;
|
|
19224
|
+
var cache$1 = new WeakMap();
|
|
19225
|
+
var requestId$1 = 0;
|
|
17176
19226
|
|
|
17177
19227
|
function getCachedRasterViewportPreview(layer, ext) {
|
|
17178
|
-
var entry = cache.get(layer);
|
|
19228
|
+
var entry = cache$1.get(layer);
|
|
17179
19229
|
var params = getRasterViewportPreviewParams(layer, ext);
|
|
17180
19230
|
if (!entry || !params || entry.key != params.key) return null;
|
|
17181
19231
|
return entry.preview;
|
|
@@ -17183,24 +19233,24 @@
|
|
|
17183
19233
|
|
|
17184
19234
|
function scheduleRasterViewportPreview(layer, ext, onReady) {
|
|
17185
19235
|
var params = getRasterViewportPreviewParams(layer, ext);
|
|
17186
|
-
var entry = cache.get(layer);
|
|
19236
|
+
var entry = cache$1.get(layer);
|
|
17187
19237
|
var id, stats, timing, preview;
|
|
17188
19238
|
if (!params || !params.needed) return;
|
|
17189
19239
|
if (entry && entry.key == params.key) return;
|
|
17190
|
-
id = ++requestId;
|
|
17191
|
-
cache.set(layer, {key: params.key, pending: id});
|
|
19240
|
+
id = ++requestId$1;
|
|
19241
|
+
cache$1.set(layer, {key: params.key, pending: id});
|
|
17192
19242
|
setTimeout(function() {
|
|
17193
|
-
var current = cache.get(layer);
|
|
19243
|
+
var current = cache$1.get(layer);
|
|
17194
19244
|
if (!current || current.pending != id) return;
|
|
17195
19245
|
timing = {};
|
|
17196
19246
|
stats = getCachedRasterScalingStats(params, timing);
|
|
17197
|
-
timing.renderStart = getTimer();
|
|
17198
|
-
preview = renderRasterViewportPreview(params.grid, params.recipe, params.bbox, params.width, params.height, stats);
|
|
17199
|
-
timing.renderMs = getTimer() - timing.renderStart;
|
|
19247
|
+
timing.renderStart = getTimer$1();
|
|
19248
|
+
preview = internal.renderRasterViewportPreview(params.grid, params.recipe, params.bbox, params.width, params.height, stats);
|
|
19249
|
+
timing.renderMs = getTimer$1() - timing.renderStart;
|
|
17200
19250
|
logRasterPreviewTiming(params, timing);
|
|
17201
|
-
current = cache.get(layer);
|
|
19251
|
+
current = cache$1.get(layer);
|
|
17202
19252
|
if (!preview || !current || current.pending != id) return;
|
|
17203
|
-
cache.set(layer, {
|
|
19253
|
+
cache$1.set(layer, {
|
|
17204
19254
|
key: params.key,
|
|
17205
19255
|
preview: preview
|
|
17206
19256
|
});
|
|
@@ -17209,23 +19259,23 @@
|
|
|
17209
19259
|
}
|
|
17210
19260
|
|
|
17211
19261
|
function invalidateRasterViewportPreview(layer) {
|
|
17212
|
-
cache.delete(layer);
|
|
19262
|
+
cache$1.delete(layer);
|
|
17213
19263
|
}
|
|
17214
19264
|
|
|
17215
19265
|
function getRasterViewportPreviewParams(layer, ext) {
|
|
17216
19266
|
var raster = layer && layer.raster;
|
|
17217
|
-
var grid = getRasterGrid(raster);
|
|
17218
|
-
var rasterBbox = getRasterBBox(raster);
|
|
19267
|
+
var grid = internal.getRasterGrid(raster);
|
|
19268
|
+
var rasterBbox = internal.getRasterBBox(raster);
|
|
17219
19269
|
var mapBbox = ext.getBounds().toArray();
|
|
17220
|
-
var visibleBbox = rasterBbox && intersectBboxes(rasterBbox, mapBbox);
|
|
17221
|
-
var preview = getRasterPreview(raster);
|
|
19270
|
+
var visibleBbox = rasterBbox && internal.intersectBboxes(rasterBbox, mapBbox);
|
|
19271
|
+
var preview = internal.getRasterPreview(raster);
|
|
17222
19272
|
var recipe, pixelRatio, t, p1, p2, displayWidth, displayHeight, crop, width, height, scale, key, needed;
|
|
17223
19273
|
if (!grid || !grid.samples || !visibleBbox || !grid.bbox) return null;
|
|
17224
19274
|
if (!supportsNorthUpRaster(grid)) return null;
|
|
17225
19275
|
crop = getRasterSourceWindow(grid, visibleBbox);
|
|
17226
19276
|
if (!crop) return null;
|
|
17227
19277
|
visibleBbox = crop.bbox;
|
|
17228
|
-
recipe = getRasterViewRecipe(grid, raster.view && raster.view.recipe);
|
|
19278
|
+
recipe = internal.getRasterViewRecipe(grid, raster.view && raster.view.recipe);
|
|
17229
19279
|
pixelRatio = GUI.getPixelRatio();
|
|
17230
19280
|
t = ext.getTransform(pixelRatio);
|
|
17231
19281
|
p1 = t.transform(mapBbox[0], mapBbox[3]);
|
|
@@ -17273,46 +19323,46 @@
|
|
|
17273
19323
|
timing.statsSource = 'none';
|
|
17274
19324
|
return null;
|
|
17275
19325
|
}
|
|
17276
|
-
key = getRasterScalingStatsKey(params.grid, params.recipe);
|
|
19326
|
+
key = internal.getRasterScalingStatsKey(params.grid, params.recipe);
|
|
17277
19327
|
if (cached && cached.key == key) {
|
|
17278
19328
|
timing.statsMs = 0;
|
|
17279
19329
|
timing.statsSource = 'raster-view-cache';
|
|
17280
19330
|
return cached.stats;
|
|
17281
19331
|
}
|
|
17282
19332
|
timing.statsSource = 'computed';
|
|
17283
|
-
timing.statsStart = getTimer();
|
|
17284
|
-
cached = getRasterViewScalingStats(params.raster, params.recipe);
|
|
17285
|
-
timing.statsMs = getTimer() - timing.statsStart;
|
|
19333
|
+
timing.statsStart = getTimer$1();
|
|
19334
|
+
cached = internal.getRasterViewScalingStats(params.raster, params.recipe);
|
|
19335
|
+
timing.statsMs = getTimer$1() - timing.statsStart;
|
|
17286
19336
|
return cached;
|
|
17287
19337
|
}
|
|
17288
19338
|
|
|
17289
19339
|
function logRasterPreviewTiming(params, timing) {
|
|
17290
|
-
if (!rasterDebugIsOn()) return;
|
|
19340
|
+
if (!rasterDebugIsOn$1()) return;
|
|
17291
19341
|
console.log([
|
|
17292
19342
|
'Raster viewport preview:',
|
|
17293
19343
|
params.width + 'x' + params.height,
|
|
17294
19344
|
'from',
|
|
17295
19345
|
params.sourceWidth + 'x' + params.sourceHeight,
|
|
17296
19346
|
'source px,',
|
|
17297
|
-
'stats=' + formatMs(timing.statsMs),
|
|
19347
|
+
'stats=' + formatMs$1(timing.statsMs),
|
|
17298
19348
|
'(' + timing.statsSource + '),',
|
|
17299
|
-
'render=' + formatMs(timing.renderMs) + ',',
|
|
17300
|
-
'total=' + formatMs(timing.statsMs + timing.renderMs) + ',',
|
|
19349
|
+
'render=' + formatMs$1(timing.renderMs) + ',',
|
|
19350
|
+
'total=' + formatMs$1(timing.statsMs + timing.renderMs) + ',',
|
|
17301
19351
|
'scaling=' + params.recipe.scaling,
|
|
17302
19352
|
'type=' + params.recipe.type
|
|
17303
19353
|
].join(' '));
|
|
17304
19354
|
}
|
|
17305
19355
|
|
|
17306
|
-
function rasterDebugIsOn() {
|
|
19356
|
+
function rasterDebugIsOn$1() {
|
|
17307
19357
|
var vars = GUI.getUrlVars();
|
|
17308
19358
|
return vars['raster-debug'] === true || vars['raster-debug'] == '1' || vars.raster_debug === true || vars.raster_debug == '1';
|
|
17309
19359
|
}
|
|
17310
19360
|
|
|
17311
|
-
function getTimer() {
|
|
19361
|
+
function getTimer$1() {
|
|
17312
19362
|
return typeof performance != 'undefined' && performance.now ? performance.now() : Date.now();
|
|
17313
19363
|
}
|
|
17314
19364
|
|
|
17315
|
-
function formatMs(ms) {
|
|
19365
|
+
function formatMs$1(ms) {
|
|
17316
19366
|
return Math.round(ms * 10) / 10 + 'ms';
|
|
17317
19367
|
}
|
|
17318
19368
|
|
|
@@ -17362,6 +19412,162 @@
|
|
|
17362
19412
|
return Math.round(val * 1e9) / 1e9;
|
|
17363
19413
|
}
|
|
17364
19414
|
|
|
19415
|
+
var MAX_REPROJECTED_PREVIEW_PIXELS = 6e6;
|
|
19416
|
+
var cache = new WeakMap();
|
|
19417
|
+
var requestId = 0;
|
|
19418
|
+
|
|
19419
|
+
function getCachedRasterReprojectedPreview(layer, ext) {
|
|
19420
|
+
var params = getRasterReprojectedPreviewParams(layer, ext);
|
|
19421
|
+
var entry = cache.get(layer);
|
|
19422
|
+
if (!entry || !entry.preview) return null;
|
|
19423
|
+
if (!params || entry.key != params.key) return entry.preview;
|
|
19424
|
+
return entry.preview;
|
|
19425
|
+
}
|
|
19426
|
+
|
|
19427
|
+
function scheduleRasterReprojectedPreview(layer, ext, onReady) {
|
|
19428
|
+
var params = getRasterReprojectedPreviewParams(layer, ext);
|
|
19429
|
+
var entry = cache.get(layer);
|
|
19430
|
+
var id, timing;
|
|
19431
|
+
if (!params) return;
|
|
19432
|
+
if (entry && entry.key == params.key) return;
|
|
19433
|
+
id = ++requestId;
|
|
19434
|
+
cache.set(layer, {key: params.key, pending: id});
|
|
19435
|
+
setTimeout(function() {
|
|
19436
|
+
var current = cache.get(layer);
|
|
19437
|
+
var grid, preview;
|
|
19438
|
+
if (!current || current.pending != id) return;
|
|
19439
|
+
timing = {};
|
|
19440
|
+
try {
|
|
19441
|
+
grid = internal.projectRasterGridForward({grid: params.grid}, params.sourceCRS, params.displayCRS, {
|
|
19442
|
+
raster_mesh_interval: params.meshInterval,
|
|
19443
|
+
output_bbox: params.bbox,
|
|
19444
|
+
output_width: params.width,
|
|
19445
|
+
output_height: params.height,
|
|
19446
|
+
sample_method: params.sampleMethod,
|
|
19447
|
+
timing: timing
|
|
19448
|
+
});
|
|
19449
|
+
} catch(e) {
|
|
19450
|
+
cache.delete(layer);
|
|
19451
|
+
if (typeof console != 'undefined' && console.warn) {
|
|
19452
|
+
console.warn('Unable to reproject raster preview', e);
|
|
19453
|
+
}
|
|
19454
|
+
return;
|
|
19455
|
+
}
|
|
19456
|
+
timing.renderStart = getTimer();
|
|
19457
|
+
preview = internal.renderRasterPreview(grid, params.recipe, grid.width, grid.height, params.stats);
|
|
19458
|
+
applyCoverageMask(preview, grid.coverage);
|
|
19459
|
+
timing.renderMs = getTimer() - timing.renderStart;
|
|
19460
|
+
logRasterReprojectionTiming(params, timing);
|
|
19461
|
+
preview.bbox = grid.bbox;
|
|
19462
|
+
current = cache.get(layer);
|
|
19463
|
+
if (!current || current.pending != id) return;
|
|
19464
|
+
cache.set(layer, {key: params.key, preview: preview});
|
|
19465
|
+
onReady();
|
|
19466
|
+
}, 0);
|
|
19467
|
+
}
|
|
19468
|
+
|
|
19469
|
+
function getRasterReprojectedPreviewParams(layer, ext) {
|
|
19470
|
+
var raster = layer && layer.raster;
|
|
19471
|
+
var gui = layer && layer.gui;
|
|
19472
|
+
var sourceDataset = gui && gui.source && gui.source.dataset;
|
|
19473
|
+
var sourceInfo = sourceDataset && internal.getDatasetCrsInfo(sourceDataset);
|
|
19474
|
+
var sourceCRS = sourceInfo && sourceInfo.crs;
|
|
19475
|
+
var displayCRS = gui && gui.dynamic_crs;
|
|
19476
|
+
var sourceGrid = internal.getRasterGrid(raster);
|
|
19477
|
+
var rasterBbox = gui && gui.bounds && gui.bounds.hasBounds() && gui.bounds.toArray();
|
|
19478
|
+
var viewBbox = ext && ext.getBounds().toArray();
|
|
19479
|
+
var bbox = rasterBbox && viewBbox && internal.intersectBboxes(rasterBbox, viewBbox);
|
|
19480
|
+
var size = bbox && getPreviewSize(ext, sourceGrid);
|
|
19481
|
+
var recipe, stats;
|
|
19482
|
+
if (!sourceCRS || !displayCRS || !sourceGrid || !sourceGrid.samples || !bbox || !size) return null;
|
|
19483
|
+
recipe = internal.getRasterViewRecipe(sourceGrid, raster.view && raster.view.recipe);
|
|
19484
|
+
stats = internal.getRasterViewScalingStats(raster, recipe);
|
|
19485
|
+
return {
|
|
19486
|
+
key: getRasterReprojectedPreviewKey(layer, bbox, size, sourceCRS, displayCRS, recipe),
|
|
19487
|
+
grid: sourceGrid,
|
|
19488
|
+
sourceCRS: sourceCRS,
|
|
19489
|
+
displayCRS: displayCRS,
|
|
19490
|
+
bbox: bbox,
|
|
19491
|
+
width: size.width,
|
|
19492
|
+
height: size.height,
|
|
19493
|
+
recipe: recipe,
|
|
19494
|
+
stats: stats,
|
|
19495
|
+
sampleMethod: getRasterReprojectionSampleMethod(),
|
|
19496
|
+
meshInterval: 32
|
|
19497
|
+
};
|
|
19498
|
+
}
|
|
19499
|
+
|
|
19500
|
+
function getPreviewSize(ext, grid) {
|
|
19501
|
+
var pixelRatio = GUI.getPixelRatio();
|
|
19502
|
+
var t = ext.getTransform(pixelRatio);
|
|
19503
|
+
var mapBbox = ext.getBounds().toArray();
|
|
19504
|
+
var p1 = t.transform(mapBbox[0], mapBbox[3]);
|
|
19505
|
+
var p2 = t.transform(mapBbox[2], mapBbox[1]);
|
|
19506
|
+
var width = Math.max(1, Math.round(Math.abs(p2[0] - p1[0])));
|
|
19507
|
+
var height = Math.max(1, Math.round(Math.abs(p2[1] - p1[1])));
|
|
19508
|
+
var scale = Math.min(1, Math.sqrt(MAX_REPROJECTED_PREVIEW_PIXELS / (width * height)));
|
|
19509
|
+
width = Math.min(grid.width, Math.max(1, Math.round(width * scale)));
|
|
19510
|
+
height = Math.min(grid.height, Math.max(1, Math.round(height * scale)));
|
|
19511
|
+
return {width: width, height: height};
|
|
19512
|
+
}
|
|
19513
|
+
|
|
19514
|
+
function getRasterReprojectedPreviewKey(layer, bbox, size, sourceCRS, displayCRS, recipe) {
|
|
19515
|
+
return [
|
|
19516
|
+
size.width,
|
|
19517
|
+
size.height,
|
|
19518
|
+
bbox.join(','),
|
|
19519
|
+
internal.crsToProj4(sourceCRS),
|
|
19520
|
+
internal.crsToProj4(displayCRS),
|
|
19521
|
+
recipe.type,
|
|
19522
|
+
recipe.scaling,
|
|
19523
|
+
recipe.scaleRange && recipe.scaleRange.join(','),
|
|
19524
|
+
recipe.percentileRange && recipe.percentileRange.join(','),
|
|
19525
|
+
getRasterReprojectionSampleMethod(),
|
|
19526
|
+
layer.raster && layer.raster.grid && layer.raster.grid.samples && layer.raster.grid.samples.length
|
|
19527
|
+
].join('|');
|
|
19528
|
+
}
|
|
19529
|
+
|
|
19530
|
+
function applyCoverageMask(preview, coverage) {
|
|
19531
|
+
var pixels = preview && preview.pixels;
|
|
19532
|
+
if (!pixels || !coverage) return;
|
|
19533
|
+
for (var i = 0; i < coverage.length; i++) {
|
|
19534
|
+
if (!coverage[i]) pixels[i * 4 + 3] = 0;
|
|
19535
|
+
}
|
|
19536
|
+
}
|
|
19537
|
+
|
|
19538
|
+
function getRasterReprojectionSampleMethod() {
|
|
19539
|
+
var vars = GUI.getUrlVars();
|
|
19540
|
+
var val = vars['raster-bilinear'] ?? vars.raster_bilinear;
|
|
19541
|
+
return val === false || val == '0' ? 'nearest' : 'bilinear';
|
|
19542
|
+
}
|
|
19543
|
+
|
|
19544
|
+
function logRasterReprojectionTiming(params, timing) {
|
|
19545
|
+
if (!rasterDebugIsOn()) return;
|
|
19546
|
+
console.log([
|
|
19547
|
+
'Raster reprojection preview:',
|
|
19548
|
+
params.width + 'x' + params.height,
|
|
19549
|
+
'mesh=' + formatMs(timing.meshMs),
|
|
19550
|
+
'rasterize=' + formatMs(timing.rasterizeMs),
|
|
19551
|
+
'render=' + formatMs(timing.renderMs),
|
|
19552
|
+
'total=' + formatMs((timing.meshMs || 0) + (timing.rasterizeMs || 0) + (timing.renderMs || 0)) + ',',
|
|
19553
|
+
'vertices=' + timing.meshVertices,
|
|
19554
|
+
'sampling=' + params.sampleMethod
|
|
19555
|
+
].join(' '));
|
|
19556
|
+
}
|
|
19557
|
+
|
|
19558
|
+
function rasterDebugIsOn() {
|
|
19559
|
+
var vars = GUI.getUrlVars();
|
|
19560
|
+
return vars['raster-debug'] === true || vars['raster-debug'] == '1' || vars.raster_debug === true || vars.raster_debug == '1';
|
|
19561
|
+
}
|
|
19562
|
+
|
|
19563
|
+
function formatMs(ms) {
|
|
19564
|
+
return Math.round((ms || 0) * 10) / 10 + 'ms';
|
|
19565
|
+
}
|
|
19566
|
+
|
|
19567
|
+
function getTimer() {
|
|
19568
|
+
return typeof performance != 'undefined' && performance.now ? performance.now() : Date.now();
|
|
19569
|
+
}
|
|
19570
|
+
|
|
17365
19571
|
var hatches = {}; // cached patterns
|
|
17366
19572
|
|
|
17367
19573
|
function getCanvasFillEffect(ctx, shp, arcs, ext, style) {
|
|
@@ -17620,11 +19826,29 @@
|
|
|
17620
19826
|
_self.drawRasterLayer = function(layer, opts) {
|
|
17621
19827
|
var raster = layer.raster;
|
|
17622
19828
|
var options = opts || {};
|
|
17623
|
-
var preview =
|
|
17624
|
-
var bbox
|
|
19829
|
+
var preview = null;
|
|
19830
|
+
var bbox;
|
|
19831
|
+
if (layer.gui && layer.gui.dynamic_crs) {
|
|
19832
|
+
preview = getCachedRasterReprojectedPreview(layer, _ext);
|
|
19833
|
+
if (!preview) {
|
|
19834
|
+
if (options.action != 'nav' && options.onViewportPreviewReady) {
|
|
19835
|
+
scheduleRasterReprojectedPreview(layer, _ext, options.onViewportPreviewReady);
|
|
19836
|
+
}
|
|
19837
|
+
return;
|
|
19838
|
+
}
|
|
19839
|
+
bbox = preview.bbox;
|
|
19840
|
+
if (!preview.pixels || !bbox) return;
|
|
19841
|
+
drawRasterPreview(preview, bbox);
|
|
19842
|
+
if (options.action != 'nav' && options.onViewportPreviewReady) {
|
|
19843
|
+
scheduleRasterReprojectedPreview(layer, _ext, options.onViewportPreviewReady);
|
|
19844
|
+
}
|
|
19845
|
+
return;
|
|
19846
|
+
}
|
|
19847
|
+
preview = options.action == 'nav' ? null : getCachedRasterViewportPreview(layer, _ext);
|
|
19848
|
+
bbox = preview && preview.bbox;
|
|
17625
19849
|
if (!preview) {
|
|
17626
|
-
preview = raster && getRasterPreview(raster);
|
|
17627
|
-
bbox = raster && getRasterBBox(raster);
|
|
19850
|
+
preview = raster && internal.getRasterPreview(raster);
|
|
19851
|
+
bbox = raster && internal.getRasterBBox(raster);
|
|
17628
19852
|
if (options.action != 'nav' && options.onViewportPreviewReady) {
|
|
17629
19853
|
scheduleRasterViewportPreview(layer, _ext, options.onViewportPreviewReady);
|
|
17630
19854
|
}
|
|
@@ -18897,6 +21121,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
18897
21121
|
this.isActiveLayer = isActiveLayer;
|
|
18898
21122
|
this.isVisibleLayer = isVisibleLayer;
|
|
18899
21123
|
this.getActiveLayer = function() { return _activeLyr; };
|
|
21124
|
+
this.getHitControl = function() { return _hit; };
|
|
18900
21125
|
// this.getViewData = function() {
|
|
18901
21126
|
// return {
|
|
18902
21127
|
// isPreview: isPreviewView(),
|
|
@@ -19289,7 +21514,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
19289
21514
|
_renderer.drawMainLayers(contentLayers, action);
|
|
19290
21515
|
|
|
19291
21516
|
// draw hover & selection overlay
|
|
19292
|
-
if (!_overlayLayers || action != 'nav') {
|
|
21517
|
+
if (!_overlayLayers || action != 'nav' || getGlobalStyleOptions().interactionMode == 'label_style') {
|
|
19293
21518
|
// cache layers to use when panning/zooming
|
|
19294
21519
|
_overlayLayers = getOverlayLayers(_activeLyr, _hit.getHitState(), getGlobalStyleOptions());
|
|
19295
21520
|
}
|
|
@@ -20068,6 +22293,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
20068
22293
|
}
|
|
20069
22294
|
gui.interaction = new InteractionMode(gui);
|
|
20070
22295
|
gui.editToolbar = new EditToolbar(gui);
|
|
22296
|
+
gui.labelTool = new LabelTool(gui);
|
|
20071
22297
|
|
|
20072
22298
|
gui.showProgressMessage = function(msg) {
|
|
20073
22299
|
if (!gui.progressMessage) {
|