From 6fd3f8f3e2e3335dda582c202549e4ef389146f3 Mon Sep 17 00:00:00 2001 From: Ib Green Date: Mon, 2 Oct 2023 00:00:57 +0200 Subject: [PATCH] feat(pmtiles): Create PMTileSource from Blob (#2668) --- modules/loader-utils/src/loader-types.ts | 1 + modules/pmtiles/src/pmtiles-source.ts | 38 ++++++++++++++++++--- modules/pmtiles/test/pmtiles-source.spec.ts | 26 +++++++++++--- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/modules/loader-utils/src/loader-types.ts b/modules/loader-utils/src/loader-types.ts index 33eb973195..719e53f9dd 100644 --- a/modules/loader-utils/src/loader-types.ts +++ b/modules/loader-utils/src/loader-types.ts @@ -166,6 +166,7 @@ export type LoaderWithParser Promise; + parseFile?: (file: Blob, options?: LoaderOptionsT, context?: LoaderContext) => Promise; /** Parse atomically from an arraybuffer synchronously */ parseSync?: ( arrayBuffer: ArrayBuffer, diff --git a/modules/pmtiles/src/pmtiles-source.ts b/modules/pmtiles/src/pmtiles-source.ts index 38b1828574..3d1b5326b3 100644 --- a/modules/pmtiles/src/pmtiles-source.ts +++ b/modules/pmtiles/src/pmtiles-source.ts @@ -4,16 +4,43 @@ import {DataSource, resolvePath} from '@loaders.gl/loader-utils'; import {ImageLoader} from '@loaders.gl/images'; import {MVTLoader, MVTLoaderOptions} from '@loaders.gl/mvt'; -import {PMTiles} from 'pmtiles'; -// import type {pPMTilesMetadata} from './lib/parse-pmtiles'; -import {PMTilesMetadata, parsePMTilesHeader} from './lib/parse-pmtiles'; +import {PMTiles, Source, RangeResponse} from 'pmtiles'; + +import type {PMTilesMetadata} from './lib/parse-pmtiles'; +import {parsePMTilesHeader} from './lib/parse-pmtiles'; import {TileLoadParameters} from 'modules/loader-utils/src/lib/sources/tile-source'; export type PMTilesSourceProps = DataSourceProps & { - url: string; + url: string | Blob; attributions?: string[]; }; +export class BlobSource implements Source { + blob: Blob; + key: string; + + constructor(blob: Blob, key: string) { + this.blob = blob; + this.key = key; + } + + // TODO - how is this used? + getKey() { + // @ts-expect-error url is only defined on File subclass + return this.blob.url || ''; + } + + async getBytes(offset: number, length: number, signal?: AbortSignal): Promise { + const slice = this.blob.slice(offset, offset + length); + const data = await slice.arrayBuffer(); + return { + data + // etag: response.headers.get('ETag') || undefined, + // cacheControl: response.headers.get('Cache-Control') || undefined, + // expires: response.headers.get('Expires') || undefined + }; + } +} /** * A PMTiles data source * @note Can be either a raster or vector tile source depending on the contents of the PMTiles file. @@ -26,7 +53,8 @@ export class PMTilesSource extends DataSource implements ImageTileSource, Vector constructor(props: PMTilesSourceProps) { super(props); this.props = props; - const url = resolvePath(props.url); + const url = + typeof props.url === 'string' ? resolvePath(props.url) : new BlobSource(props.url, 'pmtiles'); this.pmtiles = new PMTiles(url); this.getTileData = this.getTileData.bind(this); this.metadata = this.getMetadata(); diff --git a/modules/pmtiles/test/pmtiles-source.spec.ts b/modules/pmtiles/test/pmtiles-source.spec.ts index 8f15083b35..ac452bd91a 100644 --- a/modules/pmtiles/test/pmtiles-source.spec.ts +++ b/modules/pmtiles/test/pmtiles-source.spec.ts @@ -1,21 +1,37 @@ // loaders.gl, MIT license import test from 'tape-promise/tape'; -import {isBrowser} from '@loaders.gl/loader-utils'; +import {isBrowser, fetchFile} from '@loaders.gl/core'; import {PMTILESETS} from './data/tilesets'; import {PMTilesSource} from '@loaders.gl/pmtiles'; -test('PMTilesSource', async (t) => { +test('PMTilesSource#urls', async (t) => { if (!isBrowser) { t.comment('PMTilesSource currently only supported in browser'); t.end(); return; } for (const tilesetUrl of PMTILESETS) { - const source = new PMTilesSource({ - url: tilesetUrl - }); + const source = new PMTilesSource({url: tilesetUrl}); + t.ok(source); + const metadata = await source.getMetadata(); + t.ok(metadata); + // console.error(JSON.stringify(metadata.tileJSON, null, 2)); + } + t.end(); +}); + +test('PMTilesSource#Blobs', async (t) => { + if (!isBrowser) { + t.comment('PMTilesSource currently only supported in browser'); + t.end(); + return; + } + for (const tilesetUrl of PMTILESETS) { + const response = await fetchFile(tilesetUrl); + const blob = await response.blob(); + const source = new PMTilesSource({url: blob}); t.ok(source); const metadata = await source.getMetadata(); t.ok(metadata);