mapshaper 0.7.12 → 0.7.14
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 +1867 -119
- package/package.json +6 -2
- package/www/geotiff.js +26351 -0
- package/www/index.html +27 -29
- package/www/mapshaper-gui.js +2142 -336
- package/www/mapshaper.js +1867 -119
- package/www/page.css +254 -108
package/www/mapshaper-gui.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
cli = api.cli,
|
|
7
7
|
geom = api.geom,
|
|
8
8
|
internal = api.internal,
|
|
9
|
-
Bounds = internal.Bounds,
|
|
9
|
+
Bounds$1 = internal.Bounds,
|
|
10
10
|
UserError$1 = internal.UserError,
|
|
11
11
|
message$1 = internal.message, // stop, error and message are overridden in gui-proxy.js
|
|
12
12
|
stop$1 = internal.stop,
|
|
@@ -1396,6 +1396,32 @@
|
|
|
1396
1396
|
}, 400);
|
|
1397
1397
|
}
|
|
1398
1398
|
|
|
1399
|
+
function logStartupCleanup(opts) {
|
|
1400
|
+
opts = opts || {};
|
|
1401
|
+
var count = opts.count || 0;
|
|
1402
|
+
if (!count || typeof console == 'undefined' || !console.log) return;
|
|
1403
|
+
var sessionCount = opts.sessionCount || 0;
|
|
1404
|
+
var singular = opts.singular || 'item';
|
|
1405
|
+
var plural = opts.plural || singular + 's';
|
|
1406
|
+
var itemLabel = count == 1 ? singular : plural;
|
|
1407
|
+
var msg = '[mapshaper] startup cleanup reclaimed ' + count + ' ' + itemLabel;
|
|
1408
|
+
if (sessionCount > 0) {
|
|
1409
|
+
msg += ' from ' + sessionCount + ' stale session' + (sessionCount == 1 ? '' : 's');
|
|
1410
|
+
}
|
|
1411
|
+
if (opts.sizeBytes > 0) {
|
|
1412
|
+
msg += ' (' + formatCleanupSize(opts.sizeBytes) + ')';
|
|
1413
|
+
}
|
|
1414
|
+
console.log(msg);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
function formatCleanupSize(bytes) {
|
|
1418
|
+
var kb = Math.round(bytes / 1000);
|
|
1419
|
+
var mb = (bytes / 1e6).toFixed(1);
|
|
1420
|
+
if (!kb) return '';
|
|
1421
|
+
if (kb < 990) return kb + 'kB';
|
|
1422
|
+
return mb + 'MB';
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1399
1425
|
// Several dependencies are loaded via require()
|
|
1400
1426
|
var f;
|
|
1401
1427
|
if (typeof require == 'function') {
|
|
@@ -1412,10 +1438,10 @@
|
|
|
1412
1438
|
}
|
|
1413
1439
|
var require$1 = f;
|
|
1414
1440
|
|
|
1415
|
-
var idb$
|
|
1441
|
+
var idb$2 = require$1('idb-keyval');
|
|
1416
1442
|
// https://github.com/jakearchibald/idb
|
|
1417
1443
|
// https://github.com/jakearchibald/idb-keyval
|
|
1418
|
-
var sessionId = getUniqId$1('session');
|
|
1444
|
+
var sessionId$1 = getUniqId$1('session');
|
|
1419
1445
|
var snapshotCount = 0;
|
|
1420
1446
|
// IDs of snapshots created (and not removed) by this tab. Tracked in memory
|
|
1421
1447
|
// so the pagehide handler can fire a single batched delMany() without first
|
|
@@ -1539,7 +1565,7 @@
|
|
|
1539
1565
|
closeMenu(100);
|
|
1540
1566
|
}).text('restore');
|
|
1541
1567
|
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1542
|
-
var obj = await idb$
|
|
1568
|
+
var obj = await idb$2.get(item.id);
|
|
1543
1569
|
await internal.compressSnapshotForExport(obj);
|
|
1544
1570
|
var buf = internal.pack(obj);
|
|
1545
1571
|
var fileName = `snapshot-${String(item.number).padStart(2, '0')}.msx`;
|
|
@@ -1590,43 +1616,35 @@
|
|
|
1590
1616
|
// note: we don't know the size of unpacked snapshot objects
|
|
1591
1617
|
// obj = internal.pack(obj);
|
|
1592
1618
|
var entryId = String(++snapshotCount).padStart(3, '0');
|
|
1593
|
-
var snapshotId = sessionId + '_' + entryId; // e.g. session_d89fw_001
|
|
1619
|
+
var snapshotId = sessionId$1 + '_' + entryId; // e.g. session_d89fw_001
|
|
1594
1620
|
var size = obj.length;
|
|
1595
1621
|
var entry = {
|
|
1596
1622
|
created: Date.now(),
|
|
1597
|
-
session: sessionId,
|
|
1623
|
+
session: sessionId$1,
|
|
1598
1624
|
id: snapshotId,
|
|
1599
1625
|
name: snapshotCount + '.',
|
|
1600
1626
|
number: snapshotCount,
|
|
1601
1627
|
size: size,
|
|
1602
|
-
display_size:
|
|
1628
|
+
display_size: formatCleanupSize(size)
|
|
1603
1629
|
};
|
|
1604
1630
|
|
|
1605
|
-
await idb$
|
|
1631
|
+
await idb$2.set(entry.id, obj);
|
|
1606
1632
|
ownSnapshotIds.add(entry.id);
|
|
1607
1633
|
await addToIndex(entry);
|
|
1608
1634
|
renderMenu();
|
|
1609
1635
|
}
|
|
1610
1636
|
}
|
|
1611
1637
|
|
|
1612
|
-
function formatSize(bytes) {
|
|
1613
|
-
var kb = Math.round(bytes / 1000);
|
|
1614
|
-
var mb = (bytes / 1e6).toFixed(1);
|
|
1615
|
-
if (!kb) return '';
|
|
1616
|
-
if (kb < 990) return kb + 'kB';
|
|
1617
|
-
return mb + 'MB';
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
1638
|
async function fetchSnapshotList() {
|
|
1621
1639
|
await pruneIndexAgainstKeys();
|
|
1622
1640
|
var index = await fetchIndex();
|
|
1623
1641
|
var snapshots = index.snapshots;
|
|
1624
|
-
snapshots = snapshots.filter(function(o) {return o.session == sessionId;});
|
|
1642
|
+
snapshots = snapshots.filter(function(o) {return o.session == sessionId$1;});
|
|
1625
1643
|
return snapshots.sort(function(a, b) {b.created > a.created;});
|
|
1626
1644
|
}
|
|
1627
1645
|
|
|
1628
1646
|
async function removeSnapshotById(id, gui) {
|
|
1629
|
-
await idb$
|
|
1647
|
+
await idb$2.del(id);
|
|
1630
1648
|
ownSnapshotIds.delete(id);
|
|
1631
1649
|
return updateIndex(function(index) {
|
|
1632
1650
|
index.snapshots = index.snapshots.filter(function(snap) {
|
|
@@ -1638,7 +1656,7 @@
|
|
|
1638
1656
|
async function restoreSnapshotById(id, gui) {
|
|
1639
1657
|
var data;
|
|
1640
1658
|
try {
|
|
1641
|
-
data = await internal.restoreSessionData(await idb$
|
|
1659
|
+
data = await internal.restoreSessionData(await idb$2.get(id));
|
|
1642
1660
|
} catch(e) {
|
|
1643
1661
|
console.error(e);
|
|
1644
1662
|
stop$1('Snapshot is not available');
|
|
@@ -1721,12 +1739,12 @@
|
|
|
1721
1739
|
}
|
|
1722
1740
|
|
|
1723
1741
|
async function fetchIndex() {
|
|
1724
|
-
var index = await idb$
|
|
1742
|
+
var index = await idb$2.get('msx_index');
|
|
1725
1743
|
return index || {snapshots: []};
|
|
1726
1744
|
}
|
|
1727
1745
|
|
|
1728
1746
|
async function updateIndex(action) {
|
|
1729
|
-
return idb$
|
|
1747
|
+
return idb$2.update('msx_index', function(index) {
|
|
1730
1748
|
if (!index || !Array.isArray(index.snapshots)) {
|
|
1731
1749
|
index = {snapshots: []};
|
|
1732
1750
|
}
|
|
@@ -1746,7 +1764,7 @@
|
|
|
1746
1764
|
// (e.g. cleared by another tab). Cheaper than reclaimDeadSessionData; used
|
|
1747
1765
|
// before rendering the menu to keep stale entries out of the UI.
|
|
1748
1766
|
async function pruneIndexAgainstKeys() {
|
|
1749
|
-
var keys = await idb$
|
|
1767
|
+
var keys = await idb$2.keys();
|
|
1750
1768
|
return updateIndex(function(index) {
|
|
1751
1769
|
index.snapshots = index.snapshots.filter(function(snap) {
|
|
1752
1770
|
return keys.includes(snap.id);
|
|
@@ -1766,7 +1784,7 @@
|
|
|
1766
1784
|
// Delete every snapshot in IndexedDB whose session id is not in liveSessions,
|
|
1767
1785
|
// and keep the on-disk index consistent with the actual key set.
|
|
1768
1786
|
async function reclaimDeadSessionData(liveSessions) {
|
|
1769
|
-
var keys = await idb$
|
|
1787
|
+
var keys = await idb$2.keys();
|
|
1770
1788
|
var doomedKeys = [];
|
|
1771
1789
|
var doomedSessions = new Set();
|
|
1772
1790
|
keys.forEach(function(key) {
|
|
@@ -1789,12 +1807,12 @@
|
|
|
1789
1807
|
sizeBytes += snap.size;
|
|
1790
1808
|
}
|
|
1791
1809
|
});
|
|
1792
|
-
await Promise.all(doomedKeys.map(function(k) { return idb$
|
|
1810
|
+
await Promise.all(doomedKeys.map(function(k) { return idb$2.del(k); }));
|
|
1793
1811
|
}
|
|
1794
1812
|
|
|
1795
1813
|
// Drop index entries pointing to deleted snapshots, and any entries whose
|
|
1796
1814
|
// session is dead even if the underlying key was already gone.
|
|
1797
|
-
var remainingKeys = await idb$
|
|
1815
|
+
var remainingKeys = await idb$2.keys();
|
|
1798
1816
|
var keySet = new Set(remainingKeys);
|
|
1799
1817
|
await updateIndex(function(index) {
|
|
1800
1818
|
index.snapshots = index.snapshots.filter(function(snap) {
|
|
@@ -1805,13 +1823,13 @@
|
|
|
1805
1823
|
});
|
|
1806
1824
|
|
|
1807
1825
|
if (doomedKeys.length) {
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1826
|
+
logStartupCleanup({
|
|
1827
|
+
count: doomedKeys.length,
|
|
1828
|
+
sessionCount: doomedSessions.size,
|
|
1829
|
+
singular: 'snapshot',
|
|
1830
|
+
plural: 'snapshots',
|
|
1831
|
+
sizeBytes: sizeBytes
|
|
1832
|
+
});
|
|
1815
1833
|
}
|
|
1816
1834
|
}
|
|
1817
1835
|
|
|
@@ -1869,10 +1887,10 @@
|
|
|
1869
1887
|
_channel = new BroadcastChannel(BROADCAST_CHANNEL_NAME);
|
|
1870
1888
|
_channel.onmessage = function(e) {
|
|
1871
1889
|
var msg = e.data;
|
|
1872
|
-
if (!msg || msg.from === sessionId) return;
|
|
1890
|
+
if (!msg || msg.from === sessionId$1) return;
|
|
1873
1891
|
if (msg.type === 'whois') {
|
|
1874
1892
|
try {
|
|
1875
|
-
_channel.postMessage({type: 'iam', from: sessionId});
|
|
1893
|
+
_channel.postMessage({type: 'iam', from: sessionId$1});
|
|
1876
1894
|
} catch (err) {} // channel can throw if tab is being torn down
|
|
1877
1895
|
}
|
|
1878
1896
|
};
|
|
@@ -1886,7 +1904,7 @@
|
|
|
1886
1904
|
// considered alive (always includes our own).
|
|
1887
1905
|
function discoverLiveSessions() {
|
|
1888
1906
|
return new Promise(function(resolve) {
|
|
1889
|
-
var live = new Set([sessionId]);
|
|
1907
|
+
var live = new Set([sessionId$1]);
|
|
1890
1908
|
// localStorage heartbeat data is the durable signal; it survives
|
|
1891
1909
|
// backgrounded/throttled tabs that may not respond to BroadcastChannel
|
|
1892
1910
|
// promptly.
|
|
@@ -1904,13 +1922,13 @@
|
|
|
1904
1922
|
// window. This is fast and authoritative for foreground tabs.
|
|
1905
1923
|
var listener = function(e) {
|
|
1906
1924
|
var msg = e.data;
|
|
1907
|
-
if (msg && msg.type === 'iam' && msg.from && msg.from !== sessionId) {
|
|
1925
|
+
if (msg && msg.type === 'iam' && msg.from && msg.from !== sessionId$1) {
|
|
1908
1926
|
live.add(msg.from);
|
|
1909
1927
|
}
|
|
1910
1928
|
};
|
|
1911
1929
|
_channel.addEventListener('message', listener);
|
|
1912
1930
|
try {
|
|
1913
|
-
_channel.postMessage({type: 'whois', from: sessionId});
|
|
1931
|
+
_channel.postMessage({type: 'whois', from: sessionId$1});
|
|
1914
1932
|
} catch (err) {}
|
|
1915
1933
|
setTimeout(function() {
|
|
1916
1934
|
_channel.removeEventListener('message', listener);
|
|
@@ -1922,7 +1940,7 @@
|
|
|
1922
1940
|
function announceLeaving() {
|
|
1923
1941
|
if (!_channel) return;
|
|
1924
1942
|
try {
|
|
1925
|
-
_channel.postMessage({type: 'leaving', from: sessionId});
|
|
1943
|
+
_channel.postMessage({type: 'leaving', from: sessionId$1});
|
|
1926
1944
|
_channel.close();
|
|
1927
1945
|
} catch (err) {}
|
|
1928
1946
|
}
|
|
@@ -1943,7 +1961,7 @@
|
|
|
1943
1961
|
ownSnapshotIds.clear();
|
|
1944
1962
|
// Delete the blobs in a single transaction.
|
|
1945
1963
|
try {
|
|
1946
|
-
idb$
|
|
1964
|
+
idb$2.delMany(ids).catch(function() {});
|
|
1947
1965
|
} catch (err) {}
|
|
1948
1966
|
// Drop our entries from the index in a separate (also fire-and-forget)
|
|
1949
1967
|
// transaction. If only one of the two completes, startup cleanup will
|
|
@@ -1952,7 +1970,7 @@
|
|
|
1952
1970
|
try {
|
|
1953
1971
|
updateIndex(function(index) {
|
|
1954
1972
|
index.snapshots = index.snapshots.filter(function(snap) {
|
|
1955
|
-
return getSessionFromSnapshotId(snap.id) !== sessionId;
|
|
1973
|
+
return getSessionFromSnapshotId(snap.id) !== sessionId$1;
|
|
1956
1974
|
});
|
|
1957
1975
|
}).catch(function() {});
|
|
1958
1976
|
} catch (err) {}
|
|
@@ -1960,14 +1978,14 @@
|
|
|
1960
1978
|
|
|
1961
1979
|
function touchOwnSession() {
|
|
1962
1980
|
var data = readSessionData();
|
|
1963
|
-
data[sessionId] = Date.now();
|
|
1981
|
+
data[sessionId$1] = Date.now();
|
|
1964
1982
|
writeSessionData(data);
|
|
1965
1983
|
}
|
|
1966
1984
|
|
|
1967
1985
|
function removeOwnSession() {
|
|
1968
1986
|
var data = readSessionData();
|
|
1969
|
-
if (sessionId in data) {
|
|
1970
|
-
delete data[sessionId];
|
|
1987
|
+
if (sessionId$1 in data) {
|
|
1988
|
+
delete data[sessionId$1];
|
|
1971
1989
|
writeSessionData(data);
|
|
1972
1990
|
}
|
|
1973
1991
|
}
|
|
@@ -2015,7 +2033,116 @@
|
|
|
2015
2033
|
}
|
|
2016
2034
|
}
|
|
2017
2035
|
|
|
2018
|
-
var
|
|
2036
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 30 * 1000;
|
|
2037
|
+
var DEFAULT_STALE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
2038
|
+
|
|
2039
|
+
function createTempSessionLifecycle(opts) {
|
|
2040
|
+
opts = opts || {};
|
|
2041
|
+
var win = opts.window || getWindow$1();
|
|
2042
|
+
var sessionId = opts.sessionId || getUniqueSessionId(opts.prefix || 'tmp');
|
|
2043
|
+
var sessionKey = opts.sessionKey;
|
|
2044
|
+
var heartbeatInterval = opts.heartbeatInterval || DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
2045
|
+
var staleThreshold = opts.staleThreshold || DEFAULT_STALE_THRESHOLD_MS;
|
|
2046
|
+
var heartbeatTimer = null;
|
|
2047
|
+
var started = false;
|
|
2048
|
+
|
|
2049
|
+
return {
|
|
2050
|
+
start: start,
|
|
2051
|
+
stop: stop,
|
|
2052
|
+
touch: touchOwnSession,
|
|
2053
|
+
removeOwnSession: removeOwnSession,
|
|
2054
|
+
getLiveSessions: getLiveSessions,
|
|
2055
|
+
getSessionId: function() { return sessionId; }
|
|
2056
|
+
};
|
|
2057
|
+
|
|
2058
|
+
function start(onPageHide) {
|
|
2059
|
+
if (started) return;
|
|
2060
|
+
started = true;
|
|
2061
|
+
touchOwnSession();
|
|
2062
|
+
if (win && win.setInterval) {
|
|
2063
|
+
heartbeatTimer = win.setInterval(touchOwnSession, heartbeatInterval);
|
|
2064
|
+
} else if (typeof setInterval == 'function') {
|
|
2065
|
+
heartbeatTimer = setInterval(touchOwnSession, heartbeatInterval);
|
|
2066
|
+
}
|
|
2067
|
+
if (win && win.addEventListener) {
|
|
2068
|
+
win.addEventListener('pagehide', function(e) {
|
|
2069
|
+
if (e.persisted) return;
|
|
2070
|
+
stop();
|
|
2071
|
+
if (onPageHide) onPageHide();
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
function stop() {
|
|
2077
|
+
if (heartbeatTimer) {
|
|
2078
|
+
if (win && win.clearInterval) {
|
|
2079
|
+
win.clearInterval(heartbeatTimer);
|
|
2080
|
+
} else if (typeof clearInterval == 'function') {
|
|
2081
|
+
clearInterval(heartbeatTimer);
|
|
2082
|
+
}
|
|
2083
|
+
heartbeatTimer = null;
|
|
2084
|
+
}
|
|
2085
|
+
removeOwnSession();
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
function touchOwnSession() {
|
|
2089
|
+
var sessions = readSessions();
|
|
2090
|
+
sessions[sessionId] = Date.now();
|
|
2091
|
+
writeSessions(sessions);
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
function removeOwnSession() {
|
|
2095
|
+
var sessions = readSessions();
|
|
2096
|
+
if (sessions[sessionId]) {
|
|
2097
|
+
delete sessions[sessionId];
|
|
2098
|
+
writeSessions(sessions);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
function getLiveSessions() {
|
|
2103
|
+
var sessions = readSessions();
|
|
2104
|
+
var live = {};
|
|
2105
|
+
var now = Date.now();
|
|
2106
|
+
live[sessionId] = true;
|
|
2107
|
+
Object.keys(sessions).forEach(function(sid) {
|
|
2108
|
+
if (now - sessions[sid] < staleThreshold) {
|
|
2109
|
+
live[sid] = true;
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
return live;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
function readSessions() {
|
|
2116
|
+
var storage = win && win.localStorage;
|
|
2117
|
+
var raw, parsed;
|
|
2118
|
+
if (!storage || !sessionKey) return {};
|
|
2119
|
+
try {
|
|
2120
|
+
raw = storage.getItem(sessionKey);
|
|
2121
|
+
parsed = raw ? JSON.parse(raw) : null;
|
|
2122
|
+
return parsed && typeof parsed == 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
2123
|
+
} catch(e) {
|
|
2124
|
+
return {};
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
function writeSessions(sessions) {
|
|
2129
|
+
var storage = win && win.localStorage;
|
|
2130
|
+
if (!storage || !sessionKey) return;
|
|
2131
|
+
try {
|
|
2132
|
+
storage.setItem(sessionKey, JSON.stringify(sessions));
|
|
2133
|
+
} catch(e) {}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
function getWindow$1() {
|
|
2138
|
+
return typeof window == 'undefined' ? null : window;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
function getUniqueSessionId(prefix) {
|
|
2142
|
+
return prefix + '_' + (Math.random() + 1).toString(36).substring(2, 8);
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
var idb$1 = require$1('idb-keyval');
|
|
2019
2146
|
var DEFAULT_INDEX_KEY = 'msu_index';
|
|
2020
2147
|
var DEFAULT_SESSION_KEY = 'mapshaper_undo_sessions';
|
|
2021
2148
|
var DEFAULT_KEY_PREFIX = 'msu';
|
|
@@ -2032,6 +2159,13 @@
|
|
|
2032
2159
|
var keyPrefix = opts.keyPrefix || DEFAULT_KEY_PREFIX;
|
|
2033
2160
|
var heartbeatInterval = opts.heartbeatInterval || HEARTBEAT_INTERVAL_MS;
|
|
2034
2161
|
var staleThreshold = opts.staleThreshold || STALE_THRESHOLD_MS;
|
|
2162
|
+
var lifecycle = createTempSessionLifecycle({
|
|
2163
|
+
window: win,
|
|
2164
|
+
sessionId: sessionId,
|
|
2165
|
+
sessionKey: sessionKey,
|
|
2166
|
+
heartbeatInterval: heartbeatInterval,
|
|
2167
|
+
staleThreshold: staleThreshold
|
|
2168
|
+
});
|
|
2035
2169
|
var maxBytes = opts.maxBytes || 0;
|
|
2036
2170
|
var maxPayloadBytes = opts.maxPayloadBytes || 0;
|
|
2037
2171
|
var payloadCount = 0;
|
|
@@ -2039,7 +2173,6 @@
|
|
|
2039
2173
|
var ownPayloadSizes = {};
|
|
2040
2174
|
var ownPayloadItems = {};
|
|
2041
2175
|
var ownBytes = 0;
|
|
2042
|
-
var heartbeatTimer = null;
|
|
2043
2176
|
var lifecycleStarted = false;
|
|
2044
2177
|
|
|
2045
2178
|
return {
|
|
@@ -2069,7 +2202,7 @@
|
|
|
2069
2202
|
validatePayloadSize(size);
|
|
2070
2203
|
await backend.set(key, value);
|
|
2071
2204
|
notePayloadStored(item);
|
|
2072
|
-
|
|
2205
|
+
lifecycle.touch();
|
|
2073
2206
|
try {
|
|
2074
2207
|
await updateIndex(function(index) {
|
|
2075
2208
|
index.payloads.push(item);
|
|
@@ -2110,55 +2243,46 @@
|
|
|
2110
2243
|
keys.forEach(notePayloadRemoved);
|
|
2111
2244
|
await removeIndexKeys(keys);
|
|
2112
2245
|
}
|
|
2113
|
-
removeOwnSession();
|
|
2246
|
+
lifecycle.removeOwnSession();
|
|
2114
2247
|
}
|
|
2115
2248
|
|
|
2116
2249
|
function startLifecycle() {
|
|
2117
2250
|
if (lifecycleStarted) return;
|
|
2118
2251
|
lifecycleStarted = true;
|
|
2119
|
-
|
|
2120
|
-
if (win && win.setInterval) {
|
|
2121
|
-
heartbeatTimer = win.setInterval(touchOwnSession, heartbeatInterval);
|
|
2122
|
-
} else if (typeof setInterval == 'function') {
|
|
2123
|
-
heartbeatTimer = setInterval(touchOwnSession, heartbeatInterval);
|
|
2124
|
-
}
|
|
2125
|
-
if (win && win.addEventListener) {
|
|
2126
|
-
win.addEventListener('pagehide', function(e) {
|
|
2127
|
-
if (e.persisted) return;
|
|
2128
|
-
stopLifecycle();
|
|
2129
|
-
attemptOwnDataDeletion();
|
|
2130
|
-
});
|
|
2131
|
-
}
|
|
2252
|
+
lifecycle.start(attemptOwnDataDeletion);
|
|
2132
2253
|
}
|
|
2133
2254
|
|
|
2134
2255
|
async function cleanupStaleSessions() {
|
|
2135
|
-
var liveSessions = getLiveSessions();
|
|
2256
|
+
var liveSessions = lifecycle.getLiveSessions();
|
|
2136
2257
|
var keys = await backend.keys();
|
|
2258
|
+
var doomedSessions = new Set();
|
|
2259
|
+
var sizeBytes = 0;
|
|
2137
2260
|
var doomedKeys = keys.filter(function(key) {
|
|
2138
|
-
var sid = getSessionFromPayloadKey(key);
|
|
2139
|
-
|
|
2261
|
+
var sid = getSessionFromPayloadKey(key, keyPrefix);
|
|
2262
|
+
var stale = sid && !liveSessions[sid];
|
|
2263
|
+
if (stale) doomedSessions.add(sid);
|
|
2264
|
+
return stale;
|
|
2140
2265
|
});
|
|
2141
2266
|
if (doomedKeys.length > 0) {
|
|
2142
2267
|
await backend.delMany(doomedKeys);
|
|
2143
2268
|
}
|
|
2144
2269
|
await updateIndex(function(index) {
|
|
2270
|
+
var doomedKeyIndex = keyArrayToIndex(doomedKeys);
|
|
2145
2271
|
index.payloads = index.payloads.filter(function(item) {
|
|
2146
|
-
|
|
2272
|
+
if (!liveSessions[item.sessionId]) {
|
|
2273
|
+
if (doomedKeyIndex[item.key] && typeof item.size == 'number') {
|
|
2274
|
+
sizeBytes += item.size;
|
|
2275
|
+
}
|
|
2276
|
+
return false;
|
|
2277
|
+
}
|
|
2278
|
+
return true;
|
|
2147
2279
|
});
|
|
2148
2280
|
});
|
|
2149
|
-
return
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
if (win && win.clearInterval) {
|
|
2155
|
-
win.clearInterval(heartbeatTimer);
|
|
2156
|
-
} else if (typeof clearInterval == 'function') {
|
|
2157
|
-
clearInterval(heartbeatTimer);
|
|
2158
|
-
}
|
|
2159
|
-
heartbeatTimer = null;
|
|
2160
|
-
}
|
|
2161
|
-
removeOwnSession();
|
|
2281
|
+
return {
|
|
2282
|
+
keys: doomedKeys,
|
|
2283
|
+
sessionCount: doomedSessions.size,
|
|
2284
|
+
sizeBytes: sizeBytes
|
|
2285
|
+
};
|
|
2162
2286
|
}
|
|
2163
2287
|
|
|
2164
2288
|
function attemptOwnDataDeletion() {
|
|
@@ -2247,66 +2371,26 @@
|
|
|
2247
2371
|
});
|
|
2248
2372
|
}
|
|
2249
2373
|
|
|
2250
|
-
function touchOwnSession() {
|
|
2251
|
-
var sessions = readSessions();
|
|
2252
|
-
sessions[sessionId] = Date.now();
|
|
2253
|
-
writeSessions(sessions);
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
|
-
function removeOwnSession() {
|
|
2257
|
-
var sessions = readSessions();
|
|
2258
|
-
if (sessions[sessionId]) {
|
|
2259
|
-
delete sessions[sessionId];
|
|
2260
|
-
writeSessions(sessions);
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
|
|
2264
|
-
function getLiveSessions() {
|
|
2265
|
-
var sessions = readSessions();
|
|
2266
|
-
var live = {};
|
|
2267
|
-
var now = Date.now();
|
|
2268
|
-
live[sessionId] = true;
|
|
2269
|
-
Object.keys(sessions).forEach(function(sid) {
|
|
2270
|
-
if (now - sessions[sid] < staleThreshold) {
|
|
2271
|
-
live[sid] = true;
|
|
2272
|
-
}
|
|
2273
|
-
});
|
|
2274
|
-
return live;
|
|
2275
|
-
}
|
|
2276
|
-
|
|
2277
|
-
function readSessions() {
|
|
2278
|
-
var storage = win && win.localStorage;
|
|
2279
|
-
var raw, parsed;
|
|
2280
|
-
if (!storage) return {};
|
|
2281
|
-
try {
|
|
2282
|
-
raw = storage.getItem(sessionKey);
|
|
2283
|
-
parsed = raw ? JSON.parse(raw) : null;
|
|
2284
|
-
return parsed && typeof parsed == 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
2285
|
-
} catch(e) {
|
|
2286
|
-
return {};
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
function writeSessions(sessions) {
|
|
2291
|
-
var storage = win && win.localStorage;
|
|
2292
|
-
if (!storage) return;
|
|
2293
|
-
try {
|
|
2294
|
-
storage.setItem(sessionKey, JSON.stringify(sessions));
|
|
2295
|
-
} catch(e) {}
|
|
2296
|
-
}
|
|
2297
2374
|
}
|
|
2298
2375
|
|
|
2299
2376
|
function getPayloadKey(ref) {
|
|
2300
2377
|
return ref && (ref.key || ref);
|
|
2301
2378
|
}
|
|
2302
2379
|
|
|
2380
|
+
function keyArrayToIndex(keys) {
|
|
2381
|
+
return keys.reduce(function(memo, key) {
|
|
2382
|
+
memo[key] = true;
|
|
2383
|
+
return memo;
|
|
2384
|
+
}, {});
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2303
2387
|
function copyPayloadItem(item) {
|
|
2304
2388
|
return Object.assign({}, item || {});
|
|
2305
2389
|
}
|
|
2306
2390
|
|
|
2307
|
-
function getSessionFromPayloadKey(key) {
|
|
2308
|
-
var
|
|
2309
|
-
return
|
|
2391
|
+
function getSessionFromPayloadKey(key, keyPrefix) {
|
|
2392
|
+
var parts = String(key).split(':');
|
|
2393
|
+
return parts.length == 3 && parts[0] == keyPrefix ? parts[1] : null;
|
|
2310
2394
|
}
|
|
2311
2395
|
|
|
2312
2396
|
function getUniqId(prefix) {
|
|
@@ -2318,7 +2402,7 @@
|
|
|
2318
2402
|
}
|
|
2319
2403
|
|
|
2320
2404
|
function createBackend(win) {
|
|
2321
|
-
if (win && win.indexedDB && idb) {
|
|
2405
|
+
if (win && win.indexedDB && idb$1) {
|
|
2322
2406
|
return createIdbBackend();
|
|
2323
2407
|
}
|
|
2324
2408
|
return createMemoryBackend();
|
|
@@ -2327,13 +2411,13 @@
|
|
|
2327
2411
|
function createIdbBackend() {
|
|
2328
2412
|
return {
|
|
2329
2413
|
persistent: true,
|
|
2330
|
-
get: idb.get,
|
|
2331
|
-
set: idb.set,
|
|
2332
|
-
del: idb.del,
|
|
2333
|
-
keys: idb.keys,
|
|
2414
|
+
get: idb$1.get,
|
|
2415
|
+
set: idb$1.set,
|
|
2416
|
+
del: idb$1.del,
|
|
2417
|
+
keys: idb$1.keys,
|
|
2334
2418
|
delMany: function(keys) {
|
|
2335
|
-
return idb.delMany ? idb.delMany(keys) :
|
|
2336
|
-
Promise.all(keys.map(function(key) { return idb.del(key); }));
|
|
2419
|
+
return idb$1.delMany ? idb$1.delMany(keys) :
|
|
2420
|
+
Promise.all(keys.map(function(key) { return idb$1.del(key); }));
|
|
2337
2421
|
}
|
|
2338
2422
|
};
|
|
2339
2423
|
}
|
|
@@ -4237,76 +4321,384 @@
|
|
|
4237
4321
|
return maxCount;
|
|
4238
4322
|
}
|
|
4239
4323
|
|
|
4240
|
-
|
|
4241
|
-
|
|
4324
|
+
function Transform() {
|
|
4325
|
+
this.mx = this.my = 1;
|
|
4326
|
+
this.bx = this.by = 0;
|
|
4327
|
+
}
|
|
4242
4328
|
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4329
|
+
Transform.prototype.isNull = function() {
|
|
4330
|
+
return !this.mx || !this.my || isNaN(this.bx) || isNaN(this.by);
|
|
4331
|
+
};
|
|
4246
4332
|
|
|
4247
|
-
function
|
|
4248
|
-
|
|
4249
|
-
|
|
4333
|
+
Transform.prototype.invert = function() {
|
|
4334
|
+
var inv = new Transform();
|
|
4335
|
+
inv.mx = 1 / this.mx;
|
|
4336
|
+
inv.my = 1 / this.my;
|
|
4337
|
+
//inv.bx = -this.bx * inv.mx;
|
|
4338
|
+
//inv.by = -this.by * inv.my;
|
|
4339
|
+
inv.bx = -this.bx / this.mx;
|
|
4340
|
+
inv.by = -this.by / this.my;
|
|
4341
|
+
return inv;
|
|
4342
|
+
};
|
|
4250
4343
|
|
|
4251
|
-
function setActiveUndoTransaction(tx) {
|
|
4252
|
-
activeTransaction = tx || null;
|
|
4253
|
-
}
|
|
4254
4344
|
|
|
4255
|
-
function
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4345
|
+
Transform.prototype.transform = function(x, y, xy) {
|
|
4346
|
+
xy = xy || [];
|
|
4347
|
+
xy[0] = x * this.mx + this.bx;
|
|
4348
|
+
xy[1] = y * this.my + this.by;
|
|
4349
|
+
return xy;
|
|
4350
|
+
};
|
|
4260
4351
|
|
|
4261
|
-
function
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
try {
|
|
4265
|
-
return cb();
|
|
4266
|
-
} finally {
|
|
4267
|
-
activeTransaction = prev;
|
|
4268
|
-
}
|
|
4269
|
-
}
|
|
4352
|
+
Transform.prototype.toString = function() {
|
|
4353
|
+
return JSON.stringify(Object.assign({}, this));
|
|
4354
|
+
};
|
|
4270
4355
|
|
|
4271
|
-
function
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
meta = {
|
|
4275
|
-
id: 'u' + nextUndoId++,
|
|
4276
|
-
revision: 0
|
|
4277
|
-
};
|
|
4278
|
-
objectMetadata.set(obj, meta);
|
|
4356
|
+
function Bounds() {
|
|
4357
|
+
if (arguments.length > 0) {
|
|
4358
|
+
this.setBounds.apply(this, arguments);
|
|
4279
4359
|
}
|
|
4280
|
-
return meta;
|
|
4281
4360
|
}
|
|
4282
4361
|
|
|
4283
|
-
function
|
|
4284
|
-
|
|
4285
|
-
|
|
4362
|
+
Bounds.from = function() {
|
|
4363
|
+
var b = new Bounds();
|
|
4364
|
+
return b.setBounds.apply(b, arguments);
|
|
4365
|
+
};
|
|
4286
4366
|
|
|
4287
|
-
function
|
|
4288
|
-
return
|
|
4289
|
-
|
|
4367
|
+
Bounds.prototype.toString = function() {
|
|
4368
|
+
return JSON.stringify({
|
|
4369
|
+
xmin: this.xmin,
|
|
4370
|
+
xmax: this.xmax,
|
|
4371
|
+
ymin: this.ymin,
|
|
4372
|
+
ymax: this.ymax
|
|
4373
|
+
});
|
|
4374
|
+
};
|
|
4290
4375
|
|
|
4291
|
-
function
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
if (!tx && !meta) return 0;
|
|
4295
|
-
if (!meta) meta = getUndoMetadata(obj);
|
|
4296
|
-
meta.revision++;
|
|
4297
|
-
notifyTransaction(tx, 'markChanged', obj, detail);
|
|
4298
|
-
return meta.revision;
|
|
4299
|
-
}
|
|
4376
|
+
Bounds.prototype.toArray = function() {
|
|
4377
|
+
return this.hasBounds() ? [this.xmin, this.ymin, this.xmax, this.ymax] : [];
|
|
4378
|
+
};
|
|
4300
4379
|
|
|
4301
|
-
function
|
|
4302
|
-
|
|
4303
|
-
}
|
|
4380
|
+
Bounds.prototype.hasBounds = function() {
|
|
4381
|
+
return this.xmin <= this.xmax && this.ymin <= this.ymax;
|
|
4382
|
+
};
|
|
4304
4383
|
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
}
|
|
4384
|
+
Bounds.prototype.sameBounds =
|
|
4385
|
+
Bounds.prototype.equals = function(bb) {
|
|
4386
|
+
return bb && this.xmin === bb.xmin && this.xmax === bb.xmax &&
|
|
4387
|
+
this.ymin === bb.ymin && this.ymax === bb.ymax;
|
|
4388
|
+
};
|
|
4389
|
+
|
|
4390
|
+
Bounds.prototype.width = function() {
|
|
4391
|
+
return (this.xmax - this.xmin) || 0;
|
|
4392
|
+
};
|
|
4393
|
+
|
|
4394
|
+
Bounds.prototype.height = function() {
|
|
4395
|
+
return (this.ymax - this.ymin) || 0;
|
|
4396
|
+
};
|
|
4397
|
+
|
|
4398
|
+
Bounds.prototype.area = function() {
|
|
4399
|
+
return this.width() * this.height() || 0;
|
|
4400
|
+
};
|
|
4401
|
+
|
|
4402
|
+
Bounds.prototype.empty = function() {
|
|
4403
|
+
this.xmin = this.ymin = this.xmax = this.ymax = void 0;
|
|
4404
|
+
return this;
|
|
4405
|
+
};
|
|
4406
|
+
|
|
4407
|
+
Bounds.prototype.setBounds = function(a, b, c, d) {
|
|
4408
|
+
if (arguments.length == 1) {
|
|
4409
|
+
// assume first arg is a Bounds or array
|
|
4410
|
+
if (utils.isArrayLike(a)) {
|
|
4411
|
+
b = a[1];
|
|
4412
|
+
c = a[2];
|
|
4413
|
+
d = a[3];
|
|
4414
|
+
a = a[0];
|
|
4415
|
+
} else {
|
|
4416
|
+
b = a.ymin;
|
|
4417
|
+
c = a.xmax;
|
|
4418
|
+
d = a.ymax;
|
|
4419
|
+
a = a.xmin;
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4423
|
+
this.xmin = a;
|
|
4424
|
+
this.ymin = b;
|
|
4425
|
+
this.xmax = c;
|
|
4426
|
+
this.ymax = d;
|
|
4427
|
+
if (a > c || b > d) this.update();
|
|
4428
|
+
return this;
|
|
4429
|
+
};
|
|
4430
|
+
|
|
4431
|
+
|
|
4432
|
+
Bounds.prototype.centerX = function() {
|
|
4433
|
+
var x = (this.xmin + this.xmax) * 0.5;
|
|
4434
|
+
return x;
|
|
4435
|
+
};
|
|
4436
|
+
|
|
4437
|
+
Bounds.prototype.centerY = function() {
|
|
4438
|
+
var y = (this.ymax + this.ymin) * 0.5;
|
|
4439
|
+
return y;
|
|
4440
|
+
};
|
|
4441
|
+
|
|
4442
|
+
Bounds.prototype.containsPoint = function(x, y) {
|
|
4443
|
+
if (x >= this.xmin && x <= this.xmax &&
|
|
4444
|
+
y <= this.ymax && y >= this.ymin) {
|
|
4445
|
+
return true;
|
|
4446
|
+
}
|
|
4447
|
+
return false;
|
|
4448
|
+
};
|
|
4449
|
+
|
|
4450
|
+
// intended to speed up slightly bubble symbol detection; could use intersects() instead
|
|
4451
|
+
// TODO: fix false positive where circle is just outside a corner of the box
|
|
4452
|
+
Bounds.prototype.containsBufferedPoint =
|
|
4453
|
+
Bounds.prototype.containsCircle = function(x, y, buf) {
|
|
4454
|
+
if ( x + buf > this.xmin && x - buf < this.xmax ) {
|
|
4455
|
+
if ( y - buf < this.ymax && y + buf > this.ymin ) {
|
|
4456
|
+
return true;
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
return false;
|
|
4460
|
+
};
|
|
4461
|
+
|
|
4462
|
+
Bounds.prototype.intersects = function(bb) {
|
|
4463
|
+
if (bb.xmin <= this.xmax && bb.xmax >= this.xmin &&
|
|
4464
|
+
bb.ymax >= this.ymin && bb.ymin <= this.ymax) {
|
|
4465
|
+
return true;
|
|
4466
|
+
}
|
|
4467
|
+
return false;
|
|
4468
|
+
};
|
|
4469
|
+
|
|
4470
|
+
Bounds.prototype.contains = function(bb) {
|
|
4471
|
+
if (bb.xmin >= this.xmin && bb.ymax <= this.ymax &&
|
|
4472
|
+
bb.xmax <= this.xmax && bb.ymin >= this.ymin) {
|
|
4473
|
+
return true;
|
|
4474
|
+
}
|
|
4475
|
+
return false;
|
|
4476
|
+
};
|
|
4477
|
+
|
|
4478
|
+
Bounds.prototype.shift = function(x, y) {
|
|
4479
|
+
this.setBounds(this.xmin + x,
|
|
4480
|
+
this.ymin + y, this.xmax + x, this.ymax + y);
|
|
4481
|
+
};
|
|
4482
|
+
|
|
4483
|
+
Bounds.prototype.padBounds = function(a, b, c, d) {
|
|
4484
|
+
this.xmin -= a;
|
|
4485
|
+
this.ymin -= b;
|
|
4486
|
+
this.xmax += c;
|
|
4487
|
+
this.ymax += d;
|
|
4488
|
+
};
|
|
4489
|
+
|
|
4490
|
+
// Rescale the bounding box by a fraction. TODO: implement focus.
|
|
4491
|
+
// @param {number} pct Fraction of original extents
|
|
4492
|
+
// @param {number} pctY Optional amount to scale Y
|
|
4493
|
+
//
|
|
4494
|
+
Bounds.prototype.scale = function(pct, pctY) { /*, focusX, focusY*/
|
|
4495
|
+
var halfWidth = (this.xmax - this.xmin) * 0.5;
|
|
4496
|
+
var halfHeight = (this.ymax - this.ymin) * 0.5;
|
|
4497
|
+
var kx = pct - 1;
|
|
4498
|
+
var ky = pctY === undefined ? kx : pctY - 1;
|
|
4499
|
+
this.xmin -= halfWidth * kx;
|
|
4500
|
+
this.ymin -= halfHeight * ky;
|
|
4501
|
+
this.xmax += halfWidth * kx;
|
|
4502
|
+
this.ymax += halfHeight * ky;
|
|
4503
|
+
return this;
|
|
4504
|
+
};
|
|
4505
|
+
|
|
4506
|
+
// Return a bounding box with the same extent as this one.
|
|
4507
|
+
Bounds.prototype.cloneBounds = // alias so child classes can override clone()
|
|
4508
|
+
Bounds.prototype.clone = function() {
|
|
4509
|
+
return new Bounds(this.xmin, this.ymin, this.xmax, this.ymax);
|
|
4510
|
+
};
|
|
4511
|
+
|
|
4512
|
+
Bounds.prototype.clearBounds = function() {
|
|
4513
|
+
this.setBounds(new Bounds());
|
|
4514
|
+
};
|
|
4515
|
+
|
|
4516
|
+
Bounds.prototype.mergePoint = function(x, y) {
|
|
4517
|
+
if (this.xmin === void 0) {
|
|
4518
|
+
this.setBounds(x, y, x, y);
|
|
4519
|
+
} else {
|
|
4520
|
+
// this works even if x,y are NaN
|
|
4521
|
+
if (x < this.xmin) this.xmin = x;
|
|
4522
|
+
else if (x > this.xmax) this.xmax = x;
|
|
4523
|
+
|
|
4524
|
+
if (y < this.ymin) this.ymin = y;
|
|
4525
|
+
else if (y > this.ymax) this.ymax = y;
|
|
4526
|
+
}
|
|
4527
|
+
};
|
|
4528
|
+
|
|
4529
|
+
// expands either x or y dimension to match @aspect (width/height ratio)
|
|
4530
|
+
// @focusX, @focusY (optional): expansion focus, as a fraction of width and height
|
|
4531
|
+
Bounds.prototype.fillOut = function(aspect, focusX, focusY) {
|
|
4532
|
+
if (arguments.length < 3) {
|
|
4533
|
+
focusX = 0.5;
|
|
4534
|
+
focusY = 0.5;
|
|
4535
|
+
}
|
|
4536
|
+
var w = this.width(),
|
|
4537
|
+
h = this.height(),
|
|
4538
|
+
currAspect = w / h,
|
|
4539
|
+
pad;
|
|
4540
|
+
if (isNaN(aspect) || aspect <= 0) {
|
|
4541
|
+
// error condition; don't pad
|
|
4542
|
+
} else if (currAspect < aspect) { // fill out x dimension
|
|
4543
|
+
pad = h * aspect - w;
|
|
4544
|
+
this.xmin -= (1 - focusX) * pad;
|
|
4545
|
+
this.xmax += focusX * pad;
|
|
4546
|
+
} else {
|
|
4547
|
+
pad = w / aspect - h;
|
|
4548
|
+
this.ymin -= (1 - focusY) * pad;
|
|
4549
|
+
this.ymax += focusY * pad;
|
|
4550
|
+
}
|
|
4551
|
+
return this;
|
|
4552
|
+
};
|
|
4553
|
+
|
|
4554
|
+
Bounds.prototype.update = function() {
|
|
4555
|
+
var tmp;
|
|
4556
|
+
if (this.xmin > this.xmax) {
|
|
4557
|
+
tmp = this.xmin;
|
|
4558
|
+
this.xmin = this.xmax;
|
|
4559
|
+
this.xmax = tmp;
|
|
4560
|
+
}
|
|
4561
|
+
if (this.ymin > this.ymax) {
|
|
4562
|
+
tmp = this.ymin;
|
|
4563
|
+
this.ymin = this.ymax;
|
|
4564
|
+
this.ymax = tmp;
|
|
4565
|
+
}
|
|
4566
|
+
};
|
|
4567
|
+
|
|
4568
|
+
Bounds.prototype.transform = function(t) {
|
|
4569
|
+
this.xmin = this.xmin * t.mx + t.bx;
|
|
4570
|
+
this.xmax = this.xmax * t.mx + t.bx;
|
|
4571
|
+
this.ymin = this.ymin * t.my + t.by;
|
|
4572
|
+
this.ymax = this.ymax * t.my + t.by;
|
|
4573
|
+
this.update();
|
|
4574
|
+
return this;
|
|
4575
|
+
};
|
|
4576
|
+
|
|
4577
|
+
// Returns a Transform object for mapping this onto Bounds @b2
|
|
4578
|
+
// @flipY (optional) Flip y-axis coords, for converting to/from pixel coords
|
|
4579
|
+
//
|
|
4580
|
+
Bounds.prototype.getTransform = function(b2, flipY) {
|
|
4581
|
+
var t = new Transform();
|
|
4582
|
+
t.mx = b2.width() / this.width() || 1; // TODO: better handling of 0 w,h
|
|
4583
|
+
t.bx = b2.xmin - t.mx * this.xmin;
|
|
4584
|
+
if (flipY) {
|
|
4585
|
+
t.my = -b2.height() / this.height() || 1;
|
|
4586
|
+
t.by = b2.ymax - t.my * this.ymin;
|
|
4587
|
+
} else {
|
|
4588
|
+
t.my = b2.height() / this.height() || 1;
|
|
4589
|
+
t.by = b2.ymin - t.my * this.ymin;
|
|
4590
|
+
}
|
|
4591
|
+
return t;
|
|
4592
|
+
};
|
|
4593
|
+
|
|
4594
|
+
Bounds.prototype.mergeCircle = function(x, y, r) {
|
|
4595
|
+
if (r < 0) r = -r;
|
|
4596
|
+
this.mergeBounds([x - r, y - r, x + r, y + r]);
|
|
4597
|
+
};
|
|
4598
|
+
|
|
4599
|
+
Bounds.prototype.mergeBounds = function(bb) {
|
|
4600
|
+
var a, b, c, d;
|
|
4601
|
+
if (bb instanceof Bounds) {
|
|
4602
|
+
a = bb.xmin;
|
|
4603
|
+
b = bb.ymin;
|
|
4604
|
+
c = bb.xmax;
|
|
4605
|
+
d = bb.ymax;
|
|
4606
|
+
} else if (arguments.length == 4) {
|
|
4607
|
+
a = arguments[0];
|
|
4608
|
+
b = arguments[1];
|
|
4609
|
+
c = arguments[2];
|
|
4610
|
+
d = arguments[3];
|
|
4611
|
+
} else if (bb.length == 4) {
|
|
4612
|
+
// assume array: [xmin, ymin, xmax, ymax]
|
|
4613
|
+
a = bb[0];
|
|
4614
|
+
b = bb[1];
|
|
4615
|
+
c = bb[2];
|
|
4616
|
+
d = bb[3];
|
|
4617
|
+
} else {
|
|
4618
|
+
error("Bounds#mergeBounds() invalid argument:", bb);
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
if (this.xmin === void 0) {
|
|
4622
|
+
this.setBounds(a, b, c, d);
|
|
4623
|
+
} else {
|
|
4624
|
+
if (a < this.xmin) this.xmin = a;
|
|
4625
|
+
if (b < this.ymin) this.ymin = b;
|
|
4626
|
+
if (c > this.xmax) this.xmax = c;
|
|
4627
|
+
if (d > this.ymax) this.ymax = d;
|
|
4628
|
+
}
|
|
4629
|
+
return this;
|
|
4630
|
+
};
|
|
4631
|
+
|
|
4632
|
+
// Lightweight hooks that make in-place mutations observable to GUI undo.
|
|
4633
|
+
// Normal CLI runs leave activeTransaction unset, so capture hooks are no-ops.
|
|
4634
|
+
|
|
4635
|
+
var activeTransaction = null;
|
|
4636
|
+
var nextUndoId = 1;
|
|
4637
|
+
var objectMetadata = new WeakMap();
|
|
4638
|
+
|
|
4639
|
+
function getActiveUndoTransaction() {
|
|
4640
|
+
return activeTransaction;
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
function setActiveUndoTransaction(tx) {
|
|
4644
|
+
activeTransaction = tx || null;
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4647
|
+
function clearActiveUndoTransaction(tx) {
|
|
4648
|
+
if (!tx || activeTransaction == tx) {
|
|
4649
|
+
activeTransaction = null;
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
|
|
4653
|
+
function withActiveUndoTransaction(tx, cb) {
|
|
4654
|
+
var prev = activeTransaction;
|
|
4655
|
+
activeTransaction = tx || null;
|
|
4656
|
+
try {
|
|
4657
|
+
return cb();
|
|
4658
|
+
} finally {
|
|
4659
|
+
activeTransaction = prev;
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4662
|
+
|
|
4663
|
+
function getUndoMetadata(obj) {
|
|
4664
|
+
var meta = objectMetadata.get(obj);
|
|
4665
|
+
if (!meta) {
|
|
4666
|
+
meta = {
|
|
4667
|
+
id: 'u' + nextUndoId++,
|
|
4668
|
+
revision: 0
|
|
4669
|
+
};
|
|
4670
|
+
objectMetadata.set(obj, meta);
|
|
4671
|
+
}
|
|
4672
|
+
return meta;
|
|
4673
|
+
}
|
|
4674
|
+
|
|
4675
|
+
function getUndoId(obj) {
|
|
4676
|
+
return getUndoMetadata(obj).id;
|
|
4677
|
+
}
|
|
4678
|
+
|
|
4679
|
+
function getUndoRevision(obj) {
|
|
4680
|
+
return getUndoMetadata(obj).revision;
|
|
4681
|
+
}
|
|
4682
|
+
|
|
4683
|
+
function markChanged(obj, detail) {
|
|
4684
|
+
var tx = activeTransaction;
|
|
4685
|
+
var meta = objectMetadata.get(obj);
|
|
4686
|
+
if (!tx && !meta) return 0;
|
|
4687
|
+
if (!meta) meta = getUndoMetadata(obj);
|
|
4688
|
+
meta.revision++;
|
|
4689
|
+
notifyTransaction(tx, 'markChanged', obj, detail);
|
|
4690
|
+
return meta.revision;
|
|
4691
|
+
}
|
|
4692
|
+
|
|
4693
|
+
function noteTableWillChange(table, detail) {
|
|
4694
|
+
notifyTransaction(activeTransaction, 'captureTableBefore', table, detail || {});
|
|
4695
|
+
}
|
|
4696
|
+
|
|
4697
|
+
function noteTableRecordsWillChange(table, ids, detail) {
|
|
4698
|
+
notifyTransaction(activeTransaction, 'captureTableRecordsBefore', table, Object.assign({
|
|
4699
|
+
ids: normalizeIds(ids)
|
|
4700
|
+
}, detail || {}));
|
|
4701
|
+
}
|
|
4310
4702
|
|
|
4311
4703
|
function noteTableFieldsWillChange(table, fields, detail) {
|
|
4312
4704
|
notifyTransaction(activeTransaction, 'captureTableFieldsBefore', table, Object.assign({
|
|
@@ -4440,6 +4832,597 @@
|
|
|
4440
4832
|
return Array.isArray(arr) ? arr.slice() : [arr];
|
|
4441
4833
|
}
|
|
4442
4834
|
|
|
4835
|
+
var DEFAULT_MAX_PREVIEW_PIXELS = 4e6;
|
|
4836
|
+
|
|
4837
|
+
function getRasterGrid(raster) {
|
|
4838
|
+
return raster && (raster.grid || raster);
|
|
4839
|
+
}
|
|
4840
|
+
|
|
4841
|
+
function getRasterView(raster) {
|
|
4842
|
+
return raster && (raster.view || raster);
|
|
4843
|
+
}
|
|
4844
|
+
|
|
4845
|
+
function getRasterPreview(raster) {
|
|
4846
|
+
var view = getRasterView(raster);
|
|
4847
|
+
return view && (view.preview || raster.preview);
|
|
4848
|
+
}
|
|
4849
|
+
|
|
4850
|
+
function getRasterBBox(raster) {
|
|
4851
|
+
var grid = getRasterGrid(raster);
|
|
4852
|
+
return grid && grid.bbox || raster && raster.bbox || null;
|
|
4853
|
+
}
|
|
4854
|
+
|
|
4855
|
+
function getRasterTransform(raster) {
|
|
4856
|
+
var grid = getRasterGrid(raster);
|
|
4857
|
+
return grid && grid.transform || raster && raster.transform || null;
|
|
4858
|
+
}
|
|
4859
|
+
|
|
4860
|
+
function getRasterWidth(raster) {
|
|
4861
|
+
var grid = getRasterGrid(raster);
|
|
4862
|
+
return grid && grid.width || 0;
|
|
4863
|
+
}
|
|
4864
|
+
|
|
4865
|
+
function getRasterHeight(raster) {
|
|
4866
|
+
var grid = getRasterGrid(raster);
|
|
4867
|
+
return grid && grid.height || 0;
|
|
4868
|
+
}
|
|
4869
|
+
|
|
4870
|
+
function getRasterBandCount(raster) {
|
|
4871
|
+
var grid = getRasterGrid(raster);
|
|
4872
|
+
return grid && grid.bands || 0;
|
|
4873
|
+
}
|
|
4874
|
+
|
|
4875
|
+
function getRasterPixelType(raster) {
|
|
4876
|
+
var grid = getRasterGrid(raster);
|
|
4877
|
+
return grid && grid.pixelType || null;
|
|
4878
|
+
}
|
|
4879
|
+
|
|
4880
|
+
function copyRasterData(raster) {
|
|
4881
|
+
var copy = utils.extend({}, raster);
|
|
4882
|
+
if (raster.grid) {
|
|
4883
|
+
copy.grid = copyRasterGrid(raster.grid);
|
|
4884
|
+
}
|
|
4885
|
+
if (raster.view) {
|
|
4886
|
+
copy.view = copyRasterView(raster.view);
|
|
4887
|
+
}
|
|
4888
|
+
if (raster.derivation) {
|
|
4889
|
+
copy.derivation = utils.extend({}, raster.derivation);
|
|
4890
|
+
if (raster.derivation.bands) copy.derivation.bands = copyObjectOrArray(raster.derivation.bands);
|
|
4891
|
+
}
|
|
4892
|
+
if (raster.source) copy.source = utils.extend({}, raster.source);
|
|
4893
|
+
|
|
4894
|
+
// Back-compat for the first raster model.
|
|
4895
|
+
if (raster.pixels) copy.pixels = copyTypedArray(raster.pixels);
|
|
4896
|
+
if (raster.preview) copy.preview = copyRasterPreview(raster.preview);
|
|
4897
|
+
if (raster.bbox) copy.bbox = raster.bbox.concat();
|
|
4898
|
+
if (raster.transform) copy.transform = copyObjectOrArray(raster.transform);
|
|
4899
|
+
return copy;
|
|
4900
|
+
}
|
|
4901
|
+
|
|
4902
|
+
function copyRasterGrid(grid) {
|
|
4903
|
+
var copy = utils.extend({}, grid);
|
|
4904
|
+
if (grid.samples) copy.samples = copyTypedArray(grid.samples);
|
|
4905
|
+
if (grid.sampleBands) copy.sampleBands = grid.sampleBands.concat();
|
|
4906
|
+
if (grid.bbox) copy.bbox = grid.bbox.concat();
|
|
4907
|
+
if (grid.transform) copy.transform = copyObjectOrArray(grid.transform);
|
|
4908
|
+
return copy;
|
|
4909
|
+
}
|
|
4910
|
+
|
|
4911
|
+
function copyRasterView(view) {
|
|
4912
|
+
var copy = utils.extend({}, view);
|
|
4913
|
+
if (view.recipe) copy.recipe = copyObjectOrArray(view.recipe);
|
|
4914
|
+
if (view.preview) copy.preview = copyRasterPreview(view.preview);
|
|
4915
|
+
if (view.scalingStats) copy.scalingStats = copyObjectOrArray(view.scalingStats);
|
|
4916
|
+
return copy;
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
function copyRasterPreview(preview) {
|
|
4920
|
+
var copy = utils.extend({}, preview);
|
|
4921
|
+
delete copy.canvas;
|
|
4922
|
+
if (preview.pixels) copy.pixels = copyTypedArray(preview.pixels);
|
|
4923
|
+
return copy;
|
|
4924
|
+
}
|
|
4925
|
+
|
|
4926
|
+
function copyTypedArray(arr) {
|
|
4927
|
+
if (Array.isArray(arr)) return arr.map(copyTypedArray);
|
|
4928
|
+
return arr && arr.slice ? arr.slice() : arr;
|
|
4929
|
+
}
|
|
4930
|
+
|
|
4931
|
+
function createRasterPreview(raster, opts) {
|
|
4932
|
+
opts = opts || {};
|
|
4933
|
+
var grid = getRasterGrid(raster);
|
|
4934
|
+
var recipe = getRasterViewRecipe(grid, raster.view && raster.view.recipe, opts);
|
|
4935
|
+
var stats = getRasterViewScalingStats(raster, recipe);
|
|
4936
|
+
var maxPixels = opts.maxPixels || opts.raster_max_pixels || opts.rasterMaxPixels || DEFAULT_MAX_PREVIEW_PIXELS;
|
|
4937
|
+
var scale = Math.min(1, Math.sqrt(maxPixels / (grid.width * grid.height)));
|
|
4938
|
+
var width = Math.max(1, Math.round(grid.width * scale));
|
|
4939
|
+
var height = Math.max(1, Math.round(grid.height * scale));
|
|
4940
|
+
return renderRasterPreview(grid, recipe, width, height, stats);
|
|
4941
|
+
}
|
|
4942
|
+
|
|
4943
|
+
function getRasterViewRecipe(grid, recipeArg, opts) {
|
|
4944
|
+
var recipe = Object.assign({}, recipeArg || {});
|
|
4945
|
+
var scaling = opts && opts.scaling || recipe.scaling || getDefaultRasterScaling(grid, recipe);
|
|
4946
|
+
var scaleRange = opts && (opts.scale_range || opts.scaleRange) || recipe.scaleRange;
|
|
4947
|
+
var percentileRange = opts && (opts.percentile_range || opts.percentileRange) || recipe.percentileRange;
|
|
4948
|
+
if (scaling != 'none' && scaling != 'minmax' && scaling != 'percentile') {
|
|
4949
|
+
stop('Unsupported raster scaling method:', scaling);
|
|
4950
|
+
}
|
|
4951
|
+
return Object.assign(recipe, {
|
|
4952
|
+
type: recipe.type || (grid.bands >= 3 ? 'rgb' : 'gray'),
|
|
4953
|
+
bands: recipe.bands || grid.sampleBands || null,
|
|
4954
|
+
scaling: scaling,
|
|
4955
|
+
scaleRange: parseRangeOption(scaleRange, [0, 100], 'scale-range'),
|
|
4956
|
+
percentileRange: parseRangeOption(percentileRange, [2, 98], 'percentile-range')
|
|
4957
|
+
});
|
|
4958
|
+
}
|
|
4959
|
+
|
|
4960
|
+
function renderRasterPreview(grid, recipe, width, height, statsArg) {
|
|
4961
|
+
recipe = getRasterViewRecipe(grid, recipe);
|
|
4962
|
+
if (useFastRawEightBitRendering(grid, recipe)) {
|
|
4963
|
+
return renderRawEightBitPreview(grid, width, height, null);
|
|
4964
|
+
}
|
|
4965
|
+
return renderRasterGridPreview(grid, recipe, width, height, statsArg || getRasterScalingStats(grid, recipe), null);
|
|
4966
|
+
}
|
|
4967
|
+
|
|
4968
|
+
function renderRasterViewportPreview(grid, recipe, bbox, width, height, statsArg) {
|
|
4969
|
+
recipe = getRasterViewRecipe(grid, recipe);
|
|
4970
|
+
if (!intersectBboxes(grid.bbox, bbox)) return null;
|
|
4971
|
+
if (useFastRawEightBitRendering(grid, recipe)) {
|
|
4972
|
+
return Object.assign(renderRawEightBitPreview(grid, width, height, bbox), {
|
|
4973
|
+
bbox: bbox.concat()
|
|
4974
|
+
});
|
|
4975
|
+
}
|
|
4976
|
+
return Object.assign(renderRasterGridPreview(grid, recipe, width, height, statsArg || getRasterScalingStats(grid, recipe), bbox), {
|
|
4977
|
+
bbox: bbox.concat()
|
|
4978
|
+
});
|
|
4979
|
+
}
|
|
4980
|
+
|
|
4981
|
+
function getRasterScalingStats(grid, recipe) {
|
|
4982
|
+
recipe = getRasterViewRecipe(grid, recipe);
|
|
4983
|
+
return getScalingStats(grid.samples, grid.bands, grid.nodata, recipe);
|
|
4984
|
+
}
|
|
4985
|
+
|
|
4986
|
+
function getRasterViewScalingStats(raster, recipeArg) {
|
|
4987
|
+
var grid = getRasterGrid(raster);
|
|
4988
|
+
var view = getRasterView(raster);
|
|
4989
|
+
var recipe = getRasterViewRecipe(grid, recipeArg || view && view.recipe);
|
|
4990
|
+
var key, cached, stats;
|
|
4991
|
+
if (recipe.scaling == 'none') return null;
|
|
4992
|
+
key = getRasterScalingStatsKey(grid, recipe);
|
|
4993
|
+
cached = view && view.scalingStats;
|
|
4994
|
+
if (cached && cached.key == key) return cached.stats;
|
|
4995
|
+
stats = getRasterScalingStats(grid, recipe);
|
|
4996
|
+
if (view) {
|
|
4997
|
+
view.scalingStats = {
|
|
4998
|
+
key: key,
|
|
4999
|
+
stats: stats
|
|
5000
|
+
};
|
|
5001
|
+
}
|
|
5002
|
+
return stats;
|
|
5003
|
+
}
|
|
5004
|
+
|
|
5005
|
+
function renderRasterGridPreview(grid, recipe, width, height, statsArg, sourceBbox) {
|
|
5006
|
+
var samples = grid.samples;
|
|
5007
|
+
var bands = grid.bands;
|
|
5008
|
+
var noData = grid.nodata;
|
|
5009
|
+
var pixels = new Uint8ClampedArray(width * height * 4);
|
|
5010
|
+
var stats = statsArg || getScalingStats(samples, bands, noData, recipe);
|
|
5011
|
+
var sourceRange = recipe.scaling == 'none' ? getPixelTypeRange(grid.pixelType) : null;
|
|
5012
|
+
var displayRange = getDisplayRange(recipe.scaleRange);
|
|
5013
|
+
var src, dest, val, isNoData, j;
|
|
5014
|
+
for (var y = 0; y < height; y++) {
|
|
5015
|
+
for (var x = 0; x < width; x++) {
|
|
5016
|
+
src = getPreviewSourceOffset(grid, sourceBbox, x, y, width, height, bands);
|
|
5017
|
+
dest = (y * width + x) * 4;
|
|
5018
|
+
isNoData = noData !== null && noData !== undefined && allSamplesAreNoData(samples, src, bands, noData);
|
|
5019
|
+
if (bands == 1) {
|
|
5020
|
+
val = scaleSample(samples[src], stats && stats[0], sourceRange, displayRange);
|
|
5021
|
+
pixels[dest] = val;
|
|
5022
|
+
pixels[dest + 1] = val;
|
|
5023
|
+
pixels[dest + 2] = val;
|
|
5024
|
+
pixels[dest + 3] = isNoData ? 0 : 255;
|
|
5025
|
+
} else {
|
|
5026
|
+
for (j = 0; j < 3; j++) {
|
|
5027
|
+
pixels[dest + j] = scaleSample(samples[src + j], stats && stats[j], sourceRange, displayRange);
|
|
5028
|
+
}
|
|
5029
|
+
pixels[dest + 3] = isNoData ? 0 : bands >= 4 ? scaleSample(samples[src + 3], stats && stats[3], sourceRange, [0, 255]) : 255;
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5033
|
+
return {
|
|
5034
|
+
width: width,
|
|
5035
|
+
height: height,
|
|
5036
|
+
bands: 4,
|
|
5037
|
+
pixelType: 'uint8',
|
|
5038
|
+
colorModel: 'rgba',
|
|
5039
|
+
pixels: pixels
|
|
5040
|
+
};
|
|
5041
|
+
}
|
|
5042
|
+
|
|
5043
|
+
function renderRawEightBitPreview(grid, width, height, sourceBbox) {
|
|
5044
|
+
var samples = grid.samples;
|
|
5045
|
+
var bands = grid.bands;
|
|
5046
|
+
var pixels = new Uint8ClampedArray(width * height * 4);
|
|
5047
|
+
var src, dest, val;
|
|
5048
|
+
for (var y = 0; y < height; y++) {
|
|
5049
|
+
for (var x = 0; x < width; x++) {
|
|
5050
|
+
src = getPreviewSourceOffset(grid, sourceBbox, x, y, width, height, bands);
|
|
5051
|
+
dest = (y * width + x) * 4;
|
|
5052
|
+
if (bands == 1) {
|
|
5053
|
+
val = samples[src];
|
|
5054
|
+
pixels[dest] = val;
|
|
5055
|
+
pixels[dest + 1] = val;
|
|
5056
|
+
pixels[dest + 2] = val;
|
|
5057
|
+
pixels[dest + 3] = 255;
|
|
5058
|
+
} else {
|
|
5059
|
+
pixels[dest] = samples[src];
|
|
5060
|
+
pixels[dest + 1] = samples[src + 1];
|
|
5061
|
+
pixels[dest + 2] = samples[src + 2];
|
|
5062
|
+
pixels[dest + 3] = bands >= 4 ? samples[src + 3] : 255;
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
return {
|
|
5067
|
+
width: width,
|
|
5068
|
+
height: height,
|
|
5069
|
+
bands: 4,
|
|
5070
|
+
pixelType: 'uint8',
|
|
5071
|
+
colorModel: 'rgba',
|
|
5072
|
+
pixels: pixels
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
|
|
5076
|
+
function getPreviewSourceOffset(grid, sourceBbox, x, y, width, height, bands) {
|
|
5077
|
+
var sx, sy, mapX, mapY, rb;
|
|
5078
|
+
if (!sourceBbox) {
|
|
5079
|
+
sy = Math.min(grid.height - 1, Math.floor((y + 0.5) * grid.height / height));
|
|
5080
|
+
sx = Math.min(grid.width - 1, Math.floor((x + 0.5) * grid.width / width));
|
|
5081
|
+
} else {
|
|
5082
|
+
rb = grid.bbox;
|
|
5083
|
+
mapX = sourceBbox[0] + (x + 0.5) / width * (sourceBbox[2] - sourceBbox[0]);
|
|
5084
|
+
mapY = sourceBbox[3] - (y + 0.5) / height * (sourceBbox[3] - sourceBbox[1]);
|
|
5085
|
+
sx = Math.max(0, Math.min(grid.width - 1, Math.floor((mapX - rb[0]) / (rb[2] - rb[0]) * grid.width)));
|
|
5086
|
+
sy = Math.max(0, Math.min(grid.height - 1, Math.floor((rb[3] - mapY) / (rb[3] - rb[1]) * grid.height)));
|
|
5087
|
+
}
|
|
5088
|
+
return (sy * grid.width + sx) * bands;
|
|
5089
|
+
}
|
|
5090
|
+
|
|
5091
|
+
function clipRasterToBBox(lyr, bbox, opts) {
|
|
5092
|
+
var raster = lyr.raster;
|
|
5093
|
+
var grid = getRasterGrid(raster);
|
|
5094
|
+
var clipBbox = intersectBboxes(grid.bbox, bbox);
|
|
5095
|
+
if (!clipBbox) {
|
|
5096
|
+
warn('Raster clipping rectangle does not intersect the raster layer');
|
|
5097
|
+
return false;
|
|
5098
|
+
}
|
|
5099
|
+
var crop = getRasterCrop(grid, clipBbox);
|
|
5100
|
+
if (crop.width === 0 || crop.height === 0) {
|
|
5101
|
+
warn('Raster clipping rectangle does not intersect the raster layer');
|
|
5102
|
+
return false;
|
|
5103
|
+
}
|
|
5104
|
+
noteLayerWillChange(lyr, {operation: 'clipRasterToBBox', unit: 'raster'});
|
|
5105
|
+
raster.grid = cropRasterGrid(grid, crop, clipBbox);
|
|
5106
|
+
raster.view = raster.view || {};
|
|
5107
|
+
raster.view.preview = createRasterPreview(raster, opts || {});
|
|
5108
|
+
clearLegacyRasterFields(raster);
|
|
5109
|
+
markLayerChanged(lyr, {operation: 'clipRasterToBBox', unit: 'raster'});
|
|
5110
|
+
return true;
|
|
5111
|
+
}
|
|
5112
|
+
|
|
5113
|
+
function intersectBboxes(a, b) {
|
|
5114
|
+
var bbox = [
|
|
5115
|
+
Math.max(a[0], b[0]),
|
|
5116
|
+
Math.max(a[1], b[1]),
|
|
5117
|
+
Math.min(a[2], b[2]),
|
|
5118
|
+
Math.min(a[3], b[3])
|
|
5119
|
+
];
|
|
5120
|
+
return bbox[2] <= bbox[0] || bbox[3] <= bbox[1] ? null : bbox;
|
|
5121
|
+
}
|
|
5122
|
+
|
|
5123
|
+
function getRasterCrop(grid, bbox) {
|
|
5124
|
+
var rb = grid.bbox;
|
|
5125
|
+
var x0 = Math.max(0, Math.floor((bbox[0] - rb[0]) / (rb[2] - rb[0]) * grid.width));
|
|
5126
|
+
var x1 = Math.min(grid.width, Math.ceil((bbox[2] - rb[0]) / (rb[2] - rb[0]) * grid.width));
|
|
5127
|
+
var y0 = Math.max(0, Math.floor((rb[3] - bbox[3]) / (rb[3] - rb[1]) * grid.height));
|
|
5128
|
+
var y1 = Math.min(grid.height, Math.ceil((rb[3] - bbox[1]) / (rb[3] - rb[1]) * grid.height));
|
|
5129
|
+
return {
|
|
5130
|
+
x: x0,
|
|
5131
|
+
y: y0,
|
|
5132
|
+
width: Math.max(0, x1 - x0),
|
|
5133
|
+
height: Math.max(0, y1 - y0)
|
|
5134
|
+
};
|
|
5135
|
+
}
|
|
5136
|
+
|
|
5137
|
+
function cropRasterGrid(grid, crop, bbox) {
|
|
5138
|
+
var samples = new grid.samples.constructor(crop.width * crop.height * grid.bands);
|
|
5139
|
+
var rowCount = crop.width * grid.bands;
|
|
5140
|
+
var src, dest;
|
|
5141
|
+
for (var y = 0; y < crop.height; y++) {
|
|
5142
|
+
src = ((crop.y + y) * grid.width + crop.x) * grid.bands;
|
|
5143
|
+
dest = y * rowCount;
|
|
5144
|
+
samples.set(grid.samples.subarray(src, src + rowCount), dest);
|
|
5145
|
+
}
|
|
5146
|
+
return Object.assign({}, grid, {
|
|
5147
|
+
width: crop.width,
|
|
5148
|
+
height: crop.height,
|
|
5149
|
+
samples: samples,
|
|
5150
|
+
bbox: bbox,
|
|
5151
|
+
transform: updateTransformForBBox(grid.transform, bbox, crop.width, crop.height)
|
|
5152
|
+
});
|
|
5153
|
+
}
|
|
5154
|
+
|
|
5155
|
+
function getRasterLayerBounds(lyr) {
|
|
5156
|
+
var bbox = lyr.raster && getRasterBBox(lyr.raster);
|
|
5157
|
+
return bbox && bbox.length == 4 ? new Bounds(bbox) : null;
|
|
5158
|
+
}
|
|
5159
|
+
|
|
5160
|
+
function requireRasterLayer(lyr) {
|
|
5161
|
+
if (!lyr || !lyr.raster_type || !lyr.raster) {
|
|
5162
|
+
stop('Expected a raster layer');
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
|
|
5166
|
+
function clearLegacyRasterFields(raster) {
|
|
5167
|
+
delete raster.width;
|
|
5168
|
+
delete raster.height;
|
|
5169
|
+
delete raster.bands;
|
|
5170
|
+
delete raster.pixelType;
|
|
5171
|
+
delete raster.nodata;
|
|
5172
|
+
delete raster.bbox;
|
|
5173
|
+
delete raster.transform;
|
|
5174
|
+
delete raster.colorModel;
|
|
5175
|
+
delete raster.preview;
|
|
5176
|
+
delete raster.pixels;
|
|
5177
|
+
}
|
|
5178
|
+
|
|
5179
|
+
function updateTransformForBBox(transform, bbox, width, height) {
|
|
5180
|
+
if (!transform) return null;
|
|
5181
|
+
return [
|
|
5182
|
+
(bbox[2] - bbox[0]) / width,
|
|
5183
|
+
0,
|
|
5184
|
+
bbox[0],
|
|
5185
|
+
0,
|
|
5186
|
+
-(bbox[3] - bbox[1]) / height,
|
|
5187
|
+
bbox[3]
|
|
5188
|
+
];
|
|
5189
|
+
}
|
|
5190
|
+
|
|
5191
|
+
function getBandStats(data, bands, noData) {
|
|
5192
|
+
var stats = [];
|
|
5193
|
+
for (var band = 0; band < bands; band++) {
|
|
5194
|
+
stats[band] = getSingleBandStats(data, bands, band, noData);
|
|
5195
|
+
}
|
|
5196
|
+
return stats;
|
|
5197
|
+
}
|
|
5198
|
+
|
|
5199
|
+
function getSingleBandStats(data, bands, band, noData) {
|
|
5200
|
+
var min = Infinity;
|
|
5201
|
+
var max = -Infinity;
|
|
5202
|
+
var val;
|
|
5203
|
+
for (var i = band; i < data.length; i += bands) {
|
|
5204
|
+
val = data[i];
|
|
5205
|
+
if (!isValidBandValue(val, noData)) continue;
|
|
5206
|
+
if (val < min) min = val;
|
|
5207
|
+
if (val > max) max = val;
|
|
5208
|
+
}
|
|
5209
|
+
return min < Infinity && max > min ? {min: min, max: max} : {min: 0, max: 255};
|
|
5210
|
+
}
|
|
5211
|
+
|
|
5212
|
+
function getSharedBandStats(data, bands, noData) {
|
|
5213
|
+
var stats = getBandStats(data, bands, noData);
|
|
5214
|
+
var min = Math.min(stats[0].min, stats[1].min, stats[2].min);
|
|
5215
|
+
var max = Math.max(stats[0].max, stats[1].max, stats[2].max);
|
|
5216
|
+
var shared = max > min ? {min: min, max: max} : {min: 0, max: 255};
|
|
5217
|
+
stats[0] = stats[1] = stats[2] = shared;
|
|
5218
|
+
return stats;
|
|
5219
|
+
}
|
|
5220
|
+
|
|
5221
|
+
function getScalingStats(data, bands, noData, recipe) {
|
|
5222
|
+
var shared = recipe.type == 'rgb' && bands >= 3;
|
|
5223
|
+
if (recipe.scaling == 'minmax') {
|
|
5224
|
+
return shared ? getSharedBandStats(data, bands, noData) : getBandStats(data, bands, noData);
|
|
5225
|
+
}
|
|
5226
|
+
if (recipe.scaling == 'percentile') {
|
|
5227
|
+
return shared ? getSharedPercentileStats(data, bands, noData, recipe.percentileRange) :
|
|
5228
|
+
getBandPercentileStats(data, bands, noData, recipe.percentileRange);
|
|
5229
|
+
}
|
|
5230
|
+
return null;
|
|
5231
|
+
}
|
|
5232
|
+
|
|
5233
|
+
function scaleSample(val, stats, sourceRange, displayRange) {
|
|
5234
|
+
var range = stats || sourceRange || {min: 0, max: 255};
|
|
5235
|
+
var pct = range.max > range.min ? (val - range.min) / (range.max - range.min) : 0;
|
|
5236
|
+
var scaled = displayRange[0] + pct * (displayRange[1] - displayRange[0]);
|
|
5237
|
+
return Math.max(0, Math.min(255, Math.round(scaled)));
|
|
5238
|
+
}
|
|
5239
|
+
|
|
5240
|
+
function getDefaultRasterScaling(grid, recipe) {
|
|
5241
|
+
return isEightBitPixelType(grid.pixelType) ? 'none' : 'percentile';
|
|
5242
|
+
}
|
|
5243
|
+
|
|
5244
|
+
function parseRangeOption(val, def, name) {
|
|
5245
|
+
var range;
|
|
5246
|
+
if (val == null || val === '') return def;
|
|
5247
|
+
range = Array.isArray(val) ? val : String(val).split(',');
|
|
5248
|
+
if (range.length != 2) {
|
|
5249
|
+
stop('Expected ' + name + '= to contain two comma-separated numbers');
|
|
5250
|
+
}
|
|
5251
|
+
range = range.map(Number);
|
|
5252
|
+
if (!isFinite(range[0]) || !isFinite(range[1]) || range[0] < 0 || range[1] > 100 || range[1] < range[0]) {
|
|
5253
|
+
stop('Expected ' + name + '= values between 0 and 100');
|
|
5254
|
+
}
|
|
5255
|
+
return range;
|
|
5256
|
+
}
|
|
5257
|
+
|
|
5258
|
+
function getDisplayRange(scaleRange) {
|
|
5259
|
+
return [
|
|
5260
|
+
scaleRange[0] / 100 * 255,
|
|
5261
|
+
scaleRange[1] / 100 * 255
|
|
5262
|
+
];
|
|
5263
|
+
}
|
|
5264
|
+
|
|
5265
|
+
function getPixelTypeRange(pixelType) {
|
|
5266
|
+
switch (pixelType) {
|
|
5267
|
+
case 'uint8': return {min: 0, max: 255};
|
|
5268
|
+
case 'uint16': return {min: 0, max: 65535};
|
|
5269
|
+
case 'uint32': return {min: 0, max: 4294967295};
|
|
5270
|
+
case 'int8': return {min: -128, max: 127};
|
|
5271
|
+
case 'int16': return {min: -32768, max: 32767};
|
|
5272
|
+
case 'int32': return {min: -2147483648, max: 2147483647};
|
|
5273
|
+
}
|
|
5274
|
+
return null;
|
|
5275
|
+
}
|
|
5276
|
+
|
|
5277
|
+
function isEightBitPixelType(pixelType) {
|
|
5278
|
+
return pixelType == 'uint8' || pixelType == 'int8';
|
|
5279
|
+
}
|
|
5280
|
+
|
|
5281
|
+
function useFastRawEightBitRendering(grid, recipe) {
|
|
5282
|
+
return grid.pixelType == 'uint8' &&
|
|
5283
|
+
recipe.scaling == 'none' &&
|
|
5284
|
+
recipe.scaleRange[0] === 0 &&
|
|
5285
|
+
recipe.scaleRange[1] === 100 &&
|
|
5286
|
+
(recipe.type == 'gray' && grid.bands == 1 || recipe.type == 'rgb' && grid.bands >= 3) &&
|
|
5287
|
+
(grid.nodata === null || grid.nodata === undefined);
|
|
5288
|
+
}
|
|
5289
|
+
|
|
5290
|
+
function getRasterScalingStatsKey(grid, recipe) {
|
|
5291
|
+
return [
|
|
5292
|
+
grid.width,
|
|
5293
|
+
grid.height,
|
|
5294
|
+
grid.bands,
|
|
5295
|
+
grid.pixelType,
|
|
5296
|
+
grid.samples && grid.samples.length,
|
|
5297
|
+
grid.nodata,
|
|
5298
|
+
recipe.type,
|
|
5299
|
+
recipe.scaling,
|
|
5300
|
+
recipe.scaleRange && recipe.scaleRange.join(','),
|
|
5301
|
+
recipe.percentileRange && recipe.percentileRange.join(',')
|
|
5302
|
+
].join('|');
|
|
5303
|
+
}
|
|
5304
|
+
|
|
5305
|
+
function getBandPercentileStats(data, bands, noData, range) {
|
|
5306
|
+
var stats = [];
|
|
5307
|
+
for (var band = 0; band < bands; band++) {
|
|
5308
|
+
stats[band] = getPercentileStats(data, bands, [band], noData, range);
|
|
5309
|
+
}
|
|
5310
|
+
return stats;
|
|
5311
|
+
}
|
|
5312
|
+
|
|
5313
|
+
function getSharedPercentileStats(data, bands, noData, range) {
|
|
5314
|
+
var stats = getBandPercentileStats(data, bands, noData, range);
|
|
5315
|
+
var shared = getPercentileStats(data, bands, [0, 1, 2], noData, range);
|
|
5316
|
+
stats[0] = stats[1] = stats[2] = shared;
|
|
5317
|
+
return stats;
|
|
5318
|
+
}
|
|
5319
|
+
|
|
5320
|
+
function getPercentileStats(data, bands, bandIds, noData, range) {
|
|
5321
|
+
var integerRange = getSmallIntegerRange(data);
|
|
5322
|
+
return integerRange ?
|
|
5323
|
+
getHistogramPercentileStats(data, bands, bandIds, noData, range, integerRange) :
|
|
5324
|
+
getApproxHistogramPercentileStats(data, bands, bandIds, noData, range);
|
|
5325
|
+
}
|
|
5326
|
+
|
|
5327
|
+
function getSmallIntegerRange(data) {
|
|
5328
|
+
if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) return {min: 0, max: 255};
|
|
5329
|
+
if (data instanceof Int8Array) return {min: -128, max: 127};
|
|
5330
|
+
if (data instanceof Uint16Array) return {min: 0, max: 65535};
|
|
5331
|
+
if (data instanceof Int16Array) return {min: -32768, max: 32767};
|
|
5332
|
+
return null;
|
|
5333
|
+
}
|
|
5334
|
+
|
|
5335
|
+
function getHistogramPercentileStats(data, bands, bandIds, noData, range, integerRange) {
|
|
5336
|
+
var counts = new Uint32Array(integerRange.max - integerRange.min + 1);
|
|
5337
|
+
var count = 0, val;
|
|
5338
|
+
forEachBandValue(data, bands, bandIds, noData, function(v) {
|
|
5339
|
+
val = v - integerRange.min;
|
|
5340
|
+
counts[val]++;
|
|
5341
|
+
count++;
|
|
5342
|
+
});
|
|
5343
|
+
return count > 0 ? {
|
|
5344
|
+
min: getHistogramPercentileValue(counts, integerRange.min, count, range[0]),
|
|
5345
|
+
max: getHistogramPercentileValue(counts, integerRange.min, count, range[1])
|
|
5346
|
+
} : {min: 0, max: 255};
|
|
5347
|
+
}
|
|
5348
|
+
|
|
5349
|
+
function getApproxHistogramPercentileStats(data, bands, bandIds, noData, range) {
|
|
5350
|
+
var bounds = getBandValueBounds(data, bands, bandIds, noData);
|
|
5351
|
+
var binCount = 65536;
|
|
5352
|
+
var counts, count, scale, maxBin;
|
|
5353
|
+
if (!bounds || bounds.max <= bounds.min) {
|
|
5354
|
+
return bounds || {min: 0, max: 255};
|
|
5355
|
+
}
|
|
5356
|
+
counts = new Uint32Array(binCount);
|
|
5357
|
+
count = 0;
|
|
5358
|
+
scale = (binCount - 1) / (bounds.max - bounds.min);
|
|
5359
|
+
maxBin = binCount - 1;
|
|
5360
|
+
forEachBandValue(data, bands, bandIds, noData, function(val) {
|
|
5361
|
+
counts[Math.max(0, Math.min(maxBin, Math.floor((val - bounds.min) * scale)))]++;
|
|
5362
|
+
count++;
|
|
5363
|
+
});
|
|
5364
|
+
return count > 0 ? {
|
|
5365
|
+
min: getApproxHistogramPercentileValue(counts, bounds, count, range[0]),
|
|
5366
|
+
max: getApproxHistogramPercentileValue(counts, bounds, count, range[1])
|
|
5367
|
+
} : {min: 0, max: 255};
|
|
5368
|
+
}
|
|
5369
|
+
|
|
5370
|
+
function getBandValueBounds(data, bands, bandIds, noData) {
|
|
5371
|
+
var min = Infinity;
|
|
5372
|
+
var max = -Infinity;
|
|
5373
|
+
forEachBandValue(data, bands, bandIds, noData, function(val) {
|
|
5374
|
+
if (val < min) min = val;
|
|
5375
|
+
if (val > max) max = val;
|
|
5376
|
+
});
|
|
5377
|
+
return min < Infinity ? {min: min, max: max} : null;
|
|
5378
|
+
}
|
|
5379
|
+
|
|
5380
|
+
function getHistogramPercentileValue(counts, offset, count, pct) {
|
|
5381
|
+
var target = getPercentileRank(count, pct);
|
|
5382
|
+
var sum = 0;
|
|
5383
|
+
for (var i = 0; i < counts.length; i++) {
|
|
5384
|
+
sum += counts[i];
|
|
5385
|
+
if (sum > target) return i + offset;
|
|
5386
|
+
}
|
|
5387
|
+
return counts.length - 1 + offset;
|
|
5388
|
+
}
|
|
5389
|
+
|
|
5390
|
+
function getApproxHistogramPercentileValue(counts, bounds, count, pct) {
|
|
5391
|
+
var bin = getHistogramPercentileValue(counts, 0, count, pct);
|
|
5392
|
+
return bounds.min + bin / (counts.length - 1) * (bounds.max - bounds.min);
|
|
5393
|
+
}
|
|
5394
|
+
|
|
5395
|
+
function forEachBandValue(data, bands, bandIds, noData, cb) {
|
|
5396
|
+
var val;
|
|
5397
|
+
for (var i = 0; i < data.length; i += bands) {
|
|
5398
|
+
for (var j = 0; j < bandIds.length; j++) {
|
|
5399
|
+
val = data[i + bandIds[j]];
|
|
5400
|
+
if (!isValidBandValue(val, noData)) continue;
|
|
5401
|
+
cb(val);
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5404
|
+
}
|
|
5405
|
+
|
|
5406
|
+
function isValidBandValue(val, noData) {
|
|
5407
|
+
return isFinite(val) && (noData === null || noData === undefined || val != noData);
|
|
5408
|
+
}
|
|
5409
|
+
|
|
5410
|
+
function getPercentileRank(count, pct) {
|
|
5411
|
+
return Math.max(0, Math.min(count - 1, Math.floor((count - 1) * pct / 100)));
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5414
|
+
function allSamplesAreNoData(data, offset, bands, noData) {
|
|
5415
|
+
var n = Math.min(bands, 3);
|
|
5416
|
+
for (var i = 0; i < n; i++) {
|
|
5417
|
+
if (data[offset + i] != noData) return false;
|
|
5418
|
+
}
|
|
5419
|
+
return true;
|
|
5420
|
+
}
|
|
5421
|
+
|
|
5422
|
+
function copyObjectOrArray(obj) {
|
|
5423
|
+
return obj && obj.concat ? obj.concat() : utils.extend({}, obj);
|
|
5424
|
+
}
|
|
5425
|
+
|
|
4443
5426
|
function UndoTransaction(label) {
|
|
4444
5427
|
this.label = label || '';
|
|
4445
5428
|
this.units = [];
|
|
@@ -4652,6 +5635,8 @@
|
|
|
4652
5635
|
detail: copyDetail(detail),
|
|
4653
5636
|
name: layer.name,
|
|
4654
5637
|
geometry_type: layer.geometry_type,
|
|
5638
|
+
raster_type: layer.raster_type || null,
|
|
5639
|
+
raster: layer.raster ? copyRasterData(layer.raster) : null,
|
|
4655
5640
|
shapes: layer.shapes ? cloneShapes(layer.shapes) : null,
|
|
4656
5641
|
data: layer.data || null
|
|
4657
5642
|
});
|
|
@@ -4945,6 +5930,8 @@
|
|
|
4945
5930
|
revision: getUndoRevision(unit.target),
|
|
4946
5931
|
name: unit.target.name,
|
|
4947
5932
|
geometry_type: unit.target.geometry_type,
|
|
5933
|
+
raster_type: unit.target.raster_type || null,
|
|
5934
|
+
raster: unit.target.raster ? copyRasterData(unit.target.raster) : null,
|
|
4948
5935
|
shapes: unit.target.shapes ? cloneShapes(unit.target.shapes) : null,
|
|
4949
5936
|
data: unit.target.data || null
|
|
4950
5937
|
});
|
|
@@ -5071,6 +6058,8 @@
|
|
|
5071
6058
|
function restoreLayer(unit) {
|
|
5072
6059
|
unit.target.name = unit.name;
|
|
5073
6060
|
unit.target.geometry_type = unit.geometry_type;
|
|
6061
|
+
unit.target.raster_type = unit.raster_type || null;
|
|
6062
|
+
unit.target.raster = unit.raster ? copyRasterData(unit.raster) : null;
|
|
5074
6063
|
unit.target.shapes = unit.shapes ? cloneShapes(unit.shapes) : null;
|
|
5075
6064
|
unit.target.data = unit.data;
|
|
5076
6065
|
}
|
|
@@ -5177,7 +6166,7 @@
|
|
|
5177
6166
|
'table-schema': ['fields'],
|
|
5178
6167
|
arcs: ['nn', 'xx', 'yy', 'zz', 'zlimit'],
|
|
5179
6168
|
'arcs-simplification': ['zz'],
|
|
5180
|
-
layer: ['shapes']
|
|
6169
|
+
layer: ['shapes', 'raster']
|
|
5181
6170
|
};
|
|
5182
6171
|
|
|
5183
6172
|
async function storeUndoUnits(units, store, entryId, role) {
|
|
@@ -5282,9 +6271,19 @@
|
|
|
5282
6271
|
}
|
|
5283
6272
|
});
|
|
5284
6273
|
if (!hasPayload) return null;
|
|
6274
|
+
if (unit.type == 'layer') return packLayerPayload(payload);
|
|
5285
6275
|
return unit.type == 'table' ? packTablePayload(payload) : payload;
|
|
5286
6276
|
}
|
|
5287
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
|
+
|
|
5288
6287
|
async function packTablePayload(payload) {
|
|
5289
6288
|
return {
|
|
5290
6289
|
packedRecords: packRecordsAsColumns(payload.records)
|
|
@@ -5297,9 +6296,46 @@
|
|
|
5297
6296
|
records: unpackRecordsFromColumns(payload.packedRecords)
|
|
5298
6297
|
};
|
|
5299
6298
|
}
|
|
6299
|
+
if (unit.type == 'layer' && payload.raster) {
|
|
6300
|
+
return Object.assign({}, payload, {
|
|
6301
|
+
raster: unpackRasterUndoPayload(payload.raster)
|
|
6302
|
+
});
|
|
6303
|
+
}
|
|
5300
6304
|
return payload;
|
|
5301
6305
|
}
|
|
5302
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
|
+
|
|
5303
6339
|
function packRecordsAsColumns(records) {
|
|
5304
6340
|
var fields = getRecordFields(records);
|
|
5305
6341
|
return {
|
|
@@ -5458,7 +6494,15 @@
|
|
|
5458
6494
|
if (!gui.undoPayloadStore) {
|
|
5459
6495
|
gui.undoPayloadStore = createUndoPayloadStore(getUndoPayloadStoreOptions());
|
|
5460
6496
|
gui.undoPayloadStore.startLifecycle();
|
|
5461
|
-
gui.undoPayloadStore.cleanupStaleSessions()
|
|
6497
|
+
gui.undoPayloadStore.cleanupStaleSessions().then(function(result) {
|
|
6498
|
+
logStartupCleanup({
|
|
6499
|
+
count: result.keys.length,
|
|
6500
|
+
sessionCount: result.sessionCount,
|
|
6501
|
+
singular: 'undo payload',
|
|
6502
|
+
plural: 'undo payloads',
|
|
6503
|
+
sizeBytes: result.sizeBytes
|
|
6504
|
+
});
|
|
6505
|
+
}).catch(function() {});
|
|
5462
6506
|
}
|
|
5463
6507
|
return gui.undoPayloadStore;
|
|
5464
6508
|
}
|
|
@@ -5496,6 +6540,7 @@
|
|
|
5496
6540
|
delete copy.zz;
|
|
5497
6541
|
delete copy.zlimit;
|
|
5498
6542
|
delete copy.shapes;
|
|
6543
|
+
delete copy.raster;
|
|
5499
6544
|
if (copy.type != 'table-records') delete copy.records;
|
|
5500
6545
|
if (copy.type != 'table-fields') delete copy.columns;
|
|
5501
6546
|
delete copy.fields;
|
|
@@ -5717,6 +6762,7 @@
|
|
|
5717
6762
|
|
|
5718
6763
|
var geopackagePromise = null;
|
|
5719
6764
|
var geoParquetPromise = null;
|
|
6765
|
+
var geoTIFFPromise = null;
|
|
5720
6766
|
|
|
5721
6767
|
async function loadGeopackageLib() {
|
|
5722
6768
|
if (!window.modules || !window.modules['@ngageoint/geopackage']) {
|
|
@@ -5750,14 +6796,143 @@
|
|
|
5750
6796
|
}
|
|
5751
6797
|
}
|
|
5752
6798
|
|
|
5753
|
-
async function loadGeoParquetLib() {
|
|
5754
|
-
if (!window.modules || !window.modules.hyparquet || !window.modules['hyparquet-compressors'] || !window.modules['hyparquet-writer'] || !window.modules['@bokuweb/zstd-wasm']) {
|
|
5755
|
-
if (!geoParquetPromise) {
|
|
5756
|
-
geoParquetPromise = loadScript('geoparquet.js');
|
|
5757
|
-
}
|
|
5758
|
-
await geoParquetPromise;
|
|
5759
|
-
geoParquetPromise = null;
|
|
6799
|
+
async function loadGeoParquetLib() {
|
|
6800
|
+
if (!window.modules || !window.modules.hyparquet || !window.modules['hyparquet-compressors'] || !window.modules['hyparquet-writer'] || !window.modules['@bokuweb/zstd-wasm']) {
|
|
6801
|
+
if (!geoParquetPromise) {
|
|
6802
|
+
geoParquetPromise = loadScript('geoparquet.js');
|
|
6803
|
+
}
|
|
6804
|
+
await geoParquetPromise;
|
|
6805
|
+
geoParquetPromise = null;
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6808
|
+
|
|
6809
|
+
async function loadGeoTIFFLib() {
|
|
6810
|
+
if (!window.modules || !window.modules.geotiff) {
|
|
6811
|
+
if (!geoTIFFPromise) {
|
|
6812
|
+
geoTIFFPromise = loadScript('geotiff.js');
|
|
6813
|
+
}
|
|
6814
|
+
await geoTIFFPromise;
|
|
6815
|
+
geoTIFFPromise = null;
|
|
6816
|
+
}
|
|
6817
|
+
}
|
|
6818
|
+
|
|
6819
|
+
var idb = require$1('idb-keyval');
|
|
6820
|
+
var KEY_PREFIX = 'msr';
|
|
6821
|
+
var SESSION_KEY = 'mapshaper_raster_source_sessions';
|
|
6822
|
+
var lifecycle = createTempSessionLifecycle({
|
|
6823
|
+
prefix: 'raster',
|
|
6824
|
+
sessionKey: SESSION_KEY
|
|
6825
|
+
});
|
|
6826
|
+
var sessionId = lifecycle.getSessionId();
|
|
6827
|
+
var ownKeys = new Set();
|
|
6828
|
+
var sourceCount = 0;
|
|
6829
|
+
var sampleCount = 0;
|
|
6830
|
+
var lifecycleStarted = false;
|
|
6831
|
+
|
|
6832
|
+
async function persistRasterSourceForDataset(dataset, group) {
|
|
6833
|
+
if (!idb) return;
|
|
6834
|
+
var source = getRasterSourceFile(group);
|
|
6835
|
+
var sourceKey = source && source.content ?
|
|
6836
|
+
await persistRasterSourceBytes(dataset, source) : null;
|
|
6837
|
+
await persistRasterLayerSamples(dataset);
|
|
6838
|
+
lifecycle.touch();
|
|
6839
|
+
return sourceKey;
|
|
6840
|
+
}
|
|
6841
|
+
|
|
6842
|
+
function getRasterSourceFile(group) {
|
|
6843
|
+
return group && (group.geotiff || group.png || group.jpeg) || null;
|
|
6844
|
+
}
|
|
6845
|
+
|
|
6846
|
+
function startRasterSourceStoreLifecycle() {
|
|
6847
|
+
if (lifecycleStarted || !idb) return;
|
|
6848
|
+
lifecycleStarted = true;
|
|
6849
|
+
lifecycle.start(attemptOwnSourceDeletion);
|
|
6850
|
+
cleanupStaleRasterSources().then(function(result) {
|
|
6851
|
+
logStartupCleanup({
|
|
6852
|
+
count: result.keys.length,
|
|
6853
|
+
sessionCount: result.sessionCount,
|
|
6854
|
+
singular: 'raster temp file',
|
|
6855
|
+
plural: 'raster temp files'
|
|
6856
|
+
});
|
|
6857
|
+
}).catch(function() {});
|
|
6858
|
+
}
|
|
6859
|
+
|
|
6860
|
+
async function cleanupStaleRasterSources() {
|
|
6861
|
+
if (!idb) return {keys: [], sessionCount: 0};
|
|
6862
|
+
var liveSessions = lifecycle.getLiveSessions();
|
|
6863
|
+
var keys = await idb.keys();
|
|
6864
|
+
var doomedSessions = new Set();
|
|
6865
|
+
var doomedKeys = keys.filter(function(key) {
|
|
6866
|
+
var sid = getSessionFromKey(key);
|
|
6867
|
+
var stale = isRasterSourceKey(key) && !liveSessions[sid];
|
|
6868
|
+
if (stale && sid) doomedSessions.add(sid);
|
|
6869
|
+
return stale;
|
|
6870
|
+
});
|
|
6871
|
+
if (doomedKeys.length > 0) {
|
|
6872
|
+
await idb.delMany(doomedKeys);
|
|
6873
|
+
}
|
|
6874
|
+
return {
|
|
6875
|
+
keys: doomedKeys,
|
|
6876
|
+
sessionCount: doomedSessions.size
|
|
6877
|
+
};
|
|
6878
|
+
}
|
|
6879
|
+
|
|
6880
|
+
function makeRasterSourceKey(filename) {
|
|
6881
|
+
sourceCount++;
|
|
6882
|
+
return [KEY_PREFIX, sessionId, 'source', sourceCount, filename || 'raster'].join(':');
|
|
6883
|
+
}
|
|
6884
|
+
|
|
6885
|
+
function makeRasterSamplesKey(layerName) {
|
|
6886
|
+
sampleCount++;
|
|
6887
|
+
return [KEY_PREFIX, sessionId, 'samples', sampleCount, layerName || 'raster'].join(':');
|
|
6888
|
+
}
|
|
6889
|
+
|
|
6890
|
+
async function persistRasterSourceBytes(dataset, sourceFile) {
|
|
6891
|
+
var key = makeRasterSourceKey(sourceFile.filename);
|
|
6892
|
+
await idb.set(key, sourceFile.content);
|
|
6893
|
+
ownKeys.add(key);
|
|
6894
|
+
if (dataset.info && dataset.info.raster_sources) {
|
|
6895
|
+
dataset.info.raster_sources.forEach(function(source) {
|
|
6896
|
+
source.storage = 'indexeddb';
|
|
6897
|
+
source.key = key;
|
|
6898
|
+
});
|
|
5760
6899
|
}
|
|
6900
|
+
return key;
|
|
6901
|
+
}
|
|
6902
|
+
|
|
6903
|
+
async function persistRasterLayerSamples(dataset) {
|
|
6904
|
+
var promises = [];
|
|
6905
|
+
dataset.layers.forEach(function(lyr) {
|
|
6906
|
+
var grid = lyr.raster && getRasterGrid(lyr.raster);
|
|
6907
|
+
var key;
|
|
6908
|
+
if (!grid || !grid.samples) return;
|
|
6909
|
+
key = makeRasterSamplesKey(lyr.name);
|
|
6910
|
+
ownKeys.add(key);
|
|
6911
|
+
grid.storage = {
|
|
6912
|
+
type: 'indexeddb',
|
|
6913
|
+
key: key
|
|
6914
|
+
};
|
|
6915
|
+
promises.push(idb.set(key, grid.samples));
|
|
6916
|
+
});
|
|
6917
|
+
await Promise.all(promises);
|
|
6918
|
+
}
|
|
6919
|
+
|
|
6920
|
+
function attemptOwnSourceDeletion() {
|
|
6921
|
+
var keys = Array.from(ownKeys);
|
|
6922
|
+
ownKeys.clear();
|
|
6923
|
+
if (keys.length === 0) return;
|
|
6924
|
+
try {
|
|
6925
|
+
idb.delMany(keys).catch(function() {});
|
|
6926
|
+
} catch(e) {}
|
|
6927
|
+
}
|
|
6928
|
+
|
|
6929
|
+
function isRasterSourceKey(key) {
|
|
6930
|
+
return typeof key == 'string' && key.indexOf(KEY_PREFIX + ':') === 0;
|
|
6931
|
+
}
|
|
6932
|
+
|
|
6933
|
+
function getSessionFromKey(key) {
|
|
6934
|
+
var parts = String(key).split(':');
|
|
6935
|
+
return parts.length > 4 && parts[0] == KEY_PREFIX ? parts[1] : null;
|
|
5761
6936
|
}
|
|
5762
6937
|
|
|
5763
6938
|
// @cb function(<FileList>)
|
|
@@ -6097,6 +7272,7 @@
|
|
|
6097
7272
|
// gui.container.addClass('queued-files');
|
|
6098
7273
|
El('#import-options').show();
|
|
6099
7274
|
gui.container.classed('queued-files', queuedFiles.length > 0);
|
|
7275
|
+
updateSidebarTabZIndex();
|
|
6100
7276
|
El('#path-import-options').classed('hidden', !filesMayContainPaths(queuedFiles));
|
|
6101
7277
|
showQueuedFiles();
|
|
6102
7278
|
updateGeoPackageLayerSelectionMenu();
|
|
@@ -6104,9 +7280,15 @@
|
|
|
6104
7280
|
|
|
6105
7281
|
function hideImportMenu() {
|
|
6106
7282
|
// gui.container.removeClass('queued-files');
|
|
7283
|
+
gui.container.removeClass('sidebar-tabs-over-popup');
|
|
6107
7284
|
El('#import-options').hide();
|
|
6108
7285
|
}
|
|
6109
7286
|
|
|
7287
|
+
function updateSidebarTabZIndex() {
|
|
7288
|
+
gui.container.classed('sidebar-tabs-over-popup',
|
|
7289
|
+
queuedFiles.length === 0 && !gui.sidebarPanelIsOpen());
|
|
7290
|
+
}
|
|
7291
|
+
|
|
6110
7292
|
function getFileNames(files) {
|
|
6111
7293
|
return Array.from(files).map(function(f) {return f.name;});
|
|
6112
7294
|
}
|
|
@@ -6193,7 +7375,10 @@
|
|
|
6193
7375
|
if (group.parquet) {
|
|
6194
7376
|
await loadGeoParquetLib();
|
|
6195
7377
|
}
|
|
6196
|
-
if (group.
|
|
7378
|
+
if (group.geotiff) {
|
|
7379
|
+
await loadGeoTIFFLib();
|
|
7380
|
+
}
|
|
7381
|
+
if (group.gpkg || group.fgb || group.parquet || group.geotiff || group.png || group.jpeg) {
|
|
6197
7382
|
dataset = await internal.importContentAsync(group, importOpts);
|
|
6198
7383
|
} else {
|
|
6199
7384
|
dataset = internal.importContent(group, importOpts);
|
|
@@ -6204,6 +7389,7 @@
|
|
|
6204
7389
|
if (group.layername) {
|
|
6205
7390
|
d.layers.forEach(lyr => lyr.name = group.layername);
|
|
6206
7391
|
}
|
|
7392
|
+
await persistRasterSourceForDataset(d, group);
|
|
6207
7393
|
// TODO: add popup here
|
|
6208
7394
|
// save import options for use by repair control, etc.
|
|
6209
7395
|
d.info.import_options = importOpts;
|
|
@@ -6224,7 +7410,8 @@
|
|
|
6224
7410
|
function filesMayContainPaths(files) {
|
|
6225
7411
|
return utils$1.some(files, function(f) {
|
|
6226
7412
|
var type = internal.guessInputFileType(f.name);
|
|
6227
|
-
return type == 'shp' || type == 'json' || type == 'gpkg' || type == 'parquet' ||
|
|
7413
|
+
return type == 'shp' || type == 'json' || type == 'gpkg' || type == 'parquet' ||
|
|
7414
|
+
type == 'png' || type == 'jpeg' || internal.isZipFile(f.name);
|
|
6228
7415
|
});
|
|
6229
7416
|
}
|
|
6230
7417
|
|
|
@@ -6238,6 +7425,10 @@
|
|
|
6238
7425
|
return /\.(shp|shx|dbf|prj|cpg)$/i.test(name);
|
|
6239
7426
|
}
|
|
6240
7427
|
|
|
7428
|
+
function isRasterImagePart(name) {
|
|
7429
|
+
return /\.(png|jpg|jpeg|tfw|pgw|pngw|jgw|jpw|jpgw|jpegw|wld|prj)$/i.test(name);
|
|
7430
|
+
}
|
|
7431
|
+
|
|
6241
7432
|
function readImportOpts() {
|
|
6242
7433
|
var importOpts;
|
|
6243
7434
|
if (useQuickView) {
|
|
@@ -6562,10 +7753,17 @@
|
|
|
6562
7753
|
return data.some(d => fileKey(d) == shpKey);
|
|
6563
7754
|
}
|
|
6564
7755
|
|
|
7756
|
+
function hasRasterImage(basename) {
|
|
7757
|
+
return data.some(d => {
|
|
7758
|
+
var type = fileType(d);
|
|
7759
|
+
return (type == 'png' || type == 'jpeg') && fileBase(d) == basename;
|
|
7760
|
+
});
|
|
7761
|
+
}
|
|
7762
|
+
|
|
6565
7763
|
data.forEach(d => {
|
|
6566
7764
|
var basename = fileBase(d);
|
|
6567
7765
|
var type = fileType(d);
|
|
6568
|
-
if (type == 'shp'
|
|
7766
|
+
if (type == 'shp') {
|
|
6569
7767
|
d.group = key(basename, type);
|
|
6570
7768
|
d.filename = d.name;
|
|
6571
7769
|
} else if (hasShp(basename)) {
|
|
@@ -6573,6 +7771,12 @@
|
|
|
6573
7771
|
} else if (type == 'dbf') {
|
|
6574
7772
|
d.filename = d.name;
|
|
6575
7773
|
d.group = key(basename, 'dbf');
|
|
7774
|
+
} else if ((type == 'png' || type == 'jpeg') || isRasterImagePart(d.name) && hasRasterImage(basename)) {
|
|
7775
|
+
d.group = key(basename, type == 'png' || type == 'jpeg' ? type : getRasterImageGroupType(data, basename));
|
|
7776
|
+
if (type == 'png' || type == 'jpeg') d.filename = d.name;
|
|
7777
|
+
} else if (!isShapefilePart(d.name)) {
|
|
7778
|
+
d.group = key(basename, type);
|
|
7779
|
+
d.filename = d.name;
|
|
6576
7780
|
} else {
|
|
6577
7781
|
// shapefile part without a .shp file
|
|
6578
7782
|
d.group = null;
|
|
@@ -6599,6 +7803,14 @@
|
|
|
6599
7803
|
});
|
|
6600
7804
|
return groups;
|
|
6601
7805
|
}
|
|
7806
|
+
|
|
7807
|
+
function getRasterImageGroupType(data, basename) {
|
|
7808
|
+
var match = data.find(d => {
|
|
7809
|
+
var type = fileType(d);
|
|
7810
|
+
return (type == 'png' || type == 'jpeg') && fileBase(d) == basename;
|
|
7811
|
+
});
|
|
7812
|
+
return match ? fileType(match) : null;
|
|
7813
|
+
}
|
|
6602
7814
|
}
|
|
6603
7815
|
|
|
6604
7816
|
function draggable(ref) {
|
|
@@ -6726,11 +7938,18 @@
|
|
|
6726
7938
|
|
|
6727
7939
|
function message() {
|
|
6728
7940
|
var msg = GUI.formatMessageArgs(arguments);
|
|
6729
|
-
if (
|
|
7941
|
+
if (gui.notify && messageShouldGoToInbox(msg)) {
|
|
7942
|
+
gui.notify({
|
|
7943
|
+
severity: 'info',
|
|
7944
|
+
body: msg,
|
|
7945
|
+
dedupKey: 'info:' + msg
|
|
7946
|
+
});
|
|
7947
|
+
} else if (!gui.notify) {
|
|
6730
7948
|
// Fallback for early messages before MessageControl is constructed
|
|
6731
7949
|
gui.message(msg);
|
|
7950
|
+
} else {
|
|
7951
|
+
internal.logArgs(arguments);
|
|
6732
7952
|
}
|
|
6733
|
-
internal.logArgs(arguments);
|
|
6734
7953
|
}
|
|
6735
7954
|
|
|
6736
7955
|
// CLI warnings used to surface as modal alerts, which interrupt the user
|
|
@@ -6748,6 +7967,10 @@
|
|
|
6748
7967
|
internal.setLoggingFunctions(message, error, stop, warn);
|
|
6749
7968
|
}
|
|
6750
7969
|
|
|
7970
|
+
function messageShouldGoToInbox(msg) {
|
|
7971
|
+
return /^GeoTIFF renditions:/.test(msg);
|
|
7972
|
+
}
|
|
7973
|
+
|
|
6751
7974
|
function WriteFilesProxy(gui) {
|
|
6752
7975
|
// replace CLI version of writeFiles()
|
|
6753
7976
|
internal.replaceWriteFiles(async function(files, opts) {
|
|
@@ -7192,7 +8415,7 @@
|
|
|
7192
8415
|
var sourceCRS;
|
|
7193
8416
|
var emptyArcs;
|
|
7194
8417
|
|
|
7195
|
-
if (displayCRS && layer.geometry_type) {
|
|
8418
|
+
if (displayCRS && (layer.geometry_type || internal.layerHasRaster(layer))) {
|
|
7196
8419
|
var crsInfo = getDatasetCrsInfo(dataset);
|
|
7197
8420
|
if (crsInfo.error) {
|
|
7198
8421
|
// unprojectable dataset -- return empty layer
|
|
@@ -7233,6 +8456,15 @@
|
|
|
7233
8456
|
|
|
7234
8457
|
if (gui.unprojectable) {
|
|
7235
8458
|
gui.displayLayer = {shapes: []}; // TODO: improve
|
|
8459
|
+
} else if (internal.layerHasRaster(layer)) {
|
|
8460
|
+
gui.geographic = true;
|
|
8461
|
+
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
|
+
}
|
|
7236
8468
|
} else if (layer.geometry_type) {
|
|
7237
8469
|
gui.geographic = true;
|
|
7238
8470
|
gui.displayLayer = layer;
|
|
@@ -7264,8 +8496,19 @@
|
|
|
7264
8496
|
layer.gui = gui;
|
|
7265
8497
|
}
|
|
7266
8498
|
|
|
8499
|
+
function notifyRasterReprojectionBlocked(layer, opts) {
|
|
8500
|
+
if (!opts.notify) return;
|
|
8501
|
+
opts.notify({
|
|
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
|
+
});
|
|
8508
|
+
}
|
|
8509
|
+
|
|
7267
8510
|
function getDisplayBounds(lyr, arcs) {
|
|
7268
|
-
var bounds = internal.getLayerBounds(lyr, arcs) || new Bounds();
|
|
8511
|
+
var bounds = internal.getLayerBounds(lyr, arcs) || new Bounds$1();
|
|
7269
8512
|
if (lyr.geometry_type == 'point' && arcs && bounds.hasBounds() && bounds.area() > 0 === false) {
|
|
7270
8513
|
// if a point layer has no extent (e.g. contains only a single point),
|
|
7271
8514
|
// then merge with arc bounds, to place the point in context.
|
|
@@ -7927,7 +9170,7 @@
|
|
|
7927
9170
|
function Console(gui) {
|
|
7928
9171
|
var model = gui.model;
|
|
7929
9172
|
var CURSOR = '$ ';
|
|
7930
|
-
var PROMPT = 'Enter mapshaper commands or type "tips" for
|
|
9173
|
+
var PROMPT = 'Enter mapshaper commands or type "tips" for console help.';
|
|
7931
9174
|
var el = gui.container.findChild('.console').hide();
|
|
7932
9175
|
var content = el.findChild('.console-buffer');
|
|
7933
9176
|
var log = El('div').appendTo(content);
|
|
@@ -7948,6 +9191,7 @@
|
|
|
7948
9191
|
.on('keydown', function(e) {
|
|
7949
9192
|
if (e.key == 'Enter' || e.key == ' ') {
|
|
7950
9193
|
e.preventDefault();
|
|
9194
|
+
e.stopPropagation();
|
|
7951
9195
|
toggle();
|
|
7952
9196
|
}
|
|
7953
9197
|
});
|
|
@@ -7963,7 +9207,7 @@
|
|
|
7963
9207
|
this.runCommand = function(str) {
|
|
7964
9208
|
str = str.trim();
|
|
7965
9209
|
if (!str) return;
|
|
7966
|
-
|
|
9210
|
+
gui.setSidebarPanel('console');
|
|
7967
9211
|
submit(str);
|
|
7968
9212
|
};
|
|
7969
9213
|
|
|
@@ -7981,10 +9225,17 @@
|
|
|
7981
9225
|
});
|
|
7982
9226
|
|
|
7983
9227
|
function toggle() {
|
|
7984
|
-
|
|
7985
|
-
else turnOn();
|
|
9228
|
+
gui.toggleSidebarPanel('console');
|
|
7986
9229
|
}
|
|
7987
9230
|
|
|
9231
|
+
gui.on('sidebar', function(e) {
|
|
9232
|
+
if (e.name == 'console') {
|
|
9233
|
+
turnOn();
|
|
9234
|
+
} else if (e.prev == 'console') {
|
|
9235
|
+
turnOff();
|
|
9236
|
+
}
|
|
9237
|
+
});
|
|
9238
|
+
|
|
7988
9239
|
function getHistory() {
|
|
7989
9240
|
return GUI.getSavedValue('console_history') || [];
|
|
7990
9241
|
}
|
|
@@ -8004,10 +9255,30 @@
|
|
|
8004
9255
|
scrollDown();
|
|
8005
9256
|
}
|
|
8006
9257
|
|
|
9258
|
+
function toLogNode(node, cname) {
|
|
9259
|
+
var msg = El('div').appendTo(log);
|
|
9260
|
+
if (cname) {
|
|
9261
|
+
msg.addClass(cname);
|
|
9262
|
+
}
|
|
9263
|
+
msg.node().appendChild(node);
|
|
9264
|
+
scrollDown();
|
|
9265
|
+
}
|
|
9266
|
+
|
|
9267
|
+
function getStructuredConsoleMessage(args) {
|
|
9268
|
+
var arr = utils$1.toArray(args);
|
|
9269
|
+
var msg;
|
|
9270
|
+
// print() sends its arguments as one array argument.
|
|
9271
|
+
if (arr.length == 1 && Array.isArray(arr[0])) {
|
|
9272
|
+
arr = arr[0];
|
|
9273
|
+
}
|
|
9274
|
+
msg = arr[arr.length - 1];
|
|
9275
|
+
return msg && msg.type && /^mapshaper-console-/.test(msg.type) ? msg : null;
|
|
9276
|
+
}
|
|
9277
|
+
|
|
8007
9278
|
function turnOn() {
|
|
8008
9279
|
// if (!_isOpen && !model.isEmpty()) {
|
|
8009
9280
|
if (!_isOpen) {
|
|
8010
|
-
btn.addClass('active');
|
|
9281
|
+
btn.addClass('active').attr('aria-expanded', 'true');
|
|
8011
9282
|
_isOpen = true;
|
|
8012
9283
|
// Route logging output to the in-app console while it's open, so a
|
|
8013
9284
|
// user typing CLI commands here sees the results inline -- the same
|
|
@@ -8019,9 +9290,7 @@
|
|
|
8019
9290
|
// gui instances with the console open. E.g. console could close
|
|
8020
9291
|
// when an instance loses focus.
|
|
8021
9292
|
internal.setLoggingFunctions(consoleMessage, consoleError, consoleStop, consoleWarn);
|
|
8022
|
-
gui.container.addClass('console-open');
|
|
8023
9293
|
el.show();
|
|
8024
|
-
gui.dispatchEvent('resize');
|
|
8025
9294
|
input.node().focus();
|
|
8026
9295
|
history = getHistory();
|
|
8027
9296
|
}
|
|
@@ -8029,7 +9298,7 @@
|
|
|
8029
9298
|
|
|
8030
9299
|
function turnOff() {
|
|
8031
9300
|
if (_isOpen) {
|
|
8032
|
-
btn.removeClass('active');
|
|
9301
|
+
btn.removeClass('active').attr('aria-expanded', 'false');
|
|
8033
9302
|
_isOpen = false;
|
|
8034
9303
|
if (GUI.isActiveInstance(gui)) {
|
|
8035
9304
|
setLoggingForGUI(gui); // reset stop, message and error functions
|
|
@@ -8037,8 +9306,6 @@
|
|
|
8037
9306
|
el.hide();
|
|
8038
9307
|
input.node().blur();
|
|
8039
9308
|
saveHistory();
|
|
8040
|
-
gui.container.removeClass('console-open');
|
|
8041
|
-
gui.dispatchEvent('resize');
|
|
8042
9309
|
}
|
|
8043
9310
|
}
|
|
8044
9311
|
|
|
@@ -8098,7 +9365,7 @@
|
|
|
8098
9365
|
if (gui.getMode()) {
|
|
8099
9366
|
gui.clearMode(); // esc closes any open panels
|
|
8100
9367
|
} else {
|
|
8101
|
-
|
|
9368
|
+
gui.setSidebarPanel(null);
|
|
8102
9369
|
}
|
|
8103
9370
|
capture = true;
|
|
8104
9371
|
|
|
@@ -8134,8 +9401,8 @@
|
|
|
8134
9401
|
} else if (kc == 40) {
|
|
8135
9402
|
forward();
|
|
8136
9403
|
} else if (kc == 32 && (!typing || (inputText === '' && typingInConsole))) {
|
|
8137
|
-
// space bar
|
|
8138
|
-
|
|
9404
|
+
// space bar toggles the sidebar if nothing has been typed
|
|
9405
|
+
gui.toggleSidebar();
|
|
8139
9406
|
} else if (!typing && e.target != input.node() && !metaKey(e)) {
|
|
8140
9407
|
// typing returns focus, unless a meta key is down (to allow Cmd-C copy)
|
|
8141
9408
|
// or user is typing in a different input area somewhere
|
|
@@ -8152,9 +9419,9 @@
|
|
|
8152
9419
|
|
|
8153
9420
|
// various shortcuts (while not typing in an input field or editable el)
|
|
8154
9421
|
} else if (!typing) {
|
|
8155
|
-
if (kc == 32) { // space bar
|
|
9422
|
+
if (kc == 32) { // space bar toggles the sidebar
|
|
8156
9423
|
capture = true;
|
|
8157
|
-
|
|
9424
|
+
gui.toggleSidebar();
|
|
8158
9425
|
// } else if (kc == 73) { // letter i opens inspector
|
|
8159
9426
|
// gui.dispatchEvent('interaction_toggle');
|
|
8160
9427
|
} else if (kc == 72) { // letter h resets map extent
|
|
@@ -8535,9 +9802,15 @@
|
|
|
8535
9802
|
}
|
|
8536
9803
|
|
|
8537
9804
|
function consoleMessage() {
|
|
8538
|
-
var
|
|
9805
|
+
var structured = getStructuredConsoleMessage(arguments);
|
|
9806
|
+
var msg;
|
|
8539
9807
|
if (internal.loggingEnabled()) {
|
|
8540
|
-
|
|
9808
|
+
if (structured) {
|
|
9809
|
+
toLogNode(renderStructuredConsoleMessage(structured), 'console-message');
|
|
9810
|
+
} else {
|
|
9811
|
+
msg = GUI.formatMessageArgs(arguments);
|
|
9812
|
+
toLog(msg, 'console-message');
|
|
9813
|
+
}
|
|
8541
9814
|
}
|
|
8542
9815
|
}
|
|
8543
9816
|
|
|
@@ -8562,6 +9835,144 @@
|
|
|
8562
9835
|
printExample("Clear the console", "$ clear");
|
|
8563
9836
|
}
|
|
8564
9837
|
|
|
9838
|
+
function renderStructuredConsoleMessage(msg) {
|
|
9839
|
+
if (msg.type == 'mapshaper-console-help') {
|
|
9840
|
+
return renderHelpMessage(msg.lines);
|
|
9841
|
+
}
|
|
9842
|
+
if (msg.type == 'mapshaper-console-info') {
|
|
9843
|
+
return renderInfoMessage(msg.layers);
|
|
9844
|
+
}
|
|
9845
|
+
return document.createTextNode('');
|
|
9846
|
+
}
|
|
9847
|
+
|
|
9848
|
+
function renderHelpMessage(lines) {
|
|
9849
|
+
var container = document.createElement('div');
|
|
9850
|
+
var rows = [];
|
|
9851
|
+
container.className = 'console-help';
|
|
9852
|
+
(lines || []).forEach(function(line) {
|
|
9853
|
+
if (Array.isArray(line)) {
|
|
9854
|
+
rows.push(line);
|
|
9855
|
+
} else {
|
|
9856
|
+
flushRows();
|
|
9857
|
+
appendHelpLine(container, line);
|
|
9858
|
+
}
|
|
9859
|
+
});
|
|
9860
|
+
flushRows();
|
|
9861
|
+
return container;
|
|
9862
|
+
|
|
9863
|
+
function flushRows() {
|
|
9864
|
+
if (rows.length > 0) {
|
|
9865
|
+
container.appendChild(renderKeyValueTable(rows, 'console-help-table'));
|
|
9866
|
+
rows = [];
|
|
9867
|
+
}
|
|
9868
|
+
}
|
|
9869
|
+
}
|
|
9870
|
+
|
|
9871
|
+
function appendHelpLine(container, line) {
|
|
9872
|
+
var div = document.createElement('div');
|
|
9873
|
+
div.className = line ? 'console-help-line' : 'console-help-spacer';
|
|
9874
|
+
div.textContent = line || '';
|
|
9875
|
+
container.appendChild(div);
|
|
9876
|
+
}
|
|
9877
|
+
|
|
9878
|
+
function renderInfoMessage(layers) {
|
|
9879
|
+
var container = document.createElement('div');
|
|
9880
|
+
container.className = 'console-info';
|
|
9881
|
+
(layers || []).forEach(function(info) {
|
|
9882
|
+
var section = document.createElement('div');
|
|
9883
|
+
var title = document.createElement('div');
|
|
9884
|
+
section.className = 'console-info-layer';
|
|
9885
|
+
title.className = 'console-info-title';
|
|
9886
|
+
title.textContent = 'Layer: ' + (info.layer_name || '[unnamed layer]');
|
|
9887
|
+
section.appendChild(title);
|
|
9888
|
+
section.appendChild(renderKeyValueTable(getInfoRows(info), 'console-info-table'));
|
|
9889
|
+
if (!info.raster_type) {
|
|
9890
|
+
section.appendChild(renderAttributeInfoTable(info.attribute_data));
|
|
9891
|
+
}
|
|
9892
|
+
container.appendChild(section);
|
|
9893
|
+
});
|
|
9894
|
+
return container;
|
|
9895
|
+
}
|
|
9896
|
+
|
|
9897
|
+
function getInfoRows(info) {
|
|
9898
|
+
var rows = [
|
|
9899
|
+
['Type', info.raster_type ? 'raster data' : info.geometry_type || 'tabular data']
|
|
9900
|
+
];
|
|
9901
|
+
if (info.raster_type) {
|
|
9902
|
+
rows.push(['Size', utils$1.format('%,d x %,d x %,d', info.raster_width, info.raster_height, info.raster_bands)]);
|
|
9903
|
+
rows.push(['Data', info.raster_pixel_type || 'unknown']);
|
|
9904
|
+
} else {
|
|
9905
|
+
rows.push(['Records', utils$1.format('%,d', info.feature_count)]);
|
|
9906
|
+
}
|
|
9907
|
+
if (info.null_shape_count > 0) {
|
|
9908
|
+
rows.push(['Nulls', utils$1.format("%'d", info.null_shape_count)]);
|
|
9909
|
+
}
|
|
9910
|
+
if (info.raster_type || info.geometry_type && info.feature_count > info.null_shape_count) {
|
|
9911
|
+
rows.push(['Bounds', info.bbox.join(',')]);
|
|
9912
|
+
rows.push(['CRS', info.proj4]);
|
|
9913
|
+
}
|
|
9914
|
+
rows.push(['Source', info.source_file || 'n/a']);
|
|
9915
|
+
return rows;
|
|
9916
|
+
}
|
|
9917
|
+
|
|
9918
|
+
function renderAttributeInfoTable(fields) {
|
|
9919
|
+
var wrapper = document.createElement('div');
|
|
9920
|
+
var title = document.createElement('div');
|
|
9921
|
+
wrapper.className = 'console-attribute-info';
|
|
9922
|
+
title.className = 'console-info-subtitle';
|
|
9923
|
+
title.textContent = 'Attribute data';
|
|
9924
|
+
wrapper.appendChild(title);
|
|
9925
|
+
if (!fields) {
|
|
9926
|
+
var none = document.createElement('div');
|
|
9927
|
+
none.className = 'console-info-empty';
|
|
9928
|
+
none.textContent = '[none]';
|
|
9929
|
+
wrapper.appendChild(none);
|
|
9930
|
+
return wrapper;
|
|
9931
|
+
}
|
|
9932
|
+
wrapper.appendChild(renderDataTable(
|
|
9933
|
+
[['Field', 'First value']].concat(fields.map(function(o) {
|
|
9934
|
+
var valKey = 'first_value' in o ? 'first_value' : 'value';
|
|
9935
|
+
return [o.field, formatInfoValue(o[valKey])];
|
|
9936
|
+
})),
|
|
9937
|
+
'console-attribute-table'
|
|
9938
|
+
));
|
|
9939
|
+
return wrapper;
|
|
9940
|
+
}
|
|
9941
|
+
|
|
9942
|
+
function renderKeyValueTable(rows, className) {
|
|
9943
|
+
return renderDataTable(rows, className + ' console-key-value-table');
|
|
9944
|
+
}
|
|
9945
|
+
|
|
9946
|
+
function renderDataTable(rows, className) {
|
|
9947
|
+
var table = document.createElement('table');
|
|
9948
|
+
var tbody = document.createElement('tbody');
|
|
9949
|
+
table.className = className;
|
|
9950
|
+
rows.forEach(function(row, i) {
|
|
9951
|
+
var tr = document.createElement('tr');
|
|
9952
|
+
row.forEach(function(val) {
|
|
9953
|
+
var cell = document.createElement(i === 0 && rows.length > 1 && className == 'console-attribute-table' ? 'th' : 'td');
|
|
9954
|
+
cell.textContent = val == null ? '' : String(val);
|
|
9955
|
+
tr.appendChild(cell);
|
|
9956
|
+
});
|
|
9957
|
+
tbody.appendChild(tr);
|
|
9958
|
+
});
|
|
9959
|
+
table.appendChild(tbody);
|
|
9960
|
+
return table;
|
|
9961
|
+
}
|
|
9962
|
+
|
|
9963
|
+
function formatInfoValue(val) {
|
|
9964
|
+
if (val === null || val === undefined) return '';
|
|
9965
|
+
if (utils$1.isString(val)) {
|
|
9966
|
+
return "'" + val.replace(/[\r\t\n]/g, function(c) {
|
|
9967
|
+
return c == '\n' ? '\\n' : c == '\r' ? '\\r' : '\\t';
|
|
9968
|
+
}) + "'";
|
|
9969
|
+
}
|
|
9970
|
+
if (utils$1.isNumber(val) || val === true || val === false) {
|
|
9971
|
+
return String(val);
|
|
9972
|
+
}
|
|
9973
|
+
return JSON.stringify(val);
|
|
9974
|
+
}
|
|
9975
|
+
|
|
8565
9976
|
function saveRuntimeContext() {
|
|
8566
9977
|
var json = stringifyRuntimeStateContext(gui);
|
|
8567
9978
|
saveBlobToLocalFile2('mapshaper-runtime-context.json', new Blob([json], {type: 'application/json'}));
|
|
@@ -8614,7 +10025,7 @@
|
|
|
8614
10025
|
}
|
|
8615
10026
|
|
|
8616
10027
|
function getBoundsIntersection(a, b) {
|
|
8617
|
-
var c = new Bounds();
|
|
10028
|
+
var c = new Bounds$1();
|
|
8618
10029
|
if (a.intersects(b)) {
|
|
8619
10030
|
c.setBounds(Math.max(a.xmin, b.xmin), Math.max(a.ymin, b.ymin),
|
|
8620
10031
|
Math.min(a.xmax, b.xmax), Math.min(a.ymax, b.ymax));
|
|
@@ -8897,11 +10308,7 @@
|
|
|
8897
10308
|
} else {
|
|
8898
10309
|
// stack seems to change if Error is logged directly
|
|
8899
10310
|
console.error(err.stack);
|
|
8900
|
-
|
|
8901
|
-
if (err.name == 'UserError') {
|
|
8902
|
-
msg = err.message;
|
|
8903
|
-
}
|
|
8904
|
-
gui.alert(msg, 'Export failed');
|
|
10311
|
+
gui.alert(getExportErrorMessage(err), 'Export failed');
|
|
8905
10312
|
}
|
|
8906
10313
|
}).finally(function() {
|
|
8907
10314
|
gui.clearProgressMessage();
|
|
@@ -8909,6 +10316,11 @@
|
|
|
8909
10316
|
}, 20);
|
|
8910
10317
|
}
|
|
8911
10318
|
|
|
10319
|
+
function getExportErrorMessage(err) {
|
|
10320
|
+
if (err && err.message) return err.message;
|
|
10321
|
+
return 'Export failed for an unknown reason';
|
|
10322
|
+
}
|
|
10323
|
+
|
|
8912
10324
|
function getExportOpts() {
|
|
8913
10325
|
return GUI.parseFreeformOptions(getExportOptsAsString(), 'o');
|
|
8914
10326
|
}
|
|
@@ -9064,9 +10476,11 @@
|
|
|
9064
10476
|
}
|
|
9065
10477
|
|
|
9066
10478
|
function getDefaultExportFormat() {
|
|
9067
|
-
var
|
|
10479
|
+
var active = model.getActiveLayer();
|
|
10480
|
+
var dataset = active.dataset;
|
|
9068
10481
|
var inputFmt = dataset.info && dataset.info.input_formats &&
|
|
9069
10482
|
dataset.info.input_formats[0];
|
|
10483
|
+
if (active.layer && internal.layerHasRaster(active.layer)) return 'svg';
|
|
9070
10484
|
return getExportFormats().includes(inputFmt) ? inputFmt : 'geojson';
|
|
9071
10485
|
}
|
|
9072
10486
|
|
|
@@ -9148,6 +10562,7 @@
|
|
|
9148
10562
|
}
|
|
9149
10563
|
|
|
9150
10564
|
var openMenu;
|
|
10565
|
+
var openMenuId;
|
|
9151
10566
|
|
|
9152
10567
|
document.addEventListener('mousedown', function(e) {
|
|
9153
10568
|
if (e.target.classList.contains('contextmenu-item')) {
|
|
@@ -9156,17 +10571,23 @@
|
|
|
9156
10571
|
closeOpenMenu();
|
|
9157
10572
|
});
|
|
9158
10573
|
|
|
9159
|
-
function closeOpenMenu() {
|
|
10574
|
+
function closeOpenMenu(immediate) {
|
|
9160
10575
|
if (openMenu) {
|
|
9161
|
-
openMenu.close();
|
|
10576
|
+
openMenu.close(immediate);
|
|
9162
10577
|
openMenu = null;
|
|
10578
|
+
openMenuId = null;
|
|
9163
10579
|
}
|
|
9164
10580
|
}
|
|
9165
10581
|
|
|
9166
10582
|
function openContextMenu(e, lyr, parent) {
|
|
9167
10583
|
var menu = new ContextMenu(parent);
|
|
9168
|
-
|
|
10584
|
+
if (e.contextMenuId && e.contextMenuId == openMenuId) {
|
|
10585
|
+
closeOpenMenu(true);
|
|
10586
|
+
return;
|
|
10587
|
+
}
|
|
10588
|
+
closeOpenMenu(true);
|
|
9169
10589
|
menu.open(e, lyr);
|
|
10590
|
+
openMenuId = e.contextMenuId || null;
|
|
9170
10591
|
}
|
|
9171
10592
|
|
|
9172
10593
|
function ContextMenu(parentArg) {
|
|
@@ -9183,9 +10604,14 @@
|
|
|
9183
10604
|
|
|
9184
10605
|
this.close = close;
|
|
9185
10606
|
|
|
9186
|
-
function close() {
|
|
10607
|
+
function close(immediate) {
|
|
9187
10608
|
var count = _openCount;
|
|
9188
10609
|
if (!_open) return;
|
|
10610
|
+
if (immediate) {
|
|
10611
|
+
menu.hide();
|
|
10612
|
+
_open = false;
|
|
10613
|
+
return;
|
|
10614
|
+
}
|
|
9189
10615
|
setTimeout(function() {
|
|
9190
10616
|
if (count == _openCount) {
|
|
9191
10617
|
menu.hide();
|
|
@@ -9346,8 +10772,10 @@
|
|
|
9346
10772
|
function LayerControl(gui) {
|
|
9347
10773
|
var model = gui.model;
|
|
9348
10774
|
var map = gui.map;
|
|
9349
|
-
var el = gui.container.findChild(".layer-control")
|
|
10775
|
+
var el = gui.container.findChild(".layer-control");
|
|
9350
10776
|
var btn = gui.container.findChild('.layer-control-btn');
|
|
10777
|
+
var headerBtn = btn.findChild('.active-layer-label');
|
|
10778
|
+
var tab = gui.container.findChild('.layer-tab');
|
|
9351
10779
|
var isOpen = false;
|
|
9352
10780
|
var cache = new DomCache();
|
|
9353
10781
|
var pinAll = el.findChild('.pin-all'); // button for toggling layer visibility
|
|
@@ -9357,7 +10785,34 @@
|
|
|
9357
10785
|
var dragging = false;
|
|
9358
10786
|
var layerOrderSlug;
|
|
9359
10787
|
|
|
9360
|
-
|
|
10788
|
+
headerBtn.on('click', function() {
|
|
10789
|
+
toggle();
|
|
10790
|
+
}).on('keydown', function(e) {
|
|
10791
|
+
if (e.key == 'Enter' || e.key == ' ') {
|
|
10792
|
+
e.preventDefault();
|
|
10793
|
+
e.stopPropagation();
|
|
10794
|
+
toggle();
|
|
10795
|
+
}
|
|
10796
|
+
});
|
|
10797
|
+
tab.on('click', toggle).on('keydown', function(e) {
|
|
10798
|
+
if (e.key == 'Enter' || e.key == ' ') {
|
|
10799
|
+
e.preventDefault();
|
|
10800
|
+
e.stopPropagation();
|
|
10801
|
+
toggle();
|
|
10802
|
+
}
|
|
10803
|
+
});
|
|
10804
|
+
|
|
10805
|
+
function toggle() {
|
|
10806
|
+
gui.toggleSidebarPanel('layers');
|
|
10807
|
+
}
|
|
10808
|
+
|
|
10809
|
+
gui.on('sidebar', function(e) {
|
|
10810
|
+
if (e.name == 'layers') {
|
|
10811
|
+
turnOn();
|
|
10812
|
+
} else if (e.prev == 'layers') {
|
|
10813
|
+
turnOff();
|
|
10814
|
+
}
|
|
10815
|
+
});
|
|
9361
10816
|
|
|
9362
10817
|
// kludge to show menu button after initial import dialog is dismissed
|
|
9363
10818
|
gui.on('mode', function(e) {
|
|
@@ -9443,53 +10898,33 @@
|
|
|
9443
10898
|
}
|
|
9444
10899
|
|
|
9445
10900
|
function turnOn() {
|
|
10901
|
+
if (isOpen) return;
|
|
9446
10902
|
isOpen = true;
|
|
9447
|
-
|
|
10903
|
+
tab.addClass('active').attr('aria-expanded', 'true');
|
|
9448
10904
|
render();
|
|
9449
10905
|
el.show();
|
|
9450
10906
|
}
|
|
9451
10907
|
|
|
9452
10908
|
function turnOff() {
|
|
10909
|
+
if (!isOpen) return;
|
|
9453
10910
|
stopDragging();
|
|
9454
10911
|
isOpen = false;
|
|
10912
|
+
tab.removeClass('active').attr('aria-expanded', 'false');
|
|
9455
10913
|
el.hide();
|
|
9456
10914
|
}
|
|
9457
10915
|
|
|
9458
10916
|
function updateMenuBtn() {
|
|
9459
10917
|
var lyr = model.getActiveLayer()?.layer;
|
|
9460
10918
|
var lyrName = lyr?.name || '';
|
|
9461
|
-
var menuTitle = lyrName || lyr && '[unnamed layer]' || '[no data]';
|
|
9462
10919
|
var pageTitle = lyrName || 'mapshaper';
|
|
9463
|
-
btn.classed('active',
|
|
10920
|
+
btn.classed('active', !!lyr);
|
|
10921
|
+
headerBtn.text(lyr ? 'Active: ' + formatLayerNameForDisplay(lyr.name) : '');
|
|
9464
10922
|
window.document.title = pageTitle;
|
|
9465
10923
|
}
|
|
9466
10924
|
|
|
9467
10925
|
function render() {
|
|
9468
10926
|
renderLayerList();
|
|
9469
|
-
renderSourceFileList();
|
|
9470
|
-
}
|
|
9471
|
-
|
|
9472
|
-
function renderSourceFileList() {
|
|
9473
10927
|
el.findChild('.no-layer-note').classed('hidden', model.getActiveLayer());
|
|
9474
|
-
el.findChild('.source-file-section').classed('hidden', !model.getActiveLayer());
|
|
9475
|
-
var list = el.findChild('.file-list');
|
|
9476
|
-
var files = [];
|
|
9477
|
-
list.empty();
|
|
9478
|
-
model.forEachLayer(function(lyr, dataset) {
|
|
9479
|
-
var file = internal.getLayerSourceFile(lyr, dataset);
|
|
9480
|
-
if (!file || files.includes(file)) return;
|
|
9481
|
-
files.push(file);
|
|
9482
|
-
var warnings = getWarnings(lyr, dataset);
|
|
9483
|
-
var html = '<div class="layer-item">';
|
|
9484
|
-
html += rowHTML('name', file);
|
|
9485
|
-
if (warnings) {
|
|
9486
|
-
// html += rowHTML('problems', warnings, 'layer-problems');
|
|
9487
|
-
html += rowHTML('', warnings, 'layer-problems');
|
|
9488
|
-
}
|
|
9489
|
-
html += '</div>';
|
|
9490
|
-
list.appendChild(El('div').html(html).firstChild());
|
|
9491
|
-
});
|
|
9492
|
-
|
|
9493
10928
|
}
|
|
9494
10929
|
|
|
9495
10930
|
function renderLayerList() {
|
|
@@ -9550,10 +10985,10 @@
|
|
|
9550
10985
|
html = '<!-- ' + lyr.menu_id + '--><div class="' + classes + '">';
|
|
9551
10986
|
html += rowHTML('name', '<span class="layer-name colored-text dot-underline">' + formatLayerNameForDisplay(lyr.name) + '</span>', 'row1');
|
|
9552
10987
|
html += rowHTML('contents', describeLyr(lyr, dataset));
|
|
9553
|
-
|
|
10988
|
+
html += '<span class="more-btn layer-btn" role="button" tabindex="0" aria-label="More layer options"></span>';
|
|
9554
10989
|
if (opts.pinnable) {
|
|
9555
|
-
html += '<img class="eye-btn black-eye" draggable="false" src="images/eye.png">';
|
|
9556
|
-
html += '<img class="eye-btn green-eye" draggable="false" src="images/eye2.png">';
|
|
10990
|
+
html += '<img class="eye-btn black-eye layer-btn" draggable="false" src="images/eye.png">';
|
|
10991
|
+
html += '<img class="eye-btn green-eye layer-btn" draggable="false" src="images/eye2.png">';
|
|
9557
10992
|
}
|
|
9558
10993
|
html += '</div>';
|
|
9559
10994
|
return html;
|
|
@@ -9561,8 +10996,10 @@
|
|
|
9561
10996
|
|
|
9562
10997
|
function initMouseEvents(entry, id, pinnable) {
|
|
9563
10998
|
entry.on('mouseover', init);
|
|
10999
|
+
entry.on('focusin', init);
|
|
9564
11000
|
function init() {
|
|
9565
11001
|
entry.removeEventListener('mouseover', init);
|
|
11002
|
+
entry.removeEventListener('focusin', init);
|
|
9566
11003
|
initMouseEvents2(entry, id, pinnable);
|
|
9567
11004
|
}
|
|
9568
11005
|
}
|
|
@@ -9578,10 +11015,6 @@
|
|
|
9578
11015
|
}
|
|
9579
11016
|
// start dragging when button is first pressed
|
|
9580
11017
|
if (e.buttons && !dragTargetId) {
|
|
9581
|
-
// don't start dragging if pointer is over the close button
|
|
9582
|
-
// (before, clicking this button wqs finicky -- the mouse had to remain
|
|
9583
|
-
// perfectly still between mousedown and mouseup)
|
|
9584
|
-
if (El(e.target).hasClass('close-btn')) return;
|
|
9585
11018
|
dragTargetId = id;
|
|
9586
11019
|
entry.addClass('drag-target');
|
|
9587
11020
|
}
|
|
@@ -9606,6 +11039,7 @@
|
|
|
9606
11039
|
}
|
|
9607
11040
|
|
|
9608
11041
|
function initMouseEvents2(entry, id, pinnable) {
|
|
11042
|
+
var moreBtn = entry.findChild('.more-btn');
|
|
9609
11043
|
initLayerDragging(entry, id);
|
|
9610
11044
|
|
|
9611
11045
|
function deleteLayer() {
|
|
@@ -9630,7 +11064,7 @@
|
|
|
9630
11064
|
}
|
|
9631
11065
|
}
|
|
9632
11066
|
|
|
9633
|
-
function selectLayer(
|
|
11067
|
+
function selectLayer() {
|
|
9634
11068
|
var target = findLayerById(id);
|
|
9635
11069
|
// don't select if user is typing or dragging
|
|
9636
11070
|
if (GUI.textIsSelected() || dragging) return;
|
|
@@ -9639,17 +11073,24 @@
|
|
|
9639
11073
|
if (!map.isActiveLayer(target.layer)) {
|
|
9640
11074
|
model.selectLayer(target.layer, target.dataset);
|
|
9641
11075
|
}
|
|
9642
|
-
// close menu after a delay
|
|
9643
|
-
if (closeMenu === true) setTimeout(function() {
|
|
9644
|
-
gui.clearMode();
|
|
9645
|
-
}, 230);
|
|
9646
11076
|
}
|
|
9647
11077
|
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
11078
|
+
function openLayerMenu(e) {
|
|
11079
|
+
var menuEvent = e;
|
|
11080
|
+
e.stopPropagation();
|
|
11081
|
+
if (!isFinite(e.pageX) || !isFinite(e.pageY)) {
|
|
11082
|
+
var rect = moreBtn.node().getBoundingClientRect();
|
|
11083
|
+
menuEvent = {
|
|
11084
|
+
pageX: rect.right,
|
|
11085
|
+
pageY: rect.top + rect.height / 2
|
|
11086
|
+
};
|
|
11087
|
+
}
|
|
11088
|
+
menuEvent.deleteLayer = deleteLayer;
|
|
11089
|
+
menuEvent.selectLayer = selectLayer;
|
|
11090
|
+
menuEvent.contextMenuId = 'layer-' + id;
|
|
11091
|
+
openContextMenu(menuEvent, null, null);
|
|
11092
|
+
}
|
|
11093
|
+
|
|
9653
11094
|
|
|
9654
11095
|
if (pinnable) {
|
|
9655
11096
|
// init pin button
|
|
@@ -9692,18 +11133,22 @@
|
|
|
9692
11133
|
renameLayer(target, str);
|
|
9693
11134
|
});
|
|
9694
11135
|
|
|
11136
|
+
moreBtn.on('mousedown', function(e) {
|
|
11137
|
+
e.stopPropagation();
|
|
11138
|
+
});
|
|
11139
|
+
GUI.onClick(moreBtn, openLayerMenu);
|
|
11140
|
+
moreBtn.on('keydown', function(e) {
|
|
11141
|
+
if (e.key == 'Enter' || e.key == ' ') {
|
|
11142
|
+
e.preventDefault();
|
|
11143
|
+
openLayerMenu(e);
|
|
11144
|
+
}
|
|
11145
|
+
});
|
|
11146
|
+
|
|
9695
11147
|
// init click-to-select
|
|
9696
11148
|
GUI.onClick(entry, function() {
|
|
9697
|
-
selectLayer(
|
|
11149
|
+
selectLayer();
|
|
9698
11150
|
});
|
|
9699
11151
|
|
|
9700
|
-
GUI.onContextClick(entry, function(e) {
|
|
9701
|
-
e.deleteLayer = deleteLayer;
|
|
9702
|
-
e.selectLayer = selectLayer;
|
|
9703
|
-
// contextMenu.open(e);
|
|
9704
|
-
// openContextMenu(e, null, entry.node())
|
|
9705
|
-
openContextMenu(e, null, null);
|
|
9706
|
-
});
|
|
9707
11152
|
}
|
|
9708
11153
|
|
|
9709
11154
|
function describeLyr(lyr, dataset) {
|
|
@@ -9714,9 +11159,13 @@
|
|
|
9714
11159
|
type = 'data record';
|
|
9715
11160
|
} else if (lyr.geometry_type) {
|
|
9716
11161
|
type = lyr.geometry_type + ' feature';
|
|
11162
|
+
} else if (internal.layerHasRaster(lyr)) {
|
|
11163
|
+
type = 'raster layer';
|
|
9717
11164
|
}
|
|
9718
11165
|
if (isFrame) {
|
|
9719
11166
|
str = 'map frame';
|
|
11167
|
+
} else if (internal.layerHasRaster(lyr)) {
|
|
11168
|
+
str = utils$1.format('%,d x %,d %s', getRasterWidth(lyr.raster), getRasterHeight(lyr.raster), type);
|
|
9720
11169
|
} else if (type) {
|
|
9721
11170
|
str = utils$1.format('%,d %s%s', n, type, utils$1.pluralSuffix(n));
|
|
9722
11171
|
} else {
|
|
@@ -9750,32 +11199,8 @@
|
|
|
9750
11199
|
}
|
|
9751
11200
|
}
|
|
9752
11201
|
|
|
9753
|
-
function getWarnings(lyr, dataset) {
|
|
9754
|
-
var file = internal.getLayerSourceFile(lyr, dataset);
|
|
9755
|
-
var missing = [];
|
|
9756
|
-
var msg;
|
|
9757
|
-
// show missing file warning for first layer in dataset
|
|
9758
|
-
// (assuming it represents the content of the original file)
|
|
9759
|
-
if (utils$1.endsWith(file, '.shp') && lyr == dataset.layers[0]) {
|
|
9760
|
-
if (!lyr.data) {
|
|
9761
|
-
missing.push('.dbf');
|
|
9762
|
-
}
|
|
9763
|
-
if (!dataset.info.wkt1 && !dataset.info.crs) {
|
|
9764
|
-
missing.push('.prj');
|
|
9765
|
-
}
|
|
9766
|
-
}
|
|
9767
|
-
if (missing.length) {
|
|
9768
|
-
msg = 'missing ' + missing.join(' and ') + ' data';
|
|
9769
|
-
}
|
|
9770
|
-
return msg;
|
|
9771
|
-
}
|
|
9772
|
-
|
|
9773
|
-
function describeSrc(lyr, dataset) {
|
|
9774
|
-
return internal.getLayerSourceFile(lyr, dataset);
|
|
9775
|
-
}
|
|
9776
|
-
|
|
9777
11202
|
function isPinnable(lyr) {
|
|
9778
|
-
return internal.layerIsGeometric(lyr) || internal.layerHasFurniture(lyr);
|
|
11203
|
+
return internal.layerIsGeometric(lyr) || internal.layerHasRaster(lyr) || internal.layerHasFurniture(lyr);
|
|
9779
11204
|
}
|
|
9780
11205
|
|
|
9781
11206
|
function rowHTML(c1, c2, cname) {
|
|
@@ -9970,7 +11395,7 @@
|
|
|
9970
11395
|
function getRestoreDataNote(gui) {
|
|
9971
11396
|
var stats = getUndoPayloadStats(gui);
|
|
9972
11397
|
var bytes = stats ? stats.ownBytes || 0 : 0;
|
|
9973
|
-
return '
|
|
11398
|
+
return 'restore data stored on-disk: ' + formatBytes(bytes);
|
|
9974
11399
|
}
|
|
9975
11400
|
|
|
9976
11401
|
function getUndoPayloadStats(gui) {
|
|
@@ -10868,6 +12293,7 @@
|
|
|
10868
12293
|
rectangles: ['info', 'selection', 'box', 'rectangles', 'edit_polygons'],
|
|
10869
12294
|
lines: ['info', 'selection', 'box', 'edit_lines'], // 'snip_lines'
|
|
10870
12295
|
table: ['info', 'selection'],
|
|
12296
|
+
raster: ['box'],
|
|
10871
12297
|
labels: ['info', 'selection', 'box', 'labels', 'edit_points'],
|
|
10872
12298
|
points: ['info', 'selection', 'box', 'edit_points'] // , 'add-points'
|
|
10873
12299
|
};
|
|
@@ -10988,6 +12414,9 @@
|
|
|
10988
12414
|
if (!o || !o.layer) {
|
|
10989
12415
|
return menus.empty; // TODO: more sensible handling of missing layer
|
|
10990
12416
|
}
|
|
12417
|
+
if (internal.layerHasRaster(o.layer)) {
|
|
12418
|
+
return menus.raster;
|
|
12419
|
+
}
|
|
10991
12420
|
if (!o.layer.geometry_type) {
|
|
10992
12421
|
return menus.table;
|
|
10993
12422
|
}
|
|
@@ -11991,8 +13420,8 @@
|
|
|
11991
13420
|
// DELETE key
|
|
11992
13421
|
// delete pinned feature
|
|
11993
13422
|
// to help protect against inadvertent deletion, don't delete
|
|
11994
|
-
// when
|
|
11995
|
-
if (!gui.getMode() && !gui.
|
|
13423
|
+
// when a sidebar panel or popup menu is open
|
|
13424
|
+
if (!gui.getMode() && !gui.sidebarPanelIsOpen()) {
|
|
11996
13425
|
internal.deleteFeatureById(targetLayer, pinnedId());
|
|
11997
13426
|
self.clearSelection();
|
|
11998
13427
|
gui.model.updated({flags: 'filter'}); // signal map to update
|
|
@@ -13402,7 +14831,7 @@
|
|
|
13402
14831
|
|
|
13403
14832
|
// @box Bounds with pixels from t,l corner of map area.
|
|
13404
14833
|
function zoomToBbox(bbox) {
|
|
13405
|
-
var bounds = new Bounds(bbox),
|
|
14834
|
+
var bounds = new Bounds$1(bbox),
|
|
13406
14835
|
pct = Math.max(bounds.width() / ext.width(), bounds.height() / ext.height()),
|
|
13407
14836
|
fx = bounds.centerX() / ext.width() * (1 + pct) - pct / 2,
|
|
13408
14837
|
fy = bounds.centerY() / ext.height() * (1 + pct) - pct / 2;
|
|
@@ -15552,7 +16981,7 @@
|
|
|
15552
16981
|
// Get params for converting geographic coords to pixel coords
|
|
15553
16982
|
this.getTransform = function(pixScale) {
|
|
15554
16983
|
// get transform (y-flipped);
|
|
15555
|
-
var viewBounds = new Bounds(0, 0, _position.width(), _position.height());
|
|
16984
|
+
var viewBounds = new Bounds$1(0, 0, _position.width(), _position.height());
|
|
15556
16985
|
if (pixScale) {
|
|
15557
16986
|
viewBounds.xmax *= pixScale;
|
|
15558
16987
|
viewBounds.ymax *= pixScale;
|
|
@@ -15562,7 +16991,7 @@
|
|
|
15562
16991
|
|
|
15563
16992
|
// k scales the size of the bbox (used by gui to control fp error when zoomed very far)
|
|
15564
16993
|
this.getBounds = function(k) {
|
|
15565
|
-
if (!_fullBounds) return new Bounds();
|
|
16994
|
+
if (!_fullBounds) return new Bounds$1();
|
|
15566
16995
|
return calcBounds(_cx, _cy, _scale / (k || 1));
|
|
15567
16996
|
};
|
|
15568
16997
|
|
|
@@ -15577,7 +17006,7 @@
|
|
|
15577
17006
|
var b = _fullBounds = fullBounds;
|
|
15578
17007
|
if (!b.hasBounds()) return; // kludge
|
|
15579
17008
|
if (strictBounds) {
|
|
15580
|
-
_strictBounds = Array.isArray(strictBounds) ? new Bounds(strictBounds) : strictBounds;
|
|
17009
|
+
_strictBounds = Array.isArray(strictBounds) ? new Bounds$1(strictBounds) : strictBounds;
|
|
15581
17010
|
} else {
|
|
15582
17011
|
_strictBounds = null;
|
|
15583
17012
|
}
|
|
@@ -15607,7 +17036,7 @@
|
|
|
15607
17036
|
|
|
15608
17037
|
this.getSymbolScale = function() {
|
|
15609
17038
|
if (!_frame) return 1;
|
|
15610
|
-
var bounds = new Bounds(_frame.bbox);
|
|
17039
|
+
var bounds = new Bounds$1(_frame.bbox);
|
|
15611
17040
|
var bounds2 = bounds.clone().transform(this.getTransform());
|
|
15612
17041
|
return bounds2.width() / _frame.width;
|
|
15613
17042
|
};
|
|
@@ -15678,12 +17107,12 @@
|
|
|
15678
17107
|
}
|
|
15679
17108
|
var w = full.width() / scale;
|
|
15680
17109
|
var h = full.height() / scale;
|
|
15681
|
-
return new Bounds(cx - w/2, cy - h/2, cx + w/2, cy + h/2);
|
|
17110
|
+
return new Bounds$1(cx - w/2, cy - h/2, cx + w/2, cy + h/2);
|
|
15682
17111
|
}
|
|
15683
17112
|
|
|
15684
17113
|
// Calculate viewport bounds from frame data
|
|
15685
17114
|
function fillOutFrameBounds(frame) {
|
|
15686
|
-
var bounds = new Bounds(frame.bbox);
|
|
17115
|
+
var bounds = new Bounds$1(frame.bbox);
|
|
15687
17116
|
var kx = _position.width() / frame.width;
|
|
15688
17117
|
var ky = _position.height() / frame.height;
|
|
15689
17118
|
bounds.scale(kx, ky);
|
|
@@ -15695,7 +17124,7 @@
|
|
|
15695
17124
|
hpix = _position.height() - 2 * marginpix,
|
|
15696
17125
|
xpad, ypad, b2;
|
|
15697
17126
|
if (wpix <= 0 || hpix <= 0) {
|
|
15698
|
-
return new Bounds(0, 0, 0, 0);
|
|
17127
|
+
return new Bounds$1(0, 0, 0, 0);
|
|
15699
17128
|
}
|
|
15700
17129
|
b = b.clone();
|
|
15701
17130
|
b2 = b.clone();
|
|
@@ -15733,6 +17162,198 @@
|
|
|
15733
17162
|
|
|
15734
17163
|
utils$1.inherit(MapExtent, EventDispatcher);
|
|
15735
17164
|
|
|
17165
|
+
var MAX_VIEWPORT_PREVIEW_PIXELS = 6e6;
|
|
17166
|
+
var cache = new WeakMap();
|
|
17167
|
+
var requestId = 0;
|
|
17168
|
+
|
|
17169
|
+
function getCachedRasterViewportPreview(layer, ext) {
|
|
17170
|
+
var entry = cache.get(layer);
|
|
17171
|
+
var params = getRasterViewportPreviewParams(layer, ext);
|
|
17172
|
+
if (!entry || !params || entry.key != params.key) return null;
|
|
17173
|
+
return entry.preview;
|
|
17174
|
+
}
|
|
17175
|
+
|
|
17176
|
+
function scheduleRasterViewportPreview(layer, ext, onReady) {
|
|
17177
|
+
var params = getRasterViewportPreviewParams(layer, ext);
|
|
17178
|
+
var entry = cache.get(layer);
|
|
17179
|
+
var id, stats, timing, preview;
|
|
17180
|
+
if (!params || !params.needed) return;
|
|
17181
|
+
if (entry && entry.key == params.key) return;
|
|
17182
|
+
id = ++requestId;
|
|
17183
|
+
cache.set(layer, {key: params.key, pending: id});
|
|
17184
|
+
setTimeout(function() {
|
|
17185
|
+
var current = cache.get(layer);
|
|
17186
|
+
if (!current || current.pending != id) return;
|
|
17187
|
+
timing = {};
|
|
17188
|
+
stats = getCachedRasterScalingStats(params, timing);
|
|
17189
|
+
timing.renderStart = getTimer();
|
|
17190
|
+
preview = renderRasterViewportPreview(params.grid, params.recipe, params.bbox, params.width, params.height, stats);
|
|
17191
|
+
timing.renderMs = getTimer() - timing.renderStart;
|
|
17192
|
+
logRasterPreviewTiming(params, timing);
|
|
17193
|
+
current = cache.get(layer);
|
|
17194
|
+
if (!preview || !current || current.pending != id) return;
|
|
17195
|
+
cache.set(layer, {
|
|
17196
|
+
key: params.key,
|
|
17197
|
+
preview: preview
|
|
17198
|
+
});
|
|
17199
|
+
onReady();
|
|
17200
|
+
}, 0);
|
|
17201
|
+
}
|
|
17202
|
+
|
|
17203
|
+
function invalidateRasterViewportPreview(layer) {
|
|
17204
|
+
cache.delete(layer);
|
|
17205
|
+
}
|
|
17206
|
+
|
|
17207
|
+
function getRasterViewportPreviewParams(layer, ext) {
|
|
17208
|
+
var raster = layer && layer.raster;
|
|
17209
|
+
var grid = getRasterGrid(raster);
|
|
17210
|
+
var rasterBbox = getRasterBBox(raster);
|
|
17211
|
+
var mapBbox = ext.getBounds().toArray();
|
|
17212
|
+
var visibleBbox = rasterBbox && intersectBboxes(rasterBbox, mapBbox);
|
|
17213
|
+
var preview = getRasterPreview(raster);
|
|
17214
|
+
var recipe, pixelRatio, t, p1, p2, displayWidth, displayHeight, crop, width, height, scale, key, needed;
|
|
17215
|
+
if (!grid || !grid.samples || !visibleBbox || !grid.bbox) return null;
|
|
17216
|
+
if (!supportsNorthUpRaster(grid)) return null;
|
|
17217
|
+
crop = getRasterSourceWindow(grid, visibleBbox);
|
|
17218
|
+
if (!crop) return null;
|
|
17219
|
+
visibleBbox = crop.bbox;
|
|
17220
|
+
recipe = getRasterViewRecipe(grid, raster.view && raster.view.recipe);
|
|
17221
|
+
pixelRatio = GUI.getPixelRatio();
|
|
17222
|
+
t = ext.getTransform(pixelRatio);
|
|
17223
|
+
p1 = t.transform(mapBbox[0], mapBbox[3]);
|
|
17224
|
+
p2 = t.transform(mapBbox[2], mapBbox[1]);
|
|
17225
|
+
displayWidth = Math.max(1, Math.round(Math.abs(p2[0] - p1[0])));
|
|
17226
|
+
displayHeight = Math.max(1, Math.round(Math.abs(p2[1] - p1[1])));
|
|
17227
|
+
width = Math.min(displayWidth, crop.width);
|
|
17228
|
+
height = Math.min(displayHeight, crop.height);
|
|
17229
|
+
scale = Math.min(1, Math.sqrt(MAX_VIEWPORT_PREVIEW_PIXELS / (width * height)));
|
|
17230
|
+
width = Math.max(1, Math.round(width * scale));
|
|
17231
|
+
height = Math.max(1, Math.round(height * scale));
|
|
17232
|
+
needed = viewportPreviewIsSharper(preview, grid, visibleBbox, width, height);
|
|
17233
|
+
key = [
|
|
17234
|
+
visibleBbox.map(roundKeyPart).join(','),
|
|
17235
|
+
width,
|
|
17236
|
+
height,
|
|
17237
|
+
pixelRatio,
|
|
17238
|
+
grid.width,
|
|
17239
|
+
grid.height,
|
|
17240
|
+
grid.samples && grid.samples.length,
|
|
17241
|
+
recipe.type,
|
|
17242
|
+
recipe.scaling,
|
|
17243
|
+
recipe.scaleRange && recipe.scaleRange.join(','),
|
|
17244
|
+
recipe.percentileRange && recipe.percentileRange.join(',')
|
|
17245
|
+
].join('|');
|
|
17246
|
+
return {
|
|
17247
|
+
key: key,
|
|
17248
|
+
needed: needed,
|
|
17249
|
+
raster: raster,
|
|
17250
|
+
grid: grid,
|
|
17251
|
+
recipe: recipe,
|
|
17252
|
+
bbox: visibleBbox,
|
|
17253
|
+
width: width,
|
|
17254
|
+
height: height,
|
|
17255
|
+
sourceWidth: crop.width,
|
|
17256
|
+
sourceHeight: crop.height
|
|
17257
|
+
};
|
|
17258
|
+
}
|
|
17259
|
+
|
|
17260
|
+
function getCachedRasterScalingStats(params, timing) {
|
|
17261
|
+
var cached = params.raster.view && params.raster.view.scalingStats;
|
|
17262
|
+
var key;
|
|
17263
|
+
if (params.recipe.scaling == 'none') {
|
|
17264
|
+
timing.statsMs = 0;
|
|
17265
|
+
timing.statsSource = 'none';
|
|
17266
|
+
return null;
|
|
17267
|
+
}
|
|
17268
|
+
key = getRasterScalingStatsKey(params.grid, params.recipe);
|
|
17269
|
+
if (cached && cached.key == key) {
|
|
17270
|
+
timing.statsMs = 0;
|
|
17271
|
+
timing.statsSource = 'raster-view-cache';
|
|
17272
|
+
return cached.stats;
|
|
17273
|
+
}
|
|
17274
|
+
timing.statsSource = 'computed';
|
|
17275
|
+
timing.statsStart = getTimer();
|
|
17276
|
+
cached = getRasterViewScalingStats(params.raster, params.recipe);
|
|
17277
|
+
timing.statsMs = getTimer() - timing.statsStart;
|
|
17278
|
+
return cached;
|
|
17279
|
+
}
|
|
17280
|
+
|
|
17281
|
+
function logRasterPreviewTiming(params, timing) {
|
|
17282
|
+
if (!rasterDebugIsOn()) return;
|
|
17283
|
+
console.log([
|
|
17284
|
+
'Raster viewport preview:',
|
|
17285
|
+
params.width + 'x' + params.height,
|
|
17286
|
+
'from',
|
|
17287
|
+
params.sourceWidth + 'x' + params.sourceHeight,
|
|
17288
|
+
'source px,',
|
|
17289
|
+
'stats=' + formatMs(timing.statsMs),
|
|
17290
|
+
'(' + timing.statsSource + '),',
|
|
17291
|
+
'render=' + formatMs(timing.renderMs) + ',',
|
|
17292
|
+
'total=' + formatMs(timing.statsMs + timing.renderMs) + ',',
|
|
17293
|
+
'scaling=' + params.recipe.scaling,
|
|
17294
|
+
'type=' + params.recipe.type
|
|
17295
|
+
].join(' '));
|
|
17296
|
+
}
|
|
17297
|
+
|
|
17298
|
+
function rasterDebugIsOn() {
|
|
17299
|
+
var vars = GUI.getUrlVars();
|
|
17300
|
+
return vars['raster-debug'] === true || vars['raster-debug'] == '1' || vars.raster_debug === true || vars.raster_debug == '1';
|
|
17301
|
+
}
|
|
17302
|
+
|
|
17303
|
+
function getTimer() {
|
|
17304
|
+
return typeof performance != 'undefined' && performance.now ? performance.now() : Date.now();
|
|
17305
|
+
}
|
|
17306
|
+
|
|
17307
|
+
function formatMs(ms) {
|
|
17308
|
+
return Math.round(ms * 10) / 10 + 'ms';
|
|
17309
|
+
}
|
|
17310
|
+
|
|
17311
|
+
function supportsNorthUpRaster(grid) {
|
|
17312
|
+
var t = grid.transform;
|
|
17313
|
+
return !t || t[1] === 0 && t[3] === 0;
|
|
17314
|
+
}
|
|
17315
|
+
|
|
17316
|
+
function getRasterSourcePixelSize(grid, bbox) {
|
|
17317
|
+
var crop = getRasterSourceWindow(grid, bbox);
|
|
17318
|
+
return crop ? {width: crop.width, height: crop.height} : {width: 0, height: 0};
|
|
17319
|
+
}
|
|
17320
|
+
|
|
17321
|
+
function getRasterSourceWindow(grid, bbox) {
|
|
17322
|
+
var rb = grid.bbox;
|
|
17323
|
+
var dx = (rb[2] - rb[0]) / grid.width;
|
|
17324
|
+
var dy = (rb[3] - rb[1]) / grid.height;
|
|
17325
|
+
var x0 = Math.max(0, Math.floor((bbox[0] - rb[0]) / dx));
|
|
17326
|
+
var x1 = Math.min(grid.width, Math.ceil((bbox[2] - rb[0]) / dx));
|
|
17327
|
+
var y0 = Math.max(0, Math.floor((rb[3] - bbox[3]) / dy));
|
|
17328
|
+
var y1 = Math.min(grid.height, Math.ceil((rb[3] - bbox[1]) / dy));
|
|
17329
|
+
if (x1 <= x0 || y1 <= y0) return null;
|
|
17330
|
+
return {
|
|
17331
|
+
x: x0,
|
|
17332
|
+
y: y0,
|
|
17333
|
+
width: x1 - x0,
|
|
17334
|
+
height: y1 - y0,
|
|
17335
|
+
bbox: [
|
|
17336
|
+
rb[0] + x0 * dx,
|
|
17337
|
+
rb[3] - y1 * dy,
|
|
17338
|
+
rb[0] + x1 * dx,
|
|
17339
|
+
rb[3] - y0 * dy
|
|
17340
|
+
]
|
|
17341
|
+
};
|
|
17342
|
+
}
|
|
17343
|
+
|
|
17344
|
+
function viewportPreviewIsSharper(preview, grid, bbox, width, height) {
|
|
17345
|
+
var crop = getRasterSourcePixelSize(grid, bbox);
|
|
17346
|
+
var previewWidth = preview && preview.width || 0;
|
|
17347
|
+
var previewHeight = preview && preview.height || 0;
|
|
17348
|
+
var fallbackWidth = crop.width * previewWidth / grid.width;
|
|
17349
|
+
var fallbackHeight = crop.height * previewHeight / grid.height;
|
|
17350
|
+
return width > fallbackWidth * 1.25 || height > fallbackHeight * 1.25;
|
|
17351
|
+
}
|
|
17352
|
+
|
|
17353
|
+
function roundKeyPart(val) {
|
|
17354
|
+
return Math.round(val * 1e9) / 1e9;
|
|
17355
|
+
}
|
|
17356
|
+
|
|
15736
17357
|
var hatches = {}; // cached patterns
|
|
15737
17358
|
|
|
15738
17359
|
function getCanvasFillEffect(ctx, shp, arcs, ext, style) {
|
|
@@ -15935,7 +17556,7 @@
|
|
|
15935
17556
|
function getShapeFilter(arcs, shapes, ext) {
|
|
15936
17557
|
if (ext.scale() < 1.1) return null;
|
|
15937
17558
|
var view = ext.getBounds();
|
|
15938
|
-
var b = new Bounds();
|
|
17559
|
+
var b = new Bounds$1();
|
|
15939
17560
|
return function(i) {
|
|
15940
17561
|
var shp = shapes[i];
|
|
15941
17562
|
if (!shp) return false;
|
|
@@ -15988,6 +17609,22 @@
|
|
|
15988
17609
|
_ext = extent;
|
|
15989
17610
|
};
|
|
15990
17611
|
|
|
17612
|
+
_self.drawRasterLayer = function(layer, opts) {
|
|
17613
|
+
var raster = layer.raster;
|
|
17614
|
+
var options = opts || {};
|
|
17615
|
+
var preview = options.action == 'nav' ? null : getCachedRasterViewportPreview(layer, _ext);
|
|
17616
|
+
var bbox = preview && preview.bbox;
|
|
17617
|
+
if (!preview) {
|
|
17618
|
+
preview = raster && getRasterPreview(raster);
|
|
17619
|
+
bbox = raster && getRasterBBox(raster);
|
|
17620
|
+
if (options.action != 'nav' && options.onViewportPreviewReady) {
|
|
17621
|
+
scheduleRasterViewportPreview(layer, _ext, options.onViewportPreviewReady);
|
|
17622
|
+
}
|
|
17623
|
+
}
|
|
17624
|
+
if (!preview || !preview.pixels || !bbox) return;
|
|
17625
|
+
drawRasterPreview(preview, bbox);
|
|
17626
|
+
};
|
|
17627
|
+
|
|
15991
17628
|
/*
|
|
15992
17629
|
// Original function, not optimized
|
|
15993
17630
|
_self.drawStyledPaths = function(shapes, arcs, style) {
|
|
@@ -16184,6 +17821,15 @@
|
|
|
16184
17821
|
}
|
|
16185
17822
|
}
|
|
16186
17823
|
|
|
17824
|
+
function drawRasterPreview(preview, bbox) {
|
|
17825
|
+
var img = getRasterCanvas(preview);
|
|
17826
|
+
var t = _ext.getTransform(GUI.getPixelRatio());
|
|
17827
|
+
var p1 = t.transform(bbox[0], bbox[3]);
|
|
17828
|
+
var p2 = t.transform(bbox[2], bbox[1]);
|
|
17829
|
+
_ctx.imageSmoothingEnabled = true;
|
|
17830
|
+
_ctx.drawImage(img, p1[0], p1[1], p2[0] - p1[0], p2[1] - p1[1]);
|
|
17831
|
+
}
|
|
17832
|
+
|
|
16187
17833
|
// TODO: consider using drawStyledPaths(), which draws paths in batches
|
|
16188
17834
|
// for faster Canvas rendering. Downside: changes stacking order, which
|
|
16189
17835
|
// is bad if circles are graduated.
|
|
@@ -16299,6 +17945,19 @@
|
|
|
16299
17945
|
};
|
|
16300
17946
|
}
|
|
16301
17947
|
|
|
17948
|
+
function getRasterCanvas(preview) {
|
|
17949
|
+
if (preview.canvas) return preview.canvas;
|
|
17950
|
+
var canvas = document.createElement('canvas');
|
|
17951
|
+
var ctx = canvas.getContext('2d');
|
|
17952
|
+
var imageData;
|
|
17953
|
+
canvas.width = preview.width;
|
|
17954
|
+
canvas.height = preview.height;
|
|
17955
|
+
imageData = new ImageData(preview.pixels, preview.width, preview.height);
|
|
17956
|
+
ctx.putImageData(imageData, 0, 0);
|
|
17957
|
+
preview.canvas = canvas;
|
|
17958
|
+
return canvas;
|
|
17959
|
+
}
|
|
17960
|
+
|
|
16302
17961
|
function drawCircle(x, y, radius, ctx) {
|
|
16303
17962
|
if (radius > 0) {
|
|
16304
17963
|
ctx.moveTo(x + radius, y);
|
|
@@ -16599,6 +18258,7 @@
|
|
|
16599
18258
|
var _settleTimer = null;
|
|
16600
18259
|
var _redrawPending = false;
|
|
16601
18260
|
var _settleRequested = false;
|
|
18261
|
+
var _rasterNavRefreshPending = false;
|
|
16602
18262
|
|
|
16603
18263
|
// 'map_interaction_end' may arrive before the in-flight 'nav' draw runs,
|
|
16604
18264
|
// because drawLayers() schedules its work via requestAnimationFrame.
|
|
@@ -16607,6 +18267,8 @@
|
|
|
16607
18267
|
gui.on('map_interaction_end', function() {
|
|
16608
18268
|
if (_redrawPending) {
|
|
16609
18269
|
settleNow();
|
|
18270
|
+
} else if (_rasterNavRefreshPending) {
|
|
18271
|
+
settleRasterNow();
|
|
16610
18272
|
} else {
|
|
16611
18273
|
_settleRequested = true;
|
|
16612
18274
|
}
|
|
@@ -16617,6 +18279,11 @@
|
|
|
16617
18279
|
|
|
16618
18280
|
this.drawMainLayers = function(layers, action) {
|
|
16619
18281
|
if (skipMainLayerRedraw(action)) return;
|
|
18282
|
+
if (action == 'nav' && layersHaveRasters(layers)) {
|
|
18283
|
+
_rasterNavRefreshPending = true;
|
|
18284
|
+
} else if (action != 'nav') {
|
|
18285
|
+
_rasterNavRefreshPending = false;
|
|
18286
|
+
}
|
|
16620
18287
|
if (action == 'nav' && shouldUseFastNav()) {
|
|
16621
18288
|
applyFastTransform();
|
|
16622
18289
|
// SVG symbol reposition is already cheap; keep labels/symbols accurate
|
|
@@ -16646,7 +18313,7 @@
|
|
|
16646
18313
|
} else if (isSvgLayer) {
|
|
16647
18314
|
_svg.drawLayer(lyr, 'symbol');
|
|
16648
18315
|
} else {
|
|
16649
|
-
drawCanvasLayer(lyr, _mainCanv);
|
|
18316
|
+
drawCanvasLayer(lyr, _mainCanv, action);
|
|
16650
18317
|
}
|
|
16651
18318
|
});
|
|
16652
18319
|
// Force synchronous rasterization so performance.now() reflects the true
|
|
@@ -16655,6 +18322,10 @@
|
|
|
16655
18322
|
flushCanvas(_mainCanv);
|
|
16656
18323
|
_lastFrameMs = performance.now() - startTime;
|
|
16657
18324
|
captureSnapshot();
|
|
18325
|
+
if (action == 'nav' && _settleRequested && _rasterNavRefreshPending) {
|
|
18326
|
+
_settleRequested = false;
|
|
18327
|
+
settleRasterNow();
|
|
18328
|
+
}
|
|
16658
18329
|
};
|
|
16659
18330
|
|
|
16660
18331
|
// Draw highlight effect for hover and selection
|
|
@@ -16675,7 +18346,7 @@
|
|
|
16675
18346
|
_overlayCanv.hide();
|
|
16676
18347
|
}
|
|
16677
18348
|
layers.forEach(function(lyr) {
|
|
16678
|
-
drawCanvasLayer(lyr, canv);
|
|
18349
|
+
drawCanvasLayer(lyr, canv, action);
|
|
16679
18350
|
});
|
|
16680
18351
|
};
|
|
16681
18352
|
|
|
@@ -16700,9 +18371,22 @@
|
|
|
16700
18371
|
return action == 'hover' && _overlayCanv.visible();
|
|
16701
18372
|
}
|
|
16702
18373
|
|
|
16703
|
-
function
|
|
18374
|
+
function layersHaveRasters(layers) {
|
|
18375
|
+
return layers.some(function(lyr) {
|
|
18376
|
+
return lyr && lyr.gui && internal.layerHasRaster(lyr.gui.displayLayer);
|
|
18377
|
+
});
|
|
18378
|
+
}
|
|
18379
|
+
|
|
18380
|
+
function drawCanvasLayer(lyr, canv, action) {
|
|
16704
18381
|
if (!lyr) return;
|
|
16705
|
-
if (lyr.gui.
|
|
18382
|
+
if (internal.layerHasRaster(lyr.gui.displayLayer)) {
|
|
18383
|
+
canv.drawRasterLayer(lyr.gui.displayLayer, {
|
|
18384
|
+
action: action,
|
|
18385
|
+
onViewportPreviewReady: function() {
|
|
18386
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
18387
|
+
}
|
|
18388
|
+
});
|
|
18389
|
+
} else if (lyr.gui.style.type == 'outline') {
|
|
16706
18390
|
drawOutlineLayerToCanvas(lyr, canv, ext);
|
|
16707
18391
|
} else {
|
|
16708
18392
|
drawStyledLayerToCanvas(lyr, canv, ext);
|
|
@@ -16805,6 +18489,11 @@
|
|
|
16805
18489
|
gui.dispatchEvent('map-needs-refresh');
|
|
16806
18490
|
}
|
|
16807
18491
|
|
|
18492
|
+
function settleRasterNow() {
|
|
18493
|
+
_rasterNavRefreshPending = false;
|
|
18494
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
18495
|
+
}
|
|
18496
|
+
|
|
16808
18497
|
function cancelSettle() {
|
|
16809
18498
|
if (_settleTimer) {
|
|
16810
18499
|
clearTimeout(_settleTimer);
|
|
@@ -16820,6 +18509,9 @@
|
|
|
16820
18509
|
var box = new HighlightBox(gui, {name: 'box-tool', persistent: true, handles: true, draggable: true});
|
|
16821
18510
|
var popup = gui.container.findChild('.box-tool-options');
|
|
16822
18511
|
var coords = popup.findChild('.box-coords').hide();
|
|
18512
|
+
var selectBtn = popup.findChild('.select-btn');
|
|
18513
|
+
var clipBtn = popup.findChild('.clip-btn');
|
|
18514
|
+
var eraseBtn = popup.findChild('.erase-btn');
|
|
16823
18515
|
var _on = false;
|
|
16824
18516
|
var instructionsShown = false;
|
|
16825
18517
|
var alert;
|
|
@@ -16832,7 +18524,7 @@
|
|
|
16832
18524
|
reset();
|
|
16833
18525
|
});
|
|
16834
18526
|
|
|
16835
|
-
new SimpleButton(
|
|
18527
|
+
new SimpleButton(selectBtn).on('click', function() {
|
|
16836
18528
|
var coords = box.getDataCoords();
|
|
16837
18529
|
if (!coords || noData()) return;
|
|
16838
18530
|
gui.enterMode('selection_tool');
|
|
@@ -16847,11 +18539,12 @@
|
|
|
16847
18539
|
return !gui.model.getActiveLayer();
|
|
16848
18540
|
}
|
|
16849
18541
|
|
|
16850
|
-
new SimpleButton(
|
|
18542
|
+
new SimpleButton(clipBtn).on('click', function() {
|
|
16851
18543
|
runCommand('-clip bbox=' + box.getDataCoords().join(','));
|
|
16852
18544
|
});
|
|
16853
18545
|
|
|
16854
|
-
new SimpleButton(
|
|
18546
|
+
new SimpleButton(eraseBtn).on('click', function() {
|
|
18547
|
+
if (activeLayerIsRaster()) return;
|
|
16855
18548
|
runCommand('-erase bbox=' + box.getDataCoords().join(','));
|
|
16856
18549
|
});
|
|
16857
18550
|
|
|
@@ -16894,6 +18587,7 @@
|
|
|
16894
18587
|
box.on('dragend', function(e) {
|
|
16895
18588
|
if (_on) {
|
|
16896
18589
|
hideInstructions();
|
|
18590
|
+
updateOptionsForActiveLayer();
|
|
16897
18591
|
popup.show();
|
|
16898
18592
|
}
|
|
16899
18593
|
});
|
|
@@ -16947,6 +18641,7 @@
|
|
|
16947
18641
|
|
|
16948
18642
|
function turnOn() {
|
|
16949
18643
|
box.turnOn();
|
|
18644
|
+
updateOptionsForActiveLayer();
|
|
16950
18645
|
_on = true;
|
|
16951
18646
|
}
|
|
16952
18647
|
|
|
@@ -16967,6 +18662,27 @@
|
|
|
16967
18662
|
hideCoords();
|
|
16968
18663
|
}
|
|
16969
18664
|
|
|
18665
|
+
function updateOptionsForActiveLayer() {
|
|
18666
|
+
var raster = activeLayerIsRaster();
|
|
18667
|
+
setButtonVisible(selectBtn, !raster);
|
|
18668
|
+
setButtonVisible(clipBtn, true);
|
|
18669
|
+
setButtonVisible(eraseBtn, !raster);
|
|
18670
|
+
}
|
|
18671
|
+
|
|
18672
|
+
function activeLayerIsRaster() {
|
|
18673
|
+
var active = gui.model.getActiveLayer();
|
|
18674
|
+
return !!(active && internal.layerHasRaster(active.layer));
|
|
18675
|
+
}
|
|
18676
|
+
|
|
18677
|
+
function setButtonVisible(btn, visible) {
|
|
18678
|
+
btn.classed('hidden', !visible);
|
|
18679
|
+
if (visible) {
|
|
18680
|
+
btn.show();
|
|
18681
|
+
} else {
|
|
18682
|
+
btn.hide();
|
|
18683
|
+
}
|
|
18684
|
+
}
|
|
18685
|
+
|
|
16970
18686
|
function openAddFramePopup(gui, bbox) {
|
|
16971
18687
|
var popup = showPopupAlert('', 'Add a map frame');
|
|
16972
18688
|
var el = popup.container();
|
|
@@ -17336,7 +19052,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
17336
19052
|
|
|
17337
19053
|
function getDisplayOptions() {
|
|
17338
19054
|
return {
|
|
17339
|
-
crs: _dynamicCRS
|
|
19055
|
+
crs: _dynamicCRS,
|
|
19056
|
+
notify: gui.notify
|
|
17340
19057
|
};
|
|
17341
19058
|
}
|
|
17342
19059
|
|
|
@@ -17353,7 +19070,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
17353
19070
|
}
|
|
17354
19071
|
|
|
17355
19072
|
function getContentLayerBounds() {
|
|
17356
|
-
var b = new Bounds();
|
|
19073
|
+
var b = new Bounds$1();
|
|
17357
19074
|
var layers = getContentLayers();
|
|
17358
19075
|
layers.forEach(function(lyr) {
|
|
17359
19076
|
b.mergeBounds(lyr.gui.bounds);
|
|
@@ -17370,7 +19087,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
17370
19087
|
function calcFullBounds() {
|
|
17371
19088
|
var b;
|
|
17372
19089
|
if (isPreviewView()) {
|
|
17373
|
-
b = new Bounds(getFrameLayerData().bbox);
|
|
19090
|
+
b = new Bounds$1(getFrameLayerData().bbox);
|
|
17374
19091
|
} else {
|
|
17375
19092
|
b = getContentLayerBounds();
|
|
17376
19093
|
}
|
|
@@ -18326,11 +20043,16 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
18326
20043
|
|
|
18327
20044
|
|
|
18328
20045
|
gui.state = {};
|
|
20046
|
+
var sidebarPanel = null;
|
|
20047
|
+
var lastSidebarPanel = 'console';
|
|
20048
|
+
var sidebarWidth = GUI.getSavedValue('sidebar_width') || 0;
|
|
20049
|
+
var sidebarResizeFrame = null;
|
|
18329
20050
|
|
|
18330
20051
|
var msgCount = 0;
|
|
18331
20052
|
var clearMsg;
|
|
18332
20053
|
|
|
18333
20054
|
initModeRules(gui);
|
|
20055
|
+
startRasterSourceStoreLifecycle();
|
|
18334
20056
|
gui.map.init();
|
|
18335
20057
|
|
|
18336
20058
|
if (opts.saveControl) {
|
|
@@ -18366,10 +20088,50 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
18366
20088
|
// if (gui.progressMessage) gui.progressMessage.hide();
|
|
18367
20089
|
};
|
|
18368
20090
|
|
|
20091
|
+
if (sidebarWidth) {
|
|
20092
|
+
setSidebarWidth(sidebarWidth);
|
|
20093
|
+
}
|
|
20094
|
+
|
|
20095
|
+
gui.getSidebarPanel = function() {
|
|
20096
|
+
return sidebarPanel;
|
|
20097
|
+
};
|
|
20098
|
+
|
|
20099
|
+
gui.setSidebarPanel = function(name) {
|
|
20100
|
+
var prev = sidebarPanel;
|
|
20101
|
+
sidebarPanel = name || null;
|
|
20102
|
+
if (sidebarPanel && gui.getMode()) {
|
|
20103
|
+
gui.clearMode();
|
|
20104
|
+
}
|
|
20105
|
+
if (sidebarPanel == prev) return;
|
|
20106
|
+
if (sidebarPanel) {
|
|
20107
|
+
lastSidebarPanel = sidebarPanel;
|
|
20108
|
+
}
|
|
20109
|
+
gui.container
|
|
20110
|
+
.classed('sidebar-open', !!sidebarPanel)
|
|
20111
|
+
.classed('layers-open', sidebarPanel == 'layers')
|
|
20112
|
+
.classed('console-open', sidebarPanel == 'console');
|
|
20113
|
+
gui.dispatchEvent('sidebar', {name: sidebarPanel, prev: prev});
|
|
20114
|
+
gui.dispatchEvent('resize');
|
|
20115
|
+
};
|
|
20116
|
+
|
|
20117
|
+
gui.toggleSidebarPanel = function(name) {
|
|
20118
|
+
gui.setSidebarPanel(sidebarPanel == name ? null : name);
|
|
20119
|
+
};
|
|
20120
|
+
|
|
20121
|
+
gui.toggleSidebar = function() {
|
|
20122
|
+
gui.setSidebarPanel(sidebarPanel ? null : lastSidebarPanel);
|
|
20123
|
+
};
|
|
20124
|
+
|
|
20125
|
+
gui.sidebarPanelIsOpen = function() {
|
|
20126
|
+
return !!sidebarPanel;
|
|
20127
|
+
};
|
|
20128
|
+
|
|
18369
20129
|
gui.consoleIsOpen = function() {
|
|
18370
|
-
return
|
|
20130
|
+
return sidebarPanel == 'console';
|
|
18371
20131
|
};
|
|
18372
20132
|
|
|
20133
|
+
initSidebarResizing();
|
|
20134
|
+
|
|
18373
20135
|
gui.getRuntimeStateContext = function() {
|
|
18374
20136
|
return getRuntimeStateContext(gui);
|
|
18375
20137
|
};
|
|
@@ -18411,6 +20173,50 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
18411
20173
|
}
|
|
18412
20174
|
|
|
18413
20175
|
return gui;
|
|
20176
|
+
|
|
20177
|
+
function initSidebarResizing() {
|
|
20178
|
+
var handle = gui.container.findChild('.sidebar-resize-handle');
|
|
20179
|
+
if (!handle) return;
|
|
20180
|
+
handle.on('mousedown', function(e) {
|
|
20181
|
+
if (!gui.sidebarPanelIsOpen()) return;
|
|
20182
|
+
e.preventDefault();
|
|
20183
|
+
e.stopPropagation();
|
|
20184
|
+
gui.container.addClass('sidebar-resizing');
|
|
20185
|
+
window.addEventListener('mousemove', onMove);
|
|
20186
|
+
window.addEventListener('mouseup', onRelease);
|
|
20187
|
+
});
|
|
20188
|
+
|
|
20189
|
+
function onMove(e) {
|
|
20190
|
+
setSidebarWidth(e.pageX);
|
|
20191
|
+
scheduleSidebarResize();
|
|
20192
|
+
}
|
|
20193
|
+
|
|
20194
|
+
function onRelease() {
|
|
20195
|
+
window.removeEventListener('mousemove', onMove);
|
|
20196
|
+
window.removeEventListener('mouseup', onRelease);
|
|
20197
|
+
gui.container.removeClass('sidebar-resizing');
|
|
20198
|
+
GUI.setSavedValue('sidebar_width', sidebarWidth);
|
|
20199
|
+
scheduleSidebarResize();
|
|
20200
|
+
}
|
|
20201
|
+
}
|
|
20202
|
+
|
|
20203
|
+
function setSidebarWidth(width) {
|
|
20204
|
+
sidebarWidth = clampSidebarWidth(width);
|
|
20205
|
+
gui.container.node().style.setProperty('--left-sidebar-width', sidebarWidth + 'px');
|
|
20206
|
+
}
|
|
20207
|
+
|
|
20208
|
+
function clampSidebarWidth(width) {
|
|
20209
|
+
var max = Math.min(720, Math.round(window.innerWidth * 0.6));
|
|
20210
|
+
return Math.max(220, Math.min(max, Math.round(width)));
|
|
20211
|
+
}
|
|
20212
|
+
|
|
20213
|
+
function scheduleSidebarResize() {
|
|
20214
|
+
if (sidebarResizeFrame) return;
|
|
20215
|
+
sidebarResizeFrame = requestAnimationFrame(function() {
|
|
20216
|
+
sidebarResizeFrame = null;
|
|
20217
|
+
gui.dispatchEvent('resize');
|
|
20218
|
+
});
|
|
20219
|
+
}
|
|
18414
20220
|
}
|
|
18415
20221
|
|
|
18416
20222
|
function createUndoTestApi(gui) {
|