@loaders.gl/pmtiles
Version:
Framework-independent loader for the pmtiles format
149 lines (129 loc) • 4.89 kB
text/typescript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import type {Schema} from '@loaders.gl/schema';
import type {
Source,
VectorTileSource,
GetTileParameters,
GetTileDataParameters,
ImageTileSource,
ImageType
} from '@loaders.gl/loader-utils';
import {DataSource, DataSourceProps, resolvePath} from '@loaders.gl/loader-utils';
import {ImageLoader, ImageLoaderOptions} from '@loaders.gl/images';
import {MVTLoader, MVTLoaderOptions, TileJSONLoaderOptions} from '@loaders.gl/mvt';
import * as pmtiles from 'pmtiles';
const {PMTiles} = pmtiles;
import type {PMTilesMetadata} from './lib/parse-pmtiles';
import {parsePMTilesHeader} from './lib/parse-pmtiles';
import {BlobSource} from './lib/blob-source';
const VERSION = '1.0.0';
/**
* Creates vector tile data sources for PMTiles urls or blobs
*/
export const PMTilesSource = {
name: 'PMTiles',
id: 'pmtiles',
module: 'pmtiles',
version: VERSION,
extensions: ['pmtiles'],
mimeTypes: ['application/octet-stream'],
options: {url: undefined!, pmtiles: {}},
type: 'pmtiles',
fromUrl: true,
fromBlob: true,
testURL: (url: string) => url.endsWith('.pmtiles'),
createDataSource: (url: string | Blob, props: PMTilesTileSourceProps) =>
new PMTilesTileSource(url, props)
} as const satisfies Source<PMTilesTileSource, PMTilesTileSourceProps>;
export type PMTilesTileSourceProps = DataSourceProps & {
attributions?: string[];
pmtiles?: {
loadOptions?: TileJSONLoaderOptions & MVTLoaderOptions & ImageLoaderOptions;
// TODO - add options here
};
};
/**
* A PMTiles data source
* @note Can be either a raster or vector tile source depending on the contents of the PMTiles file.
*/
export class PMTilesTileSource extends DataSource implements ImageTileSource, VectorTileSource {
data: string | Blob;
props: PMTilesTileSourceProps;
mimeType: string | null = null;
pmtiles: pmtiles.PMTiles;
metadata: Promise<PMTilesMetadata>;
constructor(data: string | Blob, props: PMTilesTileSourceProps) {
super(props);
this.props = props;
const url = typeof data === 'string' ? resolvePath(data) : new BlobSource(data, 'pmtiles');
this.data = data;
this.pmtiles = new PMTiles(url);
this.getTileData = this.getTileData.bind(this);
this.metadata = this.getMetadata();
}
async getSchema(): Promise<Schema> {
return {fields: [], metadata: {}};
}
async getMetadata(): Promise<PMTilesMetadata> {
const pmtilesHeader = await this.pmtiles.getHeader();
const pmtilesMetadata = ((await this.pmtiles.getMetadata()) as Record<string, unknown>) || {};
const metadata: PMTilesMetadata = parsePMTilesHeader(
pmtilesHeader,
pmtilesMetadata,
{includeFormatHeader: false},
this.loadOptions
);
// Add additional attribution if necessary
if (this.props.attributions) {
metadata.attributions = [...this.props.attributions, ...(metadata.attributions || [])];
}
if (metadata?.tileMIMEType) {
this.mimeType = metadata?.tileMIMEType;
}
// TODO - do we need to allow tileSize to be overridden? Some PMTiles examples seem to suggest it.
return metadata;
}
async getTile(tileParams: GetTileParameters): Promise<ArrayBuffer | null> {
const {x, y, z} = tileParams;
const rangeResponse = await this.pmtiles.getZxy(z, x, y);
const arrayBuffer = rangeResponse?.data;
if (!arrayBuffer) {
// console.error('No arrayBuffer', tileParams);
return null;
}
return arrayBuffer;
}
// Tile Source interface implementation: deck.gl compatible API
// TODO - currently only handles image tiles, not vector tiles
async getTileData(tileParams: GetTileDataParameters): Promise<any> {
const {x, y, z} = tileParams.index;
const metadata = await this.metadata;
switch (metadata.tileMIMEType) {
case 'application/vnd.mapbox-vector-tile':
return await this.getVectorTile({x, y, z, layers: []});
default:
return await this.getImageTile({x, y, z, layers: []});
}
}
// ImageTileSource interface implementation
async getImageTile(tileParams: GetTileParameters): Promise<ImageType | null> {
const arrayBuffer = await this.getTile(tileParams);
return arrayBuffer ? await ImageLoader.parse(arrayBuffer, this.loadOptions) : null;
}
// VectorTileSource interface implementation
async getVectorTile(tileParams: GetTileParameters): Promise<unknown | null> {
const arrayBuffer = await this.getTile(tileParams);
const loadOptions: MVTLoaderOptions = {
shape: 'geojson-table',
mvt: {
coordinates: 'wgs84',
tileIndex: {x: tileParams.x, y: tileParams.y, z: tileParams.z},
...(this.loadOptions as MVTLoaderOptions)?.mvt
},
...this.loadOptions
};
return arrayBuffer ? await MVTLoader.parse(arrayBuffer, loadOptions) : null;
}
}