mapshaper 0.7.13 → 0.7.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mapshaper.js +1616 -21
- package/package.json +6 -2
- package/www/geotiff.js +26351 -0
- package/www/mapshaper-gui.js +1849 -306
- package/www/mapshaper.js +1616 -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
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4376
|
+
Bounds.prototype.toArray = function() {
|
|
4377
|
+
return this.hasBounds() ? [this.xmin, this.ymin, this.xmax, this.ymax] : [];
|
|
4378
|
+
};
|
|
4379
|
+
|
|
4380
|
+
Bounds.prototype.hasBounds = function() {
|
|
4381
|
+
return this.xmin <= this.xmax && this.ymin <= this.ymax;
|
|
4382
|
+
};
|
|
4383
|
+
|
|
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
|
+
});
|
|
4299
5153
|
}
|
|
4300
5154
|
|
|
4301
|
-
function
|
|
4302
|
-
|
|
5155
|
+
function getRasterLayerBounds(lyr) {
|
|
5156
|
+
var bbox = lyr.raster && getRasterBBox(lyr.raster);
|
|
5157
|
+
return bbox && bbox.length == 4 ? new Bounds(bbox) : null;
|
|
4303
5158
|
}
|
|
4304
5159
|
|
|
4305
|
-
function
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
}
|
|
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.
|
|
@@ -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
|
|
|
@@ -9913,9 +11159,13 @@
|
|
|
9913
11159
|
type = 'data record';
|
|
9914
11160
|
} else if (lyr.geometry_type) {
|
|
9915
11161
|
type = lyr.geometry_type + ' feature';
|
|
11162
|
+
} else if (internal.layerHasRaster(lyr)) {
|
|
11163
|
+
type = 'raster layer';
|
|
9916
11164
|
}
|
|
9917
11165
|
if (isFrame) {
|
|
9918
11166
|
str = 'map frame';
|
|
11167
|
+
} else if (internal.layerHasRaster(lyr)) {
|
|
11168
|
+
str = utils$1.format('%,d x %,d %s', getRasterWidth(lyr.raster), getRasterHeight(lyr.raster), type);
|
|
9919
11169
|
} else if (type) {
|
|
9920
11170
|
str = utils$1.format('%,d %s%s', n, type, utils$1.pluralSuffix(n));
|
|
9921
11171
|
} else {
|
|
@@ -9950,7 +11200,7 @@
|
|
|
9950
11200
|
}
|
|
9951
11201
|
|
|
9952
11202
|
function isPinnable(lyr) {
|
|
9953
|
-
return internal.layerIsGeometric(lyr) || internal.layerHasFurniture(lyr);
|
|
11203
|
+
return internal.layerIsGeometric(lyr) || internal.layerHasRaster(lyr) || internal.layerHasFurniture(lyr);
|
|
9954
11204
|
}
|
|
9955
11205
|
|
|
9956
11206
|
function rowHTML(c1, c2, cname) {
|
|
@@ -11043,6 +12293,7 @@
|
|
|
11043
12293
|
rectangles: ['info', 'selection', 'box', 'rectangles', 'edit_polygons'],
|
|
11044
12294
|
lines: ['info', 'selection', 'box', 'edit_lines'], // 'snip_lines'
|
|
11045
12295
|
table: ['info', 'selection'],
|
|
12296
|
+
raster: ['box'],
|
|
11046
12297
|
labels: ['info', 'selection', 'box', 'labels', 'edit_points'],
|
|
11047
12298
|
points: ['info', 'selection', 'box', 'edit_points'] // , 'add-points'
|
|
11048
12299
|
};
|
|
@@ -11163,6 +12414,9 @@
|
|
|
11163
12414
|
if (!o || !o.layer) {
|
|
11164
12415
|
return menus.empty; // TODO: more sensible handling of missing layer
|
|
11165
12416
|
}
|
|
12417
|
+
if (internal.layerHasRaster(o.layer)) {
|
|
12418
|
+
return menus.raster;
|
|
12419
|
+
}
|
|
11166
12420
|
if (!o.layer.geometry_type) {
|
|
11167
12421
|
return menus.table;
|
|
11168
12422
|
}
|
|
@@ -13577,7 +14831,7 @@
|
|
|
13577
14831
|
|
|
13578
14832
|
// @box Bounds with pixels from t,l corner of map area.
|
|
13579
14833
|
function zoomToBbox(bbox) {
|
|
13580
|
-
var bounds = new Bounds(bbox),
|
|
14834
|
+
var bounds = new Bounds$1(bbox),
|
|
13581
14835
|
pct = Math.max(bounds.width() / ext.width(), bounds.height() / ext.height()),
|
|
13582
14836
|
fx = bounds.centerX() / ext.width() * (1 + pct) - pct / 2,
|
|
13583
14837
|
fy = bounds.centerY() / ext.height() * (1 + pct) - pct / 2;
|
|
@@ -15727,7 +16981,7 @@
|
|
|
15727
16981
|
// Get params for converting geographic coords to pixel coords
|
|
15728
16982
|
this.getTransform = function(pixScale) {
|
|
15729
16983
|
// get transform (y-flipped);
|
|
15730
|
-
var viewBounds = new Bounds(0, 0, _position.width(), _position.height());
|
|
16984
|
+
var viewBounds = new Bounds$1(0, 0, _position.width(), _position.height());
|
|
15731
16985
|
if (pixScale) {
|
|
15732
16986
|
viewBounds.xmax *= pixScale;
|
|
15733
16987
|
viewBounds.ymax *= pixScale;
|
|
@@ -15737,7 +16991,7 @@
|
|
|
15737
16991
|
|
|
15738
16992
|
// k scales the size of the bbox (used by gui to control fp error when zoomed very far)
|
|
15739
16993
|
this.getBounds = function(k) {
|
|
15740
|
-
if (!_fullBounds) return new Bounds();
|
|
16994
|
+
if (!_fullBounds) return new Bounds$1();
|
|
15741
16995
|
return calcBounds(_cx, _cy, _scale / (k || 1));
|
|
15742
16996
|
};
|
|
15743
16997
|
|
|
@@ -15752,7 +17006,7 @@
|
|
|
15752
17006
|
var b = _fullBounds = fullBounds;
|
|
15753
17007
|
if (!b.hasBounds()) return; // kludge
|
|
15754
17008
|
if (strictBounds) {
|
|
15755
|
-
_strictBounds = Array.isArray(strictBounds) ? new Bounds(strictBounds) : strictBounds;
|
|
17009
|
+
_strictBounds = Array.isArray(strictBounds) ? new Bounds$1(strictBounds) : strictBounds;
|
|
15756
17010
|
} else {
|
|
15757
17011
|
_strictBounds = null;
|
|
15758
17012
|
}
|
|
@@ -15782,7 +17036,7 @@
|
|
|
15782
17036
|
|
|
15783
17037
|
this.getSymbolScale = function() {
|
|
15784
17038
|
if (!_frame) return 1;
|
|
15785
|
-
var bounds = new Bounds(_frame.bbox);
|
|
17039
|
+
var bounds = new Bounds$1(_frame.bbox);
|
|
15786
17040
|
var bounds2 = bounds.clone().transform(this.getTransform());
|
|
15787
17041
|
return bounds2.width() / _frame.width;
|
|
15788
17042
|
};
|
|
@@ -15853,12 +17107,12 @@
|
|
|
15853
17107
|
}
|
|
15854
17108
|
var w = full.width() / scale;
|
|
15855
17109
|
var h = full.height() / scale;
|
|
15856
|
-
return new Bounds(cx - w/2, cy - h/2, cx + w/2, cy + h/2);
|
|
17110
|
+
return new Bounds$1(cx - w/2, cy - h/2, cx + w/2, cy + h/2);
|
|
15857
17111
|
}
|
|
15858
17112
|
|
|
15859
17113
|
// Calculate viewport bounds from frame data
|
|
15860
17114
|
function fillOutFrameBounds(frame) {
|
|
15861
|
-
var bounds = new Bounds(frame.bbox);
|
|
17115
|
+
var bounds = new Bounds$1(frame.bbox);
|
|
15862
17116
|
var kx = _position.width() / frame.width;
|
|
15863
17117
|
var ky = _position.height() / frame.height;
|
|
15864
17118
|
bounds.scale(kx, ky);
|
|
@@ -15870,7 +17124,7 @@
|
|
|
15870
17124
|
hpix = _position.height() - 2 * marginpix,
|
|
15871
17125
|
xpad, ypad, b2;
|
|
15872
17126
|
if (wpix <= 0 || hpix <= 0) {
|
|
15873
|
-
return new Bounds(0, 0, 0, 0);
|
|
17127
|
+
return new Bounds$1(0, 0, 0, 0);
|
|
15874
17128
|
}
|
|
15875
17129
|
b = b.clone();
|
|
15876
17130
|
b2 = b.clone();
|
|
@@ -15908,6 +17162,198 @@
|
|
|
15908
17162
|
|
|
15909
17163
|
utils$1.inherit(MapExtent, EventDispatcher);
|
|
15910
17164
|
|
|
17165
|
+
var MAX_VIEWPORT_PREVIEW_PIXELS = 6e6;
|
|
17166
|
+
var cache = new WeakMap();
|
|
17167
|
+
var requestId = 0;
|
|
17168
|
+
|
|
17169
|
+
function getCachedRasterViewportPreview(layer, ext) {
|
|
17170
|
+
var entry = cache.get(layer);
|
|
17171
|
+
var params = getRasterViewportPreviewParams(layer, ext);
|
|
17172
|
+
if (!entry || !params || entry.key != params.key) return null;
|
|
17173
|
+
return entry.preview;
|
|
17174
|
+
}
|
|
17175
|
+
|
|
17176
|
+
function scheduleRasterViewportPreview(layer, ext, onReady) {
|
|
17177
|
+
var params = getRasterViewportPreviewParams(layer, ext);
|
|
17178
|
+
var entry = cache.get(layer);
|
|
17179
|
+
var id, stats, timing, preview;
|
|
17180
|
+
if (!params || !params.needed) return;
|
|
17181
|
+
if (entry && entry.key == params.key) return;
|
|
17182
|
+
id = ++requestId;
|
|
17183
|
+
cache.set(layer, {key: params.key, pending: id});
|
|
17184
|
+
setTimeout(function() {
|
|
17185
|
+
var current = cache.get(layer);
|
|
17186
|
+
if (!current || current.pending != id) return;
|
|
17187
|
+
timing = {};
|
|
17188
|
+
stats = getCachedRasterScalingStats(params, timing);
|
|
17189
|
+
timing.renderStart = getTimer();
|
|
17190
|
+
preview = renderRasterViewportPreview(params.grid, params.recipe, params.bbox, params.width, params.height, stats);
|
|
17191
|
+
timing.renderMs = getTimer() - timing.renderStart;
|
|
17192
|
+
logRasterPreviewTiming(params, timing);
|
|
17193
|
+
current = cache.get(layer);
|
|
17194
|
+
if (!preview || !current || current.pending != id) return;
|
|
17195
|
+
cache.set(layer, {
|
|
17196
|
+
key: params.key,
|
|
17197
|
+
preview: preview
|
|
17198
|
+
});
|
|
17199
|
+
onReady();
|
|
17200
|
+
}, 0);
|
|
17201
|
+
}
|
|
17202
|
+
|
|
17203
|
+
function invalidateRasterViewportPreview(layer) {
|
|
17204
|
+
cache.delete(layer);
|
|
17205
|
+
}
|
|
17206
|
+
|
|
17207
|
+
function getRasterViewportPreviewParams(layer, ext) {
|
|
17208
|
+
var raster = layer && layer.raster;
|
|
17209
|
+
var grid = getRasterGrid(raster);
|
|
17210
|
+
var rasterBbox = getRasterBBox(raster);
|
|
17211
|
+
var mapBbox = ext.getBounds().toArray();
|
|
17212
|
+
var visibleBbox = rasterBbox && intersectBboxes(rasterBbox, mapBbox);
|
|
17213
|
+
var preview = getRasterPreview(raster);
|
|
17214
|
+
var recipe, pixelRatio, t, p1, p2, displayWidth, displayHeight, crop, width, height, scale, key, needed;
|
|
17215
|
+
if (!grid || !grid.samples || !visibleBbox || !grid.bbox) return null;
|
|
17216
|
+
if (!supportsNorthUpRaster(grid)) return null;
|
|
17217
|
+
crop = getRasterSourceWindow(grid, visibleBbox);
|
|
17218
|
+
if (!crop) return null;
|
|
17219
|
+
visibleBbox = crop.bbox;
|
|
17220
|
+
recipe = getRasterViewRecipe(grid, raster.view && raster.view.recipe);
|
|
17221
|
+
pixelRatio = GUI.getPixelRatio();
|
|
17222
|
+
t = ext.getTransform(pixelRatio);
|
|
17223
|
+
p1 = t.transform(mapBbox[0], mapBbox[3]);
|
|
17224
|
+
p2 = t.transform(mapBbox[2], mapBbox[1]);
|
|
17225
|
+
displayWidth = Math.max(1, Math.round(Math.abs(p2[0] - p1[0])));
|
|
17226
|
+
displayHeight = Math.max(1, Math.round(Math.abs(p2[1] - p1[1])));
|
|
17227
|
+
width = Math.min(displayWidth, crop.width);
|
|
17228
|
+
height = Math.min(displayHeight, crop.height);
|
|
17229
|
+
scale = Math.min(1, Math.sqrt(MAX_VIEWPORT_PREVIEW_PIXELS / (width * height)));
|
|
17230
|
+
width = Math.max(1, Math.round(width * scale));
|
|
17231
|
+
height = Math.max(1, Math.round(height * scale));
|
|
17232
|
+
needed = viewportPreviewIsSharper(preview, grid, visibleBbox, width, height);
|
|
17233
|
+
key = [
|
|
17234
|
+
visibleBbox.map(roundKeyPart).join(','),
|
|
17235
|
+
width,
|
|
17236
|
+
height,
|
|
17237
|
+
pixelRatio,
|
|
17238
|
+
grid.width,
|
|
17239
|
+
grid.height,
|
|
17240
|
+
grid.samples && grid.samples.length,
|
|
17241
|
+
recipe.type,
|
|
17242
|
+
recipe.scaling,
|
|
17243
|
+
recipe.scaleRange && recipe.scaleRange.join(','),
|
|
17244
|
+
recipe.percentileRange && recipe.percentileRange.join(',')
|
|
17245
|
+
].join('|');
|
|
17246
|
+
return {
|
|
17247
|
+
key: key,
|
|
17248
|
+
needed: needed,
|
|
17249
|
+
raster: raster,
|
|
17250
|
+
grid: grid,
|
|
17251
|
+
recipe: recipe,
|
|
17252
|
+
bbox: visibleBbox,
|
|
17253
|
+
width: width,
|
|
17254
|
+
height: height,
|
|
17255
|
+
sourceWidth: crop.width,
|
|
17256
|
+
sourceHeight: crop.height
|
|
17257
|
+
};
|
|
17258
|
+
}
|
|
17259
|
+
|
|
17260
|
+
function getCachedRasterScalingStats(params, timing) {
|
|
17261
|
+
var cached = params.raster.view && params.raster.view.scalingStats;
|
|
17262
|
+
var key;
|
|
17263
|
+
if (params.recipe.scaling == 'none') {
|
|
17264
|
+
timing.statsMs = 0;
|
|
17265
|
+
timing.statsSource = 'none';
|
|
17266
|
+
return null;
|
|
17267
|
+
}
|
|
17268
|
+
key = getRasterScalingStatsKey(params.grid, params.recipe);
|
|
17269
|
+
if (cached && cached.key == key) {
|
|
17270
|
+
timing.statsMs = 0;
|
|
17271
|
+
timing.statsSource = 'raster-view-cache';
|
|
17272
|
+
return cached.stats;
|
|
17273
|
+
}
|
|
17274
|
+
timing.statsSource = 'computed';
|
|
17275
|
+
timing.statsStart = getTimer();
|
|
17276
|
+
cached = getRasterViewScalingStats(params.raster, params.recipe);
|
|
17277
|
+
timing.statsMs = getTimer() - timing.statsStart;
|
|
17278
|
+
return cached;
|
|
17279
|
+
}
|
|
17280
|
+
|
|
17281
|
+
function logRasterPreviewTiming(params, timing) {
|
|
17282
|
+
if (!rasterDebugIsOn()) return;
|
|
17283
|
+
console.log([
|
|
17284
|
+
'Raster viewport preview:',
|
|
17285
|
+
params.width + 'x' + params.height,
|
|
17286
|
+
'from',
|
|
17287
|
+
params.sourceWidth + 'x' + params.sourceHeight,
|
|
17288
|
+
'source px,',
|
|
17289
|
+
'stats=' + formatMs(timing.statsMs),
|
|
17290
|
+
'(' + timing.statsSource + '),',
|
|
17291
|
+
'render=' + formatMs(timing.renderMs) + ',',
|
|
17292
|
+
'total=' + formatMs(timing.statsMs + timing.renderMs) + ',',
|
|
17293
|
+
'scaling=' + params.recipe.scaling,
|
|
17294
|
+
'type=' + params.recipe.type
|
|
17295
|
+
].join(' '));
|
|
17296
|
+
}
|
|
17297
|
+
|
|
17298
|
+
function rasterDebugIsOn() {
|
|
17299
|
+
var vars = GUI.getUrlVars();
|
|
17300
|
+
return vars['raster-debug'] === true || vars['raster-debug'] == '1' || vars.raster_debug === true || vars.raster_debug == '1';
|
|
17301
|
+
}
|
|
17302
|
+
|
|
17303
|
+
function getTimer() {
|
|
17304
|
+
return typeof performance != 'undefined' && performance.now ? performance.now() : Date.now();
|
|
17305
|
+
}
|
|
17306
|
+
|
|
17307
|
+
function formatMs(ms) {
|
|
17308
|
+
return Math.round(ms * 10) / 10 + 'ms';
|
|
17309
|
+
}
|
|
17310
|
+
|
|
17311
|
+
function supportsNorthUpRaster(grid) {
|
|
17312
|
+
var t = grid.transform;
|
|
17313
|
+
return !t || t[1] === 0 && t[3] === 0;
|
|
17314
|
+
}
|
|
17315
|
+
|
|
17316
|
+
function getRasterSourcePixelSize(grid, bbox) {
|
|
17317
|
+
var crop = getRasterSourceWindow(grid, bbox);
|
|
17318
|
+
return crop ? {width: crop.width, height: crop.height} : {width: 0, height: 0};
|
|
17319
|
+
}
|
|
17320
|
+
|
|
17321
|
+
function getRasterSourceWindow(grid, bbox) {
|
|
17322
|
+
var rb = grid.bbox;
|
|
17323
|
+
var dx = (rb[2] - rb[0]) / grid.width;
|
|
17324
|
+
var dy = (rb[3] - rb[1]) / grid.height;
|
|
17325
|
+
var x0 = Math.max(0, Math.floor((bbox[0] - rb[0]) / dx));
|
|
17326
|
+
var x1 = Math.min(grid.width, Math.ceil((bbox[2] - rb[0]) / dx));
|
|
17327
|
+
var y0 = Math.max(0, Math.floor((rb[3] - bbox[3]) / dy));
|
|
17328
|
+
var y1 = Math.min(grid.height, Math.ceil((rb[3] - bbox[1]) / dy));
|
|
17329
|
+
if (x1 <= x0 || y1 <= y0) return null;
|
|
17330
|
+
return {
|
|
17331
|
+
x: x0,
|
|
17332
|
+
y: y0,
|
|
17333
|
+
width: x1 - x0,
|
|
17334
|
+
height: y1 - y0,
|
|
17335
|
+
bbox: [
|
|
17336
|
+
rb[0] + x0 * dx,
|
|
17337
|
+
rb[3] - y1 * dy,
|
|
17338
|
+
rb[0] + x1 * dx,
|
|
17339
|
+
rb[3] - y0 * dy
|
|
17340
|
+
]
|
|
17341
|
+
};
|
|
17342
|
+
}
|
|
17343
|
+
|
|
17344
|
+
function viewportPreviewIsSharper(preview, grid, bbox, width, height) {
|
|
17345
|
+
var crop = getRasterSourcePixelSize(grid, bbox);
|
|
17346
|
+
var previewWidth = preview && preview.width || 0;
|
|
17347
|
+
var previewHeight = preview && preview.height || 0;
|
|
17348
|
+
var fallbackWidth = crop.width * previewWidth / grid.width;
|
|
17349
|
+
var fallbackHeight = crop.height * previewHeight / grid.height;
|
|
17350
|
+
return width > fallbackWidth * 1.25 || height > fallbackHeight * 1.25;
|
|
17351
|
+
}
|
|
17352
|
+
|
|
17353
|
+
function roundKeyPart(val) {
|
|
17354
|
+
return Math.round(val * 1e9) / 1e9;
|
|
17355
|
+
}
|
|
17356
|
+
|
|
15911
17357
|
var hatches = {}; // cached patterns
|
|
15912
17358
|
|
|
15913
17359
|
function getCanvasFillEffect(ctx, shp, arcs, ext, style) {
|
|
@@ -16110,7 +17556,7 @@
|
|
|
16110
17556
|
function getShapeFilter(arcs, shapes, ext) {
|
|
16111
17557
|
if (ext.scale() < 1.1) return null;
|
|
16112
17558
|
var view = ext.getBounds();
|
|
16113
|
-
var b = new Bounds();
|
|
17559
|
+
var b = new Bounds$1();
|
|
16114
17560
|
return function(i) {
|
|
16115
17561
|
var shp = shapes[i];
|
|
16116
17562
|
if (!shp) return false;
|
|
@@ -16163,6 +17609,22 @@
|
|
|
16163
17609
|
_ext = extent;
|
|
16164
17610
|
};
|
|
16165
17611
|
|
|
17612
|
+
_self.drawRasterLayer = function(layer, opts) {
|
|
17613
|
+
var raster = layer.raster;
|
|
17614
|
+
var options = opts || {};
|
|
17615
|
+
var preview = options.action == 'nav' ? null : getCachedRasterViewportPreview(layer, _ext);
|
|
17616
|
+
var bbox = preview && preview.bbox;
|
|
17617
|
+
if (!preview) {
|
|
17618
|
+
preview = raster && getRasterPreview(raster);
|
|
17619
|
+
bbox = raster && getRasterBBox(raster);
|
|
17620
|
+
if (options.action != 'nav' && options.onViewportPreviewReady) {
|
|
17621
|
+
scheduleRasterViewportPreview(layer, _ext, options.onViewportPreviewReady);
|
|
17622
|
+
}
|
|
17623
|
+
}
|
|
17624
|
+
if (!preview || !preview.pixels || !bbox) return;
|
|
17625
|
+
drawRasterPreview(preview, bbox);
|
|
17626
|
+
};
|
|
17627
|
+
|
|
16166
17628
|
/*
|
|
16167
17629
|
// Original function, not optimized
|
|
16168
17630
|
_self.drawStyledPaths = function(shapes, arcs, style) {
|
|
@@ -16359,6 +17821,15 @@
|
|
|
16359
17821
|
}
|
|
16360
17822
|
}
|
|
16361
17823
|
|
|
17824
|
+
function drawRasterPreview(preview, bbox) {
|
|
17825
|
+
var img = getRasterCanvas(preview);
|
|
17826
|
+
var t = _ext.getTransform(GUI.getPixelRatio());
|
|
17827
|
+
var p1 = t.transform(bbox[0], bbox[3]);
|
|
17828
|
+
var p2 = t.transform(bbox[2], bbox[1]);
|
|
17829
|
+
_ctx.imageSmoothingEnabled = true;
|
|
17830
|
+
_ctx.drawImage(img, p1[0], p1[1], p2[0] - p1[0], p2[1] - p1[1]);
|
|
17831
|
+
}
|
|
17832
|
+
|
|
16362
17833
|
// TODO: consider using drawStyledPaths(), which draws paths in batches
|
|
16363
17834
|
// for faster Canvas rendering. Downside: changes stacking order, which
|
|
16364
17835
|
// is bad if circles are graduated.
|
|
@@ -16474,6 +17945,19 @@
|
|
|
16474
17945
|
};
|
|
16475
17946
|
}
|
|
16476
17947
|
|
|
17948
|
+
function getRasterCanvas(preview) {
|
|
17949
|
+
if (preview.canvas) return preview.canvas;
|
|
17950
|
+
var canvas = document.createElement('canvas');
|
|
17951
|
+
var ctx = canvas.getContext('2d');
|
|
17952
|
+
var imageData;
|
|
17953
|
+
canvas.width = preview.width;
|
|
17954
|
+
canvas.height = preview.height;
|
|
17955
|
+
imageData = new ImageData(preview.pixels, preview.width, preview.height);
|
|
17956
|
+
ctx.putImageData(imageData, 0, 0);
|
|
17957
|
+
preview.canvas = canvas;
|
|
17958
|
+
return canvas;
|
|
17959
|
+
}
|
|
17960
|
+
|
|
16477
17961
|
function drawCircle(x, y, radius, ctx) {
|
|
16478
17962
|
if (radius > 0) {
|
|
16479
17963
|
ctx.moveTo(x + radius, y);
|
|
@@ -16774,6 +18258,7 @@
|
|
|
16774
18258
|
var _settleTimer = null;
|
|
16775
18259
|
var _redrawPending = false;
|
|
16776
18260
|
var _settleRequested = false;
|
|
18261
|
+
var _rasterNavRefreshPending = false;
|
|
16777
18262
|
|
|
16778
18263
|
// 'map_interaction_end' may arrive before the in-flight 'nav' draw runs,
|
|
16779
18264
|
// because drawLayers() schedules its work via requestAnimationFrame.
|
|
@@ -16782,6 +18267,8 @@
|
|
|
16782
18267
|
gui.on('map_interaction_end', function() {
|
|
16783
18268
|
if (_redrawPending) {
|
|
16784
18269
|
settleNow();
|
|
18270
|
+
} else if (_rasterNavRefreshPending) {
|
|
18271
|
+
settleRasterNow();
|
|
16785
18272
|
} else {
|
|
16786
18273
|
_settleRequested = true;
|
|
16787
18274
|
}
|
|
@@ -16792,6 +18279,11 @@
|
|
|
16792
18279
|
|
|
16793
18280
|
this.drawMainLayers = function(layers, action) {
|
|
16794
18281
|
if (skipMainLayerRedraw(action)) return;
|
|
18282
|
+
if (action == 'nav' && layersHaveRasters(layers)) {
|
|
18283
|
+
_rasterNavRefreshPending = true;
|
|
18284
|
+
} else if (action != 'nav') {
|
|
18285
|
+
_rasterNavRefreshPending = false;
|
|
18286
|
+
}
|
|
16795
18287
|
if (action == 'nav' && shouldUseFastNav()) {
|
|
16796
18288
|
applyFastTransform();
|
|
16797
18289
|
// SVG symbol reposition is already cheap; keep labels/symbols accurate
|
|
@@ -16821,7 +18313,7 @@
|
|
|
16821
18313
|
} else if (isSvgLayer) {
|
|
16822
18314
|
_svg.drawLayer(lyr, 'symbol');
|
|
16823
18315
|
} else {
|
|
16824
|
-
drawCanvasLayer(lyr, _mainCanv);
|
|
18316
|
+
drawCanvasLayer(lyr, _mainCanv, action);
|
|
16825
18317
|
}
|
|
16826
18318
|
});
|
|
16827
18319
|
// Force synchronous rasterization so performance.now() reflects the true
|
|
@@ -16830,6 +18322,10 @@
|
|
|
16830
18322
|
flushCanvas(_mainCanv);
|
|
16831
18323
|
_lastFrameMs = performance.now() - startTime;
|
|
16832
18324
|
captureSnapshot();
|
|
18325
|
+
if (action == 'nav' && _settleRequested && _rasterNavRefreshPending) {
|
|
18326
|
+
_settleRequested = false;
|
|
18327
|
+
settleRasterNow();
|
|
18328
|
+
}
|
|
16833
18329
|
};
|
|
16834
18330
|
|
|
16835
18331
|
// Draw highlight effect for hover and selection
|
|
@@ -16850,7 +18346,7 @@
|
|
|
16850
18346
|
_overlayCanv.hide();
|
|
16851
18347
|
}
|
|
16852
18348
|
layers.forEach(function(lyr) {
|
|
16853
|
-
drawCanvasLayer(lyr, canv);
|
|
18349
|
+
drawCanvasLayer(lyr, canv, action);
|
|
16854
18350
|
});
|
|
16855
18351
|
};
|
|
16856
18352
|
|
|
@@ -16875,9 +18371,22 @@
|
|
|
16875
18371
|
return action == 'hover' && _overlayCanv.visible();
|
|
16876
18372
|
}
|
|
16877
18373
|
|
|
16878
|
-
function
|
|
18374
|
+
function layersHaveRasters(layers) {
|
|
18375
|
+
return layers.some(function(lyr) {
|
|
18376
|
+
return lyr && lyr.gui && internal.layerHasRaster(lyr.gui.displayLayer);
|
|
18377
|
+
});
|
|
18378
|
+
}
|
|
18379
|
+
|
|
18380
|
+
function drawCanvasLayer(lyr, canv, action) {
|
|
16879
18381
|
if (!lyr) return;
|
|
16880
|
-
if (lyr.gui.
|
|
18382
|
+
if (internal.layerHasRaster(lyr.gui.displayLayer)) {
|
|
18383
|
+
canv.drawRasterLayer(lyr.gui.displayLayer, {
|
|
18384
|
+
action: action,
|
|
18385
|
+
onViewportPreviewReady: function() {
|
|
18386
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
18387
|
+
}
|
|
18388
|
+
});
|
|
18389
|
+
} else if (lyr.gui.style.type == 'outline') {
|
|
16881
18390
|
drawOutlineLayerToCanvas(lyr, canv, ext);
|
|
16882
18391
|
} else {
|
|
16883
18392
|
drawStyledLayerToCanvas(lyr, canv, ext);
|
|
@@ -16980,6 +18489,11 @@
|
|
|
16980
18489
|
gui.dispatchEvent('map-needs-refresh');
|
|
16981
18490
|
}
|
|
16982
18491
|
|
|
18492
|
+
function settleRasterNow() {
|
|
18493
|
+
_rasterNavRefreshPending = false;
|
|
18494
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
18495
|
+
}
|
|
18496
|
+
|
|
16983
18497
|
function cancelSettle() {
|
|
16984
18498
|
if (_settleTimer) {
|
|
16985
18499
|
clearTimeout(_settleTimer);
|
|
@@ -16995,6 +18509,9 @@
|
|
|
16995
18509
|
var box = new HighlightBox(gui, {name: 'box-tool', persistent: true, handles: true, draggable: true});
|
|
16996
18510
|
var popup = gui.container.findChild('.box-tool-options');
|
|
16997
18511
|
var coords = popup.findChild('.box-coords').hide();
|
|
18512
|
+
var selectBtn = popup.findChild('.select-btn');
|
|
18513
|
+
var clipBtn = popup.findChild('.clip-btn');
|
|
18514
|
+
var eraseBtn = popup.findChild('.erase-btn');
|
|
16998
18515
|
var _on = false;
|
|
16999
18516
|
var instructionsShown = false;
|
|
17000
18517
|
var alert;
|
|
@@ -17007,7 +18524,7 @@
|
|
|
17007
18524
|
reset();
|
|
17008
18525
|
});
|
|
17009
18526
|
|
|
17010
|
-
new SimpleButton(
|
|
18527
|
+
new SimpleButton(selectBtn).on('click', function() {
|
|
17011
18528
|
var coords = box.getDataCoords();
|
|
17012
18529
|
if (!coords || noData()) return;
|
|
17013
18530
|
gui.enterMode('selection_tool');
|
|
@@ -17022,11 +18539,12 @@
|
|
|
17022
18539
|
return !gui.model.getActiveLayer();
|
|
17023
18540
|
}
|
|
17024
18541
|
|
|
17025
|
-
new SimpleButton(
|
|
18542
|
+
new SimpleButton(clipBtn).on('click', function() {
|
|
17026
18543
|
runCommand('-clip bbox=' + box.getDataCoords().join(','));
|
|
17027
18544
|
});
|
|
17028
18545
|
|
|
17029
|
-
new SimpleButton(
|
|
18546
|
+
new SimpleButton(eraseBtn).on('click', function() {
|
|
18547
|
+
if (activeLayerIsRaster()) return;
|
|
17030
18548
|
runCommand('-erase bbox=' + box.getDataCoords().join(','));
|
|
17031
18549
|
});
|
|
17032
18550
|
|
|
@@ -17069,6 +18587,7 @@
|
|
|
17069
18587
|
box.on('dragend', function(e) {
|
|
17070
18588
|
if (_on) {
|
|
17071
18589
|
hideInstructions();
|
|
18590
|
+
updateOptionsForActiveLayer();
|
|
17072
18591
|
popup.show();
|
|
17073
18592
|
}
|
|
17074
18593
|
});
|
|
@@ -17122,6 +18641,7 @@
|
|
|
17122
18641
|
|
|
17123
18642
|
function turnOn() {
|
|
17124
18643
|
box.turnOn();
|
|
18644
|
+
updateOptionsForActiveLayer();
|
|
17125
18645
|
_on = true;
|
|
17126
18646
|
}
|
|
17127
18647
|
|
|
@@ -17142,6 +18662,27 @@
|
|
|
17142
18662
|
hideCoords();
|
|
17143
18663
|
}
|
|
17144
18664
|
|
|
18665
|
+
function updateOptionsForActiveLayer() {
|
|
18666
|
+
var raster = activeLayerIsRaster();
|
|
18667
|
+
setButtonVisible(selectBtn, !raster);
|
|
18668
|
+
setButtonVisible(clipBtn, true);
|
|
18669
|
+
setButtonVisible(eraseBtn, !raster);
|
|
18670
|
+
}
|
|
18671
|
+
|
|
18672
|
+
function activeLayerIsRaster() {
|
|
18673
|
+
var active = gui.model.getActiveLayer();
|
|
18674
|
+
return !!(active && internal.layerHasRaster(active.layer));
|
|
18675
|
+
}
|
|
18676
|
+
|
|
18677
|
+
function setButtonVisible(btn, visible) {
|
|
18678
|
+
btn.classed('hidden', !visible);
|
|
18679
|
+
if (visible) {
|
|
18680
|
+
btn.show();
|
|
18681
|
+
} else {
|
|
18682
|
+
btn.hide();
|
|
18683
|
+
}
|
|
18684
|
+
}
|
|
18685
|
+
|
|
17145
18686
|
function openAddFramePopup(gui, bbox) {
|
|
17146
18687
|
var popup = showPopupAlert('', 'Add a map frame');
|
|
17147
18688
|
var el = popup.container();
|
|
@@ -17511,7 +19052,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
17511
19052
|
|
|
17512
19053
|
function getDisplayOptions() {
|
|
17513
19054
|
return {
|
|
17514
|
-
crs: _dynamicCRS
|
|
19055
|
+
crs: _dynamicCRS,
|
|
19056
|
+
notify: gui.notify
|
|
17515
19057
|
};
|
|
17516
19058
|
}
|
|
17517
19059
|
|
|
@@ -17528,7 +19070,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
17528
19070
|
}
|
|
17529
19071
|
|
|
17530
19072
|
function getContentLayerBounds() {
|
|
17531
|
-
var b = new Bounds();
|
|
19073
|
+
var b = new Bounds$1();
|
|
17532
19074
|
var layers = getContentLayers();
|
|
17533
19075
|
layers.forEach(function(lyr) {
|
|
17534
19076
|
b.mergeBounds(lyr.gui.bounds);
|
|
@@ -17545,7 +19087,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
17545
19087
|
function calcFullBounds() {
|
|
17546
19088
|
var b;
|
|
17547
19089
|
if (isPreviewView()) {
|
|
17548
|
-
b = new Bounds(getFrameLayerData().bbox);
|
|
19090
|
+
b = new Bounds$1(getFrameLayerData().bbox);
|
|
17549
19091
|
} else {
|
|
17550
19092
|
b = getContentLayerBounds();
|
|
17551
19093
|
}
|
|
@@ -18510,6 +20052,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
18510
20052
|
var clearMsg;
|
|
18511
20053
|
|
|
18512
20054
|
initModeRules(gui);
|
|
20055
|
+
startRasterSourceStoreLifecycle();
|
|
18513
20056
|
gui.map.init();
|
|
18514
20057
|
|
|
18515
20058
|
if (opts.saveControl) {
|