@loaders.gl/pmtiles
Version:
Framework-independent loader for the pmtiles format
107 lines (106 loc) • 4.02 kB
JavaScript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { DataSource, resolvePath } from '@loaders.gl/loader-utils';
import { ImageLoader } from '@loaders.gl/images';
import { MVTLoader } from '@loaders.gl/mvt';
import * as pmtiles from 'pmtiles';
const { PMTiles } = pmtiles;
import { parsePMTilesHeader } from "./lib/parse-pmtiles.js";
import { BlobSource } from "./lib/blob-source.js";
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) => url.endsWith('.pmtiles'),
createDataSource: (url, props) => new PMTilesTileSource(url, props)
};
/**
* 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 {
data;
props;
mimeType = null;
pmtiles;
metadata;
constructor(data, props) {
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() {
return { fields: [], metadata: {} };
}
async getMetadata() {
const pmtilesHeader = await this.pmtiles.getHeader();
const pmtilesMetadata = (await this.pmtiles.getMetadata()) || {};
const metadata = 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) {
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) {
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) {
const arrayBuffer = await this.getTile(tileParams);
return arrayBuffer ? await ImageLoader.parse(arrayBuffer, this.loadOptions) : null;
}
// VectorTileSource interface implementation
async getVectorTile(tileParams) {
const arrayBuffer = await this.getTile(tileParams);
const loadOptions = {
shape: 'geojson-table',
mvt: {
coordinates: 'wgs84',
tileIndex: { x: tileParams.x, y: tileParams.y, z: tileParams.z },
...this.loadOptions?.mvt
},
...this.loadOptions
};
return arrayBuffer ? await MVTLoader.parse(arrayBuffer, loadOptions) : null;
}
}