deck.gl layers supporting geospatial use cases and GIS formats with WebGL2/WebGPU acceleration
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Layer for rendering 3D Tiles format data including buildings, photogrammetry, point clouds, and other 3D geospatial content with efficient streaming and level-of-detail management.
Layer for rendering OGC 3D Tiles with automatic content loading, frustum culling, and hierarchical level-of-detail.
/**
* Layer for rendering 3D Tiles format data
* Supports buildings, photogrammetry, point clouds, and other 3D content
*/
class Tile3DLayer extends CompositeLayer {
constructor(props: Tile3DLayerProps);
}
interface Tile3DLayerProps<DataT = unknown> extends CompositeLayerProps {
/** URL to the 3D tileset JSON file */
data: string;
/** Color accessor for point clouds */
getPointColor?: Accessor<DataT, Color>;
/** Global radius of all points in pixels */
pointSize?: number;
/** Loader for decoding tiles (deprecated - use loaders instead) */
loader?: typeof Tiles3DLoader;
/** Called when Tileset JSON file is loaded */
onTilesetLoad?: (tileset: Tileset3D) => void;
/** Called when a tile in the tileset hierarchy is loaded */
onTileLoad?: (tile: Tile3D) => void;
/** Called when a tile is unloaded */
onTileUnload?: (tile: Tile3D) => void;
/** Called when a tile fails to load */
onTileError?: (tile: Tile3D, url: string, message: string) => void;
/** (Experimental) Accessor to change color of mesh based on properties */
_getMeshColor?: (tile: Tile3D) => Color;
}
type Accessor<DataT, ValueT> = ValueT | ((d: DataT, info?: any) => ValueT);
type Color = [r: number, g: number, b: number] | [r: number, g: number, b: number, a: number];Usage Examples:
import { Tile3DLayer } from "@deck.gl/geo-layers";
// Basic 3D building tiles
const buildingLayer = new Tile3DLayer({
id: '3d-buildings',
data: 'https://tiles.example.com/buildings/tileset.json',
onTilesetLoad: tileset => {
console.log('Tileset loaded:', tileset.root);
console.log('Bounding volume:', tileset.boundingVolume);
},
onTileLoad: tile => {
console.log(`Tile loaded: ${tile.id}, LOD: ${tile.lodMetricValue}`);
},
onTileError: (tile, error) => {
console.error(`Failed to load tile ${tile.id}:`, error);
},
pickable: true
});
// Photogrammetry with point styling
const photogrammetryLayer = new Tile3DLayer({
id: 'photogrammetry',
data: 'https://tiles.example.com/photogrammetry/tileset.json',
// Color point clouds based on tile properties
getPointColor: [120, 120, 255, 255],
pointSize: 1.5,
onTileLoad: tile => {
console.log(`Loaded tile: ${tile.id}, Type: ${tile.type}`);
},
pickable: true
});
// Point cloud visualization
const pointCloudLayer = new Tile3DLayer({
id: 'point-cloud',
data: 'https://tiles.example.com/lidar/tileset.json',
pointSize: 2, // Point size in pixels
getPointColor: [255, 128, 0, 255], // Orange points
onTileLoad: tile => {
console.log(`Loaded point cloud tile: ${tile.id}`);
console.log(`Content type: ${tile.content?.type}`);
},
pickable: true
});
// Custom mesh coloring
const coloredMeshLayer = new Tile3DLayer({
id: 'colored-mesh',
data: 'https://tiles.example.com/buildings/tileset.json',
// Experimental mesh coloring based on tile properties
_getMeshColor: tile => {
// Color buildings based on height or other properties
if (tile.boundingVolume?.sphere?.[2] > 100) {
return [255, 0, 0]; // Red for tall buildings
}
return [100, 100, 255]; // Blue for regular buildings
},
pointSize: 1.0,
pickable: true
});Support for standard 3D Tiles tileset.json format and tile hierarchy.
interface Tileset3D {
/** Root tile of the tileset */
root: Tile3DHeader;
/** Tileset metadata */
asset: {
version: string;
tilesetVersion?: string;
gltfUpAxis?: 'X' | 'Y' | 'Z';
};
/** Bounding volume for entire tileset */
boundingVolume: BoundingVolume;
/** Geometric error for the tileset */
geometricError: number;
/** Properties metadata */
properties?: {[key: string]: any};
/** Extensions used by the tileset */
extensions?: {[key: string]: any};
/** Extra data */
extras?: any;
}
interface Tile3DHeader {
/** Tile identifier */
id: string;
/** Bounding volume of the tile */
boundingVolume: BoundingVolume;
/** Geometric error of this tile */
geometricError: number;
/** Refinement strategy ('ADD' or 'REPLACE') */
refine?: 'ADD' | 'REPLACE';
/** Transform matrix for tile positioning */
transform?: number[];
/** Content information */
content?: TileContent;
/** Child tiles */
children?: Tile3DHeader[];
/** Level-of-detail metric value */
lodMetricValue?: number;
/** Whether tile is currently selected for rendering */
selected: boolean;
/** Whether tile content is loaded */
contentLoaded: boolean;
}
interface BoundingVolume {
/** Bounding box [centerX, centerY, centerZ, halfSizeX, halfSizeY, halfSizeZ] */
box?: number[];
/** Bounding sphere [centerX, centerY, centerZ, radius] */
sphere?: number[];
/** Bounding region [west, south, east, north, minHeight, maxHeight] */
region?: number[];
}
interface TileContent {
/** URI to tile content */
uri?: string;
/** Content bounding volume (optional override) */
boundingVolume?: BoundingVolume;
/** Content type */
type?: 'b3dm' | 'i3dm' | 'pnts' | 'cmpt' | 'glb' | 'gltf';
}Support for textured 3D models like buildings with batch tables for feature data.
interface B3DMContent {
/** GLTF model data */
gltf: any;
/** Batch table with feature properties */
batchTable?: {
[property: string]: any[];
};
/** Number of features in the batch */
featuresLength: number;
}B3DM Example:
const buildingsLayer = new Tile3DLayer({
id: 'buildings-b3dm',
data: 'https://tiles.example.com/manhattan/tileset.json',
onTileLoad: tile => {
if (tile.content?.batchTable) {
console.log('Building features:', tile.content.featuresLength);
console.log('Properties:', Object.keys(tile.content.batchTable));
}
},
getColor: (object, {index}) => {
// Color buildings based on batch properties
const height = object?.batchTable?.Height?.[index] || 0;
return height > 100 ? [255, 0, 0] : [100, 100, 255];
},
pickable: true
});Support for instanced models with per-instance transformations.
interface I3DMContent {
/** GLTF model template */
gltf: any;
/** Instance positions and orientations */
instances: {
positions: Float32Array;
normals?: Float32Array;
scales?: Float32Array;
};
/** Number of instances */
instancesLength: number;
/** Batch table for instance properties */
batchTable?: {[property: string]: any[]};
}Support for point cloud data with positions, colors, and classifications.
interface PNTSContent {
/** Point positions */
positions: Float32Array;
/** Point colors (optional) */
colors?: Uint8Array;
/** Point normals (optional) */
normals?: Float32Array;
/** Point classifications (optional) */
classifications?: Uint8Array;
/** Number of points */
pointsLength: number;
/** Feature table with point properties */
featureTable?: {[property: string]: any};
}Point Cloud Example:
const lidarLayer = new Tile3DLayer({
id: 'lidar-points',
data: 'https://tiles.example.com/lidar/tileset.json',
pointSize: 1.5,
onTileLoad: tile => {
if (tile.content?.pointsLength) {
console.log(`Loaded ${tile.content.pointsLength} points`);
// Access point cloud data
const positions = tile.content.positions;
const colors = tile.content.colors;
const classifications = tile.content.classifications;
}
},
// Custom point styling based on classification
getPointColor: (object, {index}) => {
const classification = object?.classifications?.[index] || 0;
switch (classification) {
case 2: return [139, 69, 19]; // Ground - brown
case 5: return [0, 255, 0]; // Vegetation - green
case 6: return [70, 130, 180]; // Building - blue
case 9: return [128, 128, 128]; // Water - gray
default: return [255, 255, 255]; // Other - white
}
},
pickable: true
});3D Tiles automatically manages LOD based on distance and screen space error.
interface LODProps {
/** Maximum screen space error in pixels */
maximumScreenSpaceError?: number;
/** Maximum number of requests for loading tiles */
maxRequests?: number;
/** Tile cache size limit */
tileCacheSize?: number;
/** Skip levels of detail for faster initial loading */
skipLevelOfDetail?: boolean;
}LOD Configuration:
// High detail for close inspection
const highDetailLayer = new Tile3DLayer({
id: 'high-detail',
data: 'https://tiles.example.com/detailed/tileset.json',
maximumScreenSpaceError: 2, // Very low error threshold
maxRequests: 20,
tileCacheSize: 200
});
// Performance optimized for overview
const performanceLayer = new Tile3DLayer({
id: 'performance',
data: 'https://tiles.example.com/overview/tileset.json',
maximumScreenSpaceError: 16, // Higher error threshold
maxRequests: 6,
skipLevelOfDetail: true,
tileCacheSize: 50
});// Monitor tile loading and memory usage
const monitoredLayer = new Tile3DLayer({
id: 'monitored',
data: 'https://tiles.example.com/large/tileset.json',
onTileLoad: tile => {
console.log(`Loaded: ${tile.id}, Memory: ${getApproxMemoryUsage(tile)}`);
},
onTileUnload: tile => {
console.log(`Unloaded: ${tile.id}`);
},
tileCacheSize: 100, // Limit cache size
maxRequests: 8
});
function getApproxMemoryUsage(tile) {
// Rough memory estimation
if (tile.content?.gltf) {
return `~${Math.round(JSON.stringify(tile.content.gltf).length / 1024)}KB`;
}
return 'Unknown';
}Install with Tessl CLI
npx tessl i tessl/npm-deck-gl--geo-layers