mapshaper 0.7.13 → 0.7.15
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 +1729 -21
- package/package.json +7 -2
- package/www/geotiff.js +26355 -0
- package/www/index.html +2 -2
- package/www/mapshaper-gui.js +1865 -314
- package/www/mapshaper.js +1729 -21
- package/www/page.css +9 -0
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,207 +4321,1106 @@
|
|
|
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
|
-
|
|
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
|
+
}
|
|
4702
|
+
|
|
4703
|
+
function noteTableFieldsWillChange(table, fields, detail) {
|
|
4704
|
+
notifyTransaction(activeTransaction, 'captureTableFieldsBefore', table, Object.assign({
|
|
4705
|
+
fields: normalizeStrings(fields)
|
|
4706
|
+
}, detail || {}));
|
|
4707
|
+
}
|
|
4708
|
+
|
|
4709
|
+
function noteTableOrderWillChange(table, ids, detail) {
|
|
4710
|
+
notifyTransaction(activeTransaction, 'captureTableOrderBefore', table, Object.assign({
|
|
4711
|
+
ids: normalizeIds(ids)
|
|
4712
|
+
}, detail || {}));
|
|
4713
|
+
}
|
|
4714
|
+
|
|
4715
|
+
function noteTableSchemaWillChange(table, detail) {
|
|
4716
|
+
notifyTransaction(activeTransaction, 'captureTableSchemaBefore', table, detail || {});
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4719
|
+
function markTableChanged(table, detail) {
|
|
4720
|
+
return markChanged(table, Object.assign({type: 'table'}, detail || {}));
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
function markTableRecordsChanged(table, ids, detail) {
|
|
4724
|
+
return markTableChanged(table, Object.assign({
|
|
4725
|
+
granularity: 'records',
|
|
4726
|
+
ids: normalizeIds(ids)
|
|
4727
|
+
}, detail || {}));
|
|
4728
|
+
}
|
|
4729
|
+
|
|
4730
|
+
function markTableFieldsChanged(table, fields, detail) {
|
|
4731
|
+
return markTableChanged(table, Object.assign({
|
|
4732
|
+
granularity: 'fields',
|
|
4733
|
+
fields: normalizeStrings(fields)
|
|
4734
|
+
}, detail || {}));
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4737
|
+
function markTableSchemaChanged(table, detail) {
|
|
4738
|
+
return markTableChanged(table, Object.assign({
|
|
4739
|
+
granularity: 'schema'
|
|
4740
|
+
}, detail || {}));
|
|
4741
|
+
}
|
|
4742
|
+
|
|
4743
|
+
function markTableOrderChanged(table, ids, detail) {
|
|
4744
|
+
return markTableChanged(table, Object.assign({
|
|
4745
|
+
granularity: 'order',
|
|
4746
|
+
ids: normalizeIds(ids)
|
|
4747
|
+
}, detail || {}));
|
|
4748
|
+
}
|
|
4749
|
+
|
|
4750
|
+
function noteArcsWillChange(arcs, detail) {
|
|
4751
|
+
notifyTransaction(activeTransaction, 'captureArcsBefore', arcs, detail || {});
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4754
|
+
function noteArcsSimplificationWillChange$1(arcs, detail) {
|
|
4755
|
+
notifyTransaction(activeTransaction, 'captureArcsSimplificationBefore', arcs, detail || {});
|
|
4756
|
+
}
|
|
4757
|
+
|
|
4758
|
+
function markArcsChanged(arcs, detail) {
|
|
4759
|
+
return markChanged(arcs, Object.assign({type: 'arcs'}, detail || {}));
|
|
4760
|
+
}
|
|
4761
|
+
|
|
4762
|
+
function markArcsSimplificationChanged$1(arcs, detail) {
|
|
4763
|
+
return markArcsChanged(arcs, Object.assign({granularity: 'simplification'}, detail || {}));
|
|
4764
|
+
}
|
|
4765
|
+
|
|
4766
|
+
function noteCatalogWillChange(catalog, detail) {
|
|
4767
|
+
notifyTransaction(activeTransaction, 'captureCatalogBefore', catalog, detail || {});
|
|
4768
|
+
}
|
|
4769
|
+
|
|
4770
|
+
function markCatalogChanged(catalog, detail) {
|
|
4771
|
+
return markChanged(catalog, Object.assign({type: 'catalog'}, detail || {}));
|
|
4772
|
+
}
|
|
4773
|
+
|
|
4774
|
+
function noteDatasetWillChange(dataset, detail) {
|
|
4775
|
+
notifyTransaction(activeTransaction, 'captureDatasetBefore', dataset, detail || {});
|
|
4776
|
+
}
|
|
4777
|
+
|
|
4778
|
+
function noteDatasetInfoWillChange(dataset, detail) {
|
|
4779
|
+
notifyTransaction(activeTransaction, 'captureDatasetInfoBefore', dataset, detail || {});
|
|
4780
|
+
}
|
|
4781
|
+
|
|
4782
|
+
function markDatasetChanged(dataset, detail) {
|
|
4783
|
+
return markChanged(dataset, Object.assign({type: 'dataset'}, detail || {}));
|
|
4784
|
+
}
|
|
4785
|
+
|
|
4786
|
+
function markDatasetInfoChanged(dataset, detail) {
|
|
4787
|
+
return markDatasetChanged(dataset, Object.assign({granularity: 'info'}, detail || {}));
|
|
4788
|
+
}
|
|
4789
|
+
|
|
4790
|
+
function noteLayerWillChange(layer, detail) {
|
|
4791
|
+
notifyTransaction(activeTransaction, 'captureLayerBefore', layer, detail || {});
|
|
4792
|
+
}
|
|
4793
|
+
|
|
4794
|
+
function noteLayerMetadataWillChange(layer, detail) {
|
|
4795
|
+
notifyTransaction(activeTransaction, 'captureLayerMetadataBefore', layer, detail || {});
|
|
4796
|
+
}
|
|
4797
|
+
|
|
4798
|
+
function noteLayerOrderWillChange(layer, ids, detail) {
|
|
4799
|
+
notifyTransaction(activeTransaction, 'captureLayerOrderBefore', layer, Object.assign({
|
|
4800
|
+
ids: normalizeIds(ids)
|
|
4801
|
+
}, detail || {}));
|
|
4802
|
+
}
|
|
4803
|
+
|
|
4804
|
+
function markLayerChanged(layer, detail) {
|
|
4805
|
+
return markChanged(layer, Object.assign({type: 'layer'}, detail || {}));
|
|
4806
|
+
}
|
|
4807
|
+
|
|
4808
|
+
function markLayerMetadataChanged(layer, detail) {
|
|
4809
|
+
return markLayerChanged(layer, Object.assign({granularity: 'metadata'}, detail || {}));
|
|
4810
|
+
}
|
|
4811
|
+
|
|
4812
|
+
function markLayerOrderChanged(layer, ids, detail) {
|
|
4813
|
+
return markLayerChanged(layer, Object.assign({
|
|
4814
|
+
granularity: 'order',
|
|
4815
|
+
ids: normalizeIds(ids)
|
|
4816
|
+
}, detail || {}));
|
|
4817
|
+
}
|
|
4818
|
+
|
|
4819
|
+
function notifyTransaction(tx, method, obj, detail) {
|
|
4820
|
+
if (tx && typeof tx[method] == 'function') {
|
|
4821
|
+
tx[method](obj, detail || {});
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
|
|
4825
|
+
function normalizeIds(ids) {
|
|
4826
|
+
if (ids == null) return [];
|
|
4827
|
+
return Array.isArray(ids) ? ids.slice() : [ids];
|
|
4828
|
+
}
|
|
4829
|
+
|
|
4830
|
+
function normalizeStrings(arr) {
|
|
4831
|
+
if (arr == null) return [];
|
|
4832
|
+
return Array.isArray(arr) ? arr.slice() : [arr];
|
|
4833
|
+
}
|
|
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
|
+
}
|
|
4309
5164
|
}
|
|
4310
5165
|
|
|
4311
|
-
function
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
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;
|
|
4315
5177
|
}
|
|
4316
5178
|
|
|
4317
|
-
function
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
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
|
+
];
|
|
4321
5189
|
}
|
|
4322
5190
|
|
|
4323
|
-
function
|
|
4324
|
-
|
|
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;
|
|
4325
5197
|
}
|
|
4326
5198
|
|
|
4327
|
-
function
|
|
4328
|
-
|
|
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};
|
|
4329
5210
|
}
|
|
4330
5211
|
|
|
4331
|
-
function
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
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;
|
|
4336
5219
|
}
|
|
4337
5220
|
|
|
4338
|
-
function
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
}
|
|
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;
|
|
4343
5231
|
}
|
|
4344
5232
|
|
|
4345
|
-
function
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
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)));
|
|
4349
5238
|
}
|
|
4350
5239
|
|
|
4351
|
-
function
|
|
4352
|
-
return
|
|
4353
|
-
granularity: 'order',
|
|
4354
|
-
ids: normalizeIds(ids)
|
|
4355
|
-
}, detail || {}));
|
|
5240
|
+
function getDefaultRasterScaling(grid, recipe) {
|
|
5241
|
+
return isEightBitPixelType(grid.pixelType) ? 'none' : 'percentile';
|
|
4356
5242
|
}
|
|
4357
5243
|
|
|
4358
|
-
function
|
|
4359
|
-
|
|
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;
|
|
4360
5256
|
}
|
|
4361
5257
|
|
|
4362
|
-
function
|
|
4363
|
-
|
|
5258
|
+
function getDisplayRange(scaleRange) {
|
|
5259
|
+
return [
|
|
5260
|
+
scaleRange[0] / 100 * 255,
|
|
5261
|
+
scaleRange[1] / 100 * 255
|
|
5262
|
+
];
|
|
4364
5263
|
}
|
|
4365
5264
|
|
|
4366
|
-
function
|
|
4367
|
-
|
|
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;
|
|
4368
5275
|
}
|
|
4369
5276
|
|
|
4370
|
-
function
|
|
4371
|
-
return
|
|
5277
|
+
function isEightBitPixelType(pixelType) {
|
|
5278
|
+
return pixelType == 'uint8' || pixelType == 'int8';
|
|
4372
5279
|
}
|
|
4373
5280
|
|
|
4374
|
-
function
|
|
4375
|
-
|
|
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);
|
|
4376
5288
|
}
|
|
4377
5289
|
|
|
4378
|
-
function
|
|
4379
|
-
return
|
|
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('|');
|
|
4380
5303
|
}
|
|
4381
5304
|
|
|
4382
|
-
function
|
|
4383
|
-
|
|
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;
|
|
4384
5311
|
}
|
|
4385
5312
|
|
|
4386
|
-
function
|
|
4387
|
-
|
|
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;
|
|
4388
5318
|
}
|
|
4389
5319
|
|
|
4390
|
-
function
|
|
4391
|
-
|
|
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);
|
|
4392
5325
|
}
|
|
4393
5326
|
|
|
4394
|
-
function
|
|
4395
|
-
|
|
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;
|
|
4396
5333
|
}
|
|
4397
5334
|
|
|
4398
|
-
function
|
|
4399
|
-
|
|
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};
|
|
4400
5368
|
}
|
|
4401
5369
|
|
|
4402
|
-
function
|
|
4403
|
-
|
|
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;
|
|
4404
5378
|
}
|
|
4405
5379
|
|
|
4406
|
-
function
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
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;
|
|
4410
5388
|
}
|
|
4411
5389
|
|
|
4412
|
-
function
|
|
4413
|
-
|
|
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);
|
|
4414
5393
|
}
|
|
4415
5394
|
|
|
4416
|
-
function
|
|
4417
|
-
|
|
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
|
+
}
|
|
4418
5404
|
}
|
|
4419
5405
|
|
|
4420
|
-
function
|
|
4421
|
-
return
|
|
4422
|
-
granularity: 'order',
|
|
4423
|
-
ids: normalizeIds(ids)
|
|
4424
|
-
}, detail || {}));
|
|
5406
|
+
function isValidBandValue(val, noData) {
|
|
5407
|
+
return isFinite(val) && (noData === null || noData === undefined || val != noData);
|
|
4425
5408
|
}
|
|
4426
5409
|
|
|
4427
|
-
function
|
|
4428
|
-
|
|
4429
|
-
tx[method](obj, detail || {});
|
|
4430
|
-
}
|
|
5410
|
+
function getPercentileRank(count, pct) {
|
|
5411
|
+
return Math.max(0, Math.min(count - 1, Math.floor((count - 1) * pct / 100)));
|
|
4431
5412
|
}
|
|
4432
5413
|
|
|
4433
|
-
function
|
|
4434
|
-
|
|
4435
|
-
|
|
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;
|
|
4436
5420
|
}
|
|
4437
5421
|
|
|
4438
|
-
function
|
|
4439
|
-
|
|
4440
|
-
return Array.isArray(arr) ? arr.slice() : [arr];
|
|
5422
|
+
function copyObjectOrArray(obj) {
|
|
5423
|
+
return obj && obj.concat ? obj.concat() : utils.extend({}, obj);
|
|
4441
5424
|
}
|
|
4442
5425
|
|
|
4443
5426
|
function UndoTransaction(label) {
|
|
@@ -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']) {
|
|
@@ -5760,6 +6806,135 @@
|
|
|
5760
6806
|
}
|
|
5761
6807
|
}
|
|
5762
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
|
+
});
|
|
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;
|
|
6936
|
+
}
|
|
6937
|
+
|
|
5763
6938
|
// @cb function(<FileList>)
|
|
5764
6939
|
function DropControl(gui, el, cb) {
|
|
5765
6940
|
var area = El(el);
|
|
@@ -6200,7 +7375,10 @@
|
|
|
6200
7375
|
if (group.parquet) {
|
|
6201
7376
|
await loadGeoParquetLib();
|
|
6202
7377
|
}
|
|
6203
|
-
if (group.
|
|
7378
|
+
if (group.geotiff) {
|
|
7379
|
+
await loadGeoTIFFLib();
|
|
7380
|
+
}
|
|
7381
|
+
if (group.gpkg || group.fgb || group.parquet || group.geotiff || group.png || group.jpeg) {
|
|
6204
7382
|
dataset = await internal.importContentAsync(group, importOpts);
|
|
6205
7383
|
} else {
|
|
6206
7384
|
dataset = internal.importContent(group, importOpts);
|
|
@@ -6211,6 +7389,7 @@
|
|
|
6211
7389
|
if (group.layername) {
|
|
6212
7390
|
d.layers.forEach(lyr => lyr.name = group.layername);
|
|
6213
7391
|
}
|
|
7392
|
+
await persistRasterSourceForDataset(d, group);
|
|
6214
7393
|
// TODO: add popup here
|
|
6215
7394
|
// save import options for use by repair control, etc.
|
|
6216
7395
|
d.info.import_options = importOpts;
|
|
@@ -6231,7 +7410,8 @@
|
|
|
6231
7410
|
function filesMayContainPaths(files) {
|
|
6232
7411
|
return utils$1.some(files, function(f) {
|
|
6233
7412
|
var type = internal.guessInputFileType(f.name);
|
|
6234
|
-
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);
|
|
6235
7415
|
});
|
|
6236
7416
|
}
|
|
6237
7417
|
|
|
@@ -6245,6 +7425,10 @@
|
|
|
6245
7425
|
return /\.(shp|shx|dbf|prj|cpg)$/i.test(name);
|
|
6246
7426
|
}
|
|
6247
7427
|
|
|
7428
|
+
function isRasterImagePart(name) {
|
|
7429
|
+
return /\.(png|jpg|jpeg|tfw|pgw|pngw|jgw|jpw|jpgw|jpegw|wld|prj)$/i.test(name);
|
|
7430
|
+
}
|
|
7431
|
+
|
|
6248
7432
|
function readImportOpts() {
|
|
6249
7433
|
var importOpts;
|
|
6250
7434
|
if (useQuickView) {
|
|
@@ -6569,10 +7753,17 @@
|
|
|
6569
7753
|
return data.some(d => fileKey(d) == shpKey);
|
|
6570
7754
|
}
|
|
6571
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
|
+
|
|
6572
7763
|
data.forEach(d => {
|
|
6573
7764
|
var basename = fileBase(d);
|
|
6574
7765
|
var type = fileType(d);
|
|
6575
|
-
if (type == 'shp'
|
|
7766
|
+
if (type == 'shp') {
|
|
6576
7767
|
d.group = key(basename, type);
|
|
6577
7768
|
d.filename = d.name;
|
|
6578
7769
|
} else if (hasShp(basename)) {
|
|
@@ -6580,6 +7771,12 @@
|
|
|
6580
7771
|
} else if (type == 'dbf') {
|
|
6581
7772
|
d.filename = d.name;
|
|
6582
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;
|
|
6583
7780
|
} else {
|
|
6584
7781
|
// shapefile part without a .shp file
|
|
6585
7782
|
d.group = null;
|
|
@@ -6606,6 +7803,14 @@
|
|
|
6606
7803
|
});
|
|
6607
7804
|
return groups;
|
|
6608
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
|
+
}
|
|
6609
7814
|
}
|
|
6610
7815
|
|
|
6611
7816
|
function draggable(ref) {
|
|
@@ -6733,11 +7938,18 @@
|
|
|
6733
7938
|
|
|
6734
7939
|
function message() {
|
|
6735
7940
|
var msg = GUI.formatMessageArgs(arguments);
|
|
6736
|
-
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) {
|
|
6737
7948
|
// Fallback for early messages before MessageControl is constructed
|
|
6738
7949
|
gui.message(msg);
|
|
7950
|
+
} else {
|
|
7951
|
+
internal.logArgs(arguments);
|
|
6739
7952
|
}
|
|
6740
|
-
internal.logArgs(arguments);
|
|
6741
7953
|
}
|
|
6742
7954
|
|
|
6743
7955
|
// CLI warnings used to surface as modal alerts, which interrupt the user
|
|
@@ -6755,6 +7967,10 @@
|
|
|
6755
7967
|
internal.setLoggingFunctions(message, error, stop, warn);
|
|
6756
7968
|
}
|
|
6757
7969
|
|
|
7970
|
+
function messageShouldGoToInbox(msg) {
|
|
7971
|
+
return /^GeoTIFF renditions:/.test(msg);
|
|
7972
|
+
}
|
|
7973
|
+
|
|
6758
7974
|
function WriteFilesProxy(gui) {
|
|
6759
7975
|
// replace CLI version of writeFiles()
|
|
6760
7976
|
internal.replaceWriteFiles(async function(files, opts) {
|
|
@@ -7199,7 +8415,7 @@
|
|
|
7199
8415
|
var sourceCRS;
|
|
7200
8416
|
var emptyArcs;
|
|
7201
8417
|
|
|
7202
|
-
if (displayCRS && layer.geometry_type) {
|
|
8418
|
+
if (displayCRS && (layer.geometry_type || internal.layerHasRaster(layer))) {
|
|
7203
8419
|
var crsInfo = getDatasetCrsInfo(dataset);
|
|
7204
8420
|
if (crsInfo.error) {
|
|
7205
8421
|
// unprojectable dataset -- return empty layer
|
|
@@ -7240,6 +8456,15 @@
|
|
|
7240
8456
|
|
|
7241
8457
|
if (gui.unprojectable) {
|
|
7242
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
|
+
}
|
|
7243
8468
|
} else if (layer.geometry_type) {
|
|
7244
8469
|
gui.geographic = true;
|
|
7245
8470
|
gui.displayLayer = layer;
|
|
@@ -7271,8 +8496,19 @@
|
|
|
7271
8496
|
layer.gui = gui;
|
|
7272
8497
|
}
|
|
7273
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
|
+
|
|
7274
8510
|
function getDisplayBounds(lyr, arcs) {
|
|
7275
|
-
var bounds = internal.getLayerBounds(lyr, arcs) || new Bounds();
|
|
8511
|
+
var bounds = internal.getLayerBounds(lyr, arcs) || new Bounds$1();
|
|
7276
8512
|
if (lyr.geometry_type == 'point' && arcs && bounds.hasBounds() && bounds.area() > 0 === false) {
|
|
7277
8513
|
// if a point layer has no extent (e.g. contains only a single point),
|
|
7278
8514
|
// then merge with arc bounds, to place the point in context.
|
|
@@ -8165,8 +9401,8 @@
|
|
|
8165
9401
|
} else if (kc == 40) {
|
|
8166
9402
|
forward();
|
|
8167
9403
|
} else if (kc == 32 && (!typing || (inputText === '' && typingInConsole))) {
|
|
8168
|
-
// space bar toggles the
|
|
8169
|
-
gui.
|
|
9404
|
+
// space bar toggles the console tab if nothing has been typed
|
|
9405
|
+
gui.toggleSidebarPanel('console');
|
|
8170
9406
|
} else if (!typing && e.target != input.node() && !metaKey(e)) {
|
|
8171
9407
|
// typing returns focus, unless a meta key is down (to allow Cmd-C copy)
|
|
8172
9408
|
// or user is typing in a different input area somewhere
|
|
@@ -8183,9 +9419,9 @@
|
|
|
8183
9419
|
|
|
8184
9420
|
// various shortcuts (while not typing in an input field or editable el)
|
|
8185
9421
|
} else if (!typing) {
|
|
8186
|
-
if (kc == 32) { // space bar toggles the
|
|
9422
|
+
if (kc == 32) { // space bar toggles the console tab
|
|
8187
9423
|
capture = true;
|
|
8188
|
-
gui.
|
|
9424
|
+
gui.toggleSidebarPanel('console');
|
|
8189
9425
|
// } else if (kc == 73) { // letter i opens inspector
|
|
8190
9426
|
// gui.dispatchEvent('interaction_toggle');
|
|
8191
9427
|
} else if (kc == 72) { // letter h resets map extent
|
|
@@ -8650,7 +9886,9 @@
|
|
|
8650
9886
|
title.textContent = 'Layer: ' + (info.layer_name || '[unnamed layer]');
|
|
8651
9887
|
section.appendChild(title);
|
|
8652
9888
|
section.appendChild(renderKeyValueTable(getInfoRows(info), 'console-info-table'));
|
|
8653
|
-
|
|
9889
|
+
if (!info.raster_type) {
|
|
9890
|
+
section.appendChild(renderAttributeInfoTable(info.attribute_data));
|
|
9891
|
+
}
|
|
8654
9892
|
container.appendChild(section);
|
|
8655
9893
|
});
|
|
8656
9894
|
return container;
|
|
@@ -8658,13 +9896,18 @@
|
|
|
8658
9896
|
|
|
8659
9897
|
function getInfoRows(info) {
|
|
8660
9898
|
var rows = [
|
|
8661
|
-
['Type', info.geometry_type || 'tabular data']
|
|
8662
|
-
['Records', utils$1.format('%,d', info.feature_count)]
|
|
9899
|
+
['Type', info.raster_type ? 'raster data' : info.geometry_type || 'tabular data']
|
|
8663
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
|
+
}
|
|
8664
9907
|
if (info.null_shape_count > 0) {
|
|
8665
9908
|
rows.push(['Nulls', utils$1.format("%'d", info.null_shape_count)]);
|
|
8666
9909
|
}
|
|
8667
|
-
if (info.geometry_type && info.feature_count > info.null_shape_count) {
|
|
9910
|
+
if (info.raster_type || info.geometry_type && info.feature_count > info.null_shape_count) {
|
|
8668
9911
|
rows.push(['Bounds', info.bbox.join(',')]);
|
|
8669
9912
|
rows.push(['CRS', info.proj4]);
|
|
8670
9913
|
}
|
|
@@ -8782,7 +10025,7 @@
|
|
|
8782
10025
|
}
|
|
8783
10026
|
|
|
8784
10027
|
function getBoundsIntersection(a, b) {
|
|
8785
|
-
var c = new Bounds();
|
|
10028
|
+
var c = new Bounds$1();
|
|
8786
10029
|
if (a.intersects(b)) {
|
|
8787
10030
|
c.setBounds(Math.max(a.xmin, b.xmin), Math.max(a.ymin, b.ymin),
|
|
8788
10031
|
Math.min(a.xmax, b.xmax), Math.min(a.ymax, b.ymax));
|
|
@@ -9065,11 +10308,7 @@
|
|
|
9065
10308
|
} else {
|
|
9066
10309
|
// stack seems to change if Error is logged directly
|
|
9067
10310
|
console.error(err.stack);
|
|
9068
|
-
|
|
9069
|
-
if (err.name == 'UserError') {
|
|
9070
|
-
msg = err.message;
|
|
9071
|
-
}
|
|
9072
|
-
gui.alert(msg, 'Export failed');
|
|
10311
|
+
gui.alert(getExportErrorMessage(err), 'Export failed');
|
|
9073
10312
|
}
|
|
9074
10313
|
}).finally(function() {
|
|
9075
10314
|
gui.clearProgressMessage();
|
|
@@ -9077,6 +10316,11 @@
|
|
|
9077
10316
|
}, 20);
|
|
9078
10317
|
}
|
|
9079
10318
|
|
|
10319
|
+
function getExportErrorMessage(err) {
|
|
10320
|
+
if (err && err.message) return err.message;
|
|
10321
|
+
return 'Export failed for an unknown reason';
|
|
10322
|
+
}
|
|
10323
|
+
|
|
9080
10324
|
function getExportOpts() {
|
|
9081
10325
|
return GUI.parseFreeformOptions(getExportOptsAsString(), 'o');
|
|
9082
10326
|
}
|
|
@@ -9232,9 +10476,11 @@
|
|
|
9232
10476
|
}
|
|
9233
10477
|
|
|
9234
10478
|
function getDefaultExportFormat() {
|
|
9235
|
-
var
|
|
10479
|
+
var active = model.getActiveLayer();
|
|
10480
|
+
var dataset = active.dataset;
|
|
9236
10481
|
var inputFmt = dataset.info && dataset.info.input_formats &&
|
|
9237
10482
|
dataset.info.input_formats[0];
|
|
10483
|
+
if (active.layer && internal.layerHasRaster(active.layer)) return 'svg';
|
|
9238
10484
|
return getExportFormats().includes(inputFmt) ? inputFmt : 'geojson';
|
|
9239
10485
|
}
|
|
9240
10486
|
|
|
@@ -9542,17 +10788,25 @@
|
|
|
9542
10788
|
headerBtn.on('click', function() {
|
|
9543
10789
|
toggle();
|
|
9544
10790
|
}).on('keydown', function(e) {
|
|
9545
|
-
if (e.key == 'Enter'
|
|
10791
|
+
if (e.key == 'Enter') {
|
|
9546
10792
|
e.preventDefault();
|
|
9547
10793
|
e.stopPropagation();
|
|
9548
10794
|
toggle();
|
|
10795
|
+
} else if (e.key == ' ') {
|
|
10796
|
+
e.preventDefault();
|
|
10797
|
+
e.stopPropagation();
|
|
10798
|
+
gui.toggleSidebarPanel('console');
|
|
9549
10799
|
}
|
|
9550
10800
|
});
|
|
9551
10801
|
tab.on('click', toggle).on('keydown', function(e) {
|
|
9552
|
-
if (e.key == 'Enter'
|
|
10802
|
+
if (e.key == 'Enter') {
|
|
9553
10803
|
e.preventDefault();
|
|
9554
10804
|
e.stopPropagation();
|
|
9555
10805
|
toggle();
|
|
10806
|
+
} else if (e.key == ' ') {
|
|
10807
|
+
e.preventDefault();
|
|
10808
|
+
e.stopPropagation();
|
|
10809
|
+
gui.toggleSidebarPanel('console');
|
|
9556
10810
|
}
|
|
9557
10811
|
});
|
|
9558
10812
|
|
|
@@ -9913,9 +11167,13 @@
|
|
|
9913
11167
|
type = 'data record';
|
|
9914
11168
|
} else if (lyr.geometry_type) {
|
|
9915
11169
|
type = lyr.geometry_type + ' feature';
|
|
11170
|
+
} else if (internal.layerHasRaster(lyr)) {
|
|
11171
|
+
type = 'raster layer';
|
|
9916
11172
|
}
|
|
9917
11173
|
if (isFrame) {
|
|
9918
11174
|
str = 'map frame';
|
|
11175
|
+
} else if (internal.layerHasRaster(lyr)) {
|
|
11176
|
+
str = utils$1.format('%,d x %,d %s', getRasterWidth(lyr.raster), getRasterHeight(lyr.raster), type);
|
|
9919
11177
|
} else if (type) {
|
|
9920
11178
|
str = utils$1.format('%,d %s%s', n, type, utils$1.pluralSuffix(n));
|
|
9921
11179
|
} else {
|
|
@@ -9950,7 +11208,7 @@
|
|
|
9950
11208
|
}
|
|
9951
11209
|
|
|
9952
11210
|
function isPinnable(lyr) {
|
|
9953
|
-
return internal.layerIsGeometric(lyr) || internal.layerHasFurniture(lyr);
|
|
11211
|
+
return internal.layerIsGeometric(lyr) || internal.layerHasRaster(lyr) || internal.layerHasFurniture(lyr);
|
|
9954
11212
|
}
|
|
9955
11213
|
|
|
9956
11214
|
function rowHTML(c1, c2, cname) {
|
|
@@ -11043,6 +12301,7 @@
|
|
|
11043
12301
|
rectangles: ['info', 'selection', 'box', 'rectangles', 'edit_polygons'],
|
|
11044
12302
|
lines: ['info', 'selection', 'box', 'edit_lines'], // 'snip_lines'
|
|
11045
12303
|
table: ['info', 'selection'],
|
|
12304
|
+
raster: ['box'],
|
|
11046
12305
|
labels: ['info', 'selection', 'box', 'labels', 'edit_points'],
|
|
11047
12306
|
points: ['info', 'selection', 'box', 'edit_points'] // , 'add-points'
|
|
11048
12307
|
};
|
|
@@ -11163,6 +12422,9 @@
|
|
|
11163
12422
|
if (!o || !o.layer) {
|
|
11164
12423
|
return menus.empty; // TODO: more sensible handling of missing layer
|
|
11165
12424
|
}
|
|
12425
|
+
if (internal.layerHasRaster(o.layer)) {
|
|
12426
|
+
return menus.raster;
|
|
12427
|
+
}
|
|
11166
12428
|
if (!o.layer.geometry_type) {
|
|
11167
12429
|
return menus.table;
|
|
11168
12430
|
}
|
|
@@ -13577,7 +14839,7 @@
|
|
|
13577
14839
|
|
|
13578
14840
|
// @box Bounds with pixels from t,l corner of map area.
|
|
13579
14841
|
function zoomToBbox(bbox) {
|
|
13580
|
-
var bounds = new Bounds(bbox),
|
|
14842
|
+
var bounds = new Bounds$1(bbox),
|
|
13581
14843
|
pct = Math.max(bounds.width() / ext.width(), bounds.height() / ext.height()),
|
|
13582
14844
|
fx = bounds.centerX() / ext.width() * (1 + pct) - pct / 2,
|
|
13583
14845
|
fy = bounds.centerY() / ext.height() * (1 + pct) - pct / 2;
|
|
@@ -15727,7 +16989,7 @@
|
|
|
15727
16989
|
// Get params for converting geographic coords to pixel coords
|
|
15728
16990
|
this.getTransform = function(pixScale) {
|
|
15729
16991
|
// get transform (y-flipped);
|
|
15730
|
-
var viewBounds = new Bounds(0, 0, _position.width(), _position.height());
|
|
16992
|
+
var viewBounds = new Bounds$1(0, 0, _position.width(), _position.height());
|
|
15731
16993
|
if (pixScale) {
|
|
15732
16994
|
viewBounds.xmax *= pixScale;
|
|
15733
16995
|
viewBounds.ymax *= pixScale;
|
|
@@ -15737,7 +16999,7 @@
|
|
|
15737
16999
|
|
|
15738
17000
|
// k scales the size of the bbox (used by gui to control fp error when zoomed very far)
|
|
15739
17001
|
this.getBounds = function(k) {
|
|
15740
|
-
if (!_fullBounds) return new Bounds();
|
|
17002
|
+
if (!_fullBounds) return new Bounds$1();
|
|
15741
17003
|
return calcBounds(_cx, _cy, _scale / (k || 1));
|
|
15742
17004
|
};
|
|
15743
17005
|
|
|
@@ -15752,7 +17014,7 @@
|
|
|
15752
17014
|
var b = _fullBounds = fullBounds;
|
|
15753
17015
|
if (!b.hasBounds()) return; // kludge
|
|
15754
17016
|
if (strictBounds) {
|
|
15755
|
-
_strictBounds = Array.isArray(strictBounds) ? new Bounds(strictBounds) : strictBounds;
|
|
17017
|
+
_strictBounds = Array.isArray(strictBounds) ? new Bounds$1(strictBounds) : strictBounds;
|
|
15756
17018
|
} else {
|
|
15757
17019
|
_strictBounds = null;
|
|
15758
17020
|
}
|
|
@@ -15782,7 +17044,7 @@
|
|
|
15782
17044
|
|
|
15783
17045
|
this.getSymbolScale = function() {
|
|
15784
17046
|
if (!_frame) return 1;
|
|
15785
|
-
var bounds = new Bounds(_frame.bbox);
|
|
17047
|
+
var bounds = new Bounds$1(_frame.bbox);
|
|
15786
17048
|
var bounds2 = bounds.clone().transform(this.getTransform());
|
|
15787
17049
|
return bounds2.width() / _frame.width;
|
|
15788
17050
|
};
|
|
@@ -15853,12 +17115,12 @@
|
|
|
15853
17115
|
}
|
|
15854
17116
|
var w = full.width() / scale;
|
|
15855
17117
|
var h = full.height() / scale;
|
|
15856
|
-
return new Bounds(cx - w/2, cy - h/2, cx + w/2, cy + h/2);
|
|
17118
|
+
return new Bounds$1(cx - w/2, cy - h/2, cx + w/2, cy + h/2);
|
|
15857
17119
|
}
|
|
15858
17120
|
|
|
15859
17121
|
// Calculate viewport bounds from frame data
|
|
15860
17122
|
function fillOutFrameBounds(frame) {
|
|
15861
|
-
var bounds = new Bounds(frame.bbox);
|
|
17123
|
+
var bounds = new Bounds$1(frame.bbox);
|
|
15862
17124
|
var kx = _position.width() / frame.width;
|
|
15863
17125
|
var ky = _position.height() / frame.height;
|
|
15864
17126
|
bounds.scale(kx, ky);
|
|
@@ -15870,7 +17132,7 @@
|
|
|
15870
17132
|
hpix = _position.height() - 2 * marginpix,
|
|
15871
17133
|
xpad, ypad, b2;
|
|
15872
17134
|
if (wpix <= 0 || hpix <= 0) {
|
|
15873
|
-
return new Bounds(0, 0, 0, 0);
|
|
17135
|
+
return new Bounds$1(0, 0, 0, 0);
|
|
15874
17136
|
}
|
|
15875
17137
|
b = b.clone();
|
|
15876
17138
|
b2 = b.clone();
|
|
@@ -15908,6 +17170,198 @@
|
|
|
15908
17170
|
|
|
15909
17171
|
utils$1.inherit(MapExtent, EventDispatcher);
|
|
15910
17172
|
|
|
17173
|
+
var MAX_VIEWPORT_PREVIEW_PIXELS = 6e6;
|
|
17174
|
+
var cache = new WeakMap();
|
|
17175
|
+
var requestId = 0;
|
|
17176
|
+
|
|
17177
|
+
function getCachedRasterViewportPreview(layer, ext) {
|
|
17178
|
+
var entry = cache.get(layer);
|
|
17179
|
+
var params = getRasterViewportPreviewParams(layer, ext);
|
|
17180
|
+
if (!entry || !params || entry.key != params.key) return null;
|
|
17181
|
+
return entry.preview;
|
|
17182
|
+
}
|
|
17183
|
+
|
|
17184
|
+
function scheduleRasterViewportPreview(layer, ext, onReady) {
|
|
17185
|
+
var params = getRasterViewportPreviewParams(layer, ext);
|
|
17186
|
+
var entry = cache.get(layer);
|
|
17187
|
+
var id, stats, timing, preview;
|
|
17188
|
+
if (!params || !params.needed) return;
|
|
17189
|
+
if (entry && entry.key == params.key) return;
|
|
17190
|
+
id = ++requestId;
|
|
17191
|
+
cache.set(layer, {key: params.key, pending: id});
|
|
17192
|
+
setTimeout(function() {
|
|
17193
|
+
var current = cache.get(layer);
|
|
17194
|
+
if (!current || current.pending != id) return;
|
|
17195
|
+
timing = {};
|
|
17196
|
+
stats = getCachedRasterScalingStats(params, timing);
|
|
17197
|
+
timing.renderStart = getTimer();
|
|
17198
|
+
preview = renderRasterViewportPreview(params.grid, params.recipe, params.bbox, params.width, params.height, stats);
|
|
17199
|
+
timing.renderMs = getTimer() - timing.renderStart;
|
|
17200
|
+
logRasterPreviewTiming(params, timing);
|
|
17201
|
+
current = cache.get(layer);
|
|
17202
|
+
if (!preview || !current || current.pending != id) return;
|
|
17203
|
+
cache.set(layer, {
|
|
17204
|
+
key: params.key,
|
|
17205
|
+
preview: preview
|
|
17206
|
+
});
|
|
17207
|
+
onReady();
|
|
17208
|
+
}, 0);
|
|
17209
|
+
}
|
|
17210
|
+
|
|
17211
|
+
function invalidateRasterViewportPreview(layer) {
|
|
17212
|
+
cache.delete(layer);
|
|
17213
|
+
}
|
|
17214
|
+
|
|
17215
|
+
function getRasterViewportPreviewParams(layer, ext) {
|
|
17216
|
+
var raster = layer && layer.raster;
|
|
17217
|
+
var grid = getRasterGrid(raster);
|
|
17218
|
+
var rasterBbox = getRasterBBox(raster);
|
|
17219
|
+
var mapBbox = ext.getBounds().toArray();
|
|
17220
|
+
var visibleBbox = rasterBbox && intersectBboxes(rasterBbox, mapBbox);
|
|
17221
|
+
var preview = getRasterPreview(raster);
|
|
17222
|
+
var recipe, pixelRatio, t, p1, p2, displayWidth, displayHeight, crop, width, height, scale, key, needed;
|
|
17223
|
+
if (!grid || !grid.samples || !visibleBbox || !grid.bbox) return null;
|
|
17224
|
+
if (!supportsNorthUpRaster(grid)) return null;
|
|
17225
|
+
crop = getRasterSourceWindow(grid, visibleBbox);
|
|
17226
|
+
if (!crop) return null;
|
|
17227
|
+
visibleBbox = crop.bbox;
|
|
17228
|
+
recipe = getRasterViewRecipe(grid, raster.view && raster.view.recipe);
|
|
17229
|
+
pixelRatio = GUI.getPixelRatio();
|
|
17230
|
+
t = ext.getTransform(pixelRatio);
|
|
17231
|
+
p1 = t.transform(mapBbox[0], mapBbox[3]);
|
|
17232
|
+
p2 = t.transform(mapBbox[2], mapBbox[1]);
|
|
17233
|
+
displayWidth = Math.max(1, Math.round(Math.abs(p2[0] - p1[0])));
|
|
17234
|
+
displayHeight = Math.max(1, Math.round(Math.abs(p2[1] - p1[1])));
|
|
17235
|
+
width = Math.min(displayWidth, crop.width);
|
|
17236
|
+
height = Math.min(displayHeight, crop.height);
|
|
17237
|
+
scale = Math.min(1, Math.sqrt(MAX_VIEWPORT_PREVIEW_PIXELS / (width * height)));
|
|
17238
|
+
width = Math.max(1, Math.round(width * scale));
|
|
17239
|
+
height = Math.max(1, Math.round(height * scale));
|
|
17240
|
+
needed = viewportPreviewIsSharper(preview, grid, visibleBbox, width, height);
|
|
17241
|
+
key = [
|
|
17242
|
+
visibleBbox.map(roundKeyPart).join(','),
|
|
17243
|
+
width,
|
|
17244
|
+
height,
|
|
17245
|
+
pixelRatio,
|
|
17246
|
+
grid.width,
|
|
17247
|
+
grid.height,
|
|
17248
|
+
grid.samples && grid.samples.length,
|
|
17249
|
+
recipe.type,
|
|
17250
|
+
recipe.scaling,
|
|
17251
|
+
recipe.scaleRange && recipe.scaleRange.join(','),
|
|
17252
|
+
recipe.percentileRange && recipe.percentileRange.join(',')
|
|
17253
|
+
].join('|');
|
|
17254
|
+
return {
|
|
17255
|
+
key: key,
|
|
17256
|
+
needed: needed,
|
|
17257
|
+
raster: raster,
|
|
17258
|
+
grid: grid,
|
|
17259
|
+
recipe: recipe,
|
|
17260
|
+
bbox: visibleBbox,
|
|
17261
|
+
width: width,
|
|
17262
|
+
height: height,
|
|
17263
|
+
sourceWidth: crop.width,
|
|
17264
|
+
sourceHeight: crop.height
|
|
17265
|
+
};
|
|
17266
|
+
}
|
|
17267
|
+
|
|
17268
|
+
function getCachedRasterScalingStats(params, timing) {
|
|
17269
|
+
var cached = params.raster.view && params.raster.view.scalingStats;
|
|
17270
|
+
var key;
|
|
17271
|
+
if (params.recipe.scaling == 'none') {
|
|
17272
|
+
timing.statsMs = 0;
|
|
17273
|
+
timing.statsSource = 'none';
|
|
17274
|
+
return null;
|
|
17275
|
+
}
|
|
17276
|
+
key = getRasterScalingStatsKey(params.grid, params.recipe);
|
|
17277
|
+
if (cached && cached.key == key) {
|
|
17278
|
+
timing.statsMs = 0;
|
|
17279
|
+
timing.statsSource = 'raster-view-cache';
|
|
17280
|
+
return cached.stats;
|
|
17281
|
+
}
|
|
17282
|
+
timing.statsSource = 'computed';
|
|
17283
|
+
timing.statsStart = getTimer();
|
|
17284
|
+
cached = getRasterViewScalingStats(params.raster, params.recipe);
|
|
17285
|
+
timing.statsMs = getTimer() - timing.statsStart;
|
|
17286
|
+
return cached;
|
|
17287
|
+
}
|
|
17288
|
+
|
|
17289
|
+
function logRasterPreviewTiming(params, timing) {
|
|
17290
|
+
if (!rasterDebugIsOn()) return;
|
|
17291
|
+
console.log([
|
|
17292
|
+
'Raster viewport preview:',
|
|
17293
|
+
params.width + 'x' + params.height,
|
|
17294
|
+
'from',
|
|
17295
|
+
params.sourceWidth + 'x' + params.sourceHeight,
|
|
17296
|
+
'source px,',
|
|
17297
|
+
'stats=' + formatMs(timing.statsMs),
|
|
17298
|
+
'(' + timing.statsSource + '),',
|
|
17299
|
+
'render=' + formatMs(timing.renderMs) + ',',
|
|
17300
|
+
'total=' + formatMs(timing.statsMs + timing.renderMs) + ',',
|
|
17301
|
+
'scaling=' + params.recipe.scaling,
|
|
17302
|
+
'type=' + params.recipe.type
|
|
17303
|
+
].join(' '));
|
|
17304
|
+
}
|
|
17305
|
+
|
|
17306
|
+
function rasterDebugIsOn() {
|
|
17307
|
+
var vars = GUI.getUrlVars();
|
|
17308
|
+
return vars['raster-debug'] === true || vars['raster-debug'] == '1' || vars.raster_debug === true || vars.raster_debug == '1';
|
|
17309
|
+
}
|
|
17310
|
+
|
|
17311
|
+
function getTimer() {
|
|
17312
|
+
return typeof performance != 'undefined' && performance.now ? performance.now() : Date.now();
|
|
17313
|
+
}
|
|
17314
|
+
|
|
17315
|
+
function formatMs(ms) {
|
|
17316
|
+
return Math.round(ms * 10) / 10 + 'ms';
|
|
17317
|
+
}
|
|
17318
|
+
|
|
17319
|
+
function supportsNorthUpRaster(grid) {
|
|
17320
|
+
var t = grid.transform;
|
|
17321
|
+
return !t || t[1] === 0 && t[3] === 0;
|
|
17322
|
+
}
|
|
17323
|
+
|
|
17324
|
+
function getRasterSourcePixelSize(grid, bbox) {
|
|
17325
|
+
var crop = getRasterSourceWindow(grid, bbox);
|
|
17326
|
+
return crop ? {width: crop.width, height: crop.height} : {width: 0, height: 0};
|
|
17327
|
+
}
|
|
17328
|
+
|
|
17329
|
+
function getRasterSourceWindow(grid, bbox) {
|
|
17330
|
+
var rb = grid.bbox;
|
|
17331
|
+
var dx = (rb[2] - rb[0]) / grid.width;
|
|
17332
|
+
var dy = (rb[3] - rb[1]) / grid.height;
|
|
17333
|
+
var x0 = Math.max(0, Math.floor((bbox[0] - rb[0]) / dx));
|
|
17334
|
+
var x1 = Math.min(grid.width, Math.ceil((bbox[2] - rb[0]) / dx));
|
|
17335
|
+
var y0 = Math.max(0, Math.floor((rb[3] - bbox[3]) / dy));
|
|
17336
|
+
var y1 = Math.min(grid.height, Math.ceil((rb[3] - bbox[1]) / dy));
|
|
17337
|
+
if (x1 <= x0 || y1 <= y0) return null;
|
|
17338
|
+
return {
|
|
17339
|
+
x: x0,
|
|
17340
|
+
y: y0,
|
|
17341
|
+
width: x1 - x0,
|
|
17342
|
+
height: y1 - y0,
|
|
17343
|
+
bbox: [
|
|
17344
|
+
rb[0] + x0 * dx,
|
|
17345
|
+
rb[3] - y1 * dy,
|
|
17346
|
+
rb[0] + x1 * dx,
|
|
17347
|
+
rb[3] - y0 * dy
|
|
17348
|
+
]
|
|
17349
|
+
};
|
|
17350
|
+
}
|
|
17351
|
+
|
|
17352
|
+
function viewportPreviewIsSharper(preview, grid, bbox, width, height) {
|
|
17353
|
+
var crop = getRasterSourcePixelSize(grid, bbox);
|
|
17354
|
+
var previewWidth = preview && preview.width || 0;
|
|
17355
|
+
var previewHeight = preview && preview.height || 0;
|
|
17356
|
+
var fallbackWidth = crop.width * previewWidth / grid.width;
|
|
17357
|
+
var fallbackHeight = crop.height * previewHeight / grid.height;
|
|
17358
|
+
return width > fallbackWidth * 1.25 || height > fallbackHeight * 1.25;
|
|
17359
|
+
}
|
|
17360
|
+
|
|
17361
|
+
function roundKeyPart(val) {
|
|
17362
|
+
return Math.round(val * 1e9) / 1e9;
|
|
17363
|
+
}
|
|
17364
|
+
|
|
15911
17365
|
var hatches = {}; // cached patterns
|
|
15912
17366
|
|
|
15913
17367
|
function getCanvasFillEffect(ctx, shp, arcs, ext, style) {
|
|
@@ -16110,7 +17564,7 @@
|
|
|
16110
17564
|
function getShapeFilter(arcs, shapes, ext) {
|
|
16111
17565
|
if (ext.scale() < 1.1) return null;
|
|
16112
17566
|
var view = ext.getBounds();
|
|
16113
|
-
var b = new Bounds();
|
|
17567
|
+
var b = new Bounds$1();
|
|
16114
17568
|
return function(i) {
|
|
16115
17569
|
var shp = shapes[i];
|
|
16116
17570
|
if (!shp) return false;
|
|
@@ -16163,6 +17617,22 @@
|
|
|
16163
17617
|
_ext = extent;
|
|
16164
17618
|
};
|
|
16165
17619
|
|
|
17620
|
+
_self.drawRasterLayer = function(layer, opts) {
|
|
17621
|
+
var raster = layer.raster;
|
|
17622
|
+
var options = opts || {};
|
|
17623
|
+
var preview = options.action == 'nav' ? null : getCachedRasterViewportPreview(layer, _ext);
|
|
17624
|
+
var bbox = preview && preview.bbox;
|
|
17625
|
+
if (!preview) {
|
|
17626
|
+
preview = raster && getRasterPreview(raster);
|
|
17627
|
+
bbox = raster && getRasterBBox(raster);
|
|
17628
|
+
if (options.action != 'nav' && options.onViewportPreviewReady) {
|
|
17629
|
+
scheduleRasterViewportPreview(layer, _ext, options.onViewportPreviewReady);
|
|
17630
|
+
}
|
|
17631
|
+
}
|
|
17632
|
+
if (!preview || !preview.pixels || !bbox) return;
|
|
17633
|
+
drawRasterPreview(preview, bbox);
|
|
17634
|
+
};
|
|
17635
|
+
|
|
16166
17636
|
/*
|
|
16167
17637
|
// Original function, not optimized
|
|
16168
17638
|
_self.drawStyledPaths = function(shapes, arcs, style) {
|
|
@@ -16359,6 +17829,15 @@
|
|
|
16359
17829
|
}
|
|
16360
17830
|
}
|
|
16361
17831
|
|
|
17832
|
+
function drawRasterPreview(preview, bbox) {
|
|
17833
|
+
var img = getRasterCanvas(preview);
|
|
17834
|
+
var t = _ext.getTransform(GUI.getPixelRatio());
|
|
17835
|
+
var p1 = t.transform(bbox[0], bbox[3]);
|
|
17836
|
+
var p2 = t.transform(bbox[2], bbox[1]);
|
|
17837
|
+
_ctx.imageSmoothingEnabled = true;
|
|
17838
|
+
_ctx.drawImage(img, p1[0], p1[1], p2[0] - p1[0], p2[1] - p1[1]);
|
|
17839
|
+
}
|
|
17840
|
+
|
|
16362
17841
|
// TODO: consider using drawStyledPaths(), which draws paths in batches
|
|
16363
17842
|
// for faster Canvas rendering. Downside: changes stacking order, which
|
|
16364
17843
|
// is bad if circles are graduated.
|
|
@@ -16474,6 +17953,19 @@
|
|
|
16474
17953
|
};
|
|
16475
17954
|
}
|
|
16476
17955
|
|
|
17956
|
+
function getRasterCanvas(preview) {
|
|
17957
|
+
if (preview.canvas) return preview.canvas;
|
|
17958
|
+
var canvas = document.createElement('canvas');
|
|
17959
|
+
var ctx = canvas.getContext('2d');
|
|
17960
|
+
var imageData;
|
|
17961
|
+
canvas.width = preview.width;
|
|
17962
|
+
canvas.height = preview.height;
|
|
17963
|
+
imageData = new ImageData(preview.pixels, preview.width, preview.height);
|
|
17964
|
+
ctx.putImageData(imageData, 0, 0);
|
|
17965
|
+
preview.canvas = canvas;
|
|
17966
|
+
return canvas;
|
|
17967
|
+
}
|
|
17968
|
+
|
|
16477
17969
|
function drawCircle(x, y, radius, ctx) {
|
|
16478
17970
|
if (radius > 0) {
|
|
16479
17971
|
ctx.moveTo(x + radius, y);
|
|
@@ -16774,6 +18266,7 @@
|
|
|
16774
18266
|
var _settleTimer = null;
|
|
16775
18267
|
var _redrawPending = false;
|
|
16776
18268
|
var _settleRequested = false;
|
|
18269
|
+
var _rasterNavRefreshPending = false;
|
|
16777
18270
|
|
|
16778
18271
|
// 'map_interaction_end' may arrive before the in-flight 'nav' draw runs,
|
|
16779
18272
|
// because drawLayers() schedules its work via requestAnimationFrame.
|
|
@@ -16782,6 +18275,8 @@
|
|
|
16782
18275
|
gui.on('map_interaction_end', function() {
|
|
16783
18276
|
if (_redrawPending) {
|
|
16784
18277
|
settleNow();
|
|
18278
|
+
} else if (_rasterNavRefreshPending) {
|
|
18279
|
+
settleRasterNow();
|
|
16785
18280
|
} else {
|
|
16786
18281
|
_settleRequested = true;
|
|
16787
18282
|
}
|
|
@@ -16792,6 +18287,11 @@
|
|
|
16792
18287
|
|
|
16793
18288
|
this.drawMainLayers = function(layers, action) {
|
|
16794
18289
|
if (skipMainLayerRedraw(action)) return;
|
|
18290
|
+
if (action == 'nav' && layersHaveRasters(layers)) {
|
|
18291
|
+
_rasterNavRefreshPending = true;
|
|
18292
|
+
} else if (action != 'nav') {
|
|
18293
|
+
_rasterNavRefreshPending = false;
|
|
18294
|
+
}
|
|
16795
18295
|
if (action == 'nav' && shouldUseFastNav()) {
|
|
16796
18296
|
applyFastTransform();
|
|
16797
18297
|
// SVG symbol reposition is already cheap; keep labels/symbols accurate
|
|
@@ -16821,7 +18321,7 @@
|
|
|
16821
18321
|
} else if (isSvgLayer) {
|
|
16822
18322
|
_svg.drawLayer(lyr, 'symbol');
|
|
16823
18323
|
} else {
|
|
16824
|
-
drawCanvasLayer(lyr, _mainCanv);
|
|
18324
|
+
drawCanvasLayer(lyr, _mainCanv, action);
|
|
16825
18325
|
}
|
|
16826
18326
|
});
|
|
16827
18327
|
// Force synchronous rasterization so performance.now() reflects the true
|
|
@@ -16830,6 +18330,10 @@
|
|
|
16830
18330
|
flushCanvas(_mainCanv);
|
|
16831
18331
|
_lastFrameMs = performance.now() - startTime;
|
|
16832
18332
|
captureSnapshot();
|
|
18333
|
+
if (action == 'nav' && _settleRequested && _rasterNavRefreshPending) {
|
|
18334
|
+
_settleRequested = false;
|
|
18335
|
+
settleRasterNow();
|
|
18336
|
+
}
|
|
16833
18337
|
};
|
|
16834
18338
|
|
|
16835
18339
|
// Draw highlight effect for hover and selection
|
|
@@ -16850,7 +18354,7 @@
|
|
|
16850
18354
|
_overlayCanv.hide();
|
|
16851
18355
|
}
|
|
16852
18356
|
layers.forEach(function(lyr) {
|
|
16853
|
-
drawCanvasLayer(lyr, canv);
|
|
18357
|
+
drawCanvasLayer(lyr, canv, action);
|
|
16854
18358
|
});
|
|
16855
18359
|
};
|
|
16856
18360
|
|
|
@@ -16875,9 +18379,22 @@
|
|
|
16875
18379
|
return action == 'hover' && _overlayCanv.visible();
|
|
16876
18380
|
}
|
|
16877
18381
|
|
|
16878
|
-
function
|
|
18382
|
+
function layersHaveRasters(layers) {
|
|
18383
|
+
return layers.some(function(lyr) {
|
|
18384
|
+
return lyr && lyr.gui && internal.layerHasRaster(lyr.gui.displayLayer);
|
|
18385
|
+
});
|
|
18386
|
+
}
|
|
18387
|
+
|
|
18388
|
+
function drawCanvasLayer(lyr, canv, action) {
|
|
16879
18389
|
if (!lyr) return;
|
|
16880
|
-
if (lyr.gui.
|
|
18390
|
+
if (internal.layerHasRaster(lyr.gui.displayLayer)) {
|
|
18391
|
+
canv.drawRasterLayer(lyr.gui.displayLayer, {
|
|
18392
|
+
action: action,
|
|
18393
|
+
onViewportPreviewReady: function() {
|
|
18394
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
18395
|
+
}
|
|
18396
|
+
});
|
|
18397
|
+
} else if (lyr.gui.style.type == 'outline') {
|
|
16881
18398
|
drawOutlineLayerToCanvas(lyr, canv, ext);
|
|
16882
18399
|
} else {
|
|
16883
18400
|
drawStyledLayerToCanvas(lyr, canv, ext);
|
|
@@ -16980,6 +18497,11 @@
|
|
|
16980
18497
|
gui.dispatchEvent('map-needs-refresh');
|
|
16981
18498
|
}
|
|
16982
18499
|
|
|
18500
|
+
function settleRasterNow() {
|
|
18501
|
+
_rasterNavRefreshPending = false;
|
|
18502
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
18503
|
+
}
|
|
18504
|
+
|
|
16983
18505
|
function cancelSettle() {
|
|
16984
18506
|
if (_settleTimer) {
|
|
16985
18507
|
clearTimeout(_settleTimer);
|
|
@@ -16995,6 +18517,9 @@
|
|
|
16995
18517
|
var box = new HighlightBox(gui, {name: 'box-tool', persistent: true, handles: true, draggable: true});
|
|
16996
18518
|
var popup = gui.container.findChild('.box-tool-options');
|
|
16997
18519
|
var coords = popup.findChild('.box-coords').hide();
|
|
18520
|
+
var selectBtn = popup.findChild('.select-btn');
|
|
18521
|
+
var clipBtn = popup.findChild('.clip-btn');
|
|
18522
|
+
var eraseBtn = popup.findChild('.erase-btn');
|
|
16998
18523
|
var _on = false;
|
|
16999
18524
|
var instructionsShown = false;
|
|
17000
18525
|
var alert;
|
|
@@ -17007,7 +18532,7 @@
|
|
|
17007
18532
|
reset();
|
|
17008
18533
|
});
|
|
17009
18534
|
|
|
17010
|
-
new SimpleButton(
|
|
18535
|
+
new SimpleButton(selectBtn).on('click', function() {
|
|
17011
18536
|
var coords = box.getDataCoords();
|
|
17012
18537
|
if (!coords || noData()) return;
|
|
17013
18538
|
gui.enterMode('selection_tool');
|
|
@@ -17022,11 +18547,12 @@
|
|
|
17022
18547
|
return !gui.model.getActiveLayer();
|
|
17023
18548
|
}
|
|
17024
18549
|
|
|
17025
|
-
new SimpleButton(
|
|
18550
|
+
new SimpleButton(clipBtn).on('click', function() {
|
|
17026
18551
|
runCommand('-clip bbox=' + box.getDataCoords().join(','));
|
|
17027
18552
|
});
|
|
17028
18553
|
|
|
17029
|
-
new SimpleButton(
|
|
18554
|
+
new SimpleButton(eraseBtn).on('click', function() {
|
|
18555
|
+
if (activeLayerIsRaster()) return;
|
|
17030
18556
|
runCommand('-erase bbox=' + box.getDataCoords().join(','));
|
|
17031
18557
|
});
|
|
17032
18558
|
|
|
@@ -17069,6 +18595,7 @@
|
|
|
17069
18595
|
box.on('dragend', function(e) {
|
|
17070
18596
|
if (_on) {
|
|
17071
18597
|
hideInstructions();
|
|
18598
|
+
updateOptionsForActiveLayer();
|
|
17072
18599
|
popup.show();
|
|
17073
18600
|
}
|
|
17074
18601
|
});
|
|
@@ -17122,6 +18649,7 @@
|
|
|
17122
18649
|
|
|
17123
18650
|
function turnOn() {
|
|
17124
18651
|
box.turnOn();
|
|
18652
|
+
updateOptionsForActiveLayer();
|
|
17125
18653
|
_on = true;
|
|
17126
18654
|
}
|
|
17127
18655
|
|
|
@@ -17142,6 +18670,27 @@
|
|
|
17142
18670
|
hideCoords();
|
|
17143
18671
|
}
|
|
17144
18672
|
|
|
18673
|
+
function updateOptionsForActiveLayer() {
|
|
18674
|
+
var raster = activeLayerIsRaster();
|
|
18675
|
+
setButtonVisible(selectBtn, !raster);
|
|
18676
|
+
setButtonVisible(clipBtn, true);
|
|
18677
|
+
setButtonVisible(eraseBtn, !raster);
|
|
18678
|
+
}
|
|
18679
|
+
|
|
18680
|
+
function activeLayerIsRaster() {
|
|
18681
|
+
var active = gui.model.getActiveLayer();
|
|
18682
|
+
return !!(active && internal.layerHasRaster(active.layer));
|
|
18683
|
+
}
|
|
18684
|
+
|
|
18685
|
+
function setButtonVisible(btn, visible) {
|
|
18686
|
+
btn.classed('hidden', !visible);
|
|
18687
|
+
if (visible) {
|
|
18688
|
+
btn.show();
|
|
18689
|
+
} else {
|
|
18690
|
+
btn.hide();
|
|
18691
|
+
}
|
|
18692
|
+
}
|
|
18693
|
+
|
|
17145
18694
|
function openAddFramePopup(gui, bbox) {
|
|
17146
18695
|
var popup = showPopupAlert('', 'Add a map frame');
|
|
17147
18696
|
var el = popup.container();
|
|
@@ -17511,7 +19060,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
17511
19060
|
|
|
17512
19061
|
function getDisplayOptions() {
|
|
17513
19062
|
return {
|
|
17514
|
-
crs: _dynamicCRS
|
|
19063
|
+
crs: _dynamicCRS,
|
|
19064
|
+
notify: gui.notify
|
|
17515
19065
|
};
|
|
17516
19066
|
}
|
|
17517
19067
|
|
|
@@ -17528,7 +19078,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
17528
19078
|
}
|
|
17529
19079
|
|
|
17530
19080
|
function getContentLayerBounds() {
|
|
17531
|
-
var b = new Bounds();
|
|
19081
|
+
var b = new Bounds$1();
|
|
17532
19082
|
var layers = getContentLayers();
|
|
17533
19083
|
layers.forEach(function(lyr) {
|
|
17534
19084
|
b.mergeBounds(lyr.gui.bounds);
|
|
@@ -17545,7 +19095,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
17545
19095
|
function calcFullBounds() {
|
|
17546
19096
|
var b;
|
|
17547
19097
|
if (isPreviewView()) {
|
|
17548
|
-
b = new Bounds(getFrameLayerData().bbox);
|
|
19098
|
+
b = new Bounds$1(getFrameLayerData().bbox);
|
|
17549
19099
|
} else {
|
|
17550
19100
|
b = getContentLayerBounds();
|
|
17551
19101
|
}
|
|
@@ -18510,6 +20060,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
18510
20060
|
var clearMsg;
|
|
18511
20061
|
|
|
18512
20062
|
initModeRules(gui);
|
|
20063
|
+
startRasterSourceStoreLifecycle();
|
|
18513
20064
|
gui.map.init();
|
|
18514
20065
|
|
|
18515
20066
|
if (opts.saveControl) {
|