react-map-gl/maplibre+deck.gl加载LiDAR点云图层报错排查
问题:React全局地图加载LiDAR点云时出现"Invalid LngLat object: (Nan, Nan)"错误
背景
正在开发一款基于React的3D点云全局地图应用,初期阶段正在熟悉deck.gl、react-map-gl和Maplibre的交互逻辑。已通过loaders.gl的LASWorkerLoader成功解析用户上传的LiDAR测试数据,且数据已投影至OGC:WGS84坐标系(适配MapView展示),但加载数据集时丢失WebGL上下文,触发**"Invalid LngLat object: (Nan, Nan)"**错误,无法正常加载图层。
已做验证
已尝试验证数据解析结果及格式兼容性,解析数据日志符合loaders.gl输出规范。
代码示例
import 'maplibre-gl/dist/maplibre-gl.css'; import React, {useState, useEffect, useMemo} from 'react'; import {Map, NavigationControl, Popup, useControl, FullscreenControl} from 'react-map-gl/maplibre'; import {GeoJsonLayer, ArcLayer, PointCloudLayer, MapView, OrthographicView} from 'deck.gl'; import { COORDINATE_SYSTEM } from 'deck.gl'; import {MapboxOverlay as DeckOverlay} from '@deck.gl/mapbox'; import ControlPanel, {COLOURS} from './ControlPanel'; import {LASWorkerLoader} from '@loaders.gl/las' import { load } from '@loaders.gl/core'; // source: Natural Earth const AIR_PORTS = 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_airports.geojson'; const INITIAL_VIEW_STATE = { longitude: 0, latitude: 0, zoom: 2, pitch: 0, bearing: 0 }; const MAP_STYLE = 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json'; function DeckGLOverlay(props) { const overlay = useControl(() => new DeckOverlay(props)); overlay.setProps(props); return null; } function MapOverlay() { const [lasData, setLasData] = useState(null); const [viewState, setViewState] = useState(INITIAL_VIEW_STATE); const [error, setError] = useState(null); const handleFileChange = async (event) => { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = async () => { try { const arrayBuffer = reader.result; const parsedData = await load(arrayBuffer, LASWorkerLoader); console.log('Parsed LAS data:', parsedData); setLasData(parsedData); if (parsedData.header && parsedData.header.boundingBox) { const [minX, minY, , maxX, maxY] = parsedData.header.boundingBox; const centerX = (minX + maxX) / 2; const centerY = (minY + maxY) / 2; setViewState({ ...INITIAL_VIEW_STATE, longitude: centerX, latitude: centerY, }); } } catch (error) { console.error('Error loading LAS data:', error); setError('Failed to load LAS data. Please try a different file.'); } }; reader.readAsArrayBuffer(file); }; const layers = useMemo(() => { if (!lasData) return []; return [ new PointCloudLayer({ id: 'lidar-point-cloud', data: lasData.attributes.POSITION.value, coordinateSystem: COORDINATE_SYSTEM.CARTESIAN, getPosition: d => [d[0], d[1], d[2]], getColor: d => { const intensity = lasData.attributes.intensity ? lasData.attributes.intensity.value[Math.floor(d[3])] : 255; return [intensity, intensity, intensity, 255]; }, pointSize: 1, opacity: 0.5, pickable: false, onHover: info => console.log('Hovered:', info), coordinateOrigin: lasData.header ? [ (lasData.header.boundingBox[0] + lasData.header.boundingBox[3]) / 2, (lasData.header.boundingBox[1] + lasData.header.boundingBox[4]) / 2, (lasData.header.boundingBox[2] + lasData.header.boundingBox[5]) / 2 ] : [0, 0, 0] }) ]; }, [lasData]); if (error) { return <div className="error-message">{error}</div>; } return ( <> <div> <input type="file" accept=".laz,.las" onChange={handleFileChange} /> </div> <div className='map-container'> <Map {...viewState} onMove={evt => setViewState(evt.viewState)} mapStyle={MAP_STYLE} > <DeckGLOverlay layers={layers} /> <NavigationControl position="top-right" /> <FullscreenControl position='top-right'/> </Map> <ControlPanel/> </div> </> ); } export default MapOverlay;
目标数据
使用的是USGS开源LAZ数据,为亚利桑那州科罗拉多河下游区域2018年LiDAR数据集。
目前不清楚如何正确访问解析后的数据,也不确定是否需要进行额外的坐标转换或数据处理。
解决方案
1. 修正坐标系统匹配问题
当前设置coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,但MapView默认使用WGS84地理坐标系(经纬度)。USGS的LAZ数据通常采用UTM投影坐标系,直接用笛卡尔坐标系会导致坐标超出地图范围,触发NaN错误。
- 将
coordinateSystem改为COORDINATE_SYSTEM.LNGLAT,适配地图的经纬度坐标系。 - 必须将UTM投影坐标转换为WGS84经纬度,可使用
proj4库实现转换。
2. 修复数据访问逻辑
解析后的LAS数据POSITION是一维FlatArray,当前的getPosition和getColor存在索引访问错误:
getPosition需按3个一组取点,因为一维数组中每3个元素对应一个点的X/Y/Z:
getPosition: (_, index) => [ lasData.attributes.POSITION.value[index * 3], lasData.attributes.POSITION.value[index * 3 + 1], lasData.attributes.POSITION.value[index * 3 + 2] ],
- 强度值需通过索引直接访问,而非
d[3](当前data是一维数组,每个元素是单个数值):
getColor: (_, index) => { const intensity = lasData.attributes.intensity?.value[index] || 255; return [intensity, intensity, intensity, 255]; },
3. 修正视图中心计算
原始数据的boundingBox是投影坐标,直接赋值给longitude/latitude会导致无效坐标,需先转换为经纬度:
// 安装proj4:npm install proj4 import proj4 from 'proj4'; // 在handleFileChange中添加转换逻辑 if (parsedData.header && parsedData.header.boundingBox) { const [minX, minY, , maxX, maxY] = parsedData.header.boundingBox; // 定义UTM 12N(对应目标数据区域)转WGS84的规则 proj4.defs('EPSG:32612', '+proj=utm +zone=12 +datum=WGS84 +units=m +no_defs'); // 转换中心点坐标 const [centerLon, centerLat] = proj4('EPSG:32612', 'EPSG:4326').forward([(minX+maxX)/2, (minY+maxY)/2]); setViewState({ ...INITIAL_VIEW_STATE, longitude: centerLon, latitude: centerLat, zoom: 14 // 调整到合适的缩放级别 }); }
4. 避免WebGL上下文丢失
添加updateTriggers控制图层更新时机,减少不必要的WebGL资源重建:
new PointCloudLayer({ // ...其他属性 updateTriggers: { getPosition: [lasData.attributes.POSITION.value], getColor: [lasData.attributes.intensity?.value] } })
完整修改后的核心代码片段
// 引入proj4 import proj4 from 'proj4'; // handleFileChange中的坐标转换 const handleFileChange = async (event) => { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = async () => { try { const arrayBuffer = reader.result; const parsedData = await load(arrayBuffer, LASWorkerLoader); console.log('Parsed LAS data:', parsedData); setLasData(parsedData); if (parsedData.header && parsedData.header.boundingBox) { const [minX, minY, , maxX, maxY] = parsedData.header.boundingBox; proj4.defs('EPSG:32612', '+proj=utm +zone=12 +datum=WGS84 +units=m +no_defs'); const [centerLon, centerLat] = proj4('EPSG:32612', 'EPSG:4326').forward([(minX+maxX)/2, (minY+maxY)/2]); setViewState({ ...INITIAL_VIEW_STATE, longitude: centerLon, latitude: centerLat, zoom: 14 }); } } catch (error) { console.error('Error loading LAS data:', error); setError('Failed to load LAS data. Please try a different file.'); } }; reader.readAsArrayBuffer(file); }; // layers中的PointCloudLayer配置 const layers = useMemo(() => { if (!lasData) return []; return [ new PointCloudLayer({ id: 'lidar-point-cloud', data: lasData.attributes.POSITION.value, coordinateSystem: COORDINATE_SYSTEM.LNGLAT, getPosition: (_, index) => [ lasData.attributes.POSITION.value[index * 3], lasData.attributes.POSITION.value[index * 3 + 1], lasData.attributes.POSITION.value[index * 3 + 2] ], getColor: (_, index) => { const intensity = lasData.attributes.intensity?.value[index] || 255; return [intensity, intensity, intensity, 255]; }, pointSize: 1, opacity: 0.5, pickable: false, updateTriggers: { getPosition: [lasData.attributes.POSITION.value], getColor: [lasData.attributes.intensity?.value] } }) ]; }, [lasData]);
内容的提问来源于stack exchange,提问作者Luke Booysen
相关产品推荐
相关产品推荐

