npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

copc-tileset-provider

v0.10.0

Published

Stream static COPC point clouds into CesiumJS with no pre-tiling step.

Readme

copc-tileset-provider

Stream static COPC point clouds into CesiumJS — no pre-tiling, no backend, no conversion step.

CI npm

A COPC file is a LAZ file whose points are already sorted into an octree. That means the parts you need can be read with HTTP Range requests from any static host — S3, nginx, GitHub Pages. This library maps that octree onto Cesium's own 3D Tiles engine as it loads, so traversal, level of detail, request priority, caching, styling and picking all stay Cesium's.

Point it at a URL and it renders:

COPCTilesetProvider.registerCrs(2992, '+proj=lcc +lat_0=41.75 +lon_0=-120.5 …');

const provider = await COPCTilesetProvider.fromUrl(
  'https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz',
);
viewer.scene.primitives.add(provider);
viewer.camera.flyTo({ destination: provider.extent });

Install

npm install copc-tileset-provider cesium

Cesium is a peer dependency, >=1.142.0 <1.145.0. Both ends of that range are rendered in a real browser before it is widened.

Quick start

Register the file's coordinate system before opening it — see Coordinate systems.

import { Viewer } from 'cesium';
import { COPCTilesetProvider } from 'copc-tileset-provider';

// EPSG:2992 — Oregon Statewide Lambert, the system Autzen is stored in.
COPCTilesetProvider.registerCrs(
  2992,
  '+proj=lcc +lat_0=41.75 +lon_0=-120.5 +lat_1=43 +lat_2=45.5 ' +
    '+x_0=399999.9999984 +y_0=0 +datum=NAD83 +units=ft +no_defs',
);

const viewer = new Viewer('cesiumContainer');

const provider = await COPCTilesetProvider.fromUrl(
  'https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz',
);

viewer.scene.primitives.add(provider);
viewer.camera.flyTo({ destination: provider.extent });

That URL is not a placeholder: it is the public Autzen scan, 81 MB, of which opening the file reads about 10 KB.

provider is a Cesium primitive — scene.primitives.add takes it directly.

A complete, runnable example is in examples/; it is the screenshot at the top.

Coordinate systems

Only EPSG:4326 is known by default. Every other system has to be registered once, before the file is opened:

COPCTilesetProvider.registerCrs(2992, '<proj4 definition>');

The library reads the EPSG code out of the file's WKT and looks it up. It does not feed the WKT to proj4 directly: proj4 either throws on some dialects (measured, on Autzen's compound WKT) or silently produces wrong coordinates when datum information is missing, and there is no way to tell which in advance. A registered definition is the only input somebody has vouched for.

An unregistered system fails with an error that names it and hands you the call to paste, including where to find the definition:

This file uses EPSG:2992, which is not registered. […]

    registerCrs(2992, '<proj4 definition>');

The definition for EPSG:2992 is at https://epsg.io/2992 […]

A registered definition's accuracy is the registrant's; this library applies what it is given.

Your server has to support Range requests

Every read is an HTTP Range request, and every response is verified.

  • The host must serve 206. Most static hosts do; some CDNs and proxies strip Range support on compressed responses. A 200 is refused rather than accepted as a fallback — it is the whole file, which is what streaming exists to avoid.
  • Cross-origin, the check is weaker. Browsers hand JavaScript only the CORS-safelisted response headers, and Content-Range is not one of them unless the server sends Access-Control-Expose-Headers: Content-Range — which no public COPC dataset does. With the header, the range is checked against it exactly; without it, on the status and the exact length of the body, which cannot confirm which bytes came back. Send the header if you control the host and want the stronger check.

Styling and picking

Tiles are standard Cesium content, so the engine's own tools work unchanged:

import { Cesium3DTileStyle } from 'cesium';

provider.tileset.style = new Cesium3DTileStyle({
  color: "${Classification} === 2 ? color('brown') : color('green')",
  show: '${Intensity} > 30',
});

Each point carries these batch-table properties:

| Property | Type | From | |---|---|---| | Classification | uint8 | LAS classification | | Intensity | uint16 | LAS intensity | | GpsTime | float64 | LAS GPS time | | ReturnNumber | uint8 | LAS return number | | NumberOfReturns | uint8 | LAS number of returns |

Unstyled, points take the file's own colour. LAS point format 6 carries none, so such a file renders in Cesium's constant dark grey until a style gives it a colour — every property above is still there to style on.

Picking goes through Cesium's own scene.pick. Every point carries a BATCH_ID, which is what lets a picked point resolve to the properties above.

