Measure and section a point cloud
Measure distances between scan points and cut through a site survey to inspect ground, buildings and vegetation.
Open in new tab
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Measure and section a point cloud</title>
<meta name="description" content="Measure distances between scan points and cut through a site survey to inspect ground, buildings and vegetation.">
<style>
html, body { height: 100%; margin: 0; }
#example-viewer-mount { position: fixed; inset: 0; }
bim-grid#ifc-viewer { display: block; position: absolute; inset: 0; width: 100%; height: 100%; min-width: 0; min-height: 0; }
.example-ui { position: fixed; z-index: 10; box-sizing: border-box; color: #253041; font: 12px/1.4 system-ui, sans-serif; }
.example-ui.example-task { right: 10px; bottom: 64px; width: 224px; max-width: calc(100vw - 64px); display: grid; gap: 6px; padding: 10px; background: #fff; border: 1px solid #dfe3e8; border-radius: 4px; box-shadow: 0 1px 5px #14203310; }
.example-ui :is(h2, p) { margin: 0; }
.example-ui h2 { font-size: 13px; line-height: 1.3; font-weight: 650; }
.example-ui p { color: #5a6575; }
.example-ui button { box-sizing: border-box; min-width: 0; max-width: 100%; font: inherit; padding: 5px 7px; border: 1px solid #cbd2dc; border-radius: 3px; background: #fff; color: #253041; cursor: pointer; }
.example-ui button:hover { background: #f3f6fa; }
.example-ui button:focus-visible { outline: 2px solid #3568ba; outline-offset: 2px; }
.example-ui [hidden] { display: none; }
.example-ui.example-credit { right: 8px; bottom: 32px; width: max-content; max-width: calc(100vw - 64px); padding: 2px 5px; background: #ffffffdf; border-radius: 2px; font-size: 9px; text-align: right; overflow-wrap: anywhere; }
.example-ui.example-credit a { color: #566171; }
@media (max-width: 600px) {
.example-ui.example-task { right: 8px; padding: 8px; }
.example-ui button { min-height: 30px; }
}
</style>
<!-- generated:example-seo:start -->
<link rel="canonical" href="https://viewer.flinker.app/examples/autzen/index.html">
<meta name="robots" content="index,follow">
<meta property="og:title" content="Measure and section a point cloud">
<meta property="og:description" content="Measure distances between scan points and cut through a site survey to inspect ground, buildings and vegetation.">
<meta property="og:url" content="https://viewer.flinker.app/examples/autzen/index.html"><meta property="og:type" content="article">
<meta property="og:site_name" content="Flinker IFC Viewer"><meta property="og:image" content="https://viewer.flinker.app/examples/autzen/thumbnail.webp">
<meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="Measure and section a point cloud">
<meta name="twitter:description" content="Measure distances between scan points and cut through a site survey to inspect ground, buildings and vegetation."><meta name="twitter:image" content="https://viewer.flinker.app/examples/autzen/thumbnail.webp">
<script type="application/ld+json">{"@context":"https://schema.org","@type":"TechArticle","headline":"Measure and section a point cloud","description":"Measure distances between scan points and cut through a site survey to inspect ground, buildings and vegetation.","url":"https://viewer.flinker.app/examples/autzen/index.html","mainEntityOfPage":"https://viewer.flinker.app/examples/autzen/index.html","image":"https://viewer.flinker.app/examples/autzen/thumbnail.webp","inLanguage":"en","author":{"@type":"Organization","name":"Flinker","url":"https://www.flinker.app/"}}</script>
<!-- generated:example-seo:end -->
<script type="application/json" id="example-data">
{
"category": "Reality capture",
"formats": [
"LAZ"
],
"capabilities": [
"LiDAR",
"Classified source",
"Large scans",
"Measurements",
"Sections"
],
"audience": "BIM workflow",
"order": 15,
"viewer": {
"assetIds": [
"autzen-classified"
],
"presentation": "default"
},
"assets": [
{
"id": "autzen-classified",
"name": "autzen-classified.laz",
"format": "LAZ",
"bytes": 74416814,
"url": "https://media.githubusercontent.com/media/PDAL/data/ce0024257c573526389c4db9ab26e82739b8aaa9/autzen/autzen-classified.laz",
"sourceUrl": "https://github.com/PDAL/data/blob/ce0024257c573526389c4db9ab26e82739b8aaa9/autzen/autzen-classified.laz",
"license": "CC BY 4.0",
"licenseUrl": "https://github.com/PDAL/data/blob/ce0024257c573526389c4db9ab26e82739b8aaa9/LICENSE",
"noticeUrl": "https://viewer.flinker.app/examples/autzen/index.html#example-notices",
"attribution": "Autzen Stadium LiDAR © Aaron Reyna, Watershed Sciences, Inc. Classification by Max Sampson, Hobu, Inc. (2021); distributed by PDAL.",
"notes": "Original file; contents unchanged.",
"points": 10653336
}
]
}
</script>
</head>
<body>
<main id="example-viewer-mount" aria-label="Model viewer"><bim-grid id="ifc-viewer"></bim-grid></main>
<aside class="example-ui example-task" aria-label="Measure and section the survey">
<h2>Measure and section the survey</h2>
<p>On the left toolbar, open <strong>Measure</strong> (ruler) → <strong>Add measurement</strong>. With <strong>Measure Mode</strong> set to <strong>Length</strong>, pick two scan points.</p>
<p>To cut the scan, open <strong>Sections</strong> (scissors) → <strong>Add clipping plane</strong>, click a surface, then drag the plane.</p>
<button id="sample-retry" type="button" hidden>Retry sample</button>
</aside>
<div class="example-ui example-credit" aria-label="Sample source and license"><a href="https://github.com/PDAL/data/blob/ce0024257c573526389c4db9ab26e82739b8aaa9/autzen/autzen-classified.laz" target="_blank" rel="noopener noreferrer">© Aaron Reyna / WSI · Classification: Max Sampson / Hobu</a> · <a href="https://github.com/PDAL/data/blob/ce0024257c573526389c4db9ab26e82739b8aaa9/LICENSE" target="_blank" rel="noopener noreferrer">CC BY 4.0</a></div>
<template id="example-guide">
<h2 data-example-title>Measure and section a point cloud</h2>
<p data-example-description>Measure distances between scan points and cut through a site survey to inspect ground, buildings and vegetation.</p>
<h3>Measure site access</h3><ol><li>Find a street or approach beside the stadium and zoom to its edges.</li><li>On the left toolbar, open Measure (ruler) → Add measurement. Set Measure Mode to Length and pick two survey points to measure a route or its width.</li><li>Measure a second route to compare the available space.</li></ol>
<h3>Inspect a scan section</h3><ol><li>Find the stadium edge and its surrounding ground.</li><li>On the left toolbar, open Sections (scissors) → Add clipping plane, then click a surface to cut through the scan and examine ground and elevated features.</li><li>Drag the section to compare nearby buildings and vegetation.</li></ol>
<h3>Developer integration</h3><p>Load the original classified LAZ survey once with <code>viewer.add()</code>. The SDK provides point picking, measurement and section controls for the same scan. Source classification is preserved. The survey was captured in 2010 and classified by Max Sampson, Hobu, Inc. in 2021; these exercises do not represent a current site access assessment.</p><p>Copy this folder and serve it over HTTP, for example <code>python -m http.server 8080</code>. Open <code>http://localhost:8080/</code>; the SDK and sample downloads require internet access.</p>
</template>
<template id="example-notices">
<h2>Sources and license notices</h2><p><a href="https://github.com/PDAL/data/blob/ce0024257c573526389c4db9ab26e82739b8aaa9/autzen/autzen-classified.laz" target="_blank" rel="noopener noreferrer">© Aaron Reyna, Watershed Sciences, Inc. · Classification: Max Sampson, Hobu, Inc. (2021)</a> · <a href="https://github.com/PDAL/data/blob/ce0024257c573526389c4db9ab26e82739b8aaa9/LICENSE" target="_blank" rel="noopener noreferrer">CC BY 4.0</a></p></template>
<script type="module">
import { Viewer } from "https://cdn.flinker.app/ifc-viewer/v4.0.3/ifc-viewer.es.js";
// Example-only download feedback. All file parsing remains in the published SDK.
async function loadExampleFile(name, url, load = blob => viewer.add(name, blob), size = 0) {
const show = stage => viewer.setMessage(name + ': ' + stage, { type: 'loading' });
const chunks = [], chunkSize = 4 * 1024 * 1024;
let offset = 0, attempts = 0, action = 'download', type = '';
try {
await show('Downloading');
while (!size || offset < size) {
try {
const end = Math.min(offset + chunkSize, size || Infinity) - 1;
const response = await fetch(url, {
headers: { Range: 'bytes=' + offset + '-' + end },
cache: 'no-store', signal: AbortSignal.timeout(60000),
});
if (!response.ok) {
await response.body?.cancel();
// GitHub raw hides Content-Range from browser JavaScript: probe EOF.
if (response.status === 416 && offset && !size) break;
throw Object.assign(new Error('HTTP ' + response.status), { retry: [408, 429].includes(response.status) || response.status >= 500 });
}
const range = response.headers.get('content-range')?.match(/^bytes (\d+)-(\d+)\/(\d+)$/);
if (range && Number(range[1]) !== offset) throw new TypeError('Unexpected byte range');
size ||= Number(range?.[3]) || 0;
const bytes = await response.arrayBuffer();
const expected = response.status === 206 ? (size ? Math.min(chunkSize, size - offset) : 0) : size;
if (!bytes.byteLength || (expected && bytes.byteLength !== expected) || (response.status === 206 && bytes.byteLength > chunkSize))
throw new TypeError('Incomplete download at byte ' + offset);
if (response.status === 200) chunks.length = 0; // Server ignored Range.
chunks.push(bytes);
offset = response.status === 200 ? bytes.byteLength : offset + bytes.byteLength;
type = response.headers.get('content-type') || '';
attempts = 0;
if (response.status === 200) break;
await show('Downloading' + (size ? ' ' + Math.round(offset / size * 100) + '%' : ''));
}
catch (error) {
if (error.retry === false || ++attempts === 3) throw error;
await show('Retrying download');
await new Promise(resolve => setTimeout(resolve, 500 * 2 ** (attempts - 1)));
}
}
action = 'open';
const result = await load(new Blob(chunks, { type }), show);
if (result?.isError) throw new Error('The viewer could not open the file');
await viewer.setMessage(null);
return result;
}
catch (error) {
throw new Error('Could not ' + action + ' ' + name + (action === 'download' ? ' from ' + url : '') + ': ' + error.message, { cause: error });
}
}
const { assets } = JSON.parse(document.getElementById("example-data").textContent);
const viewer = new Viewer('#ifc-viewer');
window.exampleViewer = viewer;
document.body.dataset.exampleState = 'loading';
const imported = new Set();
const retry = document.querySelector('#sample-retry');
let running = false;
async function load() {
if (running)
return;
running = true;
retry.hidden = true;
document.body.dataset.exampleState = 'loading';
try {
await viewer.ready;
for (const asset of assets) {
if (imported.has(asset.id))
continue;
await loadExampleFile(asset.name, asset.url, blob => {
if (blob.size !== asset.bytes)
throw new Error(`The download of ${asset.name} is incomplete (expected ${asset.bytes} bytes, received ${blob.size}).`);
return viewer.add(asset.name, blob);
}, asset.bytes);
imported.add(asset.id);
}
document.body.dataset.exampleState = 'loaded';
}
catch (error) {
document.body.dataset.exampleState = 'error';
await viewer.setMessage(error instanceof Error ? error.message : String(error), { type: 'error' });
retry.hidden = false;
}
finally {
running = false;
}
}
retry.addEventListener('click', () => { void load(); });
void load();
</script>
</body>
</html>
How it works
Measure site access
- Find a street or approach beside the stadium and zoom to its edges.
- On the left toolbar, open Measure (ruler) → Add measurement. Set Measure Mode to Length and pick two survey points to measure a route or its width.
- Measure a second route to compare the available space.
Inspect a scan section
- Find the stadium edge and its surrounding ground.
- On the left toolbar, open Sections (scissors) → Add clipping plane, then click a surface to cut through the scan and examine ground and elevated features.
- Drag the section to compare nearby buildings and vegetation.
Developer integration
Load the original classified LAZ survey once with viewer.add(). The SDK provides point picking, measurement and section controls for the same scan. Source classification is preserved. The survey was captured in 2010 and classified by Max Sampson, Hobu, Inc. in 2021; these exercises do not represent a current site access assessment.
Copy this folder and serve it over HTTP, for example python -m http.server 8080. Open http://localhost:8080/; the SDK and sample downloads require internet access.
Sources and license notices
© Aaron Reyna, Watershed Sciences, Inc. · Classification: Max Sampson, Hobu, Inc. (2021) · CC BY 4.0