itowns
Version:
A JS/WebGL framework for 3D geospatial data visualization
126 lines (121 loc) • 5.13 kB
JavaScript
import proj4 from 'proj4';
import { Binary, Info, Las } from 'copc';
import { Extent } from '@itowns/geographic';
import Fetcher from "../Provider/Fetcher.js";
import LASParser from "../Parser/LASParser.js";
import Source from "./Source.js";
import * as THREE from 'three';
/**
* @param {function(number, number):Promise<Uint8Array>} fetcher
*/
async function getHeaders(fetcher) {
const header = Las.Header.parse(await fetcher(0, Las.Constants.minHeaderLength));
const vlrs = await Las.Vlr.walk(fetcher, header);
// info VLR: required by COPC
const infoVlr = Las.Vlr.find(vlrs, 'copc', 1);
if (!infoVlr) {
return Promise.reject('COPC info VLR is required');
}
const info = Info.parse(await Las.Vlr.fetch(fetcher, infoVlr));
// OGC Coordinate System WKT: required by LAS1.4
const wktVlr = Las.Vlr.find(vlrs, 'LASF_Projection', 2112);
if (!wktVlr) {
return Promise.reject('LAS1.4 WKT VLR is required');
}
const wkt = Binary.toCString(await Las.Vlr.fetch(fetcher, wktVlr));
// Extra bytes: optional by LAS1.4
const ebVlr = Las.Vlr.find(vlrs, 'LASF_Spec', 4);
const eb = ebVlr ? Las.ExtraBytes.parse(await Las.Vlr.fetch(fetcher, ebVlr)) : [];
return {
header,
info,
wkt,
eb
};
}
/**
* A source for [Cloud Optimised Point Cloud](https://copc.io/) (COPC) data.
* Such data consists of a [LAZ 1.4](https://www.ogc.org/standard/las/) file
* that stores compressed points data organized in a clustered octree.
*
* A freshly created source fetches and parses portions of the file
* corresponding to the LAS 1.4 header, all the Variable Length Record (VLR)
* headers as well the following VLRs:
* - COPC [`info`](https://copc.io/#info-vlr) record (mandatory)
* - LAS 1.4 `OGC Coordinate System WKT` record (mandatory, see [Las 1.4
* spec](https://portal.ogc.org/files/?artifact_id=74523))
* - LAS 1.4 `Extra Bytes` record (optional, see [Las 1.4
* spec](https://portal.ogc.org/files/?artifact_id=74523))
*
* @extends {Source}
*
* @property {boolean} isCopcSource - Read-only flag to check that a given
* object is of type CopcSource.
* @property {Object} header - LAS header of the source.
* @property {Object[]} eb - List of headers of each Variable Length Records
* (VLRs).
* @property {Object} info - COPC `info` VLR.
* @property {number[]} info.cube - Bounding box of the octree as a 6-elements.
* tuple `[minX, minY, minZ, maxX, maxY, maxZ]`. Computed from `center_x`,
* `center_y`, `center_z` and `halfSize` properties.
* @property {Object} info.rootHierarchyPage - Hierarchy page of the root node.
* @property {number} info.rootHierarchyPage.pageOffset - Absolute Offset to the
* root node data chunk.
* @property {number} info.rootHierarchyPage.pageOffset - Size (in bytes) of the
* root node data chunk.
* @property {number[]} gpsTimeRange - A 2-element tuple denoting the minimum
* and maximum values of attribute `gpsTime`.
*/
class CopcSource extends Source {
/**
* @param {Object} config - Source configuration
* @param {string} config.url - URL of the COPC resource.
* @param {8 | 16} [config.colorDepth=16] - Encoding of the `color`
* attribute. Either `8` or `16` bits.
* @param {string} [config._lazPerfBaseUrl] - (experimental) Overrides base
* url of the `las-zip.wasm` file of the `laz-perf` library.
* @param {string} [config.crs='EPSG:4326'] - Native CRS of the COPC
* ressource. Note that this is not for now inferred from the COPC header.
* @param {RequestInit} [config.networkOptions] - Fetch options (passed
* directly to `fetch()`), see [the syntax for more information](
* https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax).
* @param {Object} [config.attribution] - Attribution of the data.
*/
constructor(config) {
super(config);
this.isCopcSource = true;
this.parser = LASParser.parseChunk;
this.fetcher = Fetcher.arrayBuffer;
this.colorDepth = config.colorDepth ?? 16;
const get = (/** @type {number} */begin, /** @type {number} */end) => this.fetcher(this.url, {
...this.networkOptions,
headers: {
...this.networkOptions.headers,
range: `bytes=${begin}-${end - 1}`
}
}).then(buffer => new Uint8Array(buffer));
this.whenReady = getHeaders(get).then(metadata => {
this.header = metadata.header;
this.info = metadata.info;
this.eb = metadata.eb;
proj4.defs('unknown', metadata.wkt);
let projCS;
if (proj4.defs('unknown').type === 'COMPD_CS') {
console.warn('CopcSource: compound coordinate system is not yet supported.');
projCS = proj4.defs('unknown').PROJCS;
} else {
projCS = proj4.defs('unknown');
}
this.crs = projCS.title || projCS.name || 'EPSG:4326';
if (!(this.crs in proj4.defs)) {
proj4.defs(this.crs, projCS);
}
const bbox = new THREE.Box3();
bbox.min.fromArray(this.info.cube, 0);
bbox.max.fromArray(this.info.cube, 3);
this.extent = Extent.fromBox3(this.crs, bbox);
return this;
});
}
}
export default CopcSource;