3d-tiles-renderer
Version:
https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification
239 lines (147 loc) • 5.43 kB
JavaScript
/** @import { VectorTileStyle } from './utils/VectorShapeCanvasRenderer.js' */
import { ImageOverlay } from './ImageOverlayPlugin.js';
import { MVTImageSource } from './sources/MVTImageSource.js';
import { PMTilesImageSource } from './sources/PMTilesImageSource.js';
import { PriorityQueue } from '3d-tiles-renderer/core';
/**
* @callback MVTGetStyleCallback
* @param {string} layerName Name of the MVT layer the feature belongs to.
* @param {Object|null} properties Feature properties, or `null` when queried for layer-level sort order only.
* @returns {VectorTileStyle|null} Style to apply, or `null` to use defaults.
*/
/**
* Overlay that renders XYZ-template MVT vector tiles on top of 3D tile geometry.
* See the {@link https://github.com/mapbox/vector-tile-spec Mapbox Vector Tile specification}.
* @extends ImageOverlay
* @param {Object} [options]
* @param {string} [options.url] URL template with `{x}`, `{y}`, `{z}` placeholders.
* @param {number} [options.levels=20] Number of zoom levels.
* @param {string} [options.projection='EPSG:3857'] Projection scheme identifier.
* @param {number} [options.resolution=512] Canvas resolution for generated tile textures.
* @param {MVTGetStyleCallback} [options.getStyle] Per-feature style callback. If not provided then no content will draw.
*/
export class MVTOverlay extends ImageOverlay {
get tiling() {
return this.imageSource.tiling;
}
get projection() {
return this.tiling.projection;
}
get aspectRatio() {
return this.tiling && this.isReady ? this.tiling.aspectRatio : 1;
}
get fetchOptions() {
return this.imageSource.fetchOptions;
}
set fetchOptions( v ) {
this.imageSource.fetchOptions = v;
}
get resolution() {
return this.imageSource.resolution;
}
constructor( options = {} ) {
super( options );
this.imageSource = options.imageSource ?? new MVTImageSource( options );
this._redrawQueue = new PriorityQueue();
this._redrawQueue.maxJobs = 4;
this._redrawQueue.priorityCallback = () => 0;
}
_init() {
this.imageSource.fetchData = ( ...args ) => this.fetch( ...args );
return this.imageSource.init();
}
// the MVTOverlay provides an optional resolution argument so that
// MVTAnnotationsPlugin can adjust it to prevent large amounts of
// annotations from loading.
calculateLevel( range, resolution = this.resolution ) {
const [ minX, minY, maxX, maxY ] = range;
const w = maxX - minX;
const h = maxY - minY;
const maxLevel = this.tiling.maxLevel;
let level = 0;
for ( ; level < maxLevel; level ++ ) {
const levelData = this.tiling.getLevel( level );
if ( levelData === null || levelData === undefined ) {
continue;
}
const { pixelWidth, pixelHeight } = levelData;
if ( pixelWidth >= resolution / w || pixelHeight >= resolution / h ) {
break;
}
}
return level;
}
hasContent( range, level = this.calculateLevel( range ) ) {
return this.imageSource.hasContent( ...range, level );
}
getTexture( range, level = this.calculateLevel( range ) ) {
return this.imageSource.get( ...range, level );
}
lockTexture( range, level = this.calculateLevel( range ) ) {
return this.imageSource.lock( ...range, level );
}
releaseTexture( range, level = this.calculateLevel( range ) ) {
this.imageSource.release( ...range, level );
}
setResolution( resolution ) {
this.imageSource.resolution = resolution;
}
shouldSplit( range ) {
return true;
}
setRegionVisible( range, visible ) {
super.setRegionVisible( range, visible );
if ( visible ) {
const { _redrawQueue } = this;
const key = range.join( '_' ) + '_' + this.calculateLevel( range );
if ( _redrawQueue.has( key ) ) {
_redrawQueue.flush( key );
}
}
}
redraw() {
const {
imageSource,
_redrawQueue,
_visibleRegionCounts,
} = this;
for ( const { range } of _visibleRegionCounts.values() ) {
imageSource.redraw( ...range, this.calculateLevel( range ) );
}
imageSource.forEachItem( ( _, args ) => {
const key = args.join( '_' );
if ( ! _visibleRegionCounts.has( key ) && ! _redrawQueue.has( key ) ) {
_redrawQueue.add( key, () => {
imageSource.redraw( ...args );
} );
}
} );
}
}
/**
* Overlay that renders PMTiles vector or raster data on top of 3D tile geometry.
* Projection and zoom levels are read automatically from the PMTiles archive header.
*
* @note Direct use of PMTiles requires range requests, which are not automatically cached
* by the browser. It's recommended to implement a service worker to cache response content
* to save bandwidth.
* @extends MVTOverlay
* @param {Object} [options]
* @param {string} [options.url] URL to the `.pmtiles` archive.
* @param {number} [options.resolution=512] Canvas resolution for generated tile textures.
* @param {MVTGetStyleCallback} [options.getStyle] Per-feature style callback. Only applies to vector archives.
*/
export class PMTilesOverlay extends MVTOverlay {
constructor( options = {} ) {
super( { ...options, imageSource: new PMTilesImageSource( options ) } );
}
shouldSplit( range ) {
// Vector archives can always split further for higher-resolution rasterization.
// Raster archives are capped at their max data zoom level.
if ( this.imageSource.isVectorTile ) {
return true;
} else {
return this.tiling.maxLevel > this.calculateLevel( range );
}
}
}