Limits

Heights are ellipsoidal. Every Z is height above the WGS84 ellipsoid. Orthometric data — most surveyed LiDAR — sits at a visible vertical offset until you pass the geoid separation at your dataset's location, in metres:

await COPCTilesetProvider.fromUrl(url, { geoidHeight: -23.333 });

One constant for the whole file, so it holds where the separation does not vary — a survey site, not a continent. Grid-based correction is out of scope for v1. A file that declares a vertical CRS and gets no geoidHeight loads anyway, with a console warning naming the code. That check cannot tell an already-ellipsoidal vertical CRS from a geoid-referenced one, so pass geoidHeight: 0 to silence it rather than omitting the option.

Content is PNTS, which is 3D Tiles 1.0 legacy, superseded by glTF-based content in 3D Tiles 1.1. Chosen deliberately: a Worker can hand-encode PNTS — a header, a feature table, a binary body — where glTF has to be assembled, and its batch table is what gives Cesium's style language and picking. glTF is on the roadmap after v1.

A strict worker-src CSP blocks the default Worker. It is built from a bundle inlined into the library and loaded from a blob: URL, so nothing has to be served or configured — but a policy that forbids blob: refuses it, and the library cannot work around that. Supply your own Worker instead:

// 1. Your own Worker module — the subpath installs itself when evaluated.
//    your-worker.js:
import 'copc-tileset-provider/worker';

//    and where you build the provider:
import { browserPort } from 'copc-tileset-provider';

await COPCTilesetProvider.fromUrl(url, {
  spawnWorker: () =>
    browserPort(new Worker(new URL('./your-worker.js', import.meta.url), { type: 'module' })),
});

A bundler that ignores browser fields will fail to build. Your bundler resolves laz-perf itself, and what keeps it off laz-perf's Node build — which reaches for require("fs") — is that package's own "browser": "lib/web/index.js". Vite and webpack honour it by default, esbuild when its platform is browser, plain Rollup only with @rollup/plugin-node-resolve set to { browser: true }. Otherwise alias laz-perf to laz-perf/lib/web/index.js. Only the Vite path is measured — the publish smoke builds with it.

Cesium 1.141 and earlier is not a choice: the _runtimeContentCodec slot this library installs onto arrived in 1.142, so on anything older the mechanism it depends on does not exist.

API

COPCTilesetProvider.fromUrl(url, options?)

Opens the file and returns a provider. Reads metadata and the root hierarchy page — three Range requests — before resolving.

| Option | Default | What it does | |---|---|---| | maximumScreenSpaceError | 16 | Cesium's own quality knob, passed through. Lower means more tiles and more detail. | | workerPoolSize | 4 | How many Workers decode in parallel. | | spawnWorker | bundled Worker | Supply your own Worker, as a WorkerPort. See Limits. | | fetch | globalThis.fetch | Every Range request goes through this. Use it to add auth headers, sign URLs, or route through a proxy. | | signal | — | Aborts the three reads fromUrl makes. Tile requests are cancelled by Cesium itself. | | geoidHeight | — (HAE) | The geoid's separation from the WGS84 ellipsoid at this file's location, in metres, added to every height. Omit it for a file whose Z is already ellipsoidal. See Limits. |

Provider

| Member | Type | What it is | |---|---|---| | tileset | Cesium3DTileset | The live tileset. Styling, events and traversal settings go here. | | extent | Rectangle | The file's measured extent, for camera framing. Not the inflated tile bounds. | | stats() | ProviderStats | Range counters, budget admissions, and registry size. | | destroy() | void | Releases the tileset, the Workers and every reservation. Idempotent. |

COPCTilesetProvider.registerCrs(code, proj4Definition)

Teaches this process one coordinate system. Static, because it has to be callable before any file is opened.

browserPort(worker)

Wraps a browser Worker as the WorkerPort that spawnWorker must return.

copc-tileset-provider/worker

The Worker realm's entry point. Importing it inside a Worker installs the message handler; it does not reach Cesium.

Errors

Every failure is a typed class exported from the package root, each carrying a code and a message that names the fix. Catch CopcTilesetError for all of them, or a specific class for one.

Contributing

How to run the suite, what review looks for, and how a release is cut: CONTRIBUTING.md. How the pieces fit together: docs/architecture.md. The decisions behind them, with their reasoning and measurements: OVERVIEW.md (Korean).

License

MIT. See LICENSE. The published bundles inline their dependencies, whose licenses are reproduced in THIRD-PARTY-NOTICES.md.