@deck.gl/geo-layers
Version:
deck.gl layers supporting geospatial use cases and GIS formats
414 lines (353 loc) • 10.3 kB
JavaScript
import { log } from '@deck.gl/core';
import { Matrix4 } from '@math.gl/core';
import { MVTWorkerLoader } from '@loaders.gl/mvt';
import { binaryToGeojson } from '@loaders.gl/gis';
import { COORDINATE_SYSTEM } from '@deck.gl/core';
import { ClipExtension } from '@deck.gl/extensions';
import TileLayer from '../tile-layer/tile-layer';
import { getURLFromTemplate, isURLTemplate } from '../tile-layer/utils';
import { transform } from './coordinate-transform';
import findIndexBinary from './find-index-binary';
import { GeoJsonLayer } from '@deck.gl/layers';
const WORLD_SIZE = 512;
const defaultProps = { ...GeoJsonLayer.defaultProps,
uniqueIdProperty: {
type: 'string',
value: ''
},
highlightedFeatureId: null,
loaders: [MVTWorkerLoader],
binary: true
};
export default class MVTLayer extends TileLayer {
initializeState() {
super.initializeState();
const binary = this.context.viewport.resolution !== undefined ? false : this.props.binary;
this.setState({
binary,
data: null,
tileJSON: null
});
}
get isLoaded() {
return this.state.data && this.state.tileset && super.isLoaded;
}
updateState({
props,
oldProps,
context,
changeFlags
}) {
if (changeFlags.dataChanged) {
this._updateTileData();
}
if (this.state.data) {
super.updateState({
props,
oldProps,
context,
changeFlags
});
this._setWGS84PropertyForTiles();
}
const {
highlightColor
} = props;
if (highlightColor !== oldProps.highlightColor && Array.isArray(highlightColor)) {
this.setState({
highlightColor
});
}
}
async _updateTileData() {
let {
data
} = this.props;
let tileJSON = null;
if (typeof data === 'string' && !isURLTemplate(data)) {
const {
onDataLoad,
fetch
} = this.props;
this.setState({
data: null,
tileJSON: null
});
try {
tileJSON = await fetch(data, {
propName: 'data',
layer: this,
loaders: []
});
} catch (error) {
this.raiseError(error, 'loading TileJSON');
data = null;
}
if (onDataLoad) {
onDataLoad(tileJSON);
}
} else if (data.tilejson) {
tileJSON = data;
}
if (tileJSON) {
data = tileJSON.tiles;
}
this.setState({
data,
tileJSON
});
}
_getTilesetOptions(props) {
const opts = super._getTilesetOptions(props);
const {
tileJSON
} = this.state;
if (tileJSON) {
if (Number.isFinite(tileJSON.minzoom) && tileJSON.minzoom > props.minZoom) {
opts.minZoom = tileJSON.minzoom;
}
if (Number.isFinite(tileJSON.maxzoom) && (!Number.isFinite(props.maxZoom) || tileJSON.maxzoom < props.maxZoom)) {
opts.maxZoom = tileJSON.maxzoom;
}
}
return opts;
}
renderLayers() {
if (!this.state.data) return null;
return super.renderLayers();
}
getTileData(tile) {
var _loadOptions;
const url = getURLFromTemplate(this.state.data, tile);
if (!url) {
return Promise.reject('Invalid URL');
}
let loadOptions = this.getLoadOptions();
const {
binary
} = this.state;
const {
fetch
} = this.props;
const {
signal,
x,
y,
z
} = tile;
loadOptions = { ...loadOptions,
mimeType: 'application/x-protobuf',
mvt: { ...((_loadOptions = loadOptions) === null || _loadOptions === void 0 ? void 0 : _loadOptions.mvt),
coordinates: this.context.viewport.resolution ? 'wgs84' : 'local',
tileIndex: {
x,
y,
z
}
},
gis: binary ? {
format: 'binary'
} : {}
};
return fetch(url, {
propName: 'data',
layer: this,
loadOptions,
signal
});
}
renderSubLayers(props) {
const {
tile
} = props;
const worldScale = Math.pow(2, tile.z);
const xScale = WORLD_SIZE / worldScale;
const yScale = -xScale;
const xOffset = WORLD_SIZE * tile.x / worldScale;
const yOffset = WORLD_SIZE * (1 - tile.y / worldScale);
const modelMatrix = new Matrix4().scale([xScale, yScale, 1]);
props.autoHighlight = false;
if (!this.context.viewport.resolution) {
props.modelMatrix = modelMatrix;
props.coordinateOrigin = [xOffset, yOffset, 0];
props.coordinateSystem = COORDINATE_SYSTEM.CARTESIAN;
props.extensions = [...(props.extensions || []), new ClipExtension()];
}
const subLayers = super.renderSubLayers(props);
if (this.state.binary && !(subLayers instanceof GeoJsonLayer)) {
log.warn('renderSubLayers() must return GeoJsonLayer when using binary:true')();
}
return subLayers;
}
_updateAutoHighlight(info) {
const {
uniqueIdProperty
} = this.props;
const {
hoveredFeatureId,
hoveredFeatureLayerName
} = this.state;
const hoveredFeature = info.object;
let newHoveredFeatureId;
let newHoveredFeatureLayerName;
if (hoveredFeature) {
newHoveredFeatureId = getFeatureUniqueId(hoveredFeature, uniqueIdProperty);
newHoveredFeatureLayerName = getFeatureLayerName(hoveredFeature);
}
let {
highlightColor
} = this.props;
if (typeof highlightColor === 'function') {
highlightColor = highlightColor(info);
}
if (hoveredFeatureId !== newHoveredFeatureId || hoveredFeatureLayerName !== newHoveredFeatureLayerName) {
this.setState({
highlightColor,
hoveredFeatureId: newHoveredFeatureId,
hoveredFeatureLayerName: newHoveredFeatureLayerName
});
}
}
getPickingInfo(params) {
const info = super.getPickingInfo(params);
const isWGS84 = this.context.viewport.resolution;
if (this.state.binary && info.index !== -1) {
const {
data
} = params.sourceLayer.props;
info.object = binaryToGeojson(data, {
globalFeatureId: info.index
});
}
if (info.object && !isWGS84) {
info.object = transformTileCoordsToWGS84(info.object, info.tile.bbox, this.context.viewport);
}
return info;
}
getSubLayerPropsByTile(tile) {
return {
highlightedObjectIndex: this.getHighlightedObjectIndex(tile),
highlightColor: this.state.highlightColor
};
}
getHighlightedObjectIndex(tile) {
const {
hoveredFeatureId,
hoveredFeatureLayerName,
binary
} = this.state;
const {
uniqueIdProperty,
highlightedFeatureId
} = this.props;
const data = tile.content;
const isHighlighted = isFeatureIdDefined(highlightedFeatureId);
const isFeatureIdPresent = isFeatureIdDefined(hoveredFeatureId) || isHighlighted;
if (!isFeatureIdPresent) {
return -1;
}
const featureIdToHighlight = isHighlighted ? highlightedFeatureId : hoveredFeatureId;
if (Array.isArray(data)) {
return data.findIndex(feature => {
const isMatchingId = getFeatureUniqueId(feature, uniqueIdProperty) === featureIdToHighlight;
const isMatchingLayer = isHighlighted || getFeatureLayerName(feature) === hoveredFeatureLayerName;
return isMatchingId && isMatchingLayer;
});
} else if (data && binary) {
return findIndexBinary(data, uniqueIdProperty, featureIdToHighlight, isHighlighted ? '' : hoveredFeatureLayerName);
}
return -1;
}
_pickObjects(maxObjects) {
const {
deck,
viewport
} = this.context;
const width = viewport.width;
const height = viewport.height;
const x = viewport.x;
const y = viewport.y;
const layerIds = [this.id];
return deck.pickObjects({
x,
y,
width,
height,
layerIds,
maxObjects
});
}
getRenderedFeatures(maxFeatures = null) {
const features = this._pickObjects(maxFeatures);
const featureCache = new Set();
const renderedFeatures = [];
for (const f of features) {
const featureId = getFeatureUniqueId(f.object, this.props.uniqueIdProperty);
if (featureId === undefined) {
renderedFeatures.push(f.object);
} else if (!featureCache.has(featureId)) {
featureCache.add(featureId);
renderedFeatures.push(f.object);
}
}
return renderedFeatures;
}
_setWGS84PropertyForTiles() {
const propName = 'dataInWGS84';
const {
tileset
} = this.state;
tileset.selectedTiles.forEach(tile => {
if (!tile.hasOwnProperty(propName)) {
Object.defineProperty(tile, propName, {
get: () => {
if (!tile.content) {
return null;
}
if (this.state.binary && Array.isArray(tile.content) && !tile.content.length) {
return [];
}
if (tile._contentWGS84 === undefined) {
const content = this.state.binary ? binaryToGeojson(tile.content) : tile.content;
tile._contentWGS84 = content.map(feature => transformTileCoordsToWGS84(feature, tile.bbox, this.context.viewport));
}
return tile._contentWGS84;
}
});
}
});
}
}
function getFeatureUniqueId(feature, uniqueIdProperty) {
if (uniqueIdProperty) {
return feature.properties[uniqueIdProperty];
}
if ('id' in feature) {
return feature.id;
}
return undefined;
}
function getFeatureLayerName(feature) {
var _feature$properties;
return ((_feature$properties = feature.properties) === null || _feature$properties === void 0 ? void 0 : _feature$properties.layerName) || null;
}
function isFeatureIdDefined(value) {
return value !== undefined && value !== null && value !== '';
}
function transformTileCoordsToWGS84(object, bbox, viewport) {
const feature = { ...object,
geometry: {
type: object.geometry.type
}
};
Object.defineProperty(feature.geometry, 'coordinates', {
get: () => {
const wgs84Geom = transform(object.geometry, bbox, viewport);
return wgs84Geom.coordinates;
}
});
return feature;
}
MVTLayer.layerName = 'MVTLayer';
MVTLayer.defaultProps = defaultProps;
//# sourceMappingURL=mvt-layer.js.map