deck.gl中IconLayer在3D地图导航时无法锚定指定经纬度
问题描述
我使用deck.gl的Tile3DLayer渲染3D地图,同时叠加IconLayer实现图标覆盖,期望图标在地图导航时始终锚定到特定建筑。但平移或缩放地图时,图标并未随3D图层同步移动,似乎未考虑Tile3DLayer的3D变换,无法保持在指定经纬度位置。
已做配置:给IconLayer设置了billboard: true确保图标始终朝向相机,图标放置在[INITIAL_VIEW_STATE.longitude, INITIAL_VIEW_STATE.latitude]位置。
代码示例
import React, { useState } from 'react'; import { scaleLinear } from 'd3-scale'; import DeckGL from '@deck.gl/react'; import { GeoJsonLayer, IconLayer } from '@deck.gl/layers'; import { Tile3DLayer } from '@deck.gl/geo-layers'; import { DataFilterExtension, _TerrainExtension as TerrainExtension } from '@deck.gl/extensions'; const ICON_MAPPING = { marker: { x: 0, y: 0, width: 128, height: 128, mask: false } }; const ICON_ATLAS = 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/icon-atlas.png'; const GOOGLE_MAPS_API_KEY = 'AIzaSyBYchwzbcKzBDjxrSAEcxOCcDWLQKD5FAx4s'; // eslint-disable-line const TILESET_URL = 'https://tile.googleapis.com/v1/3dtiles/root.json'; export const COLORS = [ [254, 235, 226], [251, 180, 185], [247, 104, 161], [197, 27, 138], [122, 1, 119] ]; const colorScale = scaleLinear().clamp(true).domain([0, 50, 100, 200, 300]).range(COLORS); const INITIAL_VIEW_STATE = { latitude: 48.8681586, longitude: 2.30278, zoom: 16, minZoom: 14, maxZoom: 16.5, bearing: 90, pitch: 60 }; const BUILDING_DATA = 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/google-3d-tiles/buildings.geojson'; export default function App({ data = TILESET_URL, distance = 0, opacity = 0.2 }) { const [credits, setCredits] = useState(''); const iconLayer = new IconLayer({ id: 'icon-layer', data: [{ coordinates: [INITIAL_VIEW_STATE.longitude, INITIAL_VIEW_STATE.latitude], icon: 'marker' }], pickable: true, iconAtlas: ICON_ATLAS, iconMapping: ICON_MAPPING, getIcon: d => d.icon, getSize: 60, sizeScale: 0.5, getPosition: d => d.coordinates, getColor: [255, 0, 0, 255], getAngle: 0, billboard: true, zIndex: 10, parameters: { depthTest: false } }) const layers = [ new Tile3DLayer({ id: 'google-3d-tiles', data: TILESET_URL, onTilesetLoad: tileset3d => { tileset3d.options.onTraversalComplete = selectedTiles => { const uniqueCredits = new Set(); selectedTiles.forEach(tile => { const { copyright } = tile.content.gltf.asset; copyright.split(';').forEach(uniqueCredits.add, uniqueCredits); }); setCredits([...uniqueCredits].join('; ')); return selectedTiles; }; }, loadOptions: { fetch: { headers: { 'X-GOOG-API-KEY': GOOGLE_MAPS_API_KEY } } }, operation: 'terrain+draw' }), new GeoJsonLayer({ id: 'buildings', data: BUILDING_DATA, extensions: [new DataFilterExtension({ filterSize: 1 }), new TerrainExtension()], stroked: false, filled: true, getFillColor: ({ properties }) => colorScale(properties.distance_to_nearest_tree), opacity, getFilterValue: f => f.properties.distance_to_nearest_tree, filterRange: [distance, 500] }), iconLayer ]; return ( <div style={{ position: 'relative', width: '300px', height: '300px', margin: 'auto' }}> <DeckGL style={{ backgroundColor: '#061714' }} initialViewState={INITIAL_VIEW_STATE} controller={{ touchRotate: true, inertia: 250 }} layers={layers} /> </div> ); }
解决方案
问题根源在于IconLayer默认将图标放置在海拔0的平面,未适配Tile3DLayer中3D建筑的高度与空间变换,导致视角变化时出现偏移。以下是具体修复步骤:
添加
TerrainExtension适配3D地形/建筑高度
给IconLayer引入地形扩展,让图标自动贴合3D表面的海拔高度,修改后的IconLayer配置如下:const iconLayer = new IconLayer({ id: 'icon-layer', data: [{ coordinates: [INITIAL_VIEW_STATE.longitude, INITIAL_VIEW_STATE.latitude], icon: 'marker' }], pickable: true, iconAtlas: ICON_ATLAS, iconMapping: ICON_MAPPING, getIcon: d => d.icon, getSize: 60, sizeScale: 0.5, getPosition: d => d.coordinates, getColor: [255, 0, 0, 255], getAngle: 0, billboard: true, zIndex: 10, extensions: [new TerrainExtension()], // 加入地形扩展,自动适配3D高度 getElevation: d => 0, // 若需锚定建筑顶部,可替换为对应建筑的高度值 parameters: { depthTest: true // 打开深度测试,让图标在3D空间中正确排序 } });确保坐标精准匹配
验证图标的经纬度是否对应目标建筑的准确位置,可从BUILDING_DATA的GeoJSON数据中提取目标建筑的坐标,替换当前硬编码的初始视角坐标,避免因坐标偏差导致的错位。动态获取建筑高度(可选)
若需要精准锚定到建筑顶部,可通过Tile3DLayer的onTilesetLoad回调遍历3D瓦片数据,提取目标建筑的高度信息,将其赋值给getElevation属性。
内容的提问来源于stack exchange,提问作者pam
相关产品推荐
相关产品推荐

