Allow data tile source loader to return a value or a promise

This commit is contained in:
Tim Schaub
2021-11-20 13:49:10 -07:00
parent fcb39c84ce
commit f6f34f82e5
10 changed files with 100 additions and 13 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ const map = new Map({
context.strokeRect(0, 0, size, size); context.strokeRect(0, 0, size, size);
const data = context.getImageData(0, 0, size, size).data; const data = context.getImageData(0, 0, size, size).data;
// converting to Uint8Array for increased browser compatibility // converting to Uint8Array for increased browser compatibility
return Promise.resolve(new Uint8Array(data.buffer)); return new Uint8Array(data.buffer);
}, },
// disable opacity transition to avoid overlapping labels during tile loading // disable opacity transition to avoid overlapping labels during tile loading
transition: 0, transition: 0,
+21
View File
@@ -58,3 +58,24 @@ export function memoizeOne(fn) {
return lastResult; return lastResult;
}; };
} }
/**
* @template T
* @param {function(): (T | Promise<T>)} getter A function that returns a value or a promise for a value.
* @return {Promise<T>} A promise for the value.
*/
export function toPromise(getter) {
function promiseGetter() {
let value;
try {
value = getter();
} catch (err) {
return Promise.reject(err);
}
if (value instanceof Promise) {
return value;
}
return Promise.resolve(value);
}
return promiseGetter();
}
+15 -5
View File
@@ -10,11 +10,18 @@ import {assign} from '../obj.js';
import {createXYZ, extentFromProjection} from '../tilegrid.js'; import {createXYZ, extentFromProjection} from '../tilegrid.js';
import {getKeyZXY} from '../tilecoord.js'; import {getKeyZXY} from '../tilecoord.js';
import {getUid} from '../util.js'; import {getUid} from '../util.js';
import {toPromise} from '../functions.js';
/**
* Data tile loading function. The function is called with z, x, and y tile coordinates and
* returns {@link import("../DataTile.js").Data data} for a tile or a promise for the same.
* @typedef {function(number, number, number) : (import("../DataTile.js").Data|Promise<import("../DataTile.js").Data>)} Loader
*/
/** /**
* @typedef {Object} Options * @typedef {Object} Options
* @property {function(number, number, number) : Promise<import("../DataTile.js").Data>} [loader] Data loader. Called with z, x, and y tile coordinates. * @property {Loader} [loader] Data loader. Called with z, x, and y tile coordinates.
* Returns a promise that resolves to a {@link import("../DataTile.js").Data}. * Returns {@link import("../DataTile.js").Data data} for a tile or a promise for the same.
* @property {number} [maxZoom=42] Optional max zoom level. Not used if `tileGrid` is provided. * @property {number} [maxZoom=42] Optional max zoom level. Not used if `tileGrid` is provided.
* @property {number} [minZoom=0] Optional min zoom level. Not used if `tileGrid` is provided. * @property {number} [minZoom=0] Optional min zoom level. Not used if `tileGrid` is provided.
* @property {number|import("../size.js").Size} [tileSize=[256, 256]] The pixel width and height of the tiles. * @property {number|import("../size.js").Size} [tileSize=[256, 256]] The pixel width and height of the tiles.
@@ -31,7 +38,7 @@ import {getUid} from '../util.js';
/** /**
* @classdesc * @classdesc
* Base class for sources providing tiles divided into a tile grid. * A source for typed array data tiles.
* *
* @fires import("./Tile.js").TileSourceEvent * @fires import("./Tile.js").TileSourceEvent
* @api * @api
@@ -86,7 +93,7 @@ class DataTileSource extends TileSource {
} }
/** /**
* @param {function(number, number, number) : Promise<import("../DataTile.js").Data>} loader The data loader. * @param {Loader} loader The data loader.
* @protected * @protected
*/ */
setLoader(loader) { setLoader(loader) {
@@ -109,8 +116,11 @@ class DataTileSource extends TileSource {
} }
const sourceLoader = this.loader_; const sourceLoader = this.loader_;
function loader() { function loader() {
return sourceLoader(z, x, y); return toPromise(function () {
return sourceLoader(z, x, y);
});
} }
const tile = new DataTile( const tile = new DataTile(
@@ -252,7 +252,7 @@ describe('ol/renderer/webgl/Layer', function () {
className: className, className: className,
source: new DataTileSource({ source: new DataTileSource({
loader(z, x, y) { loader(z, x, y) {
return Promise.resolve(new ImageData(256, 256)); return new ImageData(256, 256);
}, },
}), }),
}); });
@@ -25,7 +25,7 @@ describe('ol/renderer/webgl/TileLayer', function () {
context.fillStyle = 'rgba(100, 100, 100, 0.5)'; context.fillStyle = 'rgba(100, 100, 100, 0.5)';
context.fillRect(0, 0, size, size); context.fillRect(0, 0, size, size);
const data = context.getImageData(0, 0, size, size).data; const data = context.getImageData(0, 0, size, size).data;
return Promise.resolve(data); return data;
}, },
}), }),
}); });
+57 -1
View File
@@ -1,7 +1,63 @@
import expect from '../expect.js'; import expect from '../expect.js';
import {memoizeOne} from '../../../src/ol/functions.js'; import {memoizeOne, toPromise} from '../../../src/ol/functions.js';
describe('ol/functions.js', function () { describe('ol/functions.js', function () {
describe('toPromise()', () => {
it('returns a promise given a getter for a value', (done) => {
const getter = () => 'a value';
const promise = toPromise(getter);
expect(promise).to.be.a(Promise);
promise.then((value) => {
expect(value).to.be('a value');
done();
}, done);
});
it('returns a promise given a getter for a promise that resolves', (done) => {
const getter = () => Promise.resolve('a value');
const promise = toPromise(getter);
expect(promise).to.be.a(Promise);
promise.then((value) => {
expect(value).to.be('a value');
done();
}, done);
});
it('returns a promise that rejects given a getter that throws', (done) => {
const getter = () => {
throw new Error('an error');
};
const promise = toPromise(getter);
expect(promise).to.be.a(Promise);
promise.then(
(value) => {
done(new Error(`expected promise to reject, got ${value}`));
},
(err) => {
expect(err).to.be.an(Error);
expect(err.message).to.be('an error');
done();
}
);
});
it('returns a promise that rejects given a getter for a promse that rejects', (done) => {
const getter = () => Promise.reject(new Error('an error'));
const promise = toPromise(getter);
expect(promise).to.be.a(Promise);
promise.then(
(value) => {
done(new Error(`expected promise to reject, got ${value}`));
},
(err) => {
expect(err).to.be.an(Error);
expect(err.message).to.be('an error');
done();
}
);
});
});
describe('memoizeOne()', function () { describe('memoizeOne()', function () {
it('returns the result from the first call when called a second time with the same args', function () { it('returns the result from the first call when called a second time with the same args', function () {
const arg1 = {}; const arg1 = {};
@@ -34,7 +34,7 @@ new Map({
const bandCount = 3; const bandCount = 3;
const result = data.filter((_, index) => index % 4 < bandCount); const result = data.filter((_, index) => index % 4 < bandCount);
return Promise.resolve(result); return result;
}, },
tileSize: size, tileSize: size,
}), }),
@@ -53,7 +53,7 @@ new Map({
} }
} }
return Promise.resolve(output); return output;
}, },
}), }),
}), }),
@@ -66,7 +66,7 @@ new Map({
labelCanvasSize, labelCanvasSize,
labelCanvasSize labelCanvasSize
).data; ).data;
return Promise.resolve(new Uint8Array(data.buffer)); return new Uint8Array(data.buffer);
}, },
transition: 0, transition: 0,
}), }),
@@ -64,7 +64,7 @@ new Map({
labelCanvasSize, labelCanvasSize,
labelCanvasSize labelCanvasSize
).data; ).data;
return Promise.resolve(new Uint8Array(data.buffer)); return new Uint8Array(data.buffer);
}, },
transition: 0, transition: 0,
}), }),