Skip to content

Commit 872c32a

Browse files
committed
save
1 parent e799808 commit 872c32a

12 files changed

Lines changed: 225 additions & 32 deletions

File tree

example/three/mvt_globe.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
UpdateOnChangePlugin,
1414
MVTTilesPlugin,
1515
MVTTilesMeshPlugin,
16-
PMTilesPlugin
16+
PMTilesPlugin,
17+
PMTilesMeshPlugin
1718
} from '3d-tiles-renderer/plugins';
1819

1920
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
@@ -210,14 +211,15 @@ function recreateTiles() {
210211

211212
pluginOptions.url = preset.url;
212213

213-
// PMTiles currently only supports Texture mode
214214
if ( state.renderMode === 'Mesh' ) {
215215

216-
console.warn( 'PMTiles source currently only supports Texture mode. Using Texture.' );
216+
tiles.registerPlugin( new PMTilesMeshPlugin( pluginOptions ) );
217217

218-
}
218+
} else {
219219

220-
tiles.registerPlugin( new PMTilesPlugin( pluginOptions ) );
220+
tiles.registerPlugin( new PMTilesPlugin( pluginOptions ) );
221+
222+
}
221223

222224
} else {
223225

src/core/renderer/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export * from './loaders/B3DMLoaderBase.js';
77
export * from './loaders/I3DMLoaderBase.js';
88
export * from './loaders/PNTSLoaderBase.js';
99
export * from './loaders/MVTLoaderBase.js';
10+
export * from './loaders/PMTilesLoaderBase.js';
1011
export * from './loaders/CMPTLoaderBase.js';
1112
export * from './loaders/LoaderBase.js';
1213
export * from './constants.js';

src/core/renderer/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './loaders/B3DMLoaderBase.js';
55
export * from './loaders/I3DMLoaderBase.js';
66
export * from './loaders/PNTSLoaderBase.js';
77
export * from './loaders/MVTLoaderBase.js';
8+
export * from './loaders/PMTilesLoaderBase.js';
89
export * from './loaders/CMPTLoaderBase.js';
910
export * from './constants.js';
1011

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { PMTiles, Header } from 'pmtiles';
2+
3+
export class PMTilesLoaderBase {
4+
5+
instance: PMTiles | null;
6+
header: Header | null;
7+
url: string | null;
8+
9+
constructor();
10+
11+
init( url: string ): Promise<Header>;
12+
getTile( z: number, x: number, y: number, signal?: AbortSignal ): Promise<ArrayBuffer | null>;
13+
getUrl( z: number, x: number, y: number ): string;
14+
15+
static parseUrl( url: string ): { z: number, x: number, y: number };
16+
17+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// PMTiles Archive Format
2+
// https://github.com/protomaps/PMTiles
3+
4+
import { PMTiles } from 'pmtiles';
5+
6+
export class PMTilesLoaderBase {
7+
8+
constructor() {
9+
10+
this.instance = null;
11+
this.header = null;
12+
this.url = null;
13+
14+
}
15+
16+
// Initialize the PMTiles archive and load header
17+
async init( url ) {
18+
19+
this.url = url.replace( /^pmtiles:\/\//, '' );
20+
this.instance = new PMTiles( this.url );
21+
this.header = await this.instance.getHeader();
22+
23+
return this.header;
24+
25+
}
26+
27+
// Fetch a tile from the archive
28+
async getTile( z, x, y, signal ) {
29+
30+
if ( ! this.instance ) {
31+
32+
throw new Error( 'PMTilesLoaderBase: Archive not initialized. Call init() first.' );
33+
34+
}
35+
36+
const res = await this.instance.getZxy( z, x, y, signal );
37+
38+
if ( ! res || ! res.data ) {
39+
40+
return null;
41+
42+
}
43+
44+
return res.data;
45+
46+
}
47+
48+
// Generate a virtual URL for a tile (used by tiling scheme)
49+
getUrl( z, x, y ) {
50+
51+
return `pmtiles://${z}/${x}/${y}`;
52+
53+
}
54+
55+
// Parse tile coordinates from a virtual URL
56+
static parseUrl( url ) {
57+
58+
const parts = url.split( '/' );
59+
const y = parseInt( parts.pop() );
60+
const x = parseInt( parts.pop() );
61+
const z = parseInt( parts.pop() );
62+
63+
return { z, x, y };
64+
65+
}
66+
67+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { ColorRepresentation } from 'three';
2+
import { PMTilesLoaderBase } from '../../core/renderer/loaders/PMTilesLoaderBase.js';
3+
4+
export class PMTilesMeshPlugin {
5+
6+
readonly pmtilesLoader: PMTilesLoaderBase;
7+
8+
constructor( options: {
9+
url: string,
10+
tileDimension?: number,
11+
filter?: ( feature: any, layerName: string ) => boolean,
12+
styles?: { [ layerName: string ]: ColorRepresentation },
13+
14+
center?: boolean,
15+
shape?: 'ellipsoid' | 'planar',
16+
endCaps?: boolean,
17+
useRecommendedSettings?: boolean,
18+
} );
19+
20+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { MVTTilesMeshPlugin } from './MVTTilesMeshPlugin.js';
2+
import { PMTilesLoaderBase } from '../../core/renderer/loaders/PMTilesLoaderBase.js';
3+
import { ProjectionScheme } from './images/utils/ProjectionScheme.js';
4+
5+
export class PMTilesMeshPlugin extends MVTTilesMeshPlugin {
6+
7+
constructor( options = {} ) {
8+
9+
super( options );
10+
11+
this.name = 'PMTILES_MESH_PLUGIN';
12+
this.pmtilesLoader = new PMTilesLoaderBase();
13+
this._pmtilesUrl = options.url;
14+
15+
}
16+
17+
async loadRootTileset() {
18+
19+
// Initialize PMTiles and get header
20+
const header = await this.pmtilesLoader.init( this._pmtilesUrl );
21+
22+
// Configure tiling from header
23+
this.imageSource.tiling.flipY = true;
24+
this.imageSource.tiling.setProjection( new ProjectionScheme( 'EPSG:3857' ) );
25+
this.imageSource.tiling.generateLevels(
26+
header.maxZoom,
27+
this.imageSource.tiling.projection.tileCountX,
28+
this.imageSource.tiling.projection.tileCountY,
29+
{
30+
tilePixelWidth: this.imageSource.tileDimension,
31+
tilePixelHeight: this.imageSource.tileDimension,
32+
}
33+
);
34+
35+
// Override getUrl to use pmtiles:// scheme
36+
this.imageSource.getUrl = ( x, y, level ) => this.pmtilesLoader.getUrl( level, x, y );
37+
38+
return this.getTileset( this._pmtilesUrl );
39+
40+
}
41+
42+
// Intercept pmtiles:// URLs and fetch from the PMTiles archive
43+
fetchData( url, options ) {
44+
45+
if ( url.startsWith( 'pmtiles://' ) ) {
46+
47+
const { z, x, y } = PMTilesLoaderBase.parseUrl( url );
48+
49+
return this.pmtilesLoader.getTile( z, x, y, options?.signal )
50+
.then( buffer => buffer || new ArrayBuffer( 0 ) );
51+
52+
}
53+
54+
return null;
55+
56+
}
57+
58+
// Override to handle pmtiles:// URLs (no file extension)
59+
async parseToMesh( buffer, tile, extension, uri, abortSignal ) {
60+
61+
if ( abortSignal.aborted ) {
62+
63+
return null;
64+
65+
}
66+
67+
// Handle pmtiles:// URLs OR standard .pbf/.mvt extensions
68+
if ( uri.startsWith( 'pmtiles://' ) || extension === 'pbf' || extension === 'mvt' ) {
69+
70+
const result = await this.loader.parse( buffer );
71+
const group = result.scene;
72+
73+
this._projectGroupToGlobe( group, tile );
74+
75+
return group;
76+
77+
}
78+
79+
return null;
80+
81+
}
82+
83+
}

src/three/plugins/images/PMTilesPlugin.js

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { EllipsoidProjectionTilesPlugin } from './EllipsoidProjectionTilesPlugin.js';
22
import { PMTilesImageSource } from './sources/PMTilesImageSource.js';
3+
import { PMTilesLoaderBase } from '../../../core/renderer/loaders/PMTilesLoaderBase.js';
34

45
export class PMTilesPlugin extends EllipsoidProjectionTilesPlugin {
56

@@ -18,24 +19,10 @@ export class PMTilesPlugin extends EllipsoidProjectionTilesPlugin {
1819

1920
if ( url.startsWith( 'pmtiles://' ) ) {
2021

21-
const parts = url.split( '/' );
22-
const y = parseInt( parts.pop() );
23-
const x = parseInt( parts.pop() );
24-
const z = parseInt( parts.pop() );
22+
const { z, x, y } = PMTilesLoaderBase.parseUrl( url );
2523

26-
return this.imageSource.instance.getZxy( z, x, y, options?.signal )
27-
.then( res => {
28-
29-
if ( ! res || ! res.data ) {
30-
31-
return new ArrayBuffer( 0 );
32-
33-
}
34-
35-
// res.data is ArrayBuffer per PMTiles API
36-
return res.data;
37-
38-
} );
24+
return this.imageSource.pmtilesLoader.getTile( z, x, y, options?.signal )
25+
.then( buffer => buffer || new ArrayBuffer( 0 ) );
3926

4027
}
4128

src/three/plugins/images/sources/PMTilesImageSource.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { ColorRepresentation, Texture } from 'three';
22
import { PMTiles } from 'pmtiles';
3+
import { PMTilesLoaderBase } from '../../../../core/renderer/loaders/PMTilesLoaderBase.js';
34

45
export class PMTilesImageSource {
56

7+
readonly pmtilesLoader: PMTilesLoaderBase;
68
readonly pmtilesUrl: string;
79
readonly instance: PMTiles;
810

src/three/plugins/images/sources/PMTilesImageSource.js

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,40 @@
11
import { MVTImageSource } from './MVTImageSource.js';
22
import { ProjectionScheme } from '../utils/ProjectionScheme.js';
3-
import { PMTiles } from 'pmtiles';
3+
import { PMTilesLoaderBase } from '../../../../core/renderer/loaders/PMTilesLoaderBase.js';
44

55
export class PMTilesImageSource extends MVTImageSource {
66

77
constructor( options = {} ) {
88

99
super( options );
1010

11-
this.pmtilesUrl = options.url.replace( /^pmtiles:\/\//, '' );
12-
this.instance = new PMTiles( this.pmtilesUrl );
11+
this.pmtilesLoader = new PMTilesLoaderBase();
1312
this.tiling.flipY = true;
1413

1514
}
1615

16+
// Expose for backward compatibility
17+
get pmtilesUrl() {
18+
19+
return this.pmtilesLoader.url;
20+
21+
}
22+
23+
get instance() {
24+
25+
return this.pmtilesLoader.instance;
26+
27+
}
28+
1729
getUrl( x, y, level ) {
1830

19-
return `pmtiles://${level}/${x}/${y}`;
31+
return this.pmtilesLoader.getUrl( level, x, y );
2032

2133
}
2234

2335
async init() {
2436

25-
const header = await this.instance.getHeader();
37+
const header = await this.pmtilesLoader.init( this.url );
2638
this.tiling.setProjection( new ProjectionScheme( 'EPSG:3857' ) );
2739
this.tiling.generateLevels( header.maxZoom, this.tiling.projection.tileCountX, this.tiling.projection.tileCountY, {
2840
tilePixelWidth: this.tileDimension,
@@ -36,17 +48,16 @@ export class PMTilesImageSource extends MVTImageSource {
3648

3749
const [ x, y, level ] = tokens;
3850

39-
return this.instance.getZxy( level, x, y, signal )
40-
.then( res => {
51+
return this.pmtilesLoader.getTile( level, x, y, signal )
52+
.then( buffer => {
4153

42-
if ( ! res || ! res.data ) {
54+
if ( ! buffer ) {
4355

4456
return this._createEmptyTexture();
4557

4658
}
4759

48-
// res.data is ArrayBuffer per PMTiles API
49-
return this.processBufferToTexture( res.data );
60+
return this.processBufferToTexture( buffer );
5061

5162
} );
5263

0 commit comments

Comments
 (0)