UNPKG

@deck.gl/carto

Version:

CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.

178 lines 7.21 kB
// 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