Skip to content

Commit

Permalink
feat: minimal LiDAR demo
Browse files Browse the repository at this point in the history
This demonstrator is based on iTowns 2.42 with some preview functionnalities:
- support of COPC datasets (iTowns/itowns#2110)
- support of more LAS attributes (iTowns/itowns#2262)
- support of workers for LAS-based format (i.e. EPT, COPC)
  • Loading branch information
Desplandis committed Feb 26, 2024
1 parent 7097ff5 commit 5cb3cf5
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 37 deletions.
15 changes: 11 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
},
"dependencies": {
"buffer": "^6.0.3",
"itowns": "^2.42.0",
"itowns": "npm:itowns-demo-lidar@^2.42.1",
"lil-gui": "^0.19.1",
"proj4": "^2.9.2",
"three": "^0.159.0"
},
Expand Down
133 changes: 133 additions & 0 deletions src/debug/PointCloudGUI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// @ts-ignore: added in 2.42
import { PNTS_MODE, PNTS_SHAPE, PNTS_SIZE_MODE } from 'itowns';
import GUI from 'lil-gui';

import type { View, PointCloudLayer } from 'itowns';

// TODO: export type or define our own gradients here
const GRADIENTS = [
'SPECTRAL',
'PLASMA',
'YELLOW_GREEN',
'VIRIDIS',
'INFERNO',
'GRAYSCALE',
'TURBO',
'RAINBOW',
'CONTOUR',
];

interface PointCloudGUIOptions {
autoPlace?: boolean;
container?: HTMLElement;
width?: number;
title?: string;
closeFolders?: boolean;
injectStyles?: boolean;
touchStyles?: number;
parent?: GUI;
}

export class PointCloudGUI extends GUI {
pointUI: GUI;
attributeUI: GUI;

constructor(view: View, layer: PointCloudLayer, options: PointCloudGUIOptions) {
super(options);

const material = layer.material;

const update = () => view.notifyChange(layer, true);
this.add(layer, 'visible')
.name('Visible')
.onChange(update);
this.add(layer, 'opacity', 0, 1)
.name('Opacity')
.onChange(update);
this.add(layer, 'pointBudget', 0, 12000000)
.step(500000)
.name('Point budget')
.onChange(update);
this.add(layer, 'sseThreshold')
.name('SSE threshold')
.onChange(update);


// Point styling
this.pointUI = this.addFolder('Points');
const addPointSize = (obj: object, prop: string, name: string) =>
this.pointUI.add(obj, prop, 0, 15)
.step(0.1)
.name(name)
.onChange(update);

this.pointUI.add(material, 'sizeMode', PNTS_SIZE_MODE)
.name('Size mode')
.onChange(update);
this.pointUI.add(material, 'shape', PNTS_SHAPE)
.name('Shape')
.onChange(update);
addPointSize(layer, 'pointSize', 'Size');
addPointSize(material, 'minAttenuatedSize', 'Min size');
addPointSize(material, 'maxAttenuatedSize', 'Max size');


// Attribute styling
this.attributeUI = this.addFolder('Attributes');
const addUint16Property = (obj: object, prop: string) =>
this.attributeUI.add(obj, prop, 0, 65535)
.step(1)
.onChange(update);

const mode = this.attributeUI.add(material, 'mode', PNTS_MODE)
.name('Mode')
.onChange(update);

const gradient = this.attributeUI.add(material, 'gradient', GRADIENTS)
.name('Gradient')
.hide()
.onChange(update);

const minIntensity = addUint16Property(layer, 'minIntensityRange')
.name('Min intensity')
.hide();

const maxIntensity = addUint16Property(layer, 'maxIntensityRange')
.name('Max intensity')
.hide();

const minScanAngle = this.attributeUI.add(layer, 'minAngleRange', 0, 90)
.name('Min scan angle')
.hide();

const maxScanAngle = this.attributeUI.add(layer, 'maxAngleRange', 0, 90)
.name('Max scan angle')
.hide();

mode.onFinishChange((event: PNTS_MODE) => {
gradient.hide();
minIntensity.hide();
maxIntensity.hide();
minScanAngle.hide();
maxScanAngle.hide();
switch (event) {
case PNTS_MODE.INTENSITY:
gradient.show();
minIntensity.show();
maxIntensity.show();
return;
// @ts-ignore: not released yet
case PNTS_MODE.ELEVATION:
// TODO: minElevation/maxElevation when layer ready
gradient.show();
return;
// @ts-ignore: not released yet
case PNTS_MODE.SCAN_ANGLE:
minScanAngle.show();
maxScanAngle.show();
return;
}
});

}
}
123 changes: 91 additions & 32 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,94 @@
import * as itowns from 'itowns';
import { Vector3 } from 'three';
import { View, PlanarControls, PNTS_SHAPE } from 'itowns';
// @ts-ignore: COPCSource, COPCLayer not released yet
import { CopcSource, CopcLayer } from 'itowns';
import GUI from 'lil-gui';

import { PointCloudGUI } from './debug/PointCloudGUI';

import type { PerspectiveCamera } from 'three';
import type { PointCloudLayer } from 'itowns';

// TODO: add types not officially released in this repository

const uri = new URL(window.location.href);
const gui = new GUI();

// Get our `<div id="viewerId">` element. When creating a `View`, a canvas will
// be appended to this element.
const viewerDiv = document.getElementById('viewerDiv') as HTMLDivElement;
const view = new View('EPSG:4326', viewerDiv);

// @ts-ignore: argument not in type definitions
const controls = new PlanarControls(view);
view.mainLoop.gfxEngine.renderer.setClearColor(0xdddddd);

let layer: PointCloudLayer; // COPCLayer


function onLayerReady(layer: PointCloudLayer) {
const camera = view.camera.camera3D as PerspectiveCamera;

const lookAt = new Vector3();
const size = new Vector3();
const root = layer.root;

root.bbox.getSize(size);
root.bbox.getCenter(lookAt);

camera.far = 2.0 * size.length();

controls.groundLevel = root.bbox.min.z;
const position = root.bbox.min.clone().add(
size.multiply({ x: 1, y: 1, z: size.x / size.z }),
);

camera.position.copy(position);
camera.lookAt(lookAt);
camera.updateProjectionMatrix();

view.notifyChange(camera);
}


function setUrl(url: string) {
if (!url) return;

uri.searchParams.set('copc', url);
history.replaceState(null, '', `?${uri.searchParams.toString()}`);

load(url);
}


function load(url: string) {
// @ts-ignore: not released yet
const source = new CopcSource({ url });

if (layer) {
view.removeLayer('COPC');
view.notifyChange();
layer.delete();
}

// @ts-ignore: not released yet
layer = new CopcLayer('COPC', {
source,
crs: view.referenceCrs,
sseThreshold: 1,
pointBudget: 3500000,
material: {
minAttenuatedSize: 2,
maxAttenuatedSize: 5,
shape: PNTS_SHAPE.SQUARE,
},
});
view.addLayer(layer).then(onLayerReady);
new PointCloudGUI(view, layer, {
title: layer.id,
parent: gui
});
}

// Define an initial camera position
const placement = {
coord: new itowns.Coordinates('EPSG:4326', 2.351323, 48.856712),
range: 25000000,
};

// Create an empty Globe View
const view = new itowns.GlobeView(viewerDiv, placement);

// Declare your data source configuration. In this context, those are the
// parameters used in the WMTS requests.
const orthoConfig = {
'url': 'https://data.geopf.fr/wmts',
'crs': 'EPSG:3857',
'format': 'image/jpeg',
'name': 'ORTHOIMAGERY.ORTHOPHOTOS',
'tileMatrixSet': 'PM',
};

// Instantiate the WMTS source of your imagery layer.
const imagerySource = new itowns.WMTSSource(orthoConfig);

// Create your imagery layer
const imageryLayer = new itowns.ColorLayer('imagery', {
source: imagerySource,
});

// Add it to source view!
view.addLayer(imageryLayer);
const copcParams = uri.searchParams.get('copc');
if (copcParams) {
setUrl(copcParams);
}

0 comments on commit 5cb3cf5

Please sign in to comment.