@loaders.gl/pmtiles 4.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +41 -0
- package/README.md +7 -0
- package/dist/bundle.d.ts +2 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/dist.min.js +4436 -0
- package/dist/es5/bundle.js +6 -0
- package/dist/es5/bundle.js.map +1 -0
- package/dist/es5/index.js +13 -0
- package/dist/es5/index.js.map +1 -0
- package/dist/es5/lib/parse-pmtiles.js +126 -0
- package/dist/es5/lib/parse-pmtiles.js.map +1 -0
- package/dist/es5/lib/sources.js +2 -0
- package/dist/es5/lib/sources.js.map +1 -0
- package/dist/es5/pmtiles-source.js +289 -0
- package/dist/es5/pmtiles-source.js.map +1 -0
- package/dist/esm/bundle.js +4 -0
- package/dist/esm/bundle.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib/parse-pmtiles.js +69 -0
- package/dist/esm/lib/parse-pmtiles.js.map +1 -0
- package/dist/esm/lib/sources.js +2 -0
- package/dist/esm/lib/sources.js.map +1 -0
- package/dist/esm/pmtiles-source.js +106 -0
- package/dist/esm/pmtiles-source.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib/parse-pmtiles.d.ts +42 -0
- package/dist/lib/parse-pmtiles.d.ts.map +1 -0
- package/dist/lib/sources.d.ts +1 -0
- package/dist/lib/sources.d.ts.map +1 -0
- package/dist/pmtiles-source.d.ts +52 -0
- package/dist/pmtiles-source.d.ts.map +1 -0
- package/package.json +42 -0
- package/src/bundle.ts +4 -0
- package/src/index.ts +5 -0
- package/src/lib/parse-pmtiles.ts +147 -0
- package/src/lib/sources.ts +150 -0
- package/src/pmtiles-source.ts +155 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources.js","names":[],"sources":["../../../src/lib/sources.ts"],"sourcesContent":["/*\nimport {fetchFile} from '@loaders.gl/core';\n\nimport {Source as PMTilesSource, RangeResponse} from 'pmtiles';\n\n/** @note \"source\" here is a PMTiles library type, referring to *\nexport function makeSource(data: string | Blob, fetch?) {\n if (typeof data === 'string') {\n return new FetchSource(data, fetch);\n }\n if (data instanceof Blob) {\n const url = '';\n return new BlobSource(data, url);\n }\n}\n\nexport class BlobSource implements PMTilesSource {\n blob: Blob;\n key: string;\n\n constructor(blob: Blob, key: string) {\n this.blob = blob;\n this.key = key;\n }\n\n // TODO - how is this used?\n getKey() {\n return this.blob.url || '';\n }\n\n async getBytes(offset: number, length: number, signal?: AbortSignal): Promise<RangeResponse> {\n const data = await this.blob.arrayBuffer();\n return {\n data\n // etag: response.headers.get('ETag') || undefined,\n // cacheControl: response.headers.get('Cache-Control') || undefined,\n // expires: response.headers.get('Expires') || undefined\n };\n }\n}\n\nexport class FetchSource implements PMTilesSource {\n url: string;\n fetch;\n\n constructor(url: string, fetch = fetchFile) {\n this.url = url;\n this.fetch = fetch;\n }\n\n // TODO - how is this used?\n getKey() {\n return this.url;\n }\n\n async getBytes(offset: number, length: number, signal?: AbortSignal): Promise<RangeResponse> {\n let controller;\n if (!signal) {\n // ToDO why is it so important to abort in case 200?\n // TODO check this works or assert 206\n controller = new AbortController();\n signal = controller.signal;\n }\n\n let response = await fetch(this.url, {\n signal,\n headers: {Range: `bytes=${offset}-${offset + length - 1}`}\n });\n\n switch (response.status) {\n case 206: // Partial Content success\n // This is the expected success code for a range request\n break;\n\n case 200:\n // some well-behaved backends, e.g. DigitalOcean CDN, respond with 200 instead of 206\n // but we also need to detect no support for Byte Serving which is returning the whole file\n const content_length = response.headers.get('Content-Length');\n if (!content_length || Number(content_length) > length) {\n if (controller) {\n controller.abort();\n }\n throw Error(\n 'content-length header missing or exceeding request. Server must support HTTP Byte Serving.'\n );\n }\n\n case 416: // \"Range Not Satisfiable\"\n // some HTTP servers don't accept ranges beyond the end of the resource.\n // Retry with the exact length\n // TODO: can return 416 with offset > 0 if content changed, which will have a blank etag.\n // See https://github.com/protomaps/PMTiles/issues/90\n if (offset === 0) {\n const content_range = response.headers.get('Content-Range');\n if (!content_range || !content_range.startsWith('bytes *')) {\n throw Error('Missing content-length on 416 response');\n }\n const actual_length = Number(content_range.substr(8));\n response = await fetch(this.url, {\n signal,\n headers: {Range: `bytes=0-${actual_length - 1}`}\n });\n }\n break;\n\n default:\n if (response.status >= 300) {\n throw Error(`Bad response code: ${response.status}`);\n }\n }\n\n const data = await response.arrayBuffer();\n return {\n data,\n etag: response.headers.get('ETag') || undefined,\n cacheControl: response.headers.get('Cache-Control') || undefined,\n expires: response.headers.get('Expires') || undefined\n };\n }\n}\n\n/*\nclass TestNodeFileSource implements Source {\n buffer: ArrayBuffer;\n path: string;\n key: string;\n etag?: string;\n\n constructor(path: string, key: string) {\n this.path = path;\n this.buffer = fs.readFileSync(path);\n this.key = key;\n }\n\n getKey() {\n return this.key;\n }\n\n replaceData(path: string) {\n this.path = path;\n this.buffer = fs.readFileSync(path);\n }\n\n async getBytes(offset: number, length: number): Promise<RangeResponse> {\n const slice = new Uint8Array(this.buffer.slice(offset, offset + length))\n .buffer;\n return { data: slice, etag: this.etag };\n }\n}\n*/\n"],"mappings":""}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
|
|
2
|
+
import { DataSource, resolvePath } from '@loaders.gl/loader-utils';
|
|
3
|
+
import { ImageLoader } from '@loaders.gl/images';
|
|
4
|
+
import { MVTLoader } from '@loaders.gl/mvt';
|
|
5
|
+
import { PMTiles } from 'pmtiles';
|
|
6
|
+
import { parsePMTilesHeader } from './lib/parse-pmtiles';
|
|
7
|
+
export class BlobSource {
|
|
8
|
+
constructor(blob, key) {
|
|
9
|
+
_defineProperty(this, "blob", void 0);
|
|
10
|
+
_defineProperty(this, "key", void 0);
|
|
11
|
+
this.blob = blob;
|
|
12
|
+
this.key = key;
|
|
13
|
+
}
|
|
14
|
+
getKey() {
|
|
15
|
+
return this.blob.url || '';
|
|
16
|
+
}
|
|
17
|
+
async getBytes(offset, length, signal) {
|
|
18
|
+
const slice = this.blob.slice(offset, offset + length);
|
|
19
|
+
const data = await slice.arrayBuffer();
|
|
20
|
+
return {
|
|
21
|
+
data
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class PMTilesSource extends DataSource {
|
|
26
|
+
constructor(props) {
|
|
27
|
+
super(props);
|
|
28
|
+
_defineProperty(this, "props", void 0);
|
|
29
|
+
_defineProperty(this, "pmtiles", void 0);
|
|
30
|
+
_defineProperty(this, "metadata", void 0);
|
|
31
|
+
this.props = props;
|
|
32
|
+
const url = typeof props.url === 'string' ? resolvePath(props.url) : new BlobSource(props.url, 'pmtiles');
|
|
33
|
+
this.pmtiles = new PMTiles(url);
|
|
34
|
+
this.getTileData = this.getTileData.bind(this);
|
|
35
|
+
this.metadata = this.getMetadata();
|
|
36
|
+
}
|
|
37
|
+
async getMetadata() {
|
|
38
|
+
const pmtilesHeader = await this.pmtiles.getHeader();
|
|
39
|
+
const pmtilesMetadata = await this.pmtiles.getMetadata();
|
|
40
|
+
const metadata = parsePMTilesHeader(pmtilesHeader, pmtilesMetadata);
|
|
41
|
+
if (this.props.attributions) {
|
|
42
|
+
metadata.attributions = [...this.props.attributions, ...(metadata.attributions || [])];
|
|
43
|
+
}
|
|
44
|
+
return metadata;
|
|
45
|
+
}
|
|
46
|
+
async getTile(tileParams) {
|
|
47
|
+
const {
|
|
48
|
+
x,
|
|
49
|
+
y,
|
|
50
|
+
zoom: z
|
|
51
|
+
} = tileParams;
|
|
52
|
+
const rangeResponse = await this.pmtiles.getZxy(z, x, y);
|
|
53
|
+
const arrayBuffer = rangeResponse === null || rangeResponse === void 0 ? void 0 : rangeResponse.data;
|
|
54
|
+
if (!arrayBuffer) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return arrayBuffer;
|
|
58
|
+
}
|
|
59
|
+
async getTileData(tileParams) {
|
|
60
|
+
const {
|
|
61
|
+
x,
|
|
62
|
+
y,
|
|
63
|
+
z
|
|
64
|
+
} = tileParams.index;
|
|
65
|
+
const metadata = await this.metadata;
|
|
66
|
+
switch (metadata.mimeType) {
|
|
67
|
+
case 'application/vnd.mapbox-vector-tile':
|
|
68
|
+
return await this.getVectorTile({
|
|
69
|
+
x,
|
|
70
|
+
y,
|
|
71
|
+
zoom: z,
|
|
72
|
+
layers: []
|
|
73
|
+
});
|
|
74
|
+
default:
|
|
75
|
+
return await this.getImageTile({
|
|
76
|
+
x,
|
|
77
|
+
y,
|
|
78
|
+
zoom: z,
|
|
79
|
+
layers: []
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async getImageTile(tileParams) {
|
|
84
|
+
const arrayBuffer = await this.getTile(tileParams);
|
|
85
|
+
return arrayBuffer ? await ImageLoader.parse(arrayBuffer, this.loadOptions) : null;
|
|
86
|
+
}
|
|
87
|
+
async getVectorTile(tileParams) {
|
|
88
|
+
var _this$loadOptions;
|
|
89
|
+
const arrayBuffer = await this.getTile(tileParams);
|
|
90
|
+
const loadOptions = {
|
|
91
|
+
shape: 'geojson-table',
|
|
92
|
+
mvt: {
|
|
93
|
+
coordinates: 'wgs84',
|
|
94
|
+
tileIndex: {
|
|
95
|
+
x: tileParams.x,
|
|
96
|
+
y: tileParams.y,
|
|
97
|
+
z: tileParams.zoom
|
|
98
|
+
},
|
|
99
|
+
...((_this$loadOptions = this.loadOptions) === null || _this$loadOptions === void 0 ? void 0 : _this$loadOptions.mvt)
|
|
100
|
+
},
|
|
101
|
+
...this.loadOptions
|
|
102
|
+
};
|
|
103
|
+
return arrayBuffer ? await MVTLoader.parse(arrayBuffer, loadOptions) : null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=pmtiles-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pmtiles-source.js","names":["DataSource","resolvePath","ImageLoader","MVTLoader","PMTiles","parsePMTilesHeader","BlobSource","constructor","blob","key","_defineProperty","getKey","url","getBytes","offset","length","signal","slice","data","arrayBuffer","PMTilesSource","props","pmtiles","getTileData","bind","metadata","getMetadata","pmtilesHeader","getHeader","pmtilesMetadata","attributions","getTile","tileParams","x","y","zoom","z","rangeResponse","getZxy","index","mimeType","getVectorTile","layers","getImageTile","parse","loadOptions","_this$loadOptions","shape","mvt","coordinates","tileIndex"],"sources":["../../src/pmtiles-source.ts"],"sourcesContent":["// loaders.gl, MIT license\n\nimport type {GetTileParameters, ImageType, DataSourceProps} from '@loaders.gl/loader-utils';\nimport type {ImageTileSource, VectorTileSource} from '@loaders.gl/loader-utils';\nimport {DataSource, resolvePath} from '@loaders.gl/loader-utils';\nimport {ImageLoader} from '@loaders.gl/images';\nimport {MVTLoader, MVTLoaderOptions} from '@loaders.gl/mvt';\n\nimport {PMTiles, Source, RangeResponse} from 'pmtiles';\n\nimport type {PMTilesMetadata} from './lib/parse-pmtiles';\nimport {parsePMTilesHeader} from './lib/parse-pmtiles';\nimport {TileLoadParameters} from 'modules/loader-utils/src/lib/sources/tile-source';\n\n// export const PMTilesService: Service = {\n// name: 'PMTiles',\n// id: 'pmtiles',\n// module: 'pmtiles',\n// // version: VERSION,\n// extensions: ['pmtiles'],\n// mimeTypes: ['application/octet-stream'],\n// options: {\n// pmtiles: {}\n// }\n// };\n\n/**\n * WIP - Loader for pmtiles metadata\n * @note loads metadata only. To load individual tiles, use PMTilesSource\nexport const PMTilesLoader: LoaderWithParser<PMTilesMetadata, never, PMTilesLoaderOptions> = {\n name: 'PMTiles',\n id: 'pmtiles',\n module: 'pmtiles',\n version: VERSION,\n worker: true,\n extensions: ['pmtiles'],\n mimeTypes: ['application/octet-stream'],\n options: {\n pmtiles: {}\n },\n parse: async (arrayBuffer, options) => {\n throw new Error('not implemented');\n }\n};\n */\n\nexport type PMTilesSourceProps = DataSourceProps & {\n url: string | Blob;\n attributions?: string[];\n};\n\nexport class BlobSource implements Source {\n blob: Blob;\n key: string;\n\n constructor(blob: Blob, key: string) {\n this.blob = blob;\n this.key = key;\n }\n\n // TODO - how is this used?\n getKey() {\n // @ts-expect-error url is only defined on File subclass\n return this.blob.url || '';\n }\n\n async getBytes(offset: number, length: number, signal?: AbortSignal): Promise<RangeResponse> {\n const slice = this.blob.slice(offset, offset + length);\n const data = await slice.arrayBuffer();\n return {\n data\n // etag: response.headers.get('ETag') || undefined,\n // cacheControl: response.headers.get('Cache-Control') || undefined,\n // expires: response.headers.get('Expires') || undefined\n };\n }\n}\n/**\n * A PMTiles data source\n * @note Can be either a raster or vector tile source depending on the contents of the PMTiles file.\n */\nexport class PMTilesSource extends DataSource implements ImageTileSource, VectorTileSource {\n props: PMTilesSourceProps;\n pmtiles: PMTiles;\n metadata: Promise<PMTilesMetadata>;\n\n constructor(props: PMTilesSourceProps) {\n super(props);\n this.props = props;\n const url =\n typeof props.url === 'string' ? resolvePath(props.url) : new BlobSource(props.url, 'pmtiles');\n this.pmtiles = new PMTiles(url);\n this.getTileData = this.getTileData.bind(this);\n this.metadata = this.getMetadata();\n }\n\n async getMetadata(): Promise<PMTilesMetadata> {\n const pmtilesHeader = await this.pmtiles.getHeader();\n const pmtilesMetadata = await this.pmtiles.getMetadata();\n const metadata = parsePMTilesHeader(pmtilesHeader, pmtilesMetadata);\n if (this.props.attributions) {\n metadata.attributions = [...this.props.attributions, ...(metadata.attributions || [])];\n }\n return metadata;\n }\n\n async getTile(tileParams: GetTileParameters): Promise<ArrayBuffer | null> {\n const {x, y, zoom: z} = tileParams;\n const rangeResponse = await this.pmtiles.getZxy(z, x, y);\n const arrayBuffer = rangeResponse?.data;\n if (!arrayBuffer) {\n // console.error('No arrayBuffer', tileParams);\n return null;\n }\n return arrayBuffer;\n }\n\n // Tile Source interface implementation: deck.gl compatible API\n // TODO - currently only handles image tiles, not vector tiles\n\n async getTileData(tileParams: TileLoadParameters): Promise<unknown | null> {\n const {x, y, z} = tileParams.index;\n const metadata = await this.metadata;\n switch (metadata.mimeType) {\n case 'application/vnd.mapbox-vector-tile':\n return await this.getVectorTile({x, y, zoom: z, layers: []});\n default:\n return await this.getImageTile({x, y, zoom: z, layers: []});\n }\n }\n\n // ImageTileSource interface implementation\n\n async getImageTile(tileParams: GetTileParameters): Promise<ImageType | null> {\n const arrayBuffer = await this.getTile(tileParams);\n return arrayBuffer ? await ImageLoader.parse(arrayBuffer, this.loadOptions) : null;\n }\n\n // VectorTileSource interface implementation\n\n async getVectorTile(tileParams: GetTileParameters): Promise<unknown | null> {\n const arrayBuffer = await this.getTile(tileParams);\n const loadOptions: MVTLoaderOptions = {\n shape: 'geojson-table',\n mvt: {\n coordinates: 'wgs84',\n tileIndex: {x: tileParams.x, y: tileParams.y, z: tileParams.zoom},\n ...(this.loadOptions as MVTLoaderOptions)?.mvt\n },\n ...this.loadOptions\n };\n\n return arrayBuffer ? await MVTLoader.parse(arrayBuffer, loadOptions) : null;\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EAAC,eAAe,EAAC,MAAM,qBAAqB,CAAC;AACzD,YAAY,EAAC,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Source, Header, TileType } from 'pmtiles';
|
|
2
|
+
import type { TileJSON } from '@loaders.gl/mvt';
|
|
3
|
+
/** Metadata describing a PMTiles file */
|
|
4
|
+
export type PMTilesMetadata = {
|
|
5
|
+
/** Name of the tileset (extracted from JSON metadata if available) */
|
|
6
|
+
name?: string;
|
|
7
|
+
/** Attribution string (extracted from JSON metadata if available) */
|
|
8
|
+
attributions?: string[];
|
|
9
|
+
format: 'pmtiles';
|
|
10
|
+
/** Version of pm tiles format used by this tileset */
|
|
11
|
+
formatVersion: number;
|
|
12
|
+
/** PMTiles format specific header */
|
|
13
|
+
formatHeader?: Header;
|
|
14
|
+
/** MIME type for tile contents. Unknown tile types will return 'application/octet-stream */
|
|
15
|
+
mimeType: 'application/vnd.mapbox-vector-tile' | 'image/png' | 'image/jpeg' | 'image/webp' | 'image/avif' | 'application/octet-stream';
|
|
16
|
+
/** The original numeric tile type constant specified in the PMTiles tileset */
|
|
17
|
+
tileType: TileType;
|
|
18
|
+
/** Minimal zoom level of tiles in this tileset */
|
|
19
|
+
minZoom: number;
|
|
20
|
+
/** Maximal zoom level of tiles in this tileset */
|
|
21
|
+
maxZoom: number;
|
|
22
|
+
/** Bounding box of tiles in this tileset `[[w, s], [e, n]]` */
|
|
23
|
+
boundingBox: [min: [x: number, y: number], max: [x: number, y: number]];
|
|
24
|
+
/** Center long, lat of this tileset */
|
|
25
|
+
center: [number, number];
|
|
26
|
+
/** Center zoom level of this tileset */
|
|
27
|
+
centerZoom: number;
|
|
28
|
+
/** Cache tag */
|
|
29
|
+
etag?: string;
|
|
30
|
+
tileJSON?: TileJSON;
|
|
31
|
+
/** Current assumption is that this is a tileJSON style metadata generated by e.g. tippecanoe */
|
|
32
|
+
tilejsonMetadata?: Record<string, unknown>;
|
|
33
|
+
};
|
|
34
|
+
export type ParsePMTilesOptions = {
|
|
35
|
+
tileZxy?: [number, number, number];
|
|
36
|
+
};
|
|
37
|
+
export declare function loadPMTilesHeader(source: Source): Promise<PMTilesMetadata>;
|
|
38
|
+
export declare function loadPMTile(source: Source, options: ParsePMTilesOptions): Promise<ArrayBuffer | undefined>;
|
|
39
|
+
export declare function parsePMTilesHeader(header: Header, tilejsonMetadata: Record<string, unknown> | null, options?: {
|
|
40
|
+
includeFormatHeader?: boolean;
|
|
41
|
+
}): PMTilesMetadata;
|
|
42
|
+
//# sourceMappingURL=parse-pmtiles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-pmtiles.d.ts","sourceRoot":"","sources":["../../src/lib/parse-pmtiles.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,MAAM,EAAW,MAAM,EAAE,QAAQ,EAAC,MAAM,SAAS,CAAC;AAC1D,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,iBAAiB,CAAC;AAG9C,yCAAyC;AACzC,MAAM,MAAM,eAAe,GAAG;IAC5B,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB,MAAM,EAAE,SAAS,CAAC;IAClB,sDAAsD;IACtD,aAAa,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4FAA4F;IAC5F,QAAQ,EACJ,oCAAoC,GACpC,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,YAAY,GACZ,0BAA0B,CAAC;IAC/B,+EAA+E;IAC/E,QAAQ,EAAE,QAAQ,CAAC;IACnB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IACxE,uCAAuC;IACvC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,gGAAgG;IAChG,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC,CAAC;AAEF,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAShF;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAQlC;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EAChD,OAAO,CAAC,EAAE;IAAC,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAAC,GACxC,eAAe,CA2CjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=sources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../../src/lib/sources.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { GetTileParameters, ImageType, DataSourceProps } from '@loaders.gl/loader-utils';
|
|
2
|
+
import type { ImageTileSource, VectorTileSource } from '@loaders.gl/loader-utils';
|
|
3
|
+
import { DataSource } from '@loaders.gl/loader-utils';
|
|
4
|
+
import { PMTiles, Source, RangeResponse } from 'pmtiles';
|
|
5
|
+
import type { PMTilesMetadata } from './lib/parse-pmtiles';
|
|
6
|
+
import { TileLoadParameters } from 'modules/loader-utils/src/lib/sources/tile-source';
|
|
7
|
+
/**
|
|
8
|
+
* WIP - Loader for pmtiles metadata
|
|
9
|
+
* @note loads metadata only. To load individual tiles, use PMTilesSource
|
|
10
|
+
export const PMTilesLoader: LoaderWithParser<PMTilesMetadata, never, PMTilesLoaderOptions> = {
|
|
11
|
+
name: 'PMTiles',
|
|
12
|
+
id: 'pmtiles',
|
|
13
|
+
module: 'pmtiles',
|
|
14
|
+
version: VERSION,
|
|
15
|
+
worker: true,
|
|
16
|
+
extensions: ['pmtiles'],
|
|
17
|
+
mimeTypes: ['application/octet-stream'],
|
|
18
|
+
options: {
|
|
19
|
+
pmtiles: {}
|
|
20
|
+
},
|
|
21
|
+
parse: async (arrayBuffer, options) => {
|
|
22
|
+
throw new Error('not implemented');
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
*/
|
|
26
|
+
export type PMTilesSourceProps = DataSourceProps & {
|
|
27
|
+
url: string | Blob;
|
|
28
|
+
attributions?: string[];
|
|
29
|
+
};
|
|
30
|
+
export declare class BlobSource implements Source {
|
|
31
|
+
blob: Blob;
|
|
32
|
+
key: string;
|
|
33
|
+
constructor(blob: Blob, key: string);
|
|
34
|
+
getKey(): any;
|
|
35
|
+
getBytes(offset: number, length: number, signal?: AbortSignal): Promise<RangeResponse>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A PMTiles data source
|
|
39
|
+
* @note Can be either a raster or vector tile source depending on the contents of the PMTiles file.
|
|
40
|
+
*/
|
|
41
|
+
export declare class PMTilesSource extends DataSource implements ImageTileSource, VectorTileSource {
|
|
42
|
+
props: PMTilesSourceProps;
|
|
43
|
+
pmtiles: PMTiles;
|
|
44
|
+
metadata: Promise<PMTilesMetadata>;
|
|
45
|
+
constructor(props: PMTilesSourceProps);
|
|
46
|
+
getMetadata(): Promise<PMTilesMetadata>;
|
|
47
|
+
getTile(tileParams: GetTileParameters): Promise<ArrayBuffer | null>;
|
|
48
|
+
getTileData(tileParams: TileLoadParameters): Promise<unknown | null>;
|
|
49
|
+
getImageTile(tileParams: GetTileParameters): Promise<ImageType | null>;
|
|
50
|
+
getVectorTile(tileParams: GetTileParameters): Promise<unknown | null>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=pmtiles-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pmtiles-source.d.ts","sourceRoot":"","sources":["../src/pmtiles-source.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,iBAAiB,EAAE,SAAS,EAAE,eAAe,EAAC,MAAM,0BAA0B,CAAC;AAC5F,OAAO,KAAK,EAAC,eAAe,EAAE,gBAAgB,EAAC,MAAM,0BAA0B,CAAC;AAChF,OAAO,EAAC,UAAU,EAAc,MAAM,0BAA0B,CAAC;AAIjE,OAAO,EAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAC,MAAM,SAAS,CAAC;AAEvD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAC,kBAAkB,EAAC,MAAM,kDAAkD,CAAC;AAcpF;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,MAAM,kBAAkB,GAAG,eAAe,GAAG;IACjD,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB,CAAC;AAEF,qBAAa,UAAW,YAAW,MAAM;IACvC,IAAI,EAAE,IAAI,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;gBAEA,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM;IAMnC,MAAM;IAKA,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC;CAU7F;AACD;;;GAGG;AACH,qBAAa,aAAc,SAAQ,UAAW,YAAW,eAAe,EAAE,gBAAgB;IACxF,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;gBAEvB,KAAK,EAAE,kBAAkB;IAU/B,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;IAUvC,OAAO,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAcnE,WAAW,CAAC,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAapE,YAAY,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAOtE,aAAa,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;CAc5E"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@loaders.gl/pmtiles",
|
|
3
|
+
"version": "4.0.0-beta.1",
|
|
4
|
+
"description": "Framework-independent loader for the pmtiles format",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/visgl/loaders.gl"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"webgl",
|
|
15
|
+
"loader",
|
|
16
|
+
"3d",
|
|
17
|
+
"mesh",
|
|
18
|
+
"point cloud",
|
|
19
|
+
"PCD"
|
|
20
|
+
],
|
|
21
|
+
"types": "dist/index.d.ts",
|
|
22
|
+
"main": "dist/es5/index.js",
|
|
23
|
+
"module": "dist/esm/index.js",
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"files": [
|
|
26
|
+
"src",
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"pre-build": "npm run build-bundle",
|
|
32
|
+
"build-bundle": "esbuild src/bundle.ts --bundle --outfile=dist/dist.min.js"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@loaders.gl/images": "4.0.0-beta.1",
|
|
36
|
+
"@loaders.gl/loader-utils": "4.0.0-beta.1",
|
|
37
|
+
"@loaders.gl/mvt": "4.0.0-beta.1",
|
|
38
|
+
"@loaders.gl/schema": "4.0.0-beta.1",
|
|
39
|
+
"pmtiles": "^2.7.2"
|
|
40
|
+
},
|
|
41
|
+
"gitHead": "35c625e67132b0784e597d9ddabae8aefea29ff2"
|
|
42
|
+
}
|
package/src/bundle.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// loaders.gl, MIT license
|
|
2
|
+
|
|
3
|
+
import {Source, PMTiles, Header, TileType} from 'pmtiles';
|
|
4
|
+
import type {TileJSON} from '@loaders.gl/mvt';
|
|
5
|
+
import {TileJSONLoader} from '@loaders.gl/mvt';
|
|
6
|
+
|
|
7
|
+
/** Metadata describing a PMTiles file */
|
|
8
|
+
export type PMTilesMetadata = {
|
|
9
|
+
/** Name of the tileset (extracted from JSON metadata if available) */
|
|
10
|
+
name?: string;
|
|
11
|
+
/** Attribution string (extracted from JSON metadata if available) */
|
|
12
|
+
attributions?: string[];
|
|
13
|
+
|
|
14
|
+
format: 'pmtiles';
|
|
15
|
+
/** Version of pm tiles format used by this tileset */
|
|
16
|
+
formatVersion: number;
|
|
17
|
+
/** PMTiles format specific header */
|
|
18
|
+
formatHeader?: Header;
|
|
19
|
+
/** MIME type for tile contents. Unknown tile types will return 'application/octet-stream */
|
|
20
|
+
mimeType:
|
|
21
|
+
| 'application/vnd.mapbox-vector-tile'
|
|
22
|
+
| 'image/png'
|
|
23
|
+
| 'image/jpeg'
|
|
24
|
+
| 'image/webp'
|
|
25
|
+
| 'image/avif'
|
|
26
|
+
| 'application/octet-stream';
|
|
27
|
+
/** The original numeric tile type constant specified in the PMTiles tileset */
|
|
28
|
+
tileType: TileType;
|
|
29
|
+
/** Minimal zoom level of tiles in this tileset */
|
|
30
|
+
minZoom: number;
|
|
31
|
+
/** Maximal zoom level of tiles in this tileset */
|
|
32
|
+
maxZoom: number;
|
|
33
|
+
/** Bounding box of tiles in this tileset `[[w, s], [e, n]]` */
|
|
34
|
+
boundingBox: [min: [x: number, y: number], max: [x: number, y: number]];
|
|
35
|
+
/** Center long, lat of this tileset */
|
|
36
|
+
center: [number, number];
|
|
37
|
+
/** Center zoom level of this tileset */
|
|
38
|
+
centerZoom: number;
|
|
39
|
+
/** Cache tag */
|
|
40
|
+
etag?: string;
|
|
41
|
+
tileJSON?: TileJSON;
|
|
42
|
+
/** Current assumption is that this is a tileJSON style metadata generated by e.g. tippecanoe */
|
|
43
|
+
tilejsonMetadata?: Record<string, unknown>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type ParsePMTilesOptions = {
|
|
47
|
+
tileZxy?: [number, number, number];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export async function loadPMTilesHeader(source: Source): Promise<PMTilesMetadata> {
|
|
51
|
+
const pmTiles = new PMTiles(source);
|
|
52
|
+
const header = await pmTiles.getHeader();
|
|
53
|
+
const metadata = await pmTiles.getMetadata();
|
|
54
|
+
const tilejsonMetadata =
|
|
55
|
+
metadata && typeof metadata === 'object' && !Array.isArray(metadata)
|
|
56
|
+
? (metadata as Record<string, unknown>)
|
|
57
|
+
: null;
|
|
58
|
+
return parsePMTilesHeader(header, tilejsonMetadata);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function loadPMTile(
|
|
62
|
+
source: Source,
|
|
63
|
+
options: ParsePMTilesOptions
|
|
64
|
+
): Promise<ArrayBuffer | undefined> {
|
|
65
|
+
const pmTiles = new PMTiles(source);
|
|
66
|
+
if (!options.tileZxy) {
|
|
67
|
+
throw new Error('tile zxy missing');
|
|
68
|
+
}
|
|
69
|
+
const [z, x, y] = options.tileZxy;
|
|
70
|
+
const tile = await pmTiles.getZxy(z, x, y);
|
|
71
|
+
return tile?.data;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function parsePMTilesHeader(
|
|
75
|
+
header: Header,
|
|
76
|
+
tilejsonMetadata: Record<string, unknown> | null,
|
|
77
|
+
options?: {includeFormatHeader?: boolean}
|
|
78
|
+
): PMTilesMetadata {
|
|
79
|
+
const partialMetadata: Partial<PMTilesMetadata> = {};
|
|
80
|
+
|
|
81
|
+
if (typeof tilejsonMetadata?.name === 'string') {
|
|
82
|
+
partialMetadata.name = tilejsonMetadata.name;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (typeof tilejsonMetadata?.attribution === 'string') {
|
|
86
|
+
partialMetadata.attributions = [tilejsonMetadata.attribution];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const metadata: PMTilesMetadata = {
|
|
90
|
+
...partialMetadata,
|
|
91
|
+
format: 'pmtiles',
|
|
92
|
+
formatVersion: header.specVersion,
|
|
93
|
+
mimeType: decodeTileType(header.tileType),
|
|
94
|
+
tileType: header.tileType,
|
|
95
|
+
minZoom: header.minZoom,
|
|
96
|
+
maxZoom: header.maxZoom,
|
|
97
|
+
boundingBox: [
|
|
98
|
+
[header.minLon, header.minLat],
|
|
99
|
+
[header.maxLon, header.maxLat]
|
|
100
|
+
],
|
|
101
|
+
center: [header.centerLon, header.centerLat],
|
|
102
|
+
centerZoom: header.centerZoom,
|
|
103
|
+
etag: header.etag
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (tilejsonMetadata) {
|
|
107
|
+
try {
|
|
108
|
+
metadata.tileJSON =
|
|
109
|
+
TileJSONLoader.parseTextSync?.(JSON.stringify(tilejsonMetadata)) || undefined;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// console.warn('PMTiles invalid tilejson metadata', error);
|
|
112
|
+
metadata.tilejsonMetadata = tilejsonMetadata;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (options?.includeFormatHeader) {
|
|
117
|
+
metadata.formatHeader = header;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return metadata;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Extract a MIME type for tiles from vector tile header */
|
|
124
|
+
function decodeTileType(
|
|
125
|
+
tileType: TileType
|
|
126
|
+
):
|
|
127
|
+
| 'application/vnd.mapbox-vector-tile'
|
|
128
|
+
| 'image/png'
|
|
129
|
+
| 'image/jpeg'
|
|
130
|
+
| 'image/webp'
|
|
131
|
+
| 'image/avif'
|
|
132
|
+
| 'application/octet-stream' {
|
|
133
|
+
switch (tileType) {
|
|
134
|
+
case TileType.Mvt:
|
|
135
|
+
return 'application/vnd.mapbox-vector-tile';
|
|
136
|
+
case TileType.Png:
|
|
137
|
+
return 'image/png';
|
|
138
|
+
case TileType.Jpeg:
|
|
139
|
+
return 'image/jpeg';
|
|
140
|
+
case TileType.Webp:
|
|
141
|
+
return 'image/webp';
|
|
142
|
+
case TileType.Avif:
|
|
143
|
+
return 'image/avif';
|
|
144
|
+
default:
|
|
145
|
+
return 'application/octet-stream';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/*
|
|
2
|
+
import {fetchFile} from '@loaders.gl/core';
|
|
3
|
+
|
|
4
|
+
import {Source as PMTilesSource, RangeResponse} from 'pmtiles';
|
|
5
|
+
|
|
6
|
+
/** @note "source" here is a PMTiles library type, referring to *
|
|
7
|
+
export function makeSource(data: string | Blob, fetch?) {
|
|
8
|
+
if (typeof data === 'string') {
|
|
9
|
+
return new FetchSource(data, fetch);
|
|
10
|
+
}
|
|
11
|
+
if (data instanceof Blob) {
|
|
12
|
+
const url = '';
|
|
13
|
+
return new BlobSource(data, url);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class BlobSource implements PMTilesSource {
|
|
18
|
+
blob: Blob;
|
|
19
|
+
key: string;
|
|
20
|
+
|
|
21
|
+
constructor(blob: Blob, key: string) {
|
|
22
|
+
this.blob = blob;
|
|
23
|
+
this.key = key;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// TODO - how is this used?
|
|
27
|
+
getKey() {
|
|
28
|
+
return this.blob.url || '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getBytes(offset: number, length: number, signal?: AbortSignal): Promise<RangeResponse> {
|
|
32
|
+
const data = await this.blob.arrayBuffer();
|
|
33
|
+
return {
|
|
34
|
+
data
|
|
35
|
+
// etag: response.headers.get('ETag') || undefined,
|
|
36
|
+
// cacheControl: response.headers.get('Cache-Control') || undefined,
|
|
37
|
+
// expires: response.headers.get('Expires') || undefined
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class FetchSource implements PMTilesSource {
|
|
43
|
+
url: string;
|
|
44
|
+
fetch;
|
|
45
|
+
|
|
46
|
+
constructor(url: string, fetch = fetchFile) {
|
|
47
|
+
this.url = url;
|
|
48
|
+
this.fetch = fetch;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// TODO - how is this used?
|
|
52
|
+
getKey() {
|
|
53
|
+
return this.url;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getBytes(offset: number, length: number, signal?: AbortSignal): Promise<RangeResponse> {
|
|
57
|
+
let controller;
|
|
58
|
+
if (!signal) {
|
|
59
|
+
// ToDO why is it so important to abort in case 200?
|
|
60
|
+
// TODO check this works or assert 206
|
|
61
|
+
controller = new AbortController();
|
|
62
|
+
signal = controller.signal;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let response = await fetch(this.url, {
|
|
66
|
+
signal,
|
|
67
|
+
headers: {Range: `bytes=${offset}-${offset + length - 1}`}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
switch (response.status) {
|
|
71
|
+
case 206: // Partial Content success
|
|
72
|
+
// This is the expected success code for a range request
|
|
73
|
+
break;
|
|
74
|
+
|
|
75
|
+
case 200:
|
|
76
|
+
// some well-behaved backends, e.g. DigitalOcean CDN, respond with 200 instead of 206
|
|
77
|
+
// but we also need to detect no support for Byte Serving which is returning the whole file
|
|
78
|
+
const content_length = response.headers.get('Content-Length');
|
|
79
|
+
if (!content_length || Number(content_length) > length) {
|
|
80
|
+
if (controller) {
|
|
81
|
+
controller.abort();
|
|
82
|
+
}
|
|
83
|
+
throw Error(
|
|
84
|
+
'content-length header missing or exceeding request. Server must support HTTP Byte Serving.'
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
case 416: // "Range Not Satisfiable"
|
|
89
|
+
// some HTTP servers don't accept ranges beyond the end of the resource.
|
|
90
|
+
// Retry with the exact length
|
|
91
|
+
// TODO: can return 416 with offset > 0 if content changed, which will have a blank etag.
|
|
92
|
+
// See https://github.com/protomaps/PMTiles/issues/90
|
|
93
|
+
if (offset === 0) {
|
|
94
|
+
const content_range = response.headers.get('Content-Range');
|
|
95
|
+
if (!content_range || !content_range.startsWith('bytes *')) {
|
|
96
|
+
throw Error('Missing content-length on 416 response');
|
|
97
|
+
}
|
|
98
|
+
const actual_length = Number(content_range.substr(8));
|
|
99
|
+
response = await fetch(this.url, {
|
|
100
|
+
signal,
|
|
101
|
+
headers: {Range: `bytes=0-${actual_length - 1}`}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
default:
|
|
107
|
+
if (response.status >= 300) {
|
|
108
|
+
throw Error(`Bad response code: ${response.status}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const data = await response.arrayBuffer();
|
|
113
|
+
return {
|
|
114
|
+
data,
|
|
115
|
+
etag: response.headers.get('ETag') || undefined,
|
|
116
|
+
cacheControl: response.headers.get('Cache-Control') || undefined,
|
|
117
|
+
expires: response.headers.get('Expires') || undefined
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/*
|
|
123
|
+
class TestNodeFileSource implements Source {
|
|
124
|
+
buffer: ArrayBuffer;
|
|
125
|
+
path: string;
|
|
126
|
+
key: string;
|
|
127
|
+
etag?: string;
|
|
128
|
+
|
|
129
|
+
constructor(path: string, key: string) {
|
|
130
|
+
this.path = path;
|
|
131
|
+
this.buffer = fs.readFileSync(path);
|
|
132
|
+
this.key = key;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
getKey() {
|
|
136
|
+
return this.key;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
replaceData(path: string) {
|
|
140
|
+
this.path = path;
|
|
141
|
+
this.buffer = fs.readFileSync(path);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async getBytes(offset: number, length: number): Promise<RangeResponse> {
|
|
145
|
+
const slice = new Uint8Array(this.buffer.slice(offset, offset + length))
|
|
146
|
+
.buffer;
|
|
147
|
+
return { data: slice, etag: this.etag };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
*/
|