I'm creating a PWA using Open Layers. The user must have an option to download the Tiles on the Wifi to load them Offline. I read the Open Layers documentation, but, I couldn't find the answer for my problem. The section Tile Cache is empty.
Asked
Active
Viewed 1,479 times
5
-
1OpenLayers 2 had built-in caching to localStorage http://dev.openlayers.org/examples/offline-storage.html For caching to sessionStorage or localStorage in more recent versions see https://stackoverflow.com/questions/53607810/how-to-download-the-osm-tiles-for-selected-part-of-map – Mike Jul 26 '19 at 20:19
1 Answers
9
You'll need three things for this to work:
- An IndexedDB to store tiles
- A custom
tileLoadFunction
for your tile source - A component that downloads tiles for a given extent
For (1), you'll want to set up a store, e.g. tiles
. The snippet below uses the idb package (https://npmjs.com/package/idb):
import idb from 'idb';
let indexedDb;
idb.open(this.name, 1, upgradeDb => {
if (!upgradeDb.objectStoreNames.contains('tiles')) {
upgradeDb.createObjectStore('tiles');
}
}).then(db => {
indexedDb = db;
});
For (2), a starting point could look something like this:
source.setTileLoadFunction(function(tile, url) {
const tx = db.transaction('tiles', 'readonly');
tiles = tx.objectStore('tiles');
const image = tile.getImage();
tiles.get(url).then(blob => {
if (!blob) {
// use online url
image.src = url;
return;
}
const objUrl = URL.createObjectURL(blob);
image.onload = function() {
URL.revokeObjectURL(objUrl);
};
image.src = objUrl;
}).catch(() => {
// use online url
image.src = url;
});
}
For (3), you'll probably want to limit downloading to a small extent. Then, for the chosen extent
(in map units) and each zoom
level you want to cache, do something like this:
const tilegrid = source.getTileGrid();
const projection = map.getView().getProjection();
const getUrl = source.getTileUrlFunction();
tilegrid.forEachTileCoord(extent, zoom, tilecoord => {
const url = getUrl(tilecoord, devicePixelRatio, projection);
fetch(url).then(response => {
if (response.ok) {
response.blob().then(blob => {
const tx = db.transaction('tiles', 'readwrite');
const tiles = tx.objectStore('tiles');
tiles.put(url, blob);
});
}
});
});
-
Thank you for the answer. I just have one more question: Is there a limit to the data that I can put on the IndexDB? – Landinque Vonestrad Aug 05 '19 at 12:20
-
1Yes, there are limits, and those depend on several factors. See https://stackoverflow.com/a/18350531/2389327. – ahocevar Aug 06 '19 at 14:42