@deck.gl/carto
Version:
CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.
178 lines • 7.21 kB
JavaScript
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { registerLoaders } from '@loaders.gl/core';
import CartoPropertiesTileLoader from "./schema/carto-properties-tile-loader.js";
import CartoVectorTileLoader from "./schema/carto-vector-tile-loader.js";
registerLoaders([CartoPropertiesTileLoader, CartoVectorTileLoader]);
import { ClipExtension, CollisionFilterExtension } from '@deck.gl/extensions';
import { MVTLayer, _getURLFromTemplate } from '@deck.gl/geo-layers';
import { GeoJsonLayer } from '@deck.gl/layers';
import { TilejsonPropType, mergeLoadOptions, mergeBoundaryData } from "./utils.js";
import { DEFAULT_TILE_SIZE } from "../constants.js";
import { createPointsFromLines, createPointsFromPolygons } from "./label-utils.js";
import { createEmptyBinary } from "../utils.js";
import PointLabelLayer from "./point-label-layer.js";
const defaultProps = {
...MVTLayer.defaultProps,
autoLabels: false,
data: TilejsonPropType,
dataComparator: TilejsonPropType.equal,
tileSize: DEFAULT_TILE_SIZE
};
// @ts-ignore
class VectorTileLayer extends MVTLayer {
constructor(...propObjects) {
// Force externally visible props type, as it is not possible modify via extension
// @ts-ignore
super(...propObjects);
}
initializeState() {
super.initializeState();
this.setState({ binary: true });
}
updateState(parameters) {
const { props } = parameters;
if (props.data) {
super.updateState(parameters);
const formatTiles = new URL(props.data.tiles[0]).searchParams.get('formatTiles');
const mvt = formatTiles === 'mvt';
this.setState({ mvt });
}
}
getLoadOptions() {
const tileJSON = this.props.data;
return mergeLoadOptions(super.getLoadOptions(), {
fetch: { headers: { Authorization: `Bearer ${tileJSON.accessToken}` } },
gis: { format: 'binary' } // Use binary for MVT loading
});
}
/* eslint-disable camelcase */
async getTileData(tile) {
const tileJSON = this.props.data;
const { tiles, properties_tiles } = tileJSON;
const url = _getURLFromTemplate(tiles, tile);
if (!url) {
return Promise.reject('Invalid URL');
}
const loadOptions = this.getLoadOptions();
const { fetch } = this.props;
const { signal } = tile;
// Fetch geometry and attributes separately
const geometryFetch = fetch(url, { propName: 'data', layer: this, loadOptions, signal });
if (!properties_tiles) {
return await geometryFetch;
}
const propertiesUrl = _getURLFromTemplate(properties_tiles, tile);
if (!propertiesUrl) {
return Promise.reject('Invalid properties URL');
}
const attributesFetch = fetch(propertiesUrl, {
propName: 'data',
layer: this,
loadOptions,
signal
});
const [geometry, attributes] = await Promise.all([geometryFetch, attributesFetch]);
if (!geometry)
return null;
return attributes ? mergeBoundaryData(geometry, attributes) : geometry;
}
/* eslint-enable camelcase */
renderSubLayers(props) {
if (props.data === null) {
return null;
}
const tileBbox = props.tile.bbox;
const subLayers = [];
const defaultToPointLabelLayer = {
'points-text': {
type: PointLabelLayer,
...props?._subLayerProps?.['points-text'],
extensions: [
new CollisionFilterExtension(),
...(props.extensions || []),
...(props?._subLayerProps?.['points-text']?.extensions || [])
]
}
};
if (this.state.mvt) {
subLayers.push(super.renderSubLayers(props));
}
else {
const { west, south, east, north } = tileBbox;
const extensions = [new ClipExtension(), ...(props.extensions || [])];
const clipProps = {
clipBounds: [west, south, east, north]
};
const applyClipExtensionToSublayerProps = (subLayerId) => {
return {
[subLayerId]: {
...clipProps,
...props?._subLayerProps?.[subLayerId],
extensions: [...extensions, ...(props?._subLayerProps?.[subLayerId]?.extensions || [])]
}
};
};
const subLayerProps = {
...props,
data: { ...props.data, tileBbox },
autoHighlight: false,
// Do not perform clipping on points (#9059)
_subLayerProps: {
...props._subLayerProps,
...defaultToPointLabelLayer,
...applyClipExtensionToSublayerProps('polygons-fill'),
...applyClipExtensionToSublayerProps('polygons-stroke'),
...applyClipExtensionToSublayerProps('linestrings')
}
};
subLayers.push(new GeoJsonLayer(subLayerProps));
}
// Add labels
if (subLayers[0] && props.autoLabels) {
const labelData = createEmptyBinary();
if (props.data.lines && props.data.lines.positions.value.length > 0) {
labelData.points = createPointsFromLines(props.data.lines, typeof props.autoLabels === 'object' ? props.autoLabels.uniqueIdProperty : undefined);
}
if (props.data.polygons && props.data.polygons.positions.value.length > 0) {
labelData.points = createPointsFromPolygons(props.data.polygons, tileBbox, props);
}
subLayers.push(subLayers[0].clone({
id: `${props.id}-labels`,
data: labelData,
pickable: false,
autoHighlight: false
}));
}
return subLayers;
}
renderLayers() {
const layers = super.renderLayers();
if (!this.props.autoLabels) {
return layers;
}
// Sort layers so that label layers are rendered after the main layer
const validLayers = layers.flat().filter(Boolean);
validLayers.sort((a, b) => {
const aHasLabel = a.id.includes('labels');
const bHasLabel = b.id.includes('labels');
if (aHasLabel && !bHasLabel)
return 1;
if (!aHasLabel && bHasLabel)
return -1;
return 0;
});
return validLayers.map(l => l.id.includes('labels') ? l.clone({ highlightedObjectIndex: -1 }) : l);
}
_isWGS84() {
// CARTO binary tile coordinates are [lng, lat], not tile-relative like MVT.
if (this.state.mvt)
return super._isWGS84();
return true;
}
}
VectorTileLayer.layerName = 'VectorTileLayer';
VectorTileLayer.defaultProps = defaultProps;
export default VectorTileLayer;
//# sourceMappingURL=vector-tile-layer.js.map