@deck.gl/geo-layers
Version:
deck.gl layers supporting geospatial use cases and GIS formats
357 lines (313 loc) • 10.1 kB
JavaScript
import GL from '@luma.gl/constants';
import {Geometry} from '@luma.gl/core';
import {COORDINATE_SYSTEM, CompositeLayer} from '@deck.gl/core';
import {PointCloudLayer} from '@deck.gl/layers';
import {ScenegraphLayer} from '@deck.gl/mesh-layers';
import {default as _MeshLayer} from '../mesh-layer/mesh-layer';
import {log} from '@deck.gl/core';
import {load} from '@loaders.gl/core';
import {Tileset3D, TILE_TYPE} from '@loaders.gl/tiles';
import {Tiles3DLoader} from '@loaders.gl/3d-tiles';
const SINGLE_DATA = [0];
const defaultProps = {
getPointColor: {type: 'accessor', value: [0, 0, 0, 255]},
pointSize: 1.0,
data: null,
loader: Tiles3DLoader,
onTilesetLoad: {type: 'function', value: tileset3d => {}, compare: false},
onTileLoad: {type: 'function', value: tileHeader => {}, compare: false},
onTileUnload: {type: 'function', value: tileHeader => {}, compare: false},
onTileError: {type: 'function', value: (tile, message, url) => {}, compare: false},
_getMeshColor: {type: 'function', value: tileHeader => [255, 255, 255], compare: false}
};
export default class Tile3DLayer extends CompositeLayer {
initializeState() {
if ('onTileLoadFail' in this.props) {
log.removed('onTileLoadFail', 'onTileError')();
}
// prop verification
this.state = {
layerMap: {},
tileset3d: null,
activeViewports: {},
lastUpdatedViewports: null
};
}
get isLoaded() {
const {tileset3d} = this.state;
return tileset3d && tileset3d.isLoaded();
}
shouldUpdateState({changeFlags}) {
return changeFlags.somethingChanged;
}
updateState({props, oldProps, changeFlags}) {
if (props.data && props.data !== oldProps.data) {
this._loadTileset(props.data);
}
if (changeFlags.viewportChanged) {
const {activeViewports} = this.state;
const viewportsNumber = Object.keys(activeViewports).length;
if (viewportsNumber) {
this._updateTileset(activeViewports);
this.state.lastUpdatedViewports = activeViewports;
this.state.activeViewports = {};
}
}
if (changeFlags.propsChanged) {
const {layerMap} = this.state;
for (const key in layerMap) {
layerMap[key].needsUpdate = true;
}
}
}
activateViewport(viewport) {
const {activeViewports, lastUpdatedViewports} = this.state;
this.internalState.viewport = viewport;
activeViewports[viewport.id] = viewport;
const lastViewport = lastUpdatedViewports?.[viewport.id];
if (!lastViewport || !viewport.equals(lastViewport)) {
this.setChangeFlags({viewportChanged: true});
this.setNeedsUpdate();
}
}
getPickingInfo({info, sourceLayer}) {
const {layerMap} = this.state;
const layerId = sourceLayer && sourceLayer.id;
if (layerId) {
// layerId: this.id-[scenegraph|pointcloud]-tileId
const substr = layerId.substring(this.id.length + 1);
const tileId = substr.substring(substr.indexOf('-') + 1);
info.object = layerMap[tileId] && layerMap[tileId].tile;
}
return info;
}
filterSubLayer({layer, viewport}) {
const {tile} = layer.props;
const {id: viewportId} = viewport;
return tile.selected && tile.viewportIds.includes(viewportId);
}
_updateAutoHighlight(info) {
if (info.sourceLayer) {
info.sourceLayer.updateAutoHighlight(info);
}
}
async _loadTileset(tilesetUrl) {
const {loadOptions = {}} = this.props;
// TODO: deprecate `loader` in v9.0
let loader = this.props.loader || this.props.loaders;
if (Array.isArray(loader)) {
loader = loader[0];
}
const options = {loadOptions: {...loadOptions}};
if (loader.preload) {
const preloadOptions = await loader.preload(tilesetUrl, loadOptions);
if (preloadOptions.headers) {
options.loadOptions.fetch = {
...options.loadOptions.fetch,
headers: preloadOptions.headers
};
}
Object.assign(options, preloadOptions);
}
const tilesetJson = await load(tilesetUrl, loader, options.loadOptions);
const tileset3d = new Tileset3D(tilesetJson, {
onTileLoad: this._onTileLoad.bind(this),
onTileUnload: this._onTileUnload.bind(this),
onTileLoadFail: this.props.onTileError,
...options
});
this.setState({
tileset3d,
layerMap: {}
});
this._updateTileset(this.state.activeViewports);
this.props.onTilesetLoad(tileset3d);
}
_onTileLoad(tileHeader) {
const {lastUpdatedViewports} = this.state;
this.props.onTileLoad(tileHeader);
this._updateTileset(lastUpdatedViewports);
this.setNeedsUpdate();
}
_onTileUnload(tileHeader) {
// Was cleaned up from tileset cache. We no longer need to track it.
delete this.state.layerMap[tileHeader.id];
this.props.onTileUnload(tileHeader);
}
_updateTileset(viewports) {
const {tileset3d} = this.state;
const {timeline} = this.context;
const viewportsNumber = Object.keys(viewports).length;
if (!timeline || !viewportsNumber || !tileset3d) {
return;
}
const frameNumber = tileset3d.update(Object.values(viewports));
const tilesetChanged = this.state.frameNumber !== frameNumber;
if (tilesetChanged) {
this.setState({frameNumber});
}
}
_getSubLayer(tileHeader, oldLayer) {
if (!tileHeader.content) {
return null;
}
switch (tileHeader.type) {
case TILE_TYPE.POINTCLOUD:
return this._makePointCloudLayer(tileHeader, oldLayer);
case TILE_TYPE.SCENEGRAPH:
return this._make3DModelLayer(tileHeader, oldLayer);
case TILE_TYPE.MESH:
return this._makeSimpleMeshLayer(tileHeader, oldLayer);
default:
throw new Error(`Tile3DLayer: Failed to render layer of type ${tileHeader.content.type}`);
}
}
_makePointCloudLayer(tileHeader, oldLayer) {
const {attributes, pointCount, constantRGBA, cartographicOrigin, modelMatrix} =
tileHeader.content;
const {positions, normals, colors} = attributes;
if (!positions) {
return null;
}
const data = (oldLayer && oldLayer.props.data) || {
header: {
vertexCount: pointCount
},
attributes: {
POSITION: positions,
NORMAL: normals,
COLOR_0: colors
}
};
const {pointSize, getPointColor} = this.props;
const SubLayerClass = this.getSubLayerClass('pointcloud', PointCloudLayer);
return new SubLayerClass(
{
pointSize
},
this.getSubLayerProps({
id: 'pointcloud'
}),
{
id: `${this.id}-pointcloud-${tileHeader.id}`,
tile: tileHeader,
data,
coordinateSystem: COORDINATE_SYSTEM.METER_OFFSETS,
coordinateOrigin: cartographicOrigin,
modelMatrix,
getColor: constantRGBA || getPointColor,
_offset: 0
}
);
}
_make3DModelLayer(tileHeader) {
const {gltf, instances, cartographicOrigin, modelMatrix} = tileHeader.content;
const SubLayerClass = this.getSubLayerClass('scenegraph', ScenegraphLayer);
return new SubLayerClass(
{
_lighting: 'pbr'
},
this.getSubLayerProps({
id: 'scenegraph'
}),
{
id: `${this.id}-scenegraph-${tileHeader.id}`,
tile: tileHeader,
data: instances || SINGLE_DATA,
scenegraph: gltf,
coordinateSystem: COORDINATE_SYSTEM.METER_OFFSETS,
coordinateOrigin: cartographicOrigin,
modelMatrix,
getTransformMatrix: instance => instance.modelMatrix,
getPosition: [0, 0, 0],
_offset: 0
}
);
}
_makeSimpleMeshLayer(tileHeader, oldLayer) {
const content = tileHeader.content;
const {
attributes,
indices,
modelMatrix,
cartographicOrigin,
coordinateSystem = COORDINATE_SYSTEM.METER_OFFSETS,
material,
featureIds
} = content;
const {_getMeshColor} = this.props;
const geometry =
(oldLayer && oldLayer.props.mesh) ||
new Geometry({
drawMode: GL.TRIANGLES,
attributes: getMeshGeometry(attributes),
indices
});
const SubLayerClass = this.getSubLayerClass('mesh', _MeshLayer);
return new SubLayerClass(
this.getSubLayerProps({
id: 'mesh'
}),
{
id: `${this.id}-mesh-${tileHeader.id}`,
tile: tileHeader,
mesh: geometry,
data: SINGLE_DATA,
getColor: _getMeshColor(tileHeader),
pbrMaterial: material,
modelMatrix,
coordinateOrigin: cartographicOrigin,
coordinateSystem,
featureIds,
_offset: 0
}
);
}
renderLayers() {
const {tileset3d, layerMap} = this.state;
if (!tileset3d) {
return null;
}
return tileset3d.tiles
.map(tile => {
const layerCache = (layerMap[tile.id] = layerMap[tile.id] || {tile});
let {layer} = layerCache;
if (tile.selected) {
// render selected tiles
if (!layer) {
// create layer
layer = this._getSubLayer(tile);
} else if (layerCache.needsUpdate) {
// props have changed, rerender layer
layer = this._getSubLayer(tile, layer);
layerCache.needsUpdate = false;
}
}
layerCache.layer = layer;
return layer;
})
.filter(Boolean);
}
}
function getMeshGeometry(contentAttributes) {
const attributes = {};
attributes.positions = {
...contentAttributes.positions,
value: new Float32Array(contentAttributes.positions.value)
};
if (contentAttributes.normals) {
attributes.normals = contentAttributes.normals;
}
if (contentAttributes.texCoords) {
attributes.texCoords = contentAttributes.texCoords;
}
if (contentAttributes.colors) {
attributes.colors = contentAttributes.colors;
}
if (contentAttributes.uvRegions) {
attributes.uvRegions = contentAttributes.uvRegions;
}
return attributes;
}
Tile3DLayer.layerName = 'Tile3DLayer';
Tile3DLayer.defaultProps = defaultProps;