From abb2ed1722eb6fdc35aeeaacd2f2126fc9324ac9 Mon Sep 17 00:00:00 2001 From: hu de yi Date: Mon, 18 Nov 2024 14:27:19 +0800 Subject: [PATCH] tile fetch add LRU cache support --- src/LRUCache.js | 108 ++++++++++++++++++++++++++++++++++++++++++++++++ src/tileget.js | 41 +++++++++++++++--- 2 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 src/LRUCache.js diff --git a/src/LRUCache.js b/src/LRUCache.js new file mode 100644 index 0000000..625209f --- /dev/null +++ b/src/LRUCache.js @@ -0,0 +1,108 @@ + +const nullOnRemove = () => { }; + +class LRUCache { + constructor(max, onRemove) { + this.max = max; + this.onRemove = onRemove || nullOnRemove; + this.reset(); + } + + reset() { + if (this.data) { + const values = this.data.values(); + for (const p of values) { + this.onRemove(p); + } + } + + this.data = new Map(); + return this; + } + + clear() { + this.reset(); + delete this.onRemove; + } + + add(key, data) { + if (!data) { + return this; + } + if (this.has(key)) { + this.data.delete(key); + this.data.set(key, data); + if (this.data.size > this.max) { + this.shrink(); + } + } else { + this.data.set(key, data); + if (this.data.size > this.max) { + this.shrink(); + } + } + + return this; + } + + keys() { + const keys = new Array(this.data.size); + let i = 0; + const iterator = this.data.keys(); + for (const k of iterator) { + keys[i++] = k; + } + return keys; + } + + shrink() { + const iterator = this.data.keys(); + let item = iterator.next(); + while (this.data.size > this.max) { + const removedData = this.getAndRemove(item.value); + if (removedData) { + this.onRemove(removedData); + } + item = iterator.next(); + } + } + + has(key) { + return this.data.has(key); + } + + getAndRemove(key) { + if (!this.has(key)) { return null; } + + const data = this.data.get(key); + this.data.delete(key); + return data; + } + + get(key) { + if (!this.has(key)) { return null; } + + const data = this.data.get(key); + return data; + } + + remove(key) { + if (!this.has(key)) { return this; } + + const data = this.data.get(key); + this.data.delete(key); + this.onRemove(data); + + return this; + } + + setMaxSize(max) { + this.max = max; + if (this.data.size > this.max) { + this.shrink(); + } + return this; + } +}; + +export default LRUCache; diff --git a/src/tileget.js b/src/tileget.js index e764f98..b646225 100644 --- a/src/tileget.js +++ b/src/tileget.js @@ -1,4 +1,5 @@ import { getCanvas, imageFilter, imageTileScale } from './canvas'; +import LRUCache from './LRUCache'; const CANVAS_ERROR_MESSAGE = new Error('not find canvas.The current environment does not support OffscreenCanvas'); @@ -11,6 +12,37 @@ function isNumber(value) { return typeof value === 'number'; } +const tileCache = new LRUCache(200, (image) => { + if (image && image.close) { + image.close(); + } +}); + +function fetchTile(url, headers = {}) { + return new Promise((resolve, reject) => { + const copyImageBitMap = (image) => { + createImageBitmap(image).then(imagebit => { + resolve(imagebit); + }).catch(error => { + reject(error); + }); + }; + const image = tileCache.get(url); + if (image) { + copyImageBitMap(image); + } else { + fetch(url, { + headers + }).then(res => res.blob()).then(blob => createImageBitmap(blob)).then(image => { + tileCache.add(url, image); + copyImageBitMap(image); + }).catch(error => { + reject(error); + }); + } + }); +} + export function getTile(url, options = {}) { return new Promise((resolve, reject) => { if (!url) { @@ -18,9 +50,7 @@ export function getTile(url, options = {}) { return; } const headers = Object.assign({}, HEADERS, options.headers || {}); - fetch(url, { - headers - }).then(res => res.blob()).then(blob => createImageBitmap(blob)).then(imagebit => { + fetchTile(url, headers).then(imagebit => { const filter = options.filter; if (filter) { const canvas = getCanvas(); @@ -92,9 +122,8 @@ export function getTileWithMaxZoom(options = {}) { } const url = urlTemplate.replace('{x}', tileX).replace('{y}', tileY).replace('{z}', tileZ); const headers = Object.assign({}, HEADERS, options.headers || {}); - fetch(url, { - headers - }).then(res => res.blob()).then(blob => createImageBitmap(blob)).then(imagebit => { + + fetchTile(url, headers).then(imagebit => { let image; const filter = options.filter; if (filter) {