Luke Booysen
Luke Booysen

Reputation: 11

How do I load a pointcloud layer into a MapView using the react-map-gl/maplibre and deck.gl libraries?

I am busy writing a react app with the goal of having a global map that can display 3D point-clouds. Currently I am in the very beginning stages and I am still getting to grips with the deck-gl, react-map-gl and Maplibre libraries and how they interact in react code. I have gotten to the point where I can successfully parse some test LiDAR data using the LASWorkerLoader from the loaders.gl library from user input. I have it projected to the OGC:WGS84 projection as, as far as I understand, it needs to be in that projection to be able to display it in a MapView. However, it is failing to load the dataset and loses WebGL context whenever I try to load the dataset. It gives a Invalid LngLat object: (Nan, Nan) error when trying to load the layer.

I have tried to determine if the data has been parsed correctly and whether it is in a format that the loader can parse. I have logged the parsed data and the response can be seen in the image.LASMesh parsed object log

This is the code I've tried to use:

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 http://www.naturalearthdata.com/ via geojson.xyz
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;

It is suppose to display the opensource laz data of the USGS_LPC_AZ_LowerColoradoRiver_2018_B18_w1561n1557 found at https://rockyweb.usgs.gov/vdelivery/Datasets/Staged/Elevation/LPC/Projects/AZ_LowerColoradoRiver_2018_B18/AZ_LowerColoradoRiver_B1_2018/LAZ/. I am quite stuck on how to correctly access the parsed data and I don't know whether I need to apply further transformations.

Upvotes: 1

Views: 111

Answers (0)

Related Questions