(
49 | Ept.schema,
50 | await getJson(filename)
51 | )
52 |
53 | errors.forEach((e) => console.log(`${filename}:`, e))
54 |
55 | return result
56 | }
57 |
--------------------------------------------------------------------------------
/src/3d-tiles/feature-table/binary.ts:
--------------------------------------------------------------------------------
1 | import { Params } from '3d-tiles/types'
2 |
3 | import { Rgb } from './rgb'
4 | import { Xyz } from './xyz'
5 |
6 | export const Binary = { create }
7 | function create(params: Params) {
8 | return Buffer.concat([Xyz.create(params), Rgb.create(params)])
9 | }
10 |
--------------------------------------------------------------------------------
/src/3d-tiles/feature-table/feature-table.test.ts:
--------------------------------------------------------------------------------
1 | import { Bounds, DataType, Schema } from 'ept'
2 | import { Point } from 'types'
3 | import { Reproject } from 'utils'
4 |
5 | import { Pnts } from '3d-tiles'
6 |
7 | import { FeatureTable } from './feature-table'
8 |
9 | const schema: Schema = [
10 | { name: 'X', type: 'float', size: 8 },
11 | { name: 'Y', type: 'float', size: 8 },
12 | { name: 'Z', type: 'float', size: 8 },
13 | { name: 'Red', type: 'unsigned', size: 2 },
14 | { name: 'Green', type: 'unsigned', size: 2 },
15 | { name: 'Blue', type: 'unsigned', size: 2 },
16 | ]
17 |
18 | const numPoints = 2
19 | const bounds: Bounds = [0, 0, 0, 8, 8, 8]
20 |
21 | const toEcef: Reproject = (p: P) => p
22 |
23 | test('create: with rgb', async () => {
24 | const buffer = Buffer.alloc(Schema.pointSize(schema) * numPoints)
25 | const view = await DataType.view('binary', buffer, schema)
26 | const { header } = FeatureTable.create({
27 | view,
28 | tileBounds: bounds,
29 | toEcef,
30 | options: {},
31 | })
32 |
33 | expect(header).toEqual({
34 | POINTS_LENGTH: numPoints,
35 | RTC_CENTER: Bounds.mid(bounds),
36 | POSITION: { byteOffset: 0 },
37 | RGB: { byteOffset: Pnts.Constants.xyzSize * numPoints },
38 | })
39 | })
40 |
41 | test('create: no rgb', async () => {
42 | const xyzonly = schema.slice(0, 3)
43 | const buffer = Buffer.alloc(Schema.pointSize(xyzonly) * numPoints)
44 | const view = await DataType.view('binary', buffer, xyzonly)
45 | const { header } = FeatureTable.create({
46 | view,
47 | tileBounds: bounds,
48 | toEcef,
49 | options: {},
50 | })
51 |
52 | expect(header).toEqual({
53 | POINTS_LENGTH: numPoints,
54 | RTC_CENTER: Bounds.mid(bounds),
55 | POSITION: { byteOffset: 0 },
56 | })
57 | })
58 |
59 | test('create: with z offset', async () => {
60 | const zOffset = 10
61 | const buffer = Buffer.alloc(Schema.pointSize(schema) * numPoints)
62 | const view = await DataType.view('binary', buffer, schema)
63 | const { header } = FeatureTable.create({
64 | view,
65 | tileBounds: bounds,
66 | toEcef,
67 | options: { zOffset },
68 | })
69 |
70 | const mid = Bounds.mid(bounds)
71 | const raised: Point = [mid[0], mid[1], mid[2] + zOffset]
72 |
73 | expect(header).toEqual({
74 | POINTS_LENGTH: numPoints,
75 | RTC_CENTER: raised,
76 | POSITION: { byteOffset: 0 },
77 | RGB: { byteOffset: Pnts.Constants.xyzSize * numPoints },
78 | })
79 | })
80 |
--------------------------------------------------------------------------------
/src/3d-tiles/feature-table/feature-table.ts:
--------------------------------------------------------------------------------
1 | import { Params } from '3d-tiles/types'
2 | import { padEnd, sumLengths } from '3d-tiles/utils'
3 | import { Bounds, Schema } from 'ept'
4 |
5 | import { Header } from './header'
6 | import { Rgb } from './rgb'
7 | import { Xyz } from './xyz'
8 |
9 | // Work around TS namespaced re-export deficiency.
10 | type _Header = Header
11 | export declare namespace FeatureTable {
12 | export type Header = _Header
13 | }
14 |
15 | export type FeatureTable = { header: Header; binary: Buffer }
16 | export const FeatureTable = { create }
17 |
18 | function create({ view, tileBounds, toEcef, options }: Params): FeatureTable {
19 | const bounds = Bounds.reproject(
20 | Bounds.offsetHeight(tileBounds, options.zOffset || 0),
21 | toEcef
22 | )
23 | const header: Header = {
24 | POINTS_LENGTH: view.length,
25 | RTC_CENTER: Bounds.mid(bounds),
26 | POSITION: { byteOffset: 0 },
27 | }
28 |
29 | const buffers = [Xyz.create({ view, tileBounds, toEcef, options })]
30 |
31 | const has = (name: string) => Schema.has(view.schema, name)
32 |
33 | if (has('Red') && has('Green') && has('Blue')) {
34 | header.RGB = { byteOffset: sumLengths(buffers) }
35 | buffers.push(Rgb.create({ view, options }))
36 | }
37 |
38 | const binary = padEnd(Buffer.concat(buffers))
39 | return { header, binary }
40 | }
41 |
--------------------------------------------------------------------------------
/src/3d-tiles/feature-table/header.ts:
--------------------------------------------------------------------------------
1 | import { Point } from 'types'
2 |
3 | type WithByteOffset = { byteOffset: number }
4 |
5 | export declare namespace Header {
6 | export type Floating = {
7 | POSITION: WithByteOffset
8 | }
9 | export type Quantized = {
10 | POSITION_QUANTIZED: WithByteOffset
11 | QUANTIZED_VOLUME_OFFSET?: Point
12 | QUANTIZED_VOLUME_SCALE?: Point
13 | }
14 | export type WithBatchTable = {
15 | BATCH_LENGTH: number
16 | BATCH_ID: WithByteOffset
17 | }
18 | }
19 |
20 | type Base = (Header.Floating | Header.Quantized) & {
21 | // https://git.io/JIhyp
22 | POINTS_LENGTH: number
23 | RTC_CENTER?: Point
24 | CONSTANT_RGBA?: [number, number, number, number]
25 |
26 | // https://git.io/JIhSL
27 | RGBA?: WithByteOffset
28 | RGB?: WithByteOffset
29 | RGB565?: WithByteOffset
30 | NORMAL?: WithByteOffset
31 | NORMAL_OCT16P?: WithByteOffset
32 | }
33 |
34 | export type Header = Base | (Base & Header.WithBatchTable)
35 |
--------------------------------------------------------------------------------
/src/3d-tiles/feature-table/index.ts:
--------------------------------------------------------------------------------
1 | export { FeatureTable } from './feature-table'
2 |
--------------------------------------------------------------------------------
/src/3d-tiles/feature-table/rgb.test.ts:
--------------------------------------------------------------------------------
1 | import { DataType, Schema } from 'ept'
2 | import { Rgb } from './rgb'
3 |
4 | test('create: no rgb', async () => {
5 | const schema: Schema = [
6 | { name: 'X', type: 'float', size: 8 },
7 | { name: 'Y', type: 'float', size: 8 },
8 | { name: 'Z', type: 'float', size: 8 },
9 | ]
10 | const buffer = Buffer.alloc(Schema.pointSize(schema))
11 | const view = await DataType.view('binary', buffer, schema)
12 | const rgb = Rgb.create({ view, options: {} })
13 | expect(rgb).toHaveLength(0)
14 | })
15 |
--------------------------------------------------------------------------------
/src/3d-tiles/feature-table/rgb.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'ept'
2 | import * as Constants from '3d-tiles/pnts/constants'
3 | import { Params } from '3d-tiles/types'
4 |
5 | export const Rgb = { create }
6 | function create({
7 | view,
8 | options: { truncate = false },
9 | }: Pick) {
10 | if (!Schema.has(view.schema, 'Red')) return Buffer.alloc(0)
11 |
12 | const { getter, length } = view
13 | const shift = truncate ? 8 : 0
14 | const getters = ['Red', 'Green', 'Blue']
15 | .map(getter)
16 | .map((get) => (index: number) => get(index) >> shift)
17 |
18 | const buffer = Buffer.allocUnsafe(length * Constants.rgbSize)
19 |
20 | for (let index = 0, offset = 0; index < length; ++index) {
21 | getters.forEach((get) => buffer.writeUInt8(get(index), offset++))
22 | }
23 |
24 | return buffer
25 | }
26 |
--------------------------------------------------------------------------------
/src/3d-tiles/feature-table/xyz.test.ts:
--------------------------------------------------------------------------------
1 | import { Bounds, DataType, Schema } from 'ept'
2 | import { Ellipsoid, Pnts } from 'test'
3 | import { Point } from 'types'
4 | import { Reproject, Scale } from 'utils'
5 |
6 | import { Xyz } from './xyz'
7 |
8 | test('create', async () => {
9 | const schema: Schema = [
10 | { name: 'X', type: 'signed', size: 4, scale: 0.01, offset: 100 },
11 | { name: 'Y', type: 'float', size: 8 },
12 | { name: 'Z', type: 'signed', size: 4, scale: 0.0025, offset: 500 },
13 | ]
14 | const pointSize = Schema.pointSize(schema)
15 | const buffer = Buffer.alloc(pointSize * 2)
16 |
17 | // In the native SRS of the Ellipsoid: web mercator.
18 | const tileBounds = Ellipsoid.bounds
19 | const mid = Bounds.mid(tileBounds)
20 |
21 | // First point: midpoint minus 1 in native coordinate space.
22 | const a = mid.map((v) => v - 1)
23 | buffer.writeInt32LE(Scale.apply(a[0], 0.01, 100), 0)
24 | buffer.writeDoubleLE(a[1], 4)
25 | buffer.writeInt32LE(Scale.apply(a[2], 0.0025, 500), 12)
26 |
27 | // Second point: midpoint plus 1 in native coordinate space.
28 | const b = mid.map((v) => v + 1)
29 | buffer.writeInt32LE(Scale.apply(b[0], 0.01, 100), 16)
30 | buffer.writeDoubleLE(b[1], 16 + 4)
31 | buffer.writeInt32LE(Scale.apply(b[2], 0.0025, 500), 16 + 12)
32 |
33 | // Now create our supporting data structures and get our XYZ buffer.
34 | const view = await DataType.view('binary', buffer, schema)
35 |
36 | const toEcef = Reproject.create(Ellipsoid.srsCodeString, 'EPSG:4978')
37 | const ecefBounds = Bounds.reproject(tileBounds, toEcef)
38 | const ecefMid = Bounds.mid(ecefBounds)
39 | const xyz = Xyz.create({
40 | view,
41 | tileBounds,
42 | toEcef,
43 | options: Pnts.defaultOptions,
44 | })
45 |
46 | // Should have 2 points. Each point consists of 3 floats.
47 | expect(xyz.length).toEqual(4 * 3 * 2)
48 |
49 | const ecefA = toEcef(a)
50 | expect(xyz.readFloatLE(0)).toBeCloseTo(ecefA[0] - ecefMid[0], 5)
51 | expect(xyz.readFloatLE(4)).toBeCloseTo(ecefA[1] - ecefMid[1], 5)
52 | expect(xyz.readFloatLE(8)).toBeCloseTo(ecefA[2] - ecefMid[2], 5)
53 |
54 | const ecefB = toEcef(b)
55 | expect(xyz.readFloatLE(12)).toBeCloseTo(ecefB[0] - ecefMid[0], 5)
56 | expect(xyz.readFloatLE(16)).toBeCloseTo(ecefB[1] - ecefMid[1], 5)
57 | expect(xyz.readFloatLE(20)).toBeCloseTo(ecefB[2] - ecefMid[2], 5)
58 | })
59 |
60 | test('z offset', async () => {
61 | const zOffset = 500
62 | const schema: Schema = [
63 | { name: 'X', type: 'float', size: 8 },
64 | { name: 'Y', type: 'float', size: 8 },
65 | { name: 'Z', type: 'float', size: 8 },
66 | ]
67 | const pointSize = Schema.pointSize(schema)
68 | const buffer = Buffer.alloc(pointSize)
69 | const x = 1,
70 | y = 2,
71 | z = 3
72 | buffer.writeDoubleLE(x, 0)
73 | buffer.writeDoubleLE(y, 8)
74 | buffer.writeDoubleLE(z, 16)
75 |
76 | const view = await DataType.view('binary', buffer, schema)
77 |
78 | const tileBounds: Bounds = [-5, -5, -5, 5, 5, 5]
79 | const toEcef: Reproject = (p: P) => p
80 |
81 | const xyz = Xyz.create({
82 | view,
83 | tileBounds,
84 | toEcef,
85 | options: { zOffset },
86 | })
87 |
88 | expect(xyz.readFloatLE(0)).toEqual(x)
89 | expect(xyz.readFloatLE(4)).toEqual(y)
90 | expect(xyz.readFloatLE(8)).toEqual(z + zOffset)
91 | })
92 |
--------------------------------------------------------------------------------
/src/3d-tiles/feature-table/xyz.ts:
--------------------------------------------------------------------------------
1 | import { Bounds } from 'ept'
2 |
3 | import * as Constants from '3d-tiles/pnts/constants'
4 | import { Params } from '3d-tiles/types'
5 |
6 | export const Xyz = { create }
7 |
8 | function create({
9 | view,
10 | tileBounds,
11 | toEcef,
12 | options: { zOffset = 0 },
13 | }: Params) {
14 | const { getter, length } = view
15 | const getters = ['X', 'Y', 'Z'].map(getter)
16 |
17 | const buffer = Buffer.allocUnsafe(length * Constants.xyzSize)
18 | const mid = Bounds.mid(Bounds.reproject(tileBounds, toEcef))
19 |
20 | for (let index = 0, offset = 0; index < length; ++index) {
21 | const x = getters[0](index)
22 | const y = getters[1](index)
23 | const z = getters[2](index) + zOffset
24 | toEcef([x, y, z]).forEach((v, i) => {
25 | buffer.writeFloatLE(v - mid[i], offset)
26 | offset += 4
27 | })
28 | }
29 |
30 | return buffer
31 | }
32 |
--------------------------------------------------------------------------------
/src/3d-tiles/index.ts:
--------------------------------------------------------------------------------
1 | export { BoundingVolume } from './bounding-volume'
2 | export { Cache } from './cache'
3 | export type { Tile } from './tileset/tile'
4 | export { Tileset } from './tileset/tileset'
5 | export * from './pnts'
6 | export { Server, parseQuery } from './server'
7 | export * from './types'
8 | export * from './utils'
9 |
10 | export { translate } from './translate'
11 |
--------------------------------------------------------------------------------
/src/3d-tiles/pnts/constants.ts:
--------------------------------------------------------------------------------
1 | export const magic = 'pnts'
2 | export const version = 1
3 | export const headerSize = 28
4 | export const xyzSize = 12
5 | export const rgbSize = 3
6 | export const normalSize = 12
7 |
--------------------------------------------------------------------------------
/src/3d-tiles/pnts/header.test.ts:
--------------------------------------------------------------------------------
1 | import { Header } from './header'
2 |
3 | // Valid sizes must all be a multiple of 8.
4 | const buffers = {
5 | featureTableHeader: Buffer.alloc(8 * 4),
6 | featureTableBinary: Buffer.alloc(8 * 3 * 24),
7 | batchTableHeader: Buffer.alloc(8 * 6),
8 | batchTableBinary: Buffer.alloc(8 * 512),
9 | }
10 | test('invalid buffer sizes', () => {
11 | const b = Buffer.alloc(7)
12 | expect(() => Header.create({ ...buffers, featureTableHeader: b })).toThrow(
13 | /invalid feature table json/i
14 | )
15 | expect(() => Header.create({ ...buffers, featureTableBinary: b })).toThrow(
16 | /invalid feature table binary/i
17 | )
18 | expect(() => Header.create({ ...buffers, batchTableHeader: b })).toThrow(
19 | /invalid batch table json/i
20 | )
21 | expect(() => Header.create({ ...buffers, batchTableBinary: b })).toThrow(
22 | /invalid batch table binary/i
23 | )
24 | })
25 |
26 | test('success', () => {
27 | const header = Header.create(buffers)
28 |
29 | const magic = header.toString('utf8', 0, 4)
30 | const version = header.readUInt32LE(4)
31 | const total = header.readUInt32LE(8)
32 | const featureTableHeaderSize = header.readUInt32LE(12)
33 | const featureTableBinarySize = header.readUInt32LE(16)
34 | const batchTableHeaderSize = header.readUInt32LE(20)
35 | const batchTableBinarySize = header.readUInt32LE(24)
36 |
37 | expect(magic).toEqual('pnts')
38 | expect(version).toEqual(1)
39 | expect(total).toEqual(
40 | header.length +
41 | Object.values(buffers).reduce((sum, cur) => sum + cur.length, 0)
42 | )
43 | expect(featureTableHeaderSize).toEqual(buffers.featureTableHeader.length)
44 | expect(featureTableBinarySize).toEqual(buffers.featureTableBinary.length)
45 | expect(batchTableHeaderSize).toEqual(buffers.batchTableHeader.length)
46 | expect(batchTableBinarySize).toEqual(buffers.batchTableBinary.length)
47 | })
48 |
--------------------------------------------------------------------------------
/src/3d-tiles/pnts/header.ts:
--------------------------------------------------------------------------------
1 | import { EptToolsError } from 'types'
2 |
3 | import * as Constants from './constants'
4 |
5 | export const Header = { create }
6 |
7 | export type Buffers = {
8 | featureTableHeader: Buffer
9 | featureTableBinary: Buffer
10 | batchTableHeader: Buffer
11 | batchTableBinary: Buffer
12 | }
13 | function create({
14 | featureTableHeader,
15 | featureTableBinary,
16 | batchTableHeader,
17 | batchTableBinary,
18 | }: Buffers) {
19 | const buffer = Buffer.alloc(Constants.headerSize)
20 |
21 | if (featureTableHeader.length % 8 !== 0) {
22 | throw new EptToolsError(
23 | `Invalid feature table JSON size: ${featureTableHeader.length}`
24 | )
25 | }
26 | if (featureTableBinary.length % 8 !== 0) {
27 | throw new EptToolsError(
28 | `Invalid feature table binary size: ${featureTableBinary.length}`
29 | )
30 | }
31 | if (batchTableHeader.length % 8 !== 0) {
32 | throw new EptToolsError(
33 | `Invalid batch table JSON size: ${batchTableHeader.length}`
34 | )
35 | }
36 | if (batchTableBinary.length % 8 !== 0) {
37 | throw new EptToolsError(
38 | `Invalid batch table binary size: ${batchTableBinary.length}`
39 | )
40 | }
41 |
42 | const total =
43 | Constants.headerSize +
44 | featureTableHeader.length +
45 | featureTableBinary.length +
46 | batchTableHeader.length +
47 | batchTableBinary.length
48 |
49 | // https://git.io/fjP8k
50 | buffer.write(Constants.magic, 0, 'utf8')
51 | buffer.writeUInt32LE(Constants.version, 4)
52 | buffer.writeUInt32LE(total, 8)
53 | buffer.writeUInt32LE(featureTableHeader.length, 12)
54 | buffer.writeUInt32LE(featureTableBinary.length, 16)
55 | buffer.writeUInt32LE(batchTableHeader.length, 20)
56 | buffer.writeUInt32LE(batchTableBinary.length, 24)
57 | return buffer
58 | }
59 |
--------------------------------------------------------------------------------
/src/3d-tiles/pnts/index.ts:
--------------------------------------------------------------------------------
1 | export * as Pnts from './pnts'
2 |
--------------------------------------------------------------------------------
/src/3d-tiles/pnts/pnts.ts:
--------------------------------------------------------------------------------
1 | import { BatchTable } from '3d-tiles/batch-table'
2 | import { FeatureTable } from '3d-tiles/feature-table'
3 | import { padEnd } from '3d-tiles/utils'
4 |
5 | import { Header } from './header'
6 | import { Params } from '../types'
7 |
8 | export * as Constants from './constants'
9 |
10 | export type { BatchTable, FeatureTable }
11 | export function translate(params: Params) {
12 | const {
13 | header: featureTableHeaderObject,
14 | binary: featureTableBinary,
15 | } = FeatureTable.create(params)
16 |
17 | const {
18 | header: batchTableHeaderObject,
19 | binary: batchTableBinary,
20 | } = BatchTable.create(params.view, params.options)
21 |
22 | const featureTableHeader = toStringBuffer(featureTableHeaderObject)
23 | const batchTableHeader = toStringBuffer(batchTableHeaderObject)
24 |
25 | const header = Header.create({
26 | featureTableHeader,
27 | featureTableBinary,
28 | batchTableHeader,
29 | batchTableBinary,
30 | })
31 |
32 | return Buffer.concat([
33 | header,
34 | featureTableHeader,
35 | featureTableBinary,
36 | batchTableHeader,
37 | batchTableBinary,
38 | ])
39 | }
40 |
41 | function toStringBuffer(o: object) {
42 | return padEnd(Buffer.from(JSON.stringify(o)), 0x20)
43 | }
44 |
--------------------------------------------------------------------------------
/src/3d-tiles/server/cors.test.ts:
--------------------------------------------------------------------------------
1 | import Koa from 'koa'
2 | import fetch from 'node-fetch'
3 |
4 | import { Server } from 'test'
5 |
6 | import { Cors } from './cors'
7 |
8 | const port = Server.getPort(1)
9 | const url = `http://localhost:${port}`
10 | async function getOrigin(url: string, headers?: { [key: string]: string }) {
11 | const res = await fetch(url, { headers })
12 | return res.headers.get('access-control-allow-origin') || undefined
13 | }
14 |
15 | test('access control allow origin: none', async () => {
16 | const app = new Koa()
17 | app.use(Cors.create())
18 | app.use((ctx) => (ctx.body = 'asdf'))
19 |
20 | const server = await Server.listen(app, port)
21 |
22 | try {
23 | // No access control allow origin header in any responses.
24 | expect(await getOrigin(url)).toBeUndefined()
25 | expect(
26 | await getOrigin(url, { origin: 'https://entwine.io' })
27 | ).toBeUndefined()
28 | } finally {
29 | await Server.destroy(server)
30 | }
31 | })
32 |
33 | test('access control allow origin: *', async () => {
34 | const app = new Koa()
35 | app.use(Cors.create('*'))
36 | app.use((ctx) => (ctx.body = 'asdf'))
37 |
38 | const server = await Server.listen(app, port)
39 |
40 | try {
41 | // If the request does not contain an explicit origin, we should get a "*"".
42 | expect(await getOrigin(url)).toEqual('*')
43 |
44 | // With an origin set in the request, the response should be set to that
45 | // origin.
46 | expect(await getOrigin(url, { origin: 'https://entwine.io' })).toEqual(
47 | 'https://entwine.io'
48 | )
49 | } finally {
50 | await Server.destroy(server)
51 | }
52 | })
53 |
54 | test('access control allow origin: single origin', async () => {
55 | const app = new Koa()
56 | app.use(Cors.create(['https://entwine.io']))
57 | app.use((ctx) => (ctx.body = 'asdf'))
58 |
59 | const server = await Server.listen(app, port)
60 |
61 | try {
62 | // If the request doesn't contain an origin, we should get our single
63 | // allowed origin.
64 | expect(await getOrigin(url)).toEqual('https://entwine.io')
65 |
66 | // If the request contains an origin, we should get that origin whether it
67 | // matches or not.
68 | expect(await getOrigin(url, { origin: 'https://entwine.io' })).toEqual(
69 | 'https://entwine.io'
70 | )
71 | expect(await getOrigin(url, { origin: 'https://pdal.io' })).toEqual(
72 | 'https://entwine.io'
73 | )
74 | } finally {
75 | await Server.destroy(server)
76 | }
77 | })
78 |
79 | test('access control allow origin: multiple origins', async () => {
80 | const app = new Koa()
81 | app.use(Cors.create(['https://entwine.io', 'https://pdal.io']))
82 | app.use((ctx) => (ctx.body = 'asdf'))
83 |
84 | const server = await Server.listen(app, port)
85 |
86 | try {
87 | // If the request doesn't contain an origin, we should get no header.
88 | expect(await getOrigin(url)).toBeUndefined()
89 |
90 | // If the request contains an origin in our list, it should be reflected.
91 | expect(await getOrigin(url, { origin: 'https://entwine.io' })).toEqual(
92 | 'https://entwine.io'
93 | )
94 | expect(await getOrigin(url, { origin: 'https://pdal.io' })).toEqual(
95 | 'https://pdal.io'
96 | )
97 |
98 | // And if the request contains an origin *not* in our list, we should get no
99 | // header.
100 | expect(await getOrigin(url, { origin: 'https://asdf.io' })).toBeUndefined()
101 | } finally {
102 | await Server.destroy(server)
103 | }
104 | })
105 |
--------------------------------------------------------------------------------
/src/3d-tiles/server/cors.ts:
--------------------------------------------------------------------------------
1 | import { Context, Next } from 'koa'
2 |
3 | export declare namespace Cors {
4 | export type Options = '*' | string[]
5 | }
6 |
7 | export const Cors = { create }
8 | function create(allowedOrigins: '*' | string[] = []) {
9 | return async function (ctx: Context, next: Next) {
10 | ctx.set('Access-Control-Allow-Methods', 'GET, HEAD')
11 |
12 | const origin = ctx.request.get('origin')
13 |
14 | // If we have a request origin, and it's allowed either via wildcard or
15 | // explicit list, then set the response origin to reflect the request value.
16 | if (origin && (allowedOrigins === '*' || allowedOrigins.includes(origin))) {
17 | ctx.set('Access-Control-Allow-Origin', origin)
18 | ctx.set('Vary', 'Origin')
19 | } else if (allowedOrigins === '*') {
20 | ctx.set('Access-Control-Allow-Origin', '*')
21 | } else if (allowedOrigins.length === 1) {
22 | ctx.set('Access-Control-Allow-Origin', allowedOrigins[0])
23 | }
24 |
25 | return next()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/3d-tiles/server/httpx.test.ts:
--------------------------------------------------------------------------------
1 | import { Agent } from 'https'
2 | import Koa from 'koa'
3 | import fetch from 'node-fetch'
4 |
5 | import { Server, keyfile, certfile, cafile } from 'test'
6 |
7 | import { Httpx } from './httpx'
8 |
9 | const port = Server.getPort(2)
10 |
11 | test('http', async () => {
12 | const app = new Koa()
13 | app.use((ctx) => (ctx.body = 'asdf'))
14 |
15 | const url = `http://localhost:${port}`
16 | const server = await Httpx.create(app, port)
17 |
18 | try {
19 | const res = await fetch(url)
20 | expect(await res.text()).toEqual('asdf')
21 | } finally {
22 | await Server.destroy(server)
23 | }
24 | })
25 |
26 | test('https', async () => {
27 | const app = new Koa()
28 | app.use((ctx) => (ctx.body = 'asdf'))
29 |
30 | const url = `https://localhost:${port}`
31 | const server = await Httpx.create(app, port, { keyfile, certfile })
32 |
33 | try {
34 | const agent = new Agent({ rejectUnauthorized: false })
35 | const res = await fetch(url, { agent })
36 | expect(await res.text()).toEqual('asdf')
37 | } finally {
38 | await Server.destroy(server)
39 | }
40 | })
41 |
42 | test('with ca', async () => {
43 | const app = new Koa()
44 | app.use((ctx) => (ctx.body = 'asdf'))
45 |
46 | const url = `https://localhost:${port}`
47 | const server = await Httpx.create(app, port, { keyfile, certfile, cafile })
48 |
49 | try {
50 | const agent = new Agent({ rejectUnauthorized: false })
51 | const res = await fetch(url, { agent })
52 | expect(await res.text()).toEqual('asdf')
53 | } finally {
54 | await Server.destroy(server)
55 | }
56 | })
57 |
--------------------------------------------------------------------------------
/src/3d-tiles/server/httpx.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import http from 'http'
3 | import https from 'https'
4 | import Koa from 'koa'
5 |
6 | import { Ssl } from './ssl'
7 |
8 | export const Httpx = { create }
9 | async function create(
10 | app: Koa,
11 | port: number,
12 | ssl?: Ssl.Options
13 | ): Promise {
14 | if (ssl) {
15 | const { keyfile, certfile, cafile } = ssl
16 | const options = {
17 | key: await read(keyfile),
18 | cert: await read(certfile),
19 | ca: cafile ? await read(cafile) : undefined,
20 | }
21 |
22 | return new Promise((resolve, reject) => {
23 | const server = https
24 | .createServer(options, app.callback())
25 | .listen(port, () => resolve(server))
26 | .on('error', reject)
27 | })
28 | }
29 |
30 | return new Promise((resolve, reject) => {
31 | const server = http
32 | .createServer(app.callback())
33 | .listen(port, () => resolve(server))
34 | .on('error', reject)
35 | })
36 | }
37 |
38 | async function read(path: string) {
39 | return fs.promises.readFile(path, { encoding: 'utf8' })
40 | }
41 |
--------------------------------------------------------------------------------
/src/3d-tiles/server/index.ts:
--------------------------------------------------------------------------------
1 | import Koa from 'koa'
2 | import logger from 'koa-logger'
3 | import Router from '@koa/router'
4 | import { join, normalize } from 'protopath'
5 |
6 | import { Cache, translate } from '3d-tiles'
7 | import { HttpError } from 'types'
8 |
9 | import { Cors } from './cors'
10 | import { Httpx } from './httpx'
11 | import { Ssl } from './ssl'
12 | import { parseQuery } from './utils'
13 |
14 | export { parseQuery }
15 |
16 | export declare namespace Server {
17 | export type Origins = '*' | string[]
18 | export type Options = {
19 | root?: string
20 | roots: Origins
21 | port: number
22 | origins: Origins
23 | } & Partial
24 | }
25 | export const Server = { create }
26 |
27 | async function create({
28 | root,
29 | roots,
30 | port,
31 | origins,
32 | keyfile,
33 | certfile,
34 | cafile,
35 | }: Server.Options) {
36 | const app = new Koa()
37 | app.use(logger())
38 | app.use(Cors.create(origins))
39 |
40 | const cache = Cache.create()
41 | const router = new Router()
42 |
43 | if (root) {
44 | console.log('Root:', root)
45 | router.get('/:resource*/ept-tileset/:subpath+', async (ctx) => {
46 | const { resource = '', subpath } = ctx.params
47 | const filename = join(root, resource, 'ept-tileset', subpath)
48 | const options = parseQuery(ctx.query)
49 | ctx.body = await translate({ filename, options, cache })
50 | })
51 | } else if (roots) {
52 | if (roots === '*' || roots.length === 1) console.log('Roots:', roots)
53 | else {
54 | console.log('Roots:')
55 | roots.forEach(root => console.log(` ${root}`))
56 | }
57 | router.get('/roots', async (ctx) => (ctx.body = { roots }))
58 |
59 | router.get('/:subpath+', async (ctx) => {
60 | const { subpath } = ctx.params
61 | const options = parseQuery(ctx.query)
62 |
63 | if (typeof options.ept !== 'string') {
64 | throw new HttpError(400, 'Missing required "ept" parameter')
65 | }
66 |
67 | const ept = normalize(options.ept)
68 |
69 | if (!ept.endsWith('/ept.json')) {
70 | throw new HttpError(400, 'Invalid EPT path - must end with "/ept.json"')
71 | }
72 |
73 | if (roots !== '*' && !roots.some((v) => ept.startsWith(`${v}/`))) {
74 | throw new HttpError(
75 | 403,
76 | `This EPT path is not contained in the allowed roots`
77 | )
78 | }
79 |
80 | const filename = join(ept, '..', 'ept-tileset', subpath)
81 | ctx.body = await translate({ filename, options, cache })
82 | })
83 | }
84 |
85 | app.use(async (ctx, next) => {
86 | try {
87 | await next()
88 | } catch (err) {
89 | ctx.body = { message: err.message || 'Unknown error' }
90 | ctx.status = err.statusCode || 500
91 | }
92 | })
93 |
94 | app.use(router.routes())
95 | app.use(router.allowedMethods())
96 |
97 | const ssl = Ssl.maybeCreate({ keyfile, certfile, cafile })
98 | const server = await Httpx.create(app, port, ssl)
99 |
100 | console.log(`Port: ${port}`)
101 | console.log(`Allowed origins: ${origins}`)
102 | if (ssl) console.log('Using SSL')
103 |
104 | async function destroy() {
105 | await new Promise((resolve) => server.close(resolve))
106 | }
107 |
108 | return { destroy }
109 | }
110 |
--------------------------------------------------------------------------------
/src/3d-tiles/server/ssl.test.ts:
--------------------------------------------------------------------------------
1 | import { EptToolsError } from 'types'
2 |
3 | import { Ssl } from './ssl'
4 |
5 | test('create', () => {
6 | expect(Ssl.maybeCreate()).toBeUndefined()
7 | expect(Ssl.maybeCreate({})).toBeUndefined()
8 |
9 | const keyfile = 'key'
10 | const certfile = 'cert'
11 | const cafile = 'ca'
12 |
13 | expect(() => Ssl.maybeCreate({ keyfile })).toThrow(EptToolsError)
14 | expect(() => Ssl.maybeCreate({ certfile })).toThrow(EptToolsError)
15 | expect(() => Ssl.maybeCreate({ cafile })).toThrow(EptToolsError)
16 |
17 | expect(Ssl.maybeCreate({ keyfile, certfile })).toEqual({ keyfile, certfile })
18 | expect(Ssl.maybeCreate({ keyfile, certfile, cafile })).toEqual({
19 | keyfile,
20 | certfile,
21 | cafile,
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/src/3d-tiles/server/ssl.ts:
--------------------------------------------------------------------------------
1 | import { EptToolsError } from 'types'
2 |
3 | export declare namespace Ssl {
4 | export type Options = { keyfile: string; certfile: string; cafile?: string }
5 | }
6 | export const Ssl = { maybeCreate }
7 |
8 | function maybeCreate({ keyfile, certfile, cafile }: Partial = {}):
9 | | Ssl.Options
10 | | undefined {
11 | if (keyfile || certfile) {
12 | if (!keyfile || !certfile) {
13 | throw new EptToolsError(
14 | 'If SSL keyfile or certfile are provided, then both must be provided'
15 | )
16 | }
17 |
18 | return { keyfile, certfile, cafile }
19 | }
20 |
21 | if (cafile) {
22 | throw new EptToolsError(
23 | 'Cannot provide cafile without keyfile and certfile'
24 | )
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/3d-tiles/server/utils.ts:
--------------------------------------------------------------------------------
1 | import { Options } from '3d-tiles'
2 | import { normalize } from 'protopath'
3 | import { ParsedUrlQuery } from 'querystring'
4 | import { EptToolsError } from 'types'
5 |
6 | export function parseQuery(q: ParsedUrlQuery) {
7 | const options: Partial = {}
8 |
9 | const {
10 | ept,
11 | 'z-offset': zOffset,
12 | dimensions: dimstring,
13 | truncate,
14 | ...rest
15 | } = q
16 |
17 | if (typeof ept === 'string') {
18 | options.ept = normalize(ept)
19 | }
20 |
21 | if (typeof zOffset === 'string') {
22 | options.zOffset = parseFloat(zOffset)
23 | if (Number.isNaN(options.zOffset)) {
24 | throw new EptToolsError(`Invalid Z-offset: ${zOffset}`)
25 | }
26 | }
27 |
28 | if (typeof dimstring === 'string') {
29 | options.dimensions = dimstring.split(',').map((s) => s.trim())
30 | }
31 |
32 | if (typeof truncate === 'string') {
33 | // This option may be passed as one of the following:
34 | // - ?truncate
35 | // - ?truncate=true (or false)
36 | // - ?truncate=1 (or 0)
37 | //
38 | // Other values are invalid. The valueless version arrives here as ''.
39 |
40 | if (!['', 'true', 'false', '1', '0'].includes(truncate)) {
41 | throw new EptToolsError(`Invalid "truncate" setting: ${truncate}`)
42 | }
43 | options.truncate = ['', 'true', '1'].includes(truncate)
44 | }
45 |
46 | return { ...options, rest }
47 | }
48 |
--------------------------------------------------------------------------------
/src/3d-tiles/tileset/constants.ts:
--------------------------------------------------------------------------------
1 | export const geometricErrorDivisor = 16
2 |
--------------------------------------------------------------------------------
/src/3d-tiles/tileset/index.ts:
--------------------------------------------------------------------------------
1 | export { Tile } from './tile'
2 | export { Tileset } from './tileset'
3 |
--------------------------------------------------------------------------------
/src/3d-tiles/tileset/tile.ts:
--------------------------------------------------------------------------------
1 | import { Bounds, Hierarchy, Key, Step } from 'ept'
2 | import { Reproject } from 'utils'
3 |
4 | import { BoundingVolume } from '3d-tiles/bounding-volume'
5 |
6 | const steps: Step[] = [
7 | [0, 0, 0],
8 | [0, 0, 1],
9 | [0, 1, 0],
10 | [0, 1, 1],
11 | [1, 0, 0],
12 | [1, 0, 1],
13 | [1, 1, 0],
14 | [1, 1, 1],
15 | ]
16 |
17 | export declare namespace Tile {
18 | export type TranslateOptions = {
19 | bounds: Bounds
20 | code: string
21 | hierarchy: Hierarchy
22 | key: Key
23 | geometricError: number
24 | }
25 | export type Content = { uri: string }
26 | }
27 |
28 | export type Tile = {
29 | content: Tile.Content
30 | children?: Tile[]
31 | boundingVolume: BoundingVolume
32 | geometricError: number
33 | refine?: 'ADD' | 'REPLACE'
34 | }
35 |
36 | export const Tile = { translate }
37 |
38 | function translate({
39 | bounds,
40 | code,
41 | hierarchy,
42 | key,
43 | geometricError,
44 | }: Tile.TranslateOptions): Tile {
45 | const reproject = Reproject.create(code, 'EPSG:4326')
46 | const region = BoundingVolume.Region.fromWgs84(
47 | Bounds.reproject(bounds, reproject)
48 | )
49 |
50 | const children = steps.reduce((children, step) => {
51 | const nextKey = Key.step(key, step)
52 | const points = hierarchy[Key.stringify(nextKey)]
53 | if (!points) return children
54 | const nextBounds = Bounds.step(bounds, step)
55 |
56 | children.push(
57 | translate({
58 | code,
59 | hierarchy,
60 | bounds: nextBounds,
61 | key: nextKey,
62 | geometricError: geometricError / 2,
63 | })
64 | )
65 | return children
66 | }, [])
67 |
68 | const points = hierarchy[Key.stringify(key)]
69 | const extension = points === -1 ? 'json' : 'pnts'
70 |
71 | const tile: Tile = {
72 | content: { uri: `${Key.stringify(key)}.${extension}` },
73 | boundingVolume: { region },
74 | geometricError,
75 | children,
76 | }
77 | if (Key.depth(key) === 0) tile.refine = 'ADD'
78 | return tile
79 | }
80 |
--------------------------------------------------------------------------------
/src/3d-tiles/tileset/tileset.test.ts:
--------------------------------------------------------------------------------
1 | import { BoundingVolume } from '3d-tiles'
2 | import { Bounds, Ept, Hierarchy, Key } from 'ept'
3 | import { Ellipsoid } from 'test'
4 | import { Reproject } from 'utils'
5 |
6 | import { Tileset } from './tileset'
7 |
8 | test('z offset', () => {
9 | const key = Key.create()
10 | const ept: Ept = { ...Ellipsoid.ept, dataType: 'binary' }
11 | const hierarchy: Hierarchy = { '0-0-0-0': 1, '1-0-0-0': 1 }
12 | const zOffset = 50
13 | const options = { zOffset }
14 |
15 | const tileset = Tileset.translate({ key, ept, hierarchy, options })
16 |
17 | const { bounds } = ept
18 | bounds[2] += zOffset
19 | bounds[5] += zOffset
20 | const toWgs84 = Reproject.create(Ellipsoid.srsCodeString, 'EPSG:4326')
21 | const wgs84 = Bounds.reproject(bounds, toWgs84)
22 | const region = BoundingVolume.Region.fromWgs84(wgs84)
23 |
24 | const childRegion = BoundingVolume.Region.fromWgs84(
25 | Bounds.reproject(Bounds.stepTo(bounds, Key.create(1)), toWgs84)
26 | )
27 |
28 | expect(tileset).toMatchObject({
29 | root: {
30 | boundingVolume: { region },
31 | children: [{ boundingVolume: { region: childRegion } }],
32 | },
33 | })
34 | })
35 |
36 | test('failure: missing srs', async () => {
37 | // Remove the SRS from the EPT data.
38 | const { srs, ...partial } = Ellipsoid.ept
39 | const ept: Ept = { ...partial, dataType: 'laszip' }
40 | expect(() =>
41 | Tileset.translate({
42 | ept,
43 | hierarchy: Ellipsoid.rootHierarchy,
44 | key: Key.create(),
45 | options: {},
46 | })
47 | ).toThrow(/without an srs/i)
48 | })
49 |
--------------------------------------------------------------------------------
/src/3d-tiles/tileset/tileset.ts:
--------------------------------------------------------------------------------
1 | import { Bounds, Ept, Hierarchy, Key, Schema, Srs } from 'ept'
2 | import { EptToolsError } from 'types'
3 |
4 | import { Options } from '../types'
5 | import * as Constants from './constants'
6 | import { Tile } from './tile'
7 |
8 | export declare namespace Tileset {
9 | export type Create = {
10 | key: Key
11 | ept: Ept
12 | hierarchy: Hierarchy
13 | options: Partial
14 | }
15 | export type Version = '1.0'
16 | export type Asset = {
17 | version: Version
18 | [key: string]: unknown
19 | }
20 | }
21 |
22 | export type Tileset = {
23 | root: Tile
24 | geometricError: number
25 | asset: Tileset.Asset
26 | properties?: object
27 | }
28 | export const Tileset = { Constants, translate }
29 |
30 | function translate({
31 | key,
32 | ept,
33 | hierarchy,
34 | options: { zOffset = 0, dimensions = [], truncate = false } = {},
35 | }: Tileset.Create): Tileset {
36 | const rootGeometricError =
37 | Bounds.width(ept.bounds) / Constants.geometricErrorDivisor
38 | const geometricError = rootGeometricError / Math.pow(2, Key.depth(key))
39 |
40 | const bounds = Bounds.stepTo(Bounds.offsetHeight(ept.bounds, zOffset), key)
41 | const code = Srs.horizontalCodeString(ept.srs)
42 | if (!code) throw new EptToolsError('Cannot translate without an SRS code')
43 |
44 | // See "Tileset Properties" in section 2 of
45 | // https://github.com/CesiumGS/3d-tiles/blob/master/3d-tiles-overview.pdf.
46 | const root = Tile.translate({ bounds, code, hierarchy, key, geometricError })
47 |
48 | dimensions = dimensions.filter((name) => {
49 | if (Schema.has(ept.schema, name)) return true
50 | if (
51 | ['Synthetic', 'KeyPoint', 'Withheld'].includes(name) &&
52 | Schema.has(ept.schema, 'Classification')
53 | ) {
54 | return true
55 | }
56 | return false
57 | })
58 |
59 | const metadata = {
60 | software: 'EPT Tools',
61 | ept,
62 | options: { zOffset, dimensions, truncate },
63 | }
64 | const asset: Tileset.Asset = {
65 | version: '1.0',
66 | ...(Key.depth(key) === 0 ? metadata : undefined),
67 | }
68 |
69 | return { root, geometricError, asset }
70 | }
71 |
--------------------------------------------------------------------------------
/src/3d-tiles/translate.ts:
--------------------------------------------------------------------------------
1 | import { basename, dirname, join } from 'protopath'
2 |
3 | import { Bounds, DataType, Ept, Hierarchy, Key, Srs } from 'ept'
4 | import { EptToolsError } from 'types'
5 | import { JsonSchema, Reproject, getBinary, getJson } from 'utils'
6 |
7 | import { Cache } from './cache'
8 | import { Pnts } from './pnts'
9 | import { Tileset } from './tileset'
10 | import { Options } from './types'
11 |
12 | type Translate = {
13 | filename: string
14 | options?: Partial
15 | cache?: Cache
16 | }
17 |
18 | /**
19 | * Generates a 3D-Tiles file translation of an EPT dataset at the virtual path
20 | * /ept-tileset/. So the virtual "tileset.json" for an EPT
21 | * dataset at path "\~/entwine/autzen/ept.json" would be at
22 | * "\~/entwine/autzen/ept-tileset/tileset.json".
23 | */
24 | export async function translate({ filename, cache, options = {} }: Translate) {
25 | const tilesetdir = dirname(filename)
26 | if (!tilesetdir.endsWith('ept-tileset')) {
27 | throw new EptToolsError(`Invalid virtual tileset path: ${filename}`)
28 | }
29 | const eptdir = join(tilesetdir, '..')
30 | const eptfilename = join(eptdir, 'ept.json')
31 | const ept =
32 | (await cache?.get(eptfilename)) ||
33 | JsonSchema.validate(Ept.schema, await getJson(eptfilename))[0]
34 |
35 | const { bounds, dataType, schema, srs } = ept
36 | const codeString = Srs.horizontalCodeString(srs)
37 | if (!codeString) {
38 | throw new EptToolsError('Cannot translate to 3D Tiles without an SRS code')
39 | }
40 |
41 | const tilename = basename(filename)
42 | const [root, extension] = tilename.split('.')
43 |
44 | // If the extension is JSON, then our result is a translated tileset. This
45 | // includes metadata information as well as a translated hierarchy structure.
46 | if (extension === 'json') {
47 | const key = root === 'tileset' ? Key.create() : Key.parse(root)
48 | const hierarchy = JsonSchema.validate(
49 | Hierarchy.schema,
50 | await getJson(join(eptdir, 'ept-hierarchy', `${Key.stringify(key)}.json`))
51 | )[0]
52 | return Tileset.translate({ ept, hierarchy, key, options })
53 | }
54 |
55 | if (extension !== 'pnts') {
56 | throw new EptToolsError(`Invalid file extension: ${extension}`)
57 | }
58 |
59 | // Otherwise, we are returning binary point data for a single node. First
60 | // download the contents of the EPT node and then we'll translate its points
61 | // into 3D Tiles "pnts" format.
62 | const key = Key.parse(root)
63 | const bufferExtension = DataType.extension(dataType)
64 | const buffer = await getBinary(
65 | join(eptdir, 'ept-data', `${root}.${bufferExtension}`)
66 | )
67 |
68 | const view = await DataType.view(dataType, buffer, schema)
69 | const tileBounds = Bounds.stepTo(bounds, key)
70 | const toEcef = Reproject.create(codeString, 'EPSG:4978')
71 | return Pnts.translate({ view, tileBounds, toEcef, options })
72 | }
73 |
--------------------------------------------------------------------------------
/src/3d-tiles/types.ts:
--------------------------------------------------------------------------------
1 | import { Bounds, View } from 'ept'
2 | import { Reproject } from 'utils'
3 |
4 | export type Addon = [string, string]
5 | export type Addons = Addon[]
6 | export type Options = {
7 | ept?: string
8 | zOffset: number
9 | dimensions: string[]
10 | addons: Addons
11 | truncate: boolean
12 | }
13 |
14 | export type Params = {
15 | view: View.Readable
16 | tileBounds: Bounds
17 | toEcef: Reproject
18 | options: Partial
19 | }
20 |
--------------------------------------------------------------------------------
/src/3d-tiles/utils.test.ts:
--------------------------------------------------------------------------------
1 | import * as U from './utils'
2 |
3 | test('pad end', () => {
4 | expect(U.padEnd(Buffer.alloc(0))).toEqual(Buffer.alloc(0))
5 | expect(U.padEnd(Buffer.alloc(1))).toEqual(Buffer.alloc(8))
6 | expect(U.padEnd(Buffer.alloc(8))).toEqual(Buffer.alloc(8))
7 |
8 | expect(U.padEnd(Buffer.alloc(4), 0x20)).toEqual(
9 | Buffer.concat([Buffer.alloc(4), Buffer.alloc(4, 0x20)])
10 | )
11 | })
12 |
13 | test('sum lengths', () => {
14 | expect(U.sumLengths([])).toEqual(0)
15 | expect(U.sumLengths([Buffer.alloc(0)])).toEqual(0)
16 | expect(U.sumLengths([Buffer.alloc(1), Buffer.alloc(2)])).toEqual(3)
17 | })
18 |
--------------------------------------------------------------------------------
/src/3d-tiles/utils.ts:
--------------------------------------------------------------------------------
1 | // Both the feature table and the batch table JSON must be padded to a multiple
2 | // of 8 bytes with the character 0x20. Their binary complements must also be
3 | // padded to a multiple of 8 bytes, but with any value. We'll choose 0.
4 | //
5 | // See https://git.io/JIjB7 and https://git.io/JIjBj.
6 | //
7 | export function padEnd(b: Buffer, c = 0): Buffer {
8 | const remainder = b.length % 8
9 | if (!remainder) return b
10 | return Buffer.concat([b, Buffer.alloc(8 - remainder, c)])
11 | }
12 |
13 | export function sumLengths(buffers: Buffer[]) {
14 | return buffers.reduce((sum, buffer) => sum + buffer.length, 0)
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { Cli } from './cli'
3 | export * as Lambda from './lambda'
4 |
5 | export { Cli }
6 |
7 | process.title = 'ept-tools'
8 | process.on('SIGINT', () => process.exit())
9 |
10 | const isCliInvocation = require.main === module
11 | if (isCliInvocation) Cli.run()
12 |
--------------------------------------------------------------------------------
/src/app/lambda.ts:
--------------------------------------------------------------------------------
1 | import { APIGatewayProxyEventV2, APIGatewayProxyResult } from 'aws-lambda'
2 | import { join } from 'protopath'
3 | import util from 'util'
4 | import zlib from 'zlib'
5 |
6 | import * as Cesium from '3d-tiles'
7 |
8 | const gzipAsync = util.promisify(zlib.gzip)
9 |
10 | export async function handler(
11 | event: APIGatewayProxyEventV2
12 | // context: APIGatewayEventRequestContext
13 | ): Promise {
14 | const root = process.env.ROOT
15 | if (!root) throw new Error('Invalid root path')
16 |
17 | const subpath = event.pathParameters?.filename
18 | if (!subpath) throw new Error('Invalid filename')
19 |
20 | const filename = join(root, subpath)
21 | const options = Cesium.parseQuery(event.queryStringParameters || {})
22 |
23 | console.log('Filename:', filename)
24 | console.log('Options:', options)
25 |
26 | const data = await Cesium.translate({ filename, options })
27 |
28 | const isCompressed =
29 | event.headers['accept-encoding']
30 | ?.split(',')
31 | .map((s: string) => s.trim())
32 | .includes('gzip') || false
33 |
34 | return data instanceof Buffer
35 | ? formatBufferResponse(data, isCompressed)
36 | : formatJsonResponse(data, false)
37 | }
38 |
39 | async function formatBufferResponse(
40 | data: Buffer,
41 | isCompressed: boolean
42 | ): Promise {
43 | const body = isCompressed ? await gzipAsync(data) : data
44 |
45 | const headers: Record = {
46 | 'Content-Type': 'application/octet-stream',
47 | ...(isCompressed && { 'Content-Encoding': 'gzip' }),
48 | }
49 |
50 | return {
51 | statusCode: 200,
52 | headers,
53 | isBase64Encoded: true,
54 | body: body.toString('base64'),
55 | }
56 | }
57 |
58 | async function formatJsonResponse(
59 | data: unknown,
60 | isCompressed: boolean
61 | ): Promise {
62 | const stringified = JSON.stringify(data)
63 | const body = isCompressed
64 | ? await gzipAsync(stringified)
65 | : Buffer.from(stringified)
66 |
67 | const headers: Record = {
68 | 'Content-Type': 'application/json',
69 | ...(isCompressed && { 'Content-Encoding': 'gzip' }),
70 | }
71 |
72 | console.log('Data:', JSON.stringify(data, null, 2))
73 | console.log('Compressed:', body.length / stringified.length)
74 |
75 | return {
76 | statusCode: 200,
77 | headers,
78 | body: body.toString('utf8'),
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/app/tile.ts:
--------------------------------------------------------------------------------
1 | import { Forager } from 'forager'
2 | import { mkdirp } from 'fs-extra'
3 | import { getProtocol, getStem, join } from 'protopath'
4 |
5 | import * as Cesium from '3d-tiles'
6 | import { EptToolsError } from 'types'
7 | import { Pool, isReadable } from 'utils'
8 |
9 | type Tile = {
10 | input: string
11 | output: string
12 | threads: number
13 | force: boolean
14 | verbose: boolean
15 | options?: Partial
16 | }
17 | export async function tile(args: Tile) {
18 | const { force, verbose, output } = args
19 | if (!force && (await isReadable(join(output, 'tileset.json')))) {
20 | throw new EptToolsError('Output already exists - use --force to overwrite')
21 | }
22 |
23 | const protocol = getProtocol(output) || 'file'
24 | if (protocol === 'file') await mkdirp(output)
25 |
26 | // Metadata.
27 | if (verbose) {
28 | console.log('Translating metadata...')
29 | console.time('Metadata')
30 | }
31 |
32 | const cache = Cesium.Cache.create(0)
33 | await translateMetadata({ ...args, cache })
34 |
35 | if (verbose) console.timeEnd('Metadata')
36 |
37 | // Points.
38 | if (verbose) {
39 | console.log('Translating points...')
40 | console.time('Points')
41 | }
42 |
43 | await translatePoints({ ...args, cache })
44 |
45 | if (verbose) console.timeEnd('Points')
46 | }
47 |
48 | type Args = Tile & { cache: Cesium.Cache }
49 | async function translateMetadata({
50 | input,
51 | output,
52 | threads,
53 | options,
54 | verbose,
55 | cache,
56 | }: Args) {
57 | const root = join(input, 'ept-hierarchy')
58 | const list = (await Forager.list(root)).map(({ path }) =>
59 | path === '0-0-0-0.json' ? 'tileset.json' : path
60 | )
61 |
62 | return Pool.all(
63 | list.map((filename, i) => async () => {
64 | if (verbose) console.log(`${i}/${list.length}:`, filename)
65 |
66 | const data = await Cesium.translate({
67 | filename: join(input, 'ept-tileset', filename),
68 | options,
69 | cache,
70 | })
71 |
72 | if (data instanceof Buffer) {
73 | throw new EptToolsError(`Unexpected response type during ${filename}`)
74 | }
75 |
76 | return Forager.write(join(output, filename), JSON.stringify(data))
77 | }),
78 | threads
79 | )
80 | }
81 |
82 | async function translatePoints({
83 | input,
84 | output,
85 | threads,
86 | options,
87 | verbose,
88 | cache,
89 | }: Args) {
90 | const root = join(input, 'ept-data')
91 | const list = (await Forager.list(root)).map(
92 | ({ path }) => getStem(path) + '.pnts'
93 | )
94 |
95 | return Pool.all(
96 | list.map((filename, i) => async () => {
97 | if (verbose) console.log(`${i}/${list.length}:`, filename)
98 |
99 | const data = await Cesium.translate({
100 | filename: join(input, 'ept-tileset', filename),
101 | options,
102 | cache,
103 | })
104 |
105 | if (!(data instanceof Buffer)) {
106 | throw new EptToolsError(`Unexpected response type during ${filename}`)
107 | }
108 |
109 | return Forager.write(join(output, filename), data)
110 | }),
111 | threads
112 | )
113 | }
114 |
--------------------------------------------------------------------------------
/src/app/upgrade.test.ts:
--------------------------------------------------------------------------------
1 | import { Forager } from 'forager'
2 | import { copy, mkdir, remove } from 'fs-extra'
3 | import { join } from 'protopath'
4 |
5 | import { Ept, Source } from 'ept'
6 | import { JsonSchema } from 'utils'
7 |
8 | import { upgradeDir, upgradeOne } from './upgrade'
9 |
10 | const datadir = join(__dirname, '../test/data')
11 | const tmpdir = join(datadir, 'tmp')
12 |
13 | const oldsourcedir = join(datadir, 'v1.0.0')
14 | const mixsourcedir = join(datadir, 'vmixed')
15 | const newsourcedir = join(datadir, 'v1.1.0')
16 |
17 | const olddir = join(tmpdir, 'old')
18 | const mixdir = join(tmpdir, 'mix')
19 | const newdir = join(tmpdir, 'new')
20 |
21 | beforeEach(async () => {
22 | await remove(tmpdir)
23 |
24 | await copy(oldsourcedir, olddir)
25 | await copy(mixsourcedir, mixdir)
26 | await copy(newsourcedir, newdir)
27 |
28 | await mkdir(join(tmpdir, 'junk'))
29 | await Forager.write(join(tmpdir, 'junk/ept.json'), 'Junk')
30 | })
31 |
32 | afterEach(async () => {
33 | await remove(tmpdir)
34 | })
35 |
36 | test('new', async () => {
37 | // This dataset is already v1.1.0, so nothing should change, and nothing
38 | // should be backed up.
39 | const isUpgraded = await upgradeOne({ filename: join(newdir, 'ept.json') })
40 | expect(isUpgraded).toBe(false)
41 |
42 | // No backup should have been made.
43 | await expect(Forager.list(join(newdir, 'ept-backup'))).rejects.toThrow()
44 |
45 | // All files should be exactly the same as before.
46 | const files = await Forager.list(newsourcedir, true)
47 | for (const { path } of files) {
48 | expect(await Forager.read(join(newsourcedir, path))).toEqual(
49 | await Forager.read(join(newdir, path))
50 | )
51 | }
52 | {
53 | const [, errors] = JsonSchema.validate(
54 | Source.V0.summary.schema,
55 | await Forager.readJson(join(newdir, 'ept-sources/list.json'))
56 | )
57 | expect(errors).toHaveLength(0)
58 | }
59 | })
60 |
61 | test('old', async () => {
62 | const isUpgraded = await upgradeOne({ filename: join(olddir, 'ept.json') })
63 | expect(isUpgraded).toBe(true)
64 |
65 | // We should have a backup which is identical to the original contents.
66 | {
67 | const files = (await Forager.list(join(oldsourcedir), true))
68 | .map((v) => v.path)
69 | .filter((v) => !v.includes('ept-hierarchy'))
70 |
71 | for (const filename of files) {
72 | const src = await Forager.read(join(oldsourcedir, filename))
73 | const dst = await Forager.read(join(olddir, 'ept-backup', filename))
74 | if (Buffer.compare(src, dst)) console.log(`${filename} does not match`)
75 | expect(Buffer.compare(src, dst)).toEqual(0)
76 | }
77 | }
78 |
79 | const [ept, epterrors] = JsonSchema.validate(
80 | Ept.schema,
81 | await Forager.readJson(join(olddir, 'ept.json'))
82 | )
83 | expect(ept.version).toEqual('1.1.0')
84 | expect(epterrors).toHaveLength(0)
85 |
86 | const [, errors] = JsonSchema.validate(
87 | Source.summary.schema,
88 | await Forager.readJson(join(olddir, 'ept-sources/manifest.json'))
89 | )
90 | expect(errors).toHaveLength(0)
91 | })
92 |
93 | test('mix', async () => {
94 | const isUpgraded = await upgradeOne({ filename: join(mixdir, 'ept.json') })
95 | expect(isUpgraded).toBe(true)
96 |
97 | // We should have a backup which is identical to the original contents.
98 | {
99 | const files = (await Forager.list(join(mixsourcedir), true))
100 | .map((v) => v.path)
101 | .filter((v) => !v.includes('ept-hierarchy'))
102 |
103 | for (const filename of files) {
104 | const src = await Forager.read(join(mixsourcedir, filename))
105 | const dst = await Forager.read(join(mixdir, 'ept-backup', filename))
106 | if (Buffer.compare(src, dst)) console.log(`${filename} does not match`)
107 | expect(Buffer.compare(src, dst)).toEqual(0)
108 | }
109 | }
110 |
111 | const [ept, epterrors] = JsonSchema.validate(
112 | Ept.schema,
113 | await Forager.readJson(join(mixdir, 'ept.json'))
114 | )
115 | expect(ept.version).toEqual('1.1.0')
116 | expect(epterrors).toHaveLength(0)
117 |
118 | {
119 | const [, errors] = JsonSchema.validate(
120 | Source.summary.schema,
121 | await Forager.readJson(join(mixdir, 'ept-sources/manifest.json'))
122 | )
123 | expect(errors).toHaveLength(0)
124 | }
125 | {
126 | const [, errors] = JsonSchema.validate(
127 | Source.V0.summary.schema,
128 | await Forager.readJson(join(mixdir, 'ept-sources/list.json'))
129 | )
130 | expect(errors).toHaveLength(0)
131 | }
132 | })
133 |
134 | test('dir', async () => {
135 | const results = await upgradeDir({ dir: tmpdir, verbose: false })
136 | expect(results).toHaveLength(4)
137 |
138 | const o = results.find((v) => v.subdir === 'old')
139 | const m = results.find((v) => v.subdir === 'mix')
140 | const n = results.find((v) => v.subdir === 'new')
141 | const j = results.find((v) => v.subdir === 'junk')
142 |
143 | expect(o).toEqual({ subdir: 'old', isUpgraded: true })
144 | expect(m).toEqual({ subdir: 'mix', isUpgraded: true })
145 | expect(n).toEqual({ subdir: 'new', isUpgraded: false })
146 | expect(typeof j?.error === 'string').toBe(true)
147 | })
148 |
149 | test('skip', async () => {
150 | const results = await upgradeDir({ dir: tmpdir, verbose: false, skip: 2 })
151 | expect(results).toHaveLength(2)
152 |
153 | const n = results.find((v) => v.subdir === 'new')
154 | const o = results.find((v) => v.subdir === 'old')
155 |
156 | expect(o).toEqual({ subdir: 'old', isUpgraded: true })
157 | expect(n).toEqual({ subdir: 'new', isUpgraded: false })
158 | })
159 |
--------------------------------------------------------------------------------
/src/app/validate.ts:
--------------------------------------------------------------------------------
1 | import symbols from 'log-symbols'
2 |
3 | import { Ept } from 'ept'
4 | import { JsonSchema, getJson } from 'utils'
5 |
6 | export async function validate(input: string) {
7 | const [, errors] = JsonSchema.validate(Ept.schema, await getJson(input))
8 |
9 | if (errors.length) {
10 | console.log(symbols.error, 'Errors:')
11 | errors.forEach((v) => console.log(`\t• ${v}`))
12 | console.log()
13 |
14 | console.log(symbols.error, 'EPT is not valid')
15 | process.exit(1)
16 | } else console.log(symbols.success, 'EPT appears to be valid')
17 | }
18 |
--------------------------------------------------------------------------------
/src/ept/bounds.test.ts:
--------------------------------------------------------------------------------
1 | import { Bounds } from './bounds'
2 |
3 | test('extractions', () => {
4 | const b: Bounds = [0, 1, 2, 6, 7, 8]
5 | expect(Bounds.min(b)).toEqual([0, 1, 2])
6 | expect(Bounds.max(b)).toEqual([6, 7, 8])
7 | expect(Bounds.mid(b)).toEqual([3, 4, 5])
8 | })
9 |
10 | test('measures', () => {
11 | const b: Bounds = [0, 0, 0, 1, 2, 3]
12 | expect(Bounds.width(b)).toEqual(1)
13 | expect(Bounds.depth(b)).toEqual(2)
14 | expect(Bounds.height(b)).toEqual(3)
15 | })
16 |
--------------------------------------------------------------------------------
/src/ept/bounds.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'ajv'
2 | import { Point } from 'types'
3 | import { Reproject } from 'utils'
4 |
5 | import { Key } from './key'
6 | import { Step } from './step'
7 |
8 | export type Bounds = [...Point, ...Point] // Min, max.
9 | const schema: Schema = {
10 | title: 'Bounds',
11 | description:
12 | 'Bounding volume of the form [xmin, ymin, zmin, xmax, ymax, zmax]',
13 | type: 'array',
14 | items: { type: 'number' },
15 | minItems: 6,
16 | maxItems: 6,
17 | }
18 |
19 | export const Bounds = {
20 | schema,
21 | min,
22 | max,
23 | mid,
24 | width,
25 | depth,
26 | height,
27 | step,
28 | stepTo,
29 | reproject,
30 | offsetHeight,
31 | }
32 |
33 | function min(b: Bounds): Point {
34 | return [b[0], b[1], b[2]]
35 | }
36 | function max(b: Bounds): Point {
37 | return [b[3], b[4], b[5]]
38 | }
39 | function mid([minx, miny, minz, maxx, maxy, maxz]: Bounds): Point {
40 | return [
41 | minx + (maxx - minx) / 2,
42 | miny + (maxy - miny) / 2,
43 | minz + (maxz - minz) / 2,
44 | ]
45 | }
46 |
47 | function width(bounds: Bounds) {
48 | return bounds[3] - bounds[0]
49 | }
50 | function depth(bounds: Bounds) {
51 | return bounds[4] - bounds[1]
52 | }
53 | function height(bounds: Bounds) {
54 | return bounds[5] - bounds[2]
55 | }
56 |
57 | function step(bounds: Bounds, [a, b, c]: Step): Bounds {
58 | const [minx, miny, minz, maxx, maxy, maxz] = bounds
59 | const [midx, midy, midz] = mid(bounds)
60 |
61 | return [
62 | a ? midx : minx,
63 | b ? midy : miny,
64 | c ? midz : minz,
65 | a ? maxx : midx,
66 | b ? maxy : midy,
67 | c ? maxz : midz,
68 | ]
69 | }
70 |
71 | function stepTo(bounds: Bounds, [d, x, y, z]: Key) {
72 | for (let i = d - 1; i >= 0; --i) {
73 | bounds = step(bounds, [(x >> i) & 1, (y >> i) & 1, (z >> i) & 1] as Step)
74 | }
75 | return bounds
76 | }
77 |
78 | function reproject(bounds: Bounds, reproject: Reproject): Bounds {
79 | return [...reproject(min(bounds)), ...reproject(max(bounds))]
80 | }
81 |
82 | function offsetHeight(b: Bounds, zOffset: number): Bounds {
83 | return [b[0], b[1], b[2] + zOffset, b[3], b[4], b[5] + zOffset]
84 | }
85 |
--------------------------------------------------------------------------------
/src/ept/data-type/binary.test.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from '../../ept'
2 |
3 | import { Binary } from './binary'
4 |
5 | const scale = 0.1
6 | const offset = 100
7 | const schema: Schema = [
8 | { name: 'i64', type: 'signed', size: 8 },
9 | { name: 'i32', type: 'signed', size: 4 },
10 | { name: 'i16', type: 'signed', size: 2 },
11 | { name: 'i8', type: 'signed', size: 1 },
12 | { name: 'u64', type: 'unsigned', size: 8 },
13 | { name: 'u32', type: 'unsigned', size: 4 },
14 | { name: 'u16', type: 'unsigned', size: 2 },
15 | { name: 'u8', type: 'unsigned', size: 1 },
16 | { name: 'f64', type: 'float', size: 8 },
17 | { name: 'f32', type: 'float', size: 4 },
18 | { name: 's32', type: 'signed', size: 4, scale, offset },
19 | ]
20 | const pointSize = Schema.pointSize(schema)
21 |
22 | test('invalid', () => {
23 | expect(() => Binary.view(Buffer.alloc(pointSize), [])).toThrow(
24 | /invalid schema point size/i
25 | )
26 | expect(() => Binary.view(Buffer.alloc(pointSize - 1), schema)).toThrow(
27 | /invalid buffer length/i
28 | )
29 | })
30 |
31 | test('get', () => {
32 | // We'll only write into the second point.
33 | const buffer = Buffer.alloc(pointSize * 2)
34 |
35 | let value = 42
36 | buffer.writeBigInt64LE(BigInt(value++), pointSize + 0)
37 | buffer.writeInt32LE(value++, pointSize + 8)
38 | buffer.writeInt16LE(value++, pointSize + 12)
39 | buffer.writeInt8(value++, pointSize + 14)
40 | buffer.writeBigUInt64LE(BigInt(value++), pointSize + 15 + 0)
41 | buffer.writeUInt32LE(value++, pointSize + 15 + 8)
42 | buffer.writeUInt16LE(value++, pointSize + 15 + 12)
43 | buffer.writeUInt8(value++, pointSize + 15 + 14)
44 | buffer.writeDoubleLE(value++, pointSize + 15 + 15)
45 | buffer.writeFloatLE(value++, pointSize + 15 + 15 + 8)
46 | buffer.writeInt32LE((value - offset) / scale, pointSize + 15 + 15 + 12)
47 |
48 | const view = Binary.view(buffer, schema)
49 | expect(() => view.getter('bad')(0)).toThrow(/invalid dimension/i)
50 | expect(() => view.getter('f32')(2)).toThrow(/invalid point index/i)
51 |
52 | value = 42
53 | expect(view.getter('i64')(1)).toEqual(value++)
54 | expect(view.getter('i32')(1)).toEqual(value++)
55 | expect(view.getter('i16')(1)).toEqual(value++)
56 | expect(view.getter('i8')(1)).toEqual(value++)
57 | expect(view.getter('u64')(1)).toEqual(value++)
58 | expect(view.getter('u32')(1)).toEqual(value++)
59 | expect(view.getter('u16')(1)).toEqual(value++)
60 | expect(view.getter('u8')(1)).toEqual(value++)
61 | expect(view.getter('f64')(1)).toEqual(value++)
62 | expect(view.getter('f32')(1)).toEqual(value++)
63 | expect(view.getter('s32')(1)).toEqual(value++)
64 | })
65 |
--------------------------------------------------------------------------------
/src/ept/data-type/binary.ts:
--------------------------------------------------------------------------------
1 | import { View } from '../view'
2 |
3 | export const type = 'binary'
4 | export const extension = 'bin'
5 |
6 | export const Binary = { view: View.Readable.create }
7 |
--------------------------------------------------------------------------------
/src/ept/data-type/index.test.ts:
--------------------------------------------------------------------------------
1 | import { DataType } from 'ept'
2 |
3 | test('view: invalid type', async () => {
4 | await expect(() =>
5 | DataType.view('asdf' as any, Buffer.alloc(0), [])
6 | ).rejects.toThrow(/invalid data type/i)
7 | })
8 |
--------------------------------------------------------------------------------
/src/ept/data-type/index.ts:
--------------------------------------------------------------------------------
1 | import { Schema as JsonSchema } from 'ajv'
2 |
3 | import { Schema } from '../schema'
4 | import { View } from '../view'
5 |
6 | import { Binary } from './binary'
7 | import { Laszip } from './laszip'
8 | import { Zstandard } from './zstandard'
9 |
10 | export type DataType = 'binary' | 'laszip' | 'zstandard'
11 | const schema: JsonSchema = {
12 | title: 'Data type',
13 | description: 'Point data encoding',
14 | type: 'string',
15 | enum: ['binary', 'laszip', 'zstandard'],
16 | }
17 | export const DataType = { schema, extension, view }
18 |
19 | const extensions = { binary: 'bin', laszip: 'laz', zstandard: 'zst' }
20 | function extension(type: DataType): string {
21 | return extensions[type]
22 | }
23 |
24 | async function view(
25 | dataType: DataType,
26 | buffer: Buffer,
27 | schema: Schema
28 | ): Promise {
29 | switch (dataType) {
30 | case 'binary':
31 | return Binary.view(buffer, schema)
32 | case 'laszip':
33 | return Laszip.view(buffer)
34 | case 'zstandard':
35 | return Zstandard.view(buffer, schema)
36 | default:
37 | throw new Error(`Invalid data type ${dataType}`)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/ept/data-type/laszip/format.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'ept/schema'
2 | import { EptToolsError } from 'types'
3 |
4 | import { Header } from './header'
5 |
6 | export function create(header: Header) {
7 | const { dataFormatId } = header
8 | switch (dataFormatId) {
9 | case 0:
10 | return create0(header)
11 | case 1:
12 | return create1(header)
13 | case 2:
14 | return create2(header)
15 | case 3:
16 | return create3(header)
17 | default:
18 | throw new EptToolsError(`Unsupported LAS data format: ${dataFormatId}`)
19 | }
20 | }
21 |
22 | function create0({ scale, offset }: Header): Schema {
23 | return [
24 | { name: 'X', type: 'signed', size: 4, scale: scale[0], offset: offset[0] },
25 | { name: 'Y', type: 'signed', size: 4, scale: scale[1], offset: offset[1] },
26 | { name: 'Z', type: 'signed', size: 4, scale: scale[2], offset: offset[2] },
27 | { name: 'Intensity', type: 'unsigned', size: 2 },
28 | { name: 'ScanFlags', type: 'unsigned', size: 1 },
29 | { name: 'Classification', type: 'unsigned', size: 1 },
30 | { name: 'ScanAngleRank', type: 'signed', size: 1 },
31 | { name: 'UserData', type: 'unsigned', size: 1 },
32 | { name: 'PointSourceId', type: 'unsigned', size: 2 },
33 | ]
34 | }
35 |
36 | const GpsTime: Schema = [{ name: 'GpsTime', type: 'float', size: 8 }]
37 | const Rgb: Schema = [
38 | { name: 'Red', type: 'unsigned', size: 2 },
39 | { name: 'Green', type: 'unsigned', size: 2 },
40 | { name: 'Blue', type: 'unsigned', size: 2 },
41 | ]
42 |
43 | function create1(header: Header): Schema {
44 | return [...create0(header), ...GpsTime]
45 | }
46 |
47 | function create2(header: Header): Schema {
48 | return [...create0(header), ...Rgb]
49 | }
50 |
51 | function create3(header: Header): Schema {
52 | return [...create0(header), ...GpsTime, ...Rgb]
53 | }
54 |
--------------------------------------------------------------------------------
/src/ept/data-type/laszip/header.ts:
--------------------------------------------------------------------------------
1 | import { Point } from 'types'
2 |
3 | const pointOffsetPosition = 32 * 3
4 | const dataFormatIdPosition = pointOffsetPosition + 8
5 | const legacyPointCountPosition = dataFormatIdPosition + 3
6 | const scalePosition = pointOffsetPosition + 35
7 | const offsetPosition = scalePosition + 24
8 | const rangePosition = offsetPosition + 24
9 |
10 | // This isn't the full header - just the selected fields we will actually use.
11 | // See: https://www.asprs.org/a/society/committees/standards/LAS_1_4_r13.pdf.
12 | export type Header = {
13 | pointOffset: number
14 | dataFormatId: number
15 | pointSize: number
16 | pointCount: number
17 | scale: Point
18 | offset: Point
19 | bounds: [...Point, ...Point]
20 | }
21 |
22 | export const Header = { parse }
23 |
24 | function parse(buffer: Buffer): Header {
25 | return {
26 | pointOffset: buffer.readUInt32LE(pointOffsetPosition),
27 | dataFormatId: buffer.readUInt8(dataFormatIdPosition) & 0x3f,
28 | pointSize: buffer.readUInt16LE(dataFormatIdPosition + 1),
29 | pointCount: buffer.readUInt32LE(legacyPointCountPosition),
30 | scale: [
31 | buffer.readDoubleLE(scalePosition),
32 | buffer.readDoubleLE(scalePosition + 8),
33 | buffer.readDoubleLE(scalePosition + 16),
34 | ],
35 | offset: [
36 | buffer.readDoubleLE(offsetPosition),
37 | buffer.readDoubleLE(offsetPosition + 8),
38 | buffer.readDoubleLE(offsetPosition + 16),
39 | ],
40 | bounds: [
41 | buffer.readDoubleLE(rangePosition + 8),
42 | buffer.readDoubleLE(rangePosition + 8 + 16),
43 | buffer.readDoubleLE(rangePosition + 8 + 32),
44 | buffer.readDoubleLE(rangePosition),
45 | buffer.readDoubleLE(rangePosition + 16),
46 | buffer.readDoubleLE(rangePosition + 32),
47 | ],
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/ept/data-type/laszip/index.test.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'protopath'
2 |
3 | import { Ellipsoid, testdir } from 'test'
4 | import { getBinary } from 'utils'
5 |
6 | import { Laszip } from '.'
7 |
8 | test('read', async () => {
9 | const base = join(testdir, 'ellipsoid-laz')
10 | const buffer = await getBinary(join(base, 'ept-data/0-0-0-0.laz'))
11 |
12 | const view = await Laszip.view(buffer)
13 |
14 | const getx = view.getter('X')
15 | const gety = view.getter('Y')
16 | const getz = view.getter('Z')
17 |
18 | for (let i = 0; i < 1; ++i) {
19 | const x = getx(i)
20 | const y = gety(i)
21 | const z = getz(i)
22 | expect(x).toBeGreaterThanOrEqual(Ellipsoid.boundsConforming[0])
23 | expect(y).toBeGreaterThanOrEqual(Ellipsoid.boundsConforming[1])
24 | expect(z).toBeGreaterThanOrEqual(Ellipsoid.boundsConforming[2])
25 | expect(x).toBeLessThan(Ellipsoid.boundsConforming[3])
26 | expect(y).toBeLessThan(Ellipsoid.boundsConforming[4])
27 | expect(z).toBeLessThan(Ellipsoid.boundsConforming[5])
28 | }
29 | })
30 |
--------------------------------------------------------------------------------
/src/ept/data-type/laszip/index.ts:
--------------------------------------------------------------------------------
1 | import Module from 'lib/laz-perf'
2 |
3 | import { Schema } from 'ept'
4 |
5 | import { View } from '../../view'
6 |
7 | import * as Format from './format'
8 | import { Header } from './header'
9 |
10 | export type type = 'laszip'
11 | export const extension = 'laz'
12 |
13 | export const Laszip = { view }
14 |
15 | let isReady = false
16 |
17 | Module.onRuntimeInitialized = () => isReady = true
18 |
19 | async function view(input: Buffer): Promise {
20 | const header = Header.parse(input)
21 | const { pointCount } = header
22 |
23 | while (!isReady) await new Promise(resolve => setTimeout(resolve, 10))
24 |
25 | const laszip = new Module.LASZip()
26 | const filePointer = Module._malloc(input.length)
27 | const dataPointer = Module._malloc(header.pointSize)
28 |
29 | try {
30 | Module.HEAPU8.set(input, filePointer)
31 | laszip.open(filePointer, input.length)
32 |
33 | // Note that the point size within the file itself may be larger than the
34 | // schema that we create here because there may be extra-bytes (which we
35 | // will ignore). So when we unpack the point into our one-point temporary
36 | // buffer we need the larger size, but when we copy the point content into
37 | // our localized buffer we will omit these trailing extra-bytes.
38 | const schema = Format.create(header)
39 | const corePointSize = Schema.pointSize(schema)
40 | const point = Buffer.from(Module.HEAPU8.buffer, dataPointer, corePointSize)
41 |
42 | const length = corePointSize * pointCount
43 | const output = Buffer.alloc(length)
44 | for (let pos = 0; pos < length; pos += corePointSize) {
45 | // Decompress each point and copy it from the Module heap to our buffer.
46 | laszip.getPoint(dataPointer)
47 | point.copy(output, pos, 0, corePointSize)
48 | }
49 |
50 | return View.Readable.create(output, schema)
51 | } finally {
52 | Module._free(dataPointer)
53 | Module._free(filePointer)
54 | laszip.delete()
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/ept/data-type/zstandard.test.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'protopath'
2 |
3 | import { Ellipsoid, testdir } from 'test'
4 | import { getBinary } from 'utils'
5 |
6 | import { Zstandard } from './zstandard'
7 |
8 | test('read', async () => {
9 | const { schema } = Ellipsoid
10 |
11 | const base = join(testdir, 'ellipsoid-zst')
12 | const buffer = await getBinary(join(base, 'ept-data/0-0-0-0.zst'))
13 |
14 | const view = await Zstandard.view(buffer, schema)
15 |
16 | const getx = view.getter('X')
17 | const gety = view.getter('Y')
18 | const getz = view.getter('Z')
19 |
20 | for (let i = 0; i < 1; ++i) {
21 | const x = getx(i)
22 | const y = gety(i)
23 | const z = getz(i)
24 | expect(x).toBeGreaterThanOrEqual(Ellipsoid.boundsConforming[0])
25 | expect(y).toBeGreaterThanOrEqual(Ellipsoid.boundsConforming[1])
26 | expect(z).toBeGreaterThanOrEqual(Ellipsoid.boundsConforming[2])
27 | expect(x).toBeLessThan(Ellipsoid.boundsConforming[3])
28 | expect(y).toBeLessThan(Ellipsoid.boundsConforming[4])
29 | expect(z).toBeLessThan(Ellipsoid.boundsConforming[5])
30 | }
31 | })
32 |
--------------------------------------------------------------------------------
/src/ept/data-type/zstandard.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { Streaming, ZstdCodec } from 'zstd-codec'
3 |
4 | import { Schema } from 'ept'
5 | import { View } from '../view'
6 |
7 | const streamingPromise = new Promise((resolve) =>
8 | ZstdCodec.run((zstd) => resolve(new zstd.Streaming()))
9 | )
10 |
11 | export const Zstandard = {
12 | view: async (compressed: Buffer, schema: Schema) => {
13 | const streaming = await streamingPromise
14 | const buffer = Buffer.from(streaming.decompress(compressed))
15 | return View.Readable.create(buffer, schema)
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/src/ept/dimension.test.ts:
--------------------------------------------------------------------------------
1 | import { Dimension } from '.'
2 |
3 | test('ctype: invalid dimension', () => {
4 | expect(() => Dimension.ctype({ type: 'asdf' as any, size: 4 })).toThrow(
5 | /invalid dimension/i
6 | )
7 |
8 | expect(() => Dimension.ctype({ type: 'float', size: 2 })).toThrow(
9 | /invalid dimension/i
10 | )
11 | })
12 |
--------------------------------------------------------------------------------
/src/ept/dimension.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'ajv'
2 |
3 | import { Ctype, EptToolsError } from 'types'
4 |
5 | export declare namespace Dimension {
6 | export type Type = 'signed' | 'unsigned' | 'float'
7 | export type Size = 1 | 2 | 4 | 8
8 |
9 | export type Core = {
10 | name: string
11 | type: Dimension.Type
12 | size: Dimension.Size
13 | scale?: number
14 | offset?: number
15 | }
16 |
17 | export type Count = { value: number; count: number }
18 | export type Counts = Count[]
19 |
20 | export type Stats = {
21 | count: number
22 | minimum: number
23 | maximum: number
24 | mean: number
25 | stddev: number
26 | variance: number
27 | counts?: Counts
28 | }
29 | }
30 |
31 | export type Dimension = Dimension.Core | (Dimension.Core & Dimension.Stats)
32 | const schema: Schema = {
33 | title: 'Dimension',
34 | description: 'Dimension details',
35 | type: 'object',
36 | properties: {
37 | name: {
38 | type: 'string',
39 | },
40 | type: {
41 | type: 'string',
42 | enum: ['signed', 'unsigned', 'float'],
43 | },
44 | size: {
45 | type: 'integer',
46 | enum: [1, 2, 4, 8],
47 | },
48 | scale: {
49 | type: 'number',
50 | exclusiveMinimum: 0,
51 | default: 1,
52 | },
53 | offset: {
54 | type: 'number',
55 | default: 0,
56 | },
57 | count: { type: 'number' },
58 | minimum: { type: 'number' },
59 | maximum: { type: 'number' },
60 | mean: { type: 'number' },
61 | stddev: { type: 'number' },
62 | variance: { type: 'number' },
63 | counts: {
64 | type: 'array',
65 | items: {
66 | type: 'object',
67 | properties: {
68 | value: { type: 'number' },
69 | count: { type: 'number' },
70 | },
71 | required: ['value', 'count'],
72 | },
73 | },
74 | },
75 | allOf: [
76 | {
77 | if: { properties: { type: { const: 'float' } } },
78 | then: { properties: { size: { enum: [4, 8] } } },
79 | },
80 | ],
81 | required: ['name', 'type', 'size'],
82 | }
83 | export const Dimension = { schema, ctype, fromCtype }
84 |
85 | function fromCtype(ctype: Ctype): Pick {
86 | switch (ctype) {
87 | case 'int8':
88 | return { type: 'signed', size: 1 }
89 | case 'int16':
90 | return { type: 'signed', size: 2 }
91 | case 'int32':
92 | return { type: 'signed', size: 4 }
93 | case 'int64':
94 | return { type: 'signed', size: 8 }
95 | case 'uint8':
96 | return { type: 'unsigned', size: 1 }
97 | case 'uint16':
98 | return { type: 'unsigned', size: 2 }
99 | case 'uint32':
100 | return { type: 'unsigned', size: 4 }
101 | case 'uint64':
102 | return { type: 'unsigned', size: 8 }
103 | case 'float':
104 | return { type: 'float', size: 4 }
105 | case 'double':
106 | return { type: 'float', size: 8 }
107 | }
108 | }
109 |
110 | function ctype({ type, size }: Pick): Ctype {
111 | switch (type) {
112 | case 'signed': {
113 | switch (size) {
114 | case 1:
115 | return 'int8'
116 | case 2:
117 | return 'int16'
118 | case 4:
119 | return 'int32'
120 | case 8:
121 | return 'int64'
122 | }
123 | }
124 | case 'unsigned': {
125 | switch (size) {
126 | case 1:
127 | return 'uint8'
128 | case 2:
129 | return 'uint16'
130 | case 4:
131 | return 'uint32'
132 | case 8:
133 | return 'uint64'
134 | }
135 | }
136 | case 'float': {
137 | switch (size) {
138 | case 4:
139 | return 'float'
140 | case 8:
141 | return 'double'
142 | }
143 | }
144 | }
145 | throw new EptToolsError(`Invalid dimension type/size: ${type}/${size}`)
146 | }
147 |
--------------------------------------------------------------------------------
/src/ept/ept.ts:
--------------------------------------------------------------------------------
1 | import { Schema as JsonSchema } from 'ajv'
2 |
3 | import { Bounds } from './bounds'
4 | import { DataType } from './data-type'
5 | import { HierarchyType } from './hierarchy-type'
6 | import { Schema } from './schema'
7 | import { Srs } from './srs'
8 |
9 | export type Ept = {
10 | bounds: Bounds
11 | boundsConforming: Bounds
12 | dataType: DataType
13 | hierarchyType: HierarchyType
14 | points: number
15 | schema: Schema
16 | span: number
17 | srs?: Srs
18 | version: '1.0.0' | '1.1.0'
19 | }
20 |
21 | export const points = {
22 | title: 'Point count',
23 | description: 'Point count',
24 | type: 'integer',
25 | minimum: 0,
26 | }
27 |
28 | export const span = {
29 | title: 'Span',
30 | description: 'EPT node span: represents node resolution in one dimension',
31 | type: 'integer',
32 | exclusiveMinimum: 0,
33 | }
34 |
35 | export const version = {
36 | title: 'EPT version',
37 | description: 'EPT version',
38 | type: 'string',
39 | enum: ['1.0.0', '1.0.1', '1.1.0'],
40 | }
41 |
42 | // SRS is not required.
43 | const required = {
44 | bounds: Bounds.schema,
45 | boundsConforming: Bounds.schema,
46 | dataType: DataType.schema,
47 | hierarchyType: HierarchyType.schema,
48 | points,
49 | schema: Schema.schema,
50 | span,
51 | version,
52 | }
53 |
54 | const schema: JsonSchema = {
55 | title: 'EPT metadata',
56 | description: 'Top-level metadata for an EPT resource',
57 | type: 'object',
58 | properties: { ...required, srs: Srs.schema },
59 | required: Object.keys(required),
60 | }
61 |
62 | export const Ept = { schema }
63 |
--------------------------------------------------------------------------------
/src/ept/hierarchy-type.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'ajv'
2 |
3 | export type HierarchyType = 'json'
4 |
5 | const schema: Schema = {
6 | title: 'Hierarchy type',
7 | description: 'Hierarchy data encoding',
8 | type: 'string',
9 | enum: ['json'],
10 | }
11 |
12 | export const HierarchyType = { schema }
13 |
--------------------------------------------------------------------------------
/src/ept/hierarchy.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'ajv'
2 |
3 | export type Hierarchy = { [id: string]: number }
4 |
5 | const schema: Schema = {
6 | title: 'EPT hierarchy',
7 | description: 'EPT hierarchy contents',
8 | type: 'object',
9 | propertyNames: { pattern: '^\\d+-\\d+-\\d+-\\d+' },
10 | patternProperties: { '.*': { type: 'integer' } },
11 | }
12 | export const Hierarchy = { schema }
13 |
--------------------------------------------------------------------------------
/src/ept/index.ts:
--------------------------------------------------------------------------------
1 | export { Bounds } from './bounds'
2 | export { DataType } from './data-type'
3 | export { Dimension } from './dimension'
4 | export { HierarchyType } from './hierarchy-type'
5 | export { Schema } from './schema'
6 | export { Srs } from './srs'
7 |
8 | export { Ept } from './ept'
9 | export { Hierarchy } from './hierarchy'
10 | export { Source } from './source'
11 |
12 | export { Key } from './key'
13 | export { Step } from './step'
14 | export { View } from './view'
15 |
--------------------------------------------------------------------------------
/src/ept/key.test.ts:
--------------------------------------------------------------------------------
1 | import { Key } from './key'
2 |
3 |
4 | test('parse', () => {
5 | const message = /invalid key/i
6 | expect(() => Key.parse('0-0-0')).toThrow(message)
7 | expect(() => Key.parse('0-0-0-0-0')).toThrow(message)
8 | expect(() => Key.parse('0-a-0-0')).toThrow(message)
9 |
10 | expect(Key.parse('0-0-0-0')).toEqual([0,0,0,0])
11 | expect(Key.parse('8-16-128-1')).toEqual([8, 16, 128, 1])
12 | })
13 |
--------------------------------------------------------------------------------
/src/ept/key.ts:
--------------------------------------------------------------------------------
1 | import { Step } from './step'
2 |
3 | export type Key = [number, number, number, number] // D-X-Y-Z.
4 |
5 | export const Key = {
6 | create: (d = 0, x = 0, y = 0, z = 0): Key => [d, x, y, z],
7 | parse: (s: string): Key => {
8 | const [d, x, y, z, ...rest] = s.split('-').map((s) => parseInt(s, 10))
9 | const key: Key = [d, x, y, z]
10 |
11 | if (
12 | rest.length !== 0 ||
13 | key.some((v) => typeof v !== 'number' || Number.isNaN(v))
14 | ) {
15 | throw new Error(`Invalid key: ${s}`)
16 | }
17 |
18 | return key
19 | },
20 | stringify: (k: Key) => k.join('-'),
21 | step: ([d, x, y, z]: Key, [a, b, c]: Step): Key => [
22 | d + 1,
23 | x * 2 + a,
24 | y * 2 + b,
25 | z * 2 + c,
26 | ],
27 | depth: (k: Key) => k[0],
28 | }
29 |
--------------------------------------------------------------------------------
/src/ept/schema.test.ts:
--------------------------------------------------------------------------------
1 | import { EptToolsError } from 'types'
2 |
3 | import { Dimension } from './dimension'
4 | import { Schema } from './schema'
5 |
6 | const x: Dimension = {
7 | name: 'X',
8 | type: 'unsigned',
9 | size: 4,
10 | scale: 0.01,
11 | offset: 100,
12 | }
13 | const y: Dimension = { name: 'Y', type: 'float', size: 4 }
14 | const z: Dimension = { name: 'Z', type: 'signed', size: 8 }
15 | const schema: Schema = [x, y, z]
16 |
17 | test('find', () => {
18 | expect(Schema.find(schema, 'X')).toEqual(x)
19 | expect(Schema.find(schema, 'Y')).toEqual(y)
20 | expect(Schema.find(schema, 'Z')).toEqual(z)
21 | expect(Schema.find(schema, 'T')).toBeUndefined()
22 | })
23 |
24 | test('has', () => {
25 | expect(Schema.has(schema, 'X')).toEqual(true)
26 | expect(Schema.has(schema, 'Y')).toEqual(true)
27 | expect(Schema.has(schema, 'Z')).toEqual(true)
28 | expect(Schema.has(schema, 'T')).toEqual(false)
29 | })
30 |
31 | test('offset', () => {
32 | expect(Schema.offset(schema, 'X')).toEqual(0)
33 | expect(Schema.offset(schema, 'Y')).toEqual(4)
34 | expect(Schema.offset(schema, 'Z')).toEqual(8)
35 | expect(() => Schema.offset(schema, 'T')).toThrow(EptToolsError)
36 | })
37 |
38 | test('point size', () => {
39 | expect(Schema.pointSize(schema)).toEqual(16)
40 | })
41 |
--------------------------------------------------------------------------------
/src/ept/schema.ts:
--------------------------------------------------------------------------------
1 | import { Schema as JsonSchema } from 'ajv'
2 | import { EptToolsError } from 'types'
3 |
4 | import { Dimension } from './dimension'
5 |
6 | export type Schema = Dimension[]
7 | const schema: JsonSchema = {
8 | title: 'Attribute schema',
9 | description: 'Array of dimensions representing the point layout',
10 | type: 'array',
11 | items: Dimension.schema,
12 | minItems: 3, // XYZ must be present.
13 | }
14 | export const Schema = { schema, find, has, offset, pointSize }
15 |
16 | function find(schema: Schema, name: string) {
17 | return schema.find((d) => d.name === name)
18 | }
19 |
20 | function has(schema: Schema, name: string) {
21 | return Boolean(find(schema, name))
22 | }
23 |
24 | function offset(schema: Schema, name: string) {
25 | const index = schema.findIndex((v) => v.name === name)
26 | if (index === -1) throw new EptToolsError(`Failed to find dimension: ${name}`)
27 | return schema.slice(0, index).reduce((offset, dim) => offset + dim.size, 0)
28 | }
29 |
30 | function pointSize(schema: Schema) {
31 | return schema.reduce((offset, dim) => offset + dim.size, 0)
32 | }
33 |
--------------------------------------------------------------------------------
/src/ept/source/index.ts:
--------------------------------------------------------------------------------
1 | import { Bounds } from '../bounds'
2 | import { Schema } from '../schema'
3 | import { Srs } from '../srs'
4 |
5 | export declare namespace Source {
6 | export namespace Summary {
7 | export type Item = {
8 | bounds: Bounds
9 | path: string
10 | points: number
11 | inserted: boolean
12 | metadataPath: string
13 | }
14 | }
15 | export type Summary = Summary.Item[]
16 |
17 | export type Detail = {
18 | bounds: Bounds
19 | path: string
20 | points: number
21 | metadata?: object
22 | pipeline?: (object | string)[]
23 | schema?: Schema
24 | srs?: Srs
25 | }
26 |
27 | export namespace V0 {
28 | export type Status = 'inserted' | 'error' | 'omitted'
29 | export namespace Summary {
30 | export type Item = {
31 | bounds?: Bounds
32 | id: string
33 | path: string
34 | status: Status
35 | url?: string
36 | inserts?: number
37 | points?: number
38 | }
39 | }
40 | export type Summary = Summary.Item[]
41 |
42 | export namespace Detail {
43 | export type Item = {
44 | bounds: Bounds
45 | srs: Srs
46 | metadata?: object
47 | }
48 | }
49 | export type Detail = Record
50 | }
51 |
52 | }
53 |
54 | export const Source = {
55 | summary: {
56 | schema: {
57 | title: 'EPT source data manifest',
58 | type: 'array',
59 | items: {
60 | title: 'EPT source data manifest',
61 | type: 'object',
62 | properties: {
63 | path: { type: 'string' },
64 | bounds: Bounds.schema,
65 | points: { type: 'integer' },
66 | inserted: { type: 'boolean' },
67 | metadataPath: { type: 'string' },
68 | },
69 | required: ['path', 'bounds', 'points'],
70 | },
71 | },
72 | },
73 | detail: {
74 | schema: {
75 | title: 'EPT source data detail object v1.0.0',
76 | type: 'object',
77 | properties: {
78 | path: { type: 'string' },
79 | bounds: Bounds.schema,
80 | schema: Schema.schema,
81 | srs: Srs.schema,
82 | metadata: { type: 'object' },
83 | },
84 | required: ['path', 'bounds'],
85 | },
86 | },
87 | V0: {
88 | summary: {
89 | schema: {
90 | title: 'EPT source data summary list v1.0.0',
91 | type: 'array',
92 | items: {
93 | title: 'EPT source data summary item v1.0.0',
94 | type: 'object',
95 | properties: {
96 | bounds: Bounds.schema,
97 | id: { type: 'string' },
98 | inserts: { type: 'integer' },
99 | path: { type: 'string' },
100 | points: { type: 'integer' },
101 | status: { type: 'string', enum: ['inserted', 'error', 'omitted'] },
102 | url: { type: 'string' },
103 | },
104 | required: ['path'],
105 | },
106 | },
107 | },
108 | detail: {
109 | schema: {
110 | title: 'EPT source data detail object v1.0.0',
111 | type: 'object',
112 | patternProperties: {
113 | '.*': {
114 | type: 'object',
115 | properties: {
116 | bounds: Bounds.schema,
117 | srs: Srs.schema,
118 | metadata: { type: 'object' },
119 | },
120 | },
121 | },
122 | },
123 | },
124 | },
125 | }
126 |
--------------------------------------------------------------------------------
/src/ept/srs.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'ajv'
2 |
3 | export type Srs = {
4 | wkt?: string
5 | authority?: string
6 | horizontal?: string
7 | vertical?: string
8 | }
9 |
10 | const schema: Schema = {
11 | title: 'Spatial reference',
12 | description: 'Spatial reference codes and WKT',
13 | type: 'object',
14 | properties: {
15 | authority: { type: 'string' },
16 | horizontal: { type: 'string' },
17 | vertical: { type: 'string' },
18 | wkt: { type: 'string' },
19 | },
20 | dependencies: {
21 | authority: ['horizontal'],
22 | horizontal: ['authority'],
23 | vertical: ['horizontal'],
24 | },
25 | }
26 |
27 | export const Srs = { schema, codeString, horizontalCodeString }
28 |
29 | function horizontalCodeString(srs: Srs = {}): string | undefined {
30 | const { authority, horizontal } = srs
31 | if (authority && horizontal) return `${authority}:${horizontal}`
32 | }
33 |
34 | function codeString(srs: Srs = {}): string | undefined {
35 | const { authority, horizontal, vertical } = srs
36 | if (authority && horizontal) {
37 | if (vertical) return `${authority}:${horizontal}+${vertical}`
38 | return `${authority}:${horizontal}`
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/ept/step.ts:
--------------------------------------------------------------------------------
1 | export type Step = [0 | 1, 0 | 1, 0 | 1]
2 |
--------------------------------------------------------------------------------
/src/ept/view.ts:
--------------------------------------------------------------------------------
1 | import { Dimension } from './dimension'
2 | import { Schema } from './schema'
3 |
4 | import { EptToolsError } from 'types'
5 | import { Bytes, Scale } from 'utils'
6 |
7 | export declare namespace View {
8 | export type Getter = (index: number) => number
9 | export type Getters = { [name: string]: Getter | undefined }
10 |
11 | export type Setter = (value: number, index: number) => void
12 | export type Setters = { [name: string]: Setter | undefined }
13 |
14 | export type Base = { schema: Schema; length: number }
15 | export type Readable = Base & { getter: (name: string) => Getter }
16 | export type Writable = Base & { setter: (name: string) => Setter }
17 | }
18 |
19 | export const View = {
20 | Readable: { create: createReadable },
21 | Writable: { create: createWritable },
22 | }
23 |
24 | function getLength(buffer: Buffer, schema: Schema): number {
25 | const pointSize = Schema.pointSize(schema)
26 |
27 | if (pointSize === 0) {
28 | throw new EptToolsError(`Invalid schema point size: ${pointSize}`)
29 | }
30 |
31 | const length = buffer.length / pointSize
32 | if (buffer.length % pointSize !== 0) {
33 | throw new EptToolsError('Invalid buffer length for this schema')
34 | }
35 |
36 | return length
37 | }
38 |
39 | function extract(map: { [name: string]: T | undefined }, name: string): T {
40 | const v = map[name]
41 | if (!v) throw new EptToolsError(`Invalid dimension: ${name}`)
42 | return v
43 | }
44 |
45 | function createReadable(buffer: Buffer, schema: Schema): View.Readable {
46 | const length = getLength(buffer, schema)
47 | const pointSize = Schema.pointSize(schema)
48 |
49 | const map = schema.reduce((map, dim) => {
50 | const { scale = 1, offset = 0 } = dim
51 | const get = Bytes.getter(buffer, Dimension.ctype(dim))
52 | const dimOffset = Schema.offset(schema, dim.name)
53 |
54 | if (dim.name === 'ScanFlags') {
55 | const getFlags = (index: number) => {
56 | if (index >= length) {
57 | throw new EptToolsError(`Invalid point index: ${index} >= ${length}`)
58 | }
59 | return get(index * pointSize + dimOffset)
60 | }
61 | map['ReturnNumber'] = (index) => getFlags(index) & 0b0000_0111
62 | map['NumberOfReturns'] = (index) => (getFlags(index) & 0b0011_1000) >> 3
63 | map['ScanDirectionFlag'] = (index) => (getFlags(index) & 0b0100_0000) >> 6
64 | map['EdgeOfFlightLine'] = (index) => (getFlags(index) & 0b1000_0000) >> 7
65 | } else if (dim.name === 'ClassFlags') {
66 | const getFlags = (index: number) => {
67 | if (index >= length) {
68 | throw new EptToolsError(`Invalid point index: ${index} >= ${length}`)
69 | }
70 | return get(index * pointSize + dimOffset)
71 | }
72 | map['Synthetic'] = (index) => getFlags(index) & 0b0001
73 | map['KeyPoint'] = (index) => (getFlags(index) & 0b0010) >> 1
74 | map['Withheld'] = (index) => (getFlags(index) & 0b0100) >> 2
75 | map['Overlap'] = (index) => (getFlags(index) & 0b1000) >> 3
76 | } else if (
77 | // If there is a Classification dimension, but no ClassFlags dimension,
78 | // then the upper 3 bits of Classification represent the ClassFlags.
79 | dim.name === 'Classification' &&
80 | !Schema.has(schema, 'ClassFlags')
81 | ) {
82 | const getFull = (index: number) => get(index * pointSize + dimOffset)
83 | map['Classification'] = (index) => getFull(index) & 0b0001_1111
84 | map['Synthetic'] = (index) => (getFull(index) & 0b0010_0000) >> 5
85 | map['KeyPoint'] = (index) => (getFull(index) & 0b0100_0000) >> 6
86 | map['Withheld'] = (index) => (getFull(index) & 0b1000_0000) >> 7
87 | map['Overlap'] = (index) => (getFull(index) & 0b0001_1111) === 12 ? 1 : 0
88 | } else {
89 | map[dim.name] = (index: number) => {
90 | if (index >= length) {
91 | throw new EptToolsError(`Invalid point index: ${index} >= ${length}`)
92 | }
93 | return Scale.unapply(get(index * pointSize + dimOffset), scale, offset)
94 | }
95 | }
96 | return map
97 | }, {})
98 |
99 | const getter = (name: string) => extract(map, name)
100 | return { schema, length, getter }
101 | }
102 |
103 | function createWritable(buffer: Buffer, schema: Schema): View.Writable {
104 | const length = getLength(buffer, schema)
105 | const pointSize = Schema.pointSize(schema)
106 |
107 | const map = schema.reduce((map, dim) => {
108 | const { scale = 1, offset = 0 } = dim
109 | const set = Bytes.setter(buffer, Dimension.ctype(dim))
110 | const dimOffset = Schema.offset(schema, dim.name)
111 |
112 | map[dim.name] = (value: number, index: number) => {
113 | if (index >= length) {
114 | throw new EptToolsError(`Invalid point index: ${index} >= ${length}`)
115 | }
116 |
117 | return set(
118 | Scale.apply(value, scale, offset),
119 | index * pointSize + dimOffset
120 | )
121 | }
122 |
123 | return map
124 | }, {})
125 |
126 | const setter = (name: string) => extract(map, name)
127 | return { schema, length, setter }
128 | }
129 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * as Cesium from './3d-tiles'
2 | export * from './ept'
3 | export * from './types'
4 | export * as Utils from './utils'
5 |
--------------------------------------------------------------------------------
/src/lib/laz-perf.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'lib/laz-perf' {
2 | // Represents an offset into the HEAPU8.
3 | type Pointer = number
4 |
5 | function onRuntimeInitialized(): void
6 |
7 | class LASZip {
8 | constructor()
9 | open(pointer: Pointer, length: number): void
10 | delete(): void
11 | getPoint(pointer: Pointer): void
12 | }
13 |
14 | class HEAPU8 {
15 | static buffer: ArrayBuffer
16 | static set(buffer: ArrayBuffer, pointer: Pointer): void
17 | }
18 |
19 | function _free(pointer: Pointer): void
20 | function _malloc(length: number): Pointer
21 | }
22 |
--------------------------------------------------------------------------------
/src/lib/laz-perf.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/lib/laz-perf.wasm
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-build.json:
--------------------------------------------------------------------------------
1 | {
2 | "hierarchyStep": 2,
3 | "maxNodeSize": 4096,
4 | "minNodeSize": 1024,
5 | "software": "Entwine",
6 | "version": "2.1.0"
7 | }
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/0-0-0-0.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/0-0-0-0.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/1-0-0-0.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/1-0-0-0.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/1-0-0-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/1-0-0-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/1-0-1-0.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/1-0-1-0.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/1-0-1-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/1-0-1-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/1-1-0-0.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/1-1-0-0.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/1-1-0-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/1-1-0-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/1-1-1-0.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/1-1-1-0.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/1-1-1-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/1-1-1-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-0-1-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-0-1-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-0-1-2.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-0-1-2.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-0-2-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-0-2-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-0-2-2.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-0-2-2.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-1-0-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-1-0-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-1-1-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-1-1-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-1-1-2.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-1-1-2.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-1-2-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-1-2-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-1-2-2.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-1-2-2.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-1-3-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-1-3-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-2-0-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-2-0-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-2-1-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-2-1-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-2-1-2.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-2-1-2.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-2-2-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-2-2-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-2-2-2.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-2-2-2.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-2-3-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-2-3-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-3-1-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-3-1-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-3-1-2.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-3-1-2.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-3-2-1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-3-2-1.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/2-3-2-2.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/2-3-2-2.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/3-0-3-3.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/3-0-3-3.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/3-0-3-4.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/3-0-3-4.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/3-0-4-3.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/3-0-4-3.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/3-0-4-4.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/3-0-4-4.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/3-7-3-3.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/3-7-3-3.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/3-7-3-4.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/3-7-3-4.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/3-7-4-3.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/3-7-4-3.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-data/3-7-4-4.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-bin/ept-data/3-7-4-4.bin
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/0-0-0-0.json:
--------------------------------------------------------------------------------
1 | {
2 | "0-0-0-0": 1900,
3 | "1-0-0-0": 1299,
4 | "1-0-0-1": 3697,
5 | "1-0-1-0": 1299,
6 | "1-0-1-1": 3698,
7 | "1-1-0-0": 1287,
8 | "1-1-0-1": 3712,
9 | "1-1-1-0": 1290,
10 | "1-1-1-1": 3716,
11 | "2-0-1-1": -1,
12 | "2-0-1-2": -1,
13 | "2-0-2-1": -1,
14 | "2-0-2-2": -1,
15 | "2-1-0-1": -1,
16 | "2-1-1-1": -1,
17 | "2-1-1-2": -1,
18 | "2-1-2-1": -1,
19 | "2-1-2-2": -1,
20 | "2-1-3-1": -1,
21 | "2-2-0-1": -1,
22 | "2-2-1-1": -1,
23 | "2-2-1-2": -1,
24 | "2-2-2-1": -1,
25 | "2-2-2-2": -1,
26 | "2-2-3-1": -1,
27 | "2-3-1-1": -1,
28 | "2-3-1-2": -1,
29 | "2-3-2-1": -1,
30 | "2-3-2-2": -1
31 | }
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-0-1-1.json:
--------------------------------------------------------------------------------
1 | {"2-0-1-1":3779,"3-0-3-3":1494}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-0-1-2.json:
--------------------------------------------------------------------------------
1 | {"2-0-1-2":3779,"3-0-3-4":1494}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-0-2-1.json:
--------------------------------------------------------------------------------
1 | {"2-0-2-1":3795,"3-0-4-3":1526}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-0-2-2.json:
--------------------------------------------------------------------------------
1 | {"2-0-2-2":3795,"3-0-4-4":1527}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-1-0-1.json:
--------------------------------------------------------------------------------
1 | {"2-1-0-1":2398}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-1-1-1.json:
--------------------------------------------------------------------------------
1 | {"2-1-1-1":3229}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-1-1-2.json:
--------------------------------------------------------------------------------
1 | {"2-1-1-2":3229}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-1-2-1.json:
--------------------------------------------------------------------------------
1 | {"2-1-2-1":3257}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-1-2-2.json:
--------------------------------------------------------------------------------
1 | {"2-1-2-2":3257}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-1-3-1.json:
--------------------------------------------------------------------------------
1 | {"2-1-3-1":2399}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-2-0-1.json:
--------------------------------------------------------------------------------
1 | {"2-2-0-1":2424}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-2-1-1.json:
--------------------------------------------------------------------------------
1 | {"2-2-1-1":3247}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-2-1-2.json:
--------------------------------------------------------------------------------
1 | {"2-2-1-2":3247}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-2-2-1.json:
--------------------------------------------------------------------------------
1 | {"2-2-2-1":3294}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-2-2-2.json:
--------------------------------------------------------------------------------
1 | {"2-2-2-2":3294}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-2-3-1.json:
--------------------------------------------------------------------------------
1 | {"2-2-3-1":2425}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-3-1-1.json:
--------------------------------------------------------------------------------
1 | {"2-3-1-1":3790,"3-7-3-3":1468}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-3-1-2.json:
--------------------------------------------------------------------------------
1 | {"2-3-1-2":3790,"3-7-3-4":1468}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-3-2-1.json:
--------------------------------------------------------------------------------
1 | {"2-3-2-1":3816,"3-7-4-3":1532}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-hierarchy/2-3-2-2.json:
--------------------------------------------------------------------------------
1 | {"2-3-2-2":3816,"3-7-4-4":1533}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-bin/ept-sources/manifest.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "bounds": [
4 | -8242746.0,
5 | 4966506.0,
6 | -50.0,
7 | -8242446.0,
8 | 4966706.0,
9 | 50.0
10 | ],
11 | "inserted": true,
12 | "metadataPath": "ellipsoid.json",
13 | "path": "/Users/connor/data/ellipsoid.laz",
14 | "points": 100000
15 | }
16 | ]
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-build.json:
--------------------------------------------------------------------------------
1 | {
2 | "hierarchyStep": 2,
3 | "maxNodeSize": 4096,
4 | "minNodeSize": 1024,
5 | "software": "Entwine",
6 | "version": "2.1.0"
7 | }
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/0-0-0-0.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/0-0-0-0.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/1-0-0-0.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/1-0-0-0.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/1-0-0-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/1-0-0-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/1-0-1-0.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/1-0-1-0.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/1-0-1-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/1-0-1-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/1-1-0-0.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/1-1-0-0.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/1-1-0-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/1-1-0-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/1-1-1-0.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/1-1-1-0.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/1-1-1-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/1-1-1-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-0-1-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-0-1-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-0-1-2.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-0-1-2.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-0-2-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-0-2-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-0-2-2.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-0-2-2.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-1-0-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-1-0-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-1-1-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-1-1-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-1-1-2.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-1-1-2.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-1-2-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-1-2-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-1-2-2.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-1-2-2.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-1-3-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-1-3-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-2-0-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-2-0-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-2-1-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-2-1-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-2-1-2.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-2-1-2.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-2-2-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-2-2-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-2-2-2.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-2-2-2.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-2-3-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-2-3-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-3-1-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-3-1-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-3-1-2.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-3-1-2.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-3-2-1.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-3-2-1.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/2-3-2-2.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/2-3-2-2.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/3-0-3-3.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/3-0-3-3.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/3-0-3-4.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/3-0-3-4.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/3-0-4-3.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/3-0-4-3.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/3-0-4-4.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/3-0-4-4.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/3-7-3-3.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/3-7-3-3.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/3-7-3-4.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/3-7-3-4.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/3-7-4-3.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/3-7-4-3.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-data/3-7-4-4.laz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-laz/ept-data/3-7-4-4.laz
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/0-0-0-0.json:
--------------------------------------------------------------------------------
1 | {
2 | "0-0-0-0": 1900,
3 | "1-0-0-0": 1299,
4 | "1-0-0-1": 3697,
5 | "1-0-1-0": 1299,
6 | "1-0-1-1": 3698,
7 | "1-1-0-0": 1287,
8 | "1-1-0-1": 3712,
9 | "1-1-1-0": 1290,
10 | "1-1-1-1": 3716,
11 | "2-0-1-1": -1,
12 | "2-0-1-2": -1,
13 | "2-0-2-1": -1,
14 | "2-0-2-2": -1,
15 | "2-1-0-1": -1,
16 | "2-1-1-1": -1,
17 | "2-1-1-2": -1,
18 | "2-1-2-1": -1,
19 | "2-1-2-2": -1,
20 | "2-1-3-1": -1,
21 | "2-2-0-1": -1,
22 | "2-2-1-1": -1,
23 | "2-2-1-2": -1,
24 | "2-2-2-1": -1,
25 | "2-2-2-2": -1,
26 | "2-2-3-1": -1,
27 | "2-3-1-1": -1,
28 | "2-3-1-2": -1,
29 | "2-3-2-1": -1,
30 | "2-3-2-2": -1
31 | }
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-0-1-1.json:
--------------------------------------------------------------------------------
1 | {"2-0-1-1":3779,"3-0-3-3":1494}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-0-1-2.json:
--------------------------------------------------------------------------------
1 | {"2-0-1-2":3779,"3-0-3-4":1494}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-0-2-1.json:
--------------------------------------------------------------------------------
1 | {"2-0-2-1":3795,"3-0-4-3":1526}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-0-2-2.json:
--------------------------------------------------------------------------------
1 | {"2-0-2-2":3795,"3-0-4-4":1527}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-1-0-1.json:
--------------------------------------------------------------------------------
1 | {"2-1-0-1":2398}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-1-1-1.json:
--------------------------------------------------------------------------------
1 | {"2-1-1-1":3229}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-1-1-2.json:
--------------------------------------------------------------------------------
1 | {"2-1-1-2":3229}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-1-2-1.json:
--------------------------------------------------------------------------------
1 | {"2-1-2-1":3257}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-1-2-2.json:
--------------------------------------------------------------------------------
1 | {"2-1-2-2":3257}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-1-3-1.json:
--------------------------------------------------------------------------------
1 | {"2-1-3-1":2399}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-2-0-1.json:
--------------------------------------------------------------------------------
1 | {"2-2-0-1":2424}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-2-1-1.json:
--------------------------------------------------------------------------------
1 | {"2-2-1-1":3247}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-2-1-2.json:
--------------------------------------------------------------------------------
1 | {"2-2-1-2":3247}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-2-2-1.json:
--------------------------------------------------------------------------------
1 | {"2-2-2-1":3294}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-2-2-2.json:
--------------------------------------------------------------------------------
1 | {"2-2-2-2":3294}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-2-3-1.json:
--------------------------------------------------------------------------------
1 | {"2-2-3-1":2425}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-3-1-1.json:
--------------------------------------------------------------------------------
1 | {"2-3-1-1":3790,"3-7-3-3":1468}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-3-1-2.json:
--------------------------------------------------------------------------------
1 | {"2-3-1-2":3790,"3-7-3-4":1468}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-3-2-1.json:
--------------------------------------------------------------------------------
1 | {"2-3-2-1":3816,"3-7-4-3":1532}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-hierarchy/2-3-2-2.json:
--------------------------------------------------------------------------------
1 | {"2-3-2-2":3816,"3-7-4-4":1533}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-laz/ept-sources/manifest.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "bounds": [
4 | -8242746.0,
5 | 4966506.0,
6 | -50.0,
7 | -8242446.0,
8 | 4966706.0,
9 | 50.0
10 | ],
11 | "inserted": true,
12 | "metadataPath": "ellipsoid.json",
13 | "path": "/Users/connor/data/ellipsoid.laz",
14 | "points": 100000
15 | }
16 | ]
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-build.json:
--------------------------------------------------------------------------------
1 | {
2 | "hierarchyStep": 2,
3 | "maxNodeSize": 4096,
4 | "minNodeSize": 1024,
5 | "software": "Entwine",
6 | "version": "2.1.0"
7 | }
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/0-0-0-0.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/0-0-0-0.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/1-0-0-0.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/1-0-0-0.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/1-0-0-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/1-0-0-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/1-0-1-0.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/1-0-1-0.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/1-0-1-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/1-0-1-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/1-1-0-0.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/1-1-0-0.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/1-1-0-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/1-1-0-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/1-1-1-0.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/1-1-1-0.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/1-1-1-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/1-1-1-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-0-1-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-0-1-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-0-1-2.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-0-1-2.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-0-2-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-0-2-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-0-2-2.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-0-2-2.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-1-0-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-1-0-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-1-1-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-1-1-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-1-1-2.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-1-1-2.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-1-2-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-1-2-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-1-2-2.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-1-2-2.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-1-3-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-1-3-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-2-0-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-2-0-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-2-1-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-2-1-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-2-1-2.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-2-1-2.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-2-2-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-2-2-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-2-2-2.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-2-2-2.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-2-3-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-2-3-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-3-1-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-3-1-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-3-1-2.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-3-1-2.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-3-2-1.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-3-2-1.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/2-3-2-2.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/2-3-2-2.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/3-0-3-3.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/3-0-3-3.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/3-0-3-4.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/3-0-3-4.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/3-0-4-3.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/3-0-4-3.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/3-0-4-4.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/3-0-4-4.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/3-7-3-3.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/3-7-3-3.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/3-7-3-4.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/3-7-3-4.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/3-7-4-3.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/3-7-4-3.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-data/3-7-4-4.zst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connormanning/ept-tools/d6cc3d8eff0c0da0e75de73bf4ec43f6e338b807/src/test/data/ellipsoid-zst/ept-data/3-7-4-4.zst
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/0-0-0-0.json:
--------------------------------------------------------------------------------
1 | {
2 | "0-0-0-0": 1900,
3 | "1-0-0-0": 1299,
4 | "1-0-0-1": 3697,
5 | "1-0-1-0": 1299,
6 | "1-0-1-1": 3698,
7 | "1-1-0-0": 1287,
8 | "1-1-0-1": 3712,
9 | "1-1-1-0": 1290,
10 | "1-1-1-1": 3716,
11 | "2-0-1-1": -1,
12 | "2-0-1-2": -1,
13 | "2-0-2-1": -1,
14 | "2-0-2-2": -1,
15 | "2-1-0-1": -1,
16 | "2-1-1-1": -1,
17 | "2-1-1-2": -1,
18 | "2-1-2-1": -1,
19 | "2-1-2-2": -1,
20 | "2-1-3-1": -1,
21 | "2-2-0-1": -1,
22 | "2-2-1-1": -1,
23 | "2-2-1-2": -1,
24 | "2-2-2-1": -1,
25 | "2-2-2-2": -1,
26 | "2-2-3-1": -1,
27 | "2-3-1-1": -1,
28 | "2-3-1-2": -1,
29 | "2-3-2-1": -1,
30 | "2-3-2-2": -1
31 | }
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-0-1-1.json:
--------------------------------------------------------------------------------
1 | {"2-0-1-1":3779,"3-0-3-3":1494}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-0-1-2.json:
--------------------------------------------------------------------------------
1 | {"2-0-1-2":3779,"3-0-3-4":1494}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-0-2-1.json:
--------------------------------------------------------------------------------
1 | {"2-0-2-1":3795,"3-0-4-3":1526}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-0-2-2.json:
--------------------------------------------------------------------------------
1 | {"2-0-2-2":3795,"3-0-4-4":1527}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-1-0-1.json:
--------------------------------------------------------------------------------
1 | {"2-1-0-1":2398}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-1-1-1.json:
--------------------------------------------------------------------------------
1 | {"2-1-1-1":3229}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-1-1-2.json:
--------------------------------------------------------------------------------
1 | {"2-1-1-2":3229}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-1-2-1.json:
--------------------------------------------------------------------------------
1 | {"2-1-2-1":3257}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-1-2-2.json:
--------------------------------------------------------------------------------
1 | {"2-1-2-2":3257}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-1-3-1.json:
--------------------------------------------------------------------------------
1 | {"2-1-3-1":2399}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-2-0-1.json:
--------------------------------------------------------------------------------
1 | {"2-2-0-1":2424}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-2-1-1.json:
--------------------------------------------------------------------------------
1 | {"2-2-1-1":3247}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-2-1-2.json:
--------------------------------------------------------------------------------
1 | {"2-2-1-2":3247}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-2-2-1.json:
--------------------------------------------------------------------------------
1 | {"2-2-2-1":3294}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-2-2-2.json:
--------------------------------------------------------------------------------
1 | {"2-2-2-2":3294}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-2-3-1.json:
--------------------------------------------------------------------------------
1 | {"2-2-3-1":2425}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-3-1-1.json:
--------------------------------------------------------------------------------
1 | {"2-3-1-1":3790,"3-7-3-3":1468}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-3-1-2.json:
--------------------------------------------------------------------------------
1 | {"2-3-1-2":3790,"3-7-3-4":1468}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-3-2-1.json:
--------------------------------------------------------------------------------
1 | {"2-3-2-1":3816,"3-7-4-3":1532}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-hierarchy/2-3-2-2.json:
--------------------------------------------------------------------------------
1 | {"2-3-2-2":3816,"3-7-4-4":1533}
--------------------------------------------------------------------------------
/src/test/data/ellipsoid-zst/ept-sources/manifest.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "bounds": [
4 | -8242746.0,
5 | 4966506.0,
6 | -50.0,
7 | -8242446.0,
8 | 4966706.0,
9 | 50.0
10 | ],
11 | "inserted": true,
12 | "metadataPath": "ellipsoid.json",
13 | "path": "/Users/connor/data/ellipsoid.laz",
14 | "points": 100000
15 | }
16 | ]
--------------------------------------------------------------------------------
/src/test/data/no-srs-code/ept.json:
--------------------------------------------------------------------------------
1 | {
2 | "bounds": [-8242747, 4966455, -151, -8242445, 4966757, 151],
3 | "boundsConforming": [
4 | -8242747.0,
5 | 4966505.0,
6 | -51.0,
7 | -8242445.0,
8 | 4966707.0,
9 | 51.0
10 | ],
11 | "dataType": "binary",
12 | "hierarchyType": "json",
13 | "points": 100000,
14 | "schema": [
15 | {
16 | "count": 100000,
17 | "maximum": -8242446,
18 | "mean": -8242596.000019999,
19 | "minimum": -8242746,
20 | "name": "X",
21 | "offset": -8242596,
22 | "scale": 0.01,
23 | "size": 4,
24 | "stddev": 86.60636418888501,
25 | "type": "signed",
26 | "variance": 7500.662318017784
27 | },
28 | {
29 | "count": 100000,
30 | "maximum": 4966706,
31 | "mean": 4966605.99999997,
32 | "minimum": 4966506,
33 | "name": "Y",
34 | "offset": 4966606,
35 | "scale": 0.01,
36 | "size": 4,
37 | "stddev": 57.73755444966075,
38 | "type": "signed",
39 | "variance": 3333.6251938275395
40 | },
41 | {
42 | "count": 100000,
43 | "maximum": 50,
44 | "mean": 1.5547061471473398e-14,
45 | "minimum": -50,
46 | "name": "Z",
47 | "scale": 0.01,
48 | "size": 4,
49 | "stddev": 28.86530644968098,
50 | "type": "signed",
51 | "variance": 833.2059164339943
52 | },
53 | {
54 | "count": 100000,
55 | "maximum": 255,
56 | "mean": 191.5000000000007,
57 | "minimum": 128,
58 | "name": "Intensity",
59 | "size": 2,
60 | "stddev": 63.50000000000017,
61 | "type": "unsigned",
62 | "variance": 4032.2500000000214
63 | },
64 | {
65 | "count": 100000,
66 | "maximum": 2,
67 | "mean": 1.4999799999999934,
68 | "minimum": 1,
69 | "name": "ReturnNumber",
70 | "size": 1,
71 | "stddev": 0.49999999959999736,
72 | "type": "unsigned",
73 | "variance": 0.24999999959999736
74 | },
75 | {
76 | "count": 100000,
77 | "maximum": 2,
78 | "mean": 2,
79 | "minimum": 2,
80 | "name": "NumberOfReturns",
81 | "size": 1,
82 | "stddev": 0,
83 | "type": "unsigned",
84 | "variance": 0
85 | },
86 | {
87 | "count": 100000,
88 | "maximum": 0,
89 | "mean": 0,
90 | "minimum": 0,
91 | "name": "ScanDirectionFlag",
92 | "size": 1,
93 | "stddev": 0,
94 | "type": "unsigned",
95 | "variance": 0
96 | },
97 | {
98 | "count": 100000,
99 | "maximum": 1,
100 | "mean": 0.30909999999999815,
101 | "minimum": 0,
102 | "name": "EdgeOfFlightLine",
103 | "size": 1,
104 | "stddev": 0.4621224837637749,
105 | "type": "unsigned",
106 | "variance": 0.2135571900000004
107 | },
108 | {
109 | "count": 100000,
110 | "counts": [
111 | {
112 | "count": 1656,
113 | "value": 2
114 | },
115 | {
116 | "count": 5135,
117 | "value": 3
118 | },
119 | {
120 | "count": 10307,
121 | "value": 4
122 | },
123 | {
124 | "count": 32904,
125 | "value": 5
126 | },
127 | {
128 | "count": 1656,
129 | "value": 15
130 | },
131 | {
132 | "count": 5135,
133 | "value": 16
134 | },
135 | {
136 | "count": 10307,
137 | "value": 17
138 | },
139 | {
140 | "count": 32900,
141 | "value": 18
142 | }
143 | ],
144 | "maximum": 18,
145 | "mean": 10.988840000000208,
146 | "minimum": 2,
147 | "name": "Classification",
148 | "size": 1,
149 | "stddev": 6.5500943088173305,
150 | "type": "unsigned",
151 | "variance": 42.903735454401186
152 | },
153 | {
154 | "count": 100000,
155 | "maximum": 45,
156 | "mean": 0.0036199999999919673,
157 | "minimum": -45,
158 | "name": "ScanAngleRank",
159 | "size": 4,
160 | "stddev": 25.98422034419339,
161 | "type": "float",
162 | "variance": 675.1797068955935
163 | },
164 | {
165 | "count": 100000,
166 | "maximum": 0,
167 | "mean": 0,
168 | "minimum": 0,
169 | "name": "UserData",
170 | "size": 1,
171 | "stddev": 0,
172 | "type": "unsigned",
173 | "variance": 0
174 | },
175 | {
176 | "count": 100000,
177 | "maximum": 7,
178 | "mean": 3.504369999999963,
179 | "minimum": 0,
180 | "name": "PointSourceId",
181 | "size": 2,
182 | "stddev": 2.2919644201208853,
183 | "type": "unsigned",
184 | "variance": 5.253100903100067
185 | },
186 | {
187 | "count": 100000,
188 | "maximum": 42.99999,
189 | "mean": 42.49999499999921,
190 | "minimum": 42,
191 | "name": "GpsTime",
192 | "size": 8,
193 | "stddev": 0.28867513458031685,
194 | "type": "float",
195 | "variance": 0.08333333332496404
196 | },
197 | {
198 | "count": 100000,
199 | "maximum": 255,
200 | "mean": 141.4995800000003,
201 | "minimum": 0,
202 | "name": "Red",
203 | "size": 2,
204 | "stddev": 83.487900080333,
205 | "type": "unsigned",
206 | "variance": 6970.229459823668
207 | },
208 | {
209 | "count": 100000,
210 | "maximum": 255,
211 | "mean": 141.4982199999975,
212 | "minimum": 0,
213 | "name": "Green",
214 | "size": 2,
215 | "stddev": 83.48573013893717,
216 | "type": "unsigned",
217 | "variance": 6969.867136831442
218 | },
219 | {
220 | "count": 100000,
221 | "maximum": 255,
222 | "mean": 141.49958000000203,
223 | "minimum": 0,
224 | "name": "Blue",
225 | "size": 2,
226 | "stddev": 83.48790008033261,
227 | "type": "unsigned",
228 | "variance": 6970.2294598236
229 | }
230 | ],
231 | "span": 32,
232 | "srs": {
233 | "wkt": "PROJCS[\"WGS 84 / Pseudo-Mercator\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Mercator_1SP\"],PARAMETER[\"central_meridian\",0],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],EXTENSION[\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs\"],AUTHORITY[\"EPSG\",\"3857\"]]"
234 | },
235 | "version": "1.0.0"
236 | }
237 |
--------------------------------------------------------------------------------
/src/test/data/v1.0.0/ept-hierarchy/0-0-0-0.json:
--------------------------------------------------------------------------------
1 | {
2 | "0-0-0-0" : 28941,
3 | "1-0-0-0" : 58414,
4 | "1-0-1-0" : 27735,
5 | "1-1-0-0" : 27339,
6 | "1-1-1-0" : 14572,
7 | "2-0-1-1" : 102734,
8 | "2-0-2-1" : 80678,
9 | "2-1-1-1" : 129529,
10 | "2-1-2-1" : 29536,
11 | "2-2-1-1" : 54041,
12 | "2-2-2-1" : 33268,
13 | "2-3-1-1" : 46126,
14 | "2-3-2-1" : 18530,
15 | "3-0-2-2" : 98243,
16 | "3-0-3-2" : 44506,
17 | "3-0-4-2" : 68265,
18 | "3-0-5-2" : 57629,
19 | "3-1-2-2" : 111337,
20 | "3-1-3-2" : 53518,
21 | "3-1-4-2" : 39888,
22 | "3-1-5-2" : 38412,
23 | "3-2-2-2" : 122305,
24 | "3-2-3-2" : 84881,
25 | "3-2-4-2" : 68044,
26 | "3-2-5-2" : 4,
27 | "3-3-2-2" : 116080,
28 | "3-3-3-2" : 83972,
29 | "3-3-4-2" : 45204,
30 | "3-3-5-2" : 503,
31 | "3-4-2-2" : 59685,
32 | "3-4-3-2" : 52171,
33 | "3-4-4-2" : 47976,
34 | "3-4-5-2" : 626,
35 | "3-5-2-2" : 28188,
36 | "3-5-3-2" : 49240,
37 | "3-5-4-2" : 77347,
38 | "3-5-5-2" : 235,
39 | "3-6-2-2" : 53114,
40 | "3-6-3-2" : 48352,
41 | "3-6-4-2" : 48502,
42 | "3-6-5-2" : 1762,
43 | "3-7-2-2" : 25737,
44 | "3-7-3-2" : 45786,
45 | "3-7-4-2" : 49103,
46 | "4-0-10-5" : 14128,
47 | "4-0-11-5" : 4537,
48 | "4-0-4-5" : 30233,
49 | "4-0-5-5" : 51277,
50 | "4-0-6-5" : 34413,
51 | "4-0-7-5" : 7341,
52 | "4-0-8-5" : 2605,
53 | "4-0-9-5" : 19266,
54 | "4-1-10-5" : 10362,
55 | "4-1-11-5" : 9640,
56 | "4-1-4-5" : 27882,
57 | "4-1-5-5" : 76208,
58 | "4-1-6-5" : 49752,
59 | "4-1-7-5" : 16751,
60 | "4-1-8-5" : 13722,
61 | "4-1-9-5" : 41111,
62 | "4-10-6-5" : 52286,
63 | "4-10-7-5" : 8436,
64 | "4-11-6-5" : 47523,
65 | "4-11-7-5" : 5996,
66 | "4-12-6-5" : 46733,
67 | "4-12-7-5" : 6306,
68 | "4-13-6-5" : 48521,
69 | "4-13-7-5" : 5578,
70 | "4-14-4-5" : 15,
71 | "4-14-5-5" : 24711,
72 | "4-14-6-5" : 52227,
73 | "4-14-7-5" : 4628,
74 | "4-15-4-5" : 30,
75 | "4-15-5-5" : 26438,
76 | "4-15-6-5" : 56073,
77 | "4-15-7-5" : 8668,
78 | "4-2-10-5" : 21603,
79 | "4-2-11-5" : 8641,
80 | "4-2-4-5" : 65207,
81 | "4-2-5-5" : 27179,
82 | "4-2-6-5" : 48254,
83 | "4-2-7-5" : 15773,
84 | "4-2-8-5" : 9599,
85 | "4-2-9-5" : 34302,
86 | "4-3-10-5" : 3465,
87 | "4-3-11-5" : 1065,
88 | "4-3-4-5" : 64188,
89 | "4-3-5-5" : 70978,
90 | "4-3-6-5" : 74369,
91 | "4-3-7-5" : 14329,
92 | "4-3-8-5" : 15697,
93 | "4-3-9-5" : 4967,
94 | "4-4-4-5" : 51512,
95 | "4-4-5-5" : 65970,
96 | "4-4-6-5" : 79894,
97 | "4-4-7-5" : 20870,
98 | "4-5-4-5" : 52725,
99 | "4-5-5-5" : 65997,
100 | "4-5-6-5" : 75483,
101 | "4-5-7-5" : 63177,
102 | "4-6-4-5" : 61829,
103 | "4-6-5-5" : 78482,
104 | "4-6-6-5" : 79826,
105 | "4-6-7-5" : 34152,
106 | "4-6-8-5" : 6606,
107 | "4-6-9-5" : 8147,
108 | "4-7-4-5" : 38628,
109 | "4-7-5-5" : 76490,
110 | "4-7-6-5" : 73133,
111 | "4-7-7-5" : 13795,
112 | "4-7-8-5" : 10120,
113 | "4-7-9-5" : 8834,
114 | "4-8-4-5" : 40637,
115 | "4-8-5-5" : 76976,
116 | "4-8-6-5" : 63310,
117 | "4-8-7-5" : 12999,
118 | "4-8-8-5" : 9395,
119 | "4-8-9-5" : 10107,
120 | "4-9-4-5" : 2001,
121 | "4-9-5-5" : 36833,
122 | "4-9-6-5" : 63037,
123 | "4-9-7-5" : 11290,
124 | "4-9-8-5" : 7304,
125 | "4-9-9-5" : 6028
126 | }
127 |
--------------------------------------------------------------------------------
/src/test/data/v1.0.0/ept-sources/list.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "bounds" :
4 | [
5 | -8537266.23682,
6 | 4648829.64673,
7 | -21.5100405421,
8 | -8535317.83835,
9 | 4650752.26489,
10 | 1058.75995948
11 | ],
12 | "id" : "MD_GoldenBeach_2012_000001.laz",
13 | "inserts" : 3346722,
14 | "path" : "s3://usgs-lidar/Projects/MD_GoldenBeach_2012/laz/MD_GoldenBeach_2012_000001.laz",
15 | "points" : 3346722,
16 | "status" : "inserted",
17 | "url" : "0.json"
18 | },
19 | {
20 | "bounds" :
21 | [
22 | -8535350.00385,
23 | 4648864.82088,
24 | -1.23004054371,
25 | -8533403.96792,
26 | 4650674.81282,
27 | 1068.01995948
28 | ],
29 | "id" : "MD_GoldenBeach_2012_000002.laz",
30 | "inserts" : 1513936,
31 | "path" : "s3://usgs-lidar/Projects/MD_GoldenBeach_2012/laz/MD_GoldenBeach_2012_000002.laz",
32 | "points" : 1513936,
33 | "status" : "inserted",
34 | "url" : "0.json"
35 | }
36 | ]
--------------------------------------------------------------------------------
/src/test/data/v1.0.0/ept.json:
--------------------------------------------------------------------------------
1 | {
2 | "bounds" :
3 | [
4 | -8537268,
5 | 4647858,
6 | -1409,
7 | -8533402,
8 | 4651724,
9 | 2457
10 | ],
11 | "boundsConforming" :
12 | [
13 | -8537267,
14 | 4648829,
15 | -22,
16 | -8533403,
17 | 4650753,
18 | 1069
19 | ],
20 | "dataType" : "laszip",
21 | "hierarchyType" : "json",
22 | "points" : 4860658,
23 | "schema" :
24 | [
25 | {
26 | "name" : "X",
27 | "offset" : -8535335,
28 | "scale" : 0.01,
29 | "size" : 4,
30 | "type" : "signed"
31 | },
32 | {
33 | "name" : "Y",
34 | "offset" : 4649791,
35 | "scale" : 0.01,
36 | "size" : 4,
37 | "type" : "signed"
38 | },
39 | {
40 | "name" : "Z",
41 | "offset" : 523,
42 | "scale" : 0.01,
43 | "size" : 4,
44 | "type" : "signed"
45 | },
46 | {
47 | "name" : "Intensity",
48 | "size" : 2,
49 | "type" : "unsigned"
50 | },
51 | {
52 | "name" : "ReturnNumber",
53 | "size" : 1,
54 | "type" : "unsigned"
55 | },
56 | {
57 | "name" : "NumberOfReturns",
58 | "size" : 1,
59 | "type" : "unsigned"
60 | },
61 | {
62 | "name" : "ScanDirectionFlag",
63 | "size" : 1,
64 | "type" : "unsigned"
65 | },
66 | {
67 | "name" : "EdgeOfFlightLine",
68 | "size" : 1,
69 | "type" : "unsigned"
70 | },
71 | {
72 | "name" : "Classification",
73 | "size" : 1,
74 | "type" : "unsigned"
75 | },
76 | {
77 | "name" : "ScanAngleRank",
78 | "size" : 4,
79 | "type" : "float"
80 | },
81 | {
82 | "name" : "UserData",
83 | "size" : 1,
84 | "type" : "unsigned"
85 | },
86 | {
87 | "name" : "PointSourceId",
88 | "size" : 2,
89 | "type" : "unsigned"
90 | },
91 | {
92 | "name" : "GpsTime",
93 | "size" : 8,
94 | "type" : "float"
95 | },
96 | {
97 | "name" : "OriginId",
98 | "size" : 4,
99 | "type" : "unsigned"
100 | }
101 | ],
102 | "span" : 256,
103 | "srs" :
104 | {
105 | "authority" : "EPSG",
106 | "horizontal" : "3857",
107 | "wkt" : "PROJCS[\"WGS 84 / Pseudo-Mercator\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Mercator_1SP\"],PARAMETER[\"central_meridian\",0],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH],EXTENSION[\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs\"],AUTHORITY[\"EPSG\",\"3857\"]]"
108 | },
109 | "version" : "1.0.0"
110 | }
--------------------------------------------------------------------------------
/src/test/data/v1.1.0/ept-sources/manifest.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "bounds": [
4 | -9734547.744911846,
5 | 3679489.224339797,
6 | 77.262,
7 | -9732784.957760049,
8 | 3681260.6731083132,
9 | 144.093
10 | ],
11 | "inserted": true,
12 | "metadataPath": "USGS_LPC_AL_25_County_Lidar_2017_B17_16R_DV_5769.json",
13 | "path": "s3://usgs-lidar/Projects/AL_25_County_Lidar_2017_B17/AL_25Co_TL_2017/LAZ/USGS_LPC_AL_25_County_Lidar_2017_B17_16R_DV_5769.laz",
14 | "points": 8401458
15 | },
16 | {
17 | "bounds": [
18 | -9598270.086306026,
19 | 3831938.523117474,
20 | -20.137,
21 | -9596479.071857166,
22 | 3833738.1180462562,
23 | 680.845
24 | ],
25 | "inserted": true,
26 | "metadataPath": "USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_7398.json",
27 | "path": "s3://usgs-lidar/Projects/AL_25_County_Lidar_2017_B17/AL_25Co_TL_2017/LAZ/USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_7398.laz",
28 | "points": 8619828
29 | },
30 | {
31 | "bounds": [
32 | -9589487.068212114,
33 | 3818627.438515928,
34 | 42.342,
35 | -9587705.268703448,
36 | 3819385.8839263823,
37 | 84.982
38 | ],
39 | "inserted": true,
40 | "metadataPath": "USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_8086.json",
41 | "path": "s3://usgs-lidar/Projects/AL_25_County_Lidar_2017_B17/AL_25Co_TL_2017/LAZ/USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_8086.laz",
42 | "points": 823529
43 | },
44 | {
45 | "bounds": [
46 | -9589481.139647068,
47 | 3819371.4617570043,
48 | 36.651,
49 | -9587690.767287895,
50 | 3821170.428511235,
51 | 131.13
52 | ],
53 | "inserted": true,
54 | "metadataPath": "USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_8088.json",
55 | "path": "s3://usgs-lidar/Projects/AL_25_County_Lidar_2017_B17/AL_25Co_TL_2017/LAZ/USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_8088.laz",
56 | "points": 7513803
57 | }
58 | ]
--------------------------------------------------------------------------------
/src/test/data/v1.1.0/ept.json:
--------------------------------------------------------------------------------
1 | {
2 | "bounds": [
3 | -9738245,
4 | 3679488,
5 | -76796,
6 | -9583993,
7 | 3833740,
8 | 77456
9 | ],
10 | "boundsConforming": [
11 | -9734548.0,
12 | 3679489.0,
13 | -21.0,
14 | -9587690.0,
15 | 3833739.0,
16 | 681.0
17 | ],
18 | "dataType": "laszip",
19 | "hierarchyType": "json",
20 | "points": 25358618,
21 | "schema": [
22 | {
23 | "name": "X",
24 | "offset": -9661119,
25 | "scale": 0.001,
26 | "size": 4,
27 | "type": "signed"
28 | },
29 | {
30 | "name": "Y",
31 | "offset": 3756614,
32 | "scale": 0.001,
33 | "size": 4,
34 | "type": "signed"
35 | },
36 | {
37 | "name": "Z",
38 | "offset": 330,
39 | "scale": 0.001,
40 | "size": 4,
41 | "type": "signed"
42 | },
43 | {
44 | "name": "Intensity",
45 | "size": 2,
46 | "type": "unsigned"
47 | },
48 | {
49 | "name": "ReturnNumber",
50 | "size": 1,
51 | "type": "unsigned"
52 | },
53 | {
54 | "name": "NumberOfReturns",
55 | "size": 1,
56 | "type": "unsigned"
57 | },
58 | {
59 | "name": "ScanDirectionFlag",
60 | "size": 1,
61 | "type": "unsigned"
62 | },
63 | {
64 | "name": "EdgeOfFlightLine",
65 | "size": 1,
66 | "type": "unsigned"
67 | },
68 | {
69 | "name": "Classification",
70 | "size": 1,
71 | "type": "unsigned"
72 | },
73 | {
74 | "name": "ScanAngleRank",
75 | "size": 4,
76 | "type": "float"
77 | },
78 | {
79 | "name": "UserData",
80 | "size": 1,
81 | "type": "unsigned"
82 | },
83 | {
84 | "name": "PointSourceId",
85 | "size": 2,
86 | "type": "unsigned"
87 | },
88 | {
89 | "name": "GpsTime",
90 | "size": 8,
91 | "type": "float"
92 | },
93 | {
94 | "name": "ScanChannel",
95 | "size": 1,
96 | "type": "unsigned"
97 | },
98 | {
99 | "name": "ClassFlags",
100 | "size": 1,
101 | "type": "unsigned"
102 | }
103 | ],
104 | "span": 128,
105 | "srs": {
106 | "authority": "EPSG",
107 | "horizontal": "3857",
108 | "wkt": "PROJCS[\"WGS 84 / Pseudo-Mercator\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Mercator_1SP\"],PARAMETER[\"central_meridian\",0],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH],EXTENSION[\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs\"],AUTHORITY[\"EPSG\",\"3857\"]]"
109 | },
110 | "version": "1.1.0"
111 | }
--------------------------------------------------------------------------------
/src/test/data/vmixed/ept-sources/manifest.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "bounds": [
4 | -9734547.744911846,
5 | 3679489.224339797,
6 | 77.262,
7 | -9732784.957760049,
8 | 3681260.6731083132,
9 | 144.093
10 | ],
11 | "inserted": true,
12 | "metadataPath": "USGS_LPC_AL_25_County_Lidar_2017_B17_16R_DV_5769.json",
13 | "path": "s3://usgs-lidar/Projects/AL_25_County_Lidar_2017_B17/AL_25Co_TL_2017/LAZ/USGS_LPC_AL_25_County_Lidar_2017_B17_16R_DV_5769.laz",
14 | "points": 8401458
15 | },
16 | {
17 | "bounds": [
18 | -9598270.086306026,
19 | 3831938.523117474,
20 | -20.137,
21 | -9596479.071857166,
22 | 3833738.1180462562,
23 | 680.845
24 | ],
25 | "inserted": true,
26 | "metadataPath": "USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_7398.json",
27 | "path": "s3://usgs-lidar/Projects/AL_25_County_Lidar_2017_B17/AL_25Co_TL_2017/LAZ/USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_7398.laz",
28 | "points": 8619828
29 | },
30 | {
31 | "bounds": [
32 | -9589487.068212114,
33 | 3818627.438515928,
34 | 42.342,
35 | -9587705.268703448,
36 | 3819385.8839263823,
37 | 84.982
38 | ],
39 | "inserted": true,
40 | "metadataPath": "USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_8086.json",
41 | "path": "s3://usgs-lidar/Projects/AL_25_County_Lidar_2017_B17/AL_25Co_TL_2017/LAZ/USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_8086.laz",
42 | "points": 823529
43 | },
44 | {
45 | "bounds": [
46 | -9589481.139647068,
47 | 3819371.4617570043,
48 | 36.651,
49 | -9587690.767287895,
50 | 3821170.428511235,
51 | 131.13
52 | ],
53 | "inserted": true,
54 | "metadataPath": "USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_8088.json",
55 | "path": "s3://usgs-lidar/Projects/AL_25_County_Lidar_2017_B17/AL_25Co_TL_2017/LAZ/USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_8088.laz",
56 | "points": 7513803
57 | }
58 | ]
--------------------------------------------------------------------------------
/src/test/data/vmixed/ept.json:
--------------------------------------------------------------------------------
1 | {
2 | "bounds": [
3 | -9738245,
4 | 3679488,
5 | -76796,
6 | -9583993,
7 | 3833740,
8 | 77456
9 | ],
10 | "boundsConforming": [
11 | -9734548.0,
12 | 3679489.0,
13 | -21.0,
14 | -9587690.0,
15 | 3833739.0,
16 | 681.0
17 | ],
18 | "dataType": "laszip",
19 | "hierarchyType": "json",
20 | "points": 25358618,
21 | "schema": [
22 | {
23 | "name": "X",
24 | "offset": -9661119,
25 | "scale": 0.001,
26 | "size": 4,
27 | "type": "signed"
28 | },
29 | {
30 | "name": "Y",
31 | "offset": 3756614,
32 | "scale": 0.001,
33 | "size": 4,
34 | "type": "signed"
35 | },
36 | {
37 | "name": "Z",
38 | "offset": 330,
39 | "scale": 0.001,
40 | "size": 4,
41 | "type": "signed"
42 | },
43 | {
44 | "name": "Intensity",
45 | "size": 2,
46 | "type": "unsigned"
47 | },
48 | {
49 | "name": "ReturnNumber",
50 | "size": 1,
51 | "type": "unsigned"
52 | },
53 | {
54 | "name": "NumberOfReturns",
55 | "size": 1,
56 | "type": "unsigned"
57 | },
58 | {
59 | "name": "ScanDirectionFlag",
60 | "size": 1,
61 | "type": "unsigned"
62 | },
63 | {
64 | "name": "EdgeOfFlightLine",
65 | "size": 1,
66 | "type": "unsigned"
67 | },
68 | {
69 | "name": "Classification",
70 | "size": 1,
71 | "type": "unsigned"
72 | },
73 | {
74 | "name": "ScanAngleRank",
75 | "size": 4,
76 | "type": "float"
77 | },
78 | {
79 | "name": "UserData",
80 | "size": 1,
81 | "type": "unsigned"
82 | },
83 | {
84 | "name": "PointSourceId",
85 | "size": 2,
86 | "type": "unsigned"
87 | },
88 | {
89 | "name": "GpsTime",
90 | "size": 8,
91 | "type": "float"
92 | },
93 | {
94 | "name": "ScanChannel",
95 | "size": 1,
96 | "type": "unsigned"
97 | },
98 | {
99 | "name": "ClassFlags",
100 | "size": 1,
101 | "type": "unsigned"
102 | }
103 | ],
104 | "span": 128,
105 | "srs": {
106 | "authority": "EPSG",
107 | "horizontal": "3857",
108 | "wkt": "PROJCS[\"WGS 84 / Pseudo-Mercator\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Mercator_1SP\"],PARAMETER[\"central_meridian\",0],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH],EXTENSION[\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs\"],AUTHORITY[\"EPSG\",\"3857\"]]"
109 | },
110 | "version": "1.0.0"
111 | }
--------------------------------------------------------------------------------
/src/test/dummy.ts:
--------------------------------------------------------------------------------
1 | import * as Ept from 'ept'
2 |
3 | const xyz: Ept.Schema = [
4 | { name: 'X', type: 'signed', size: 4, scale: 0.01, offset: 0 },
5 | { name: 'Y', type: 'signed', size: 4, scale: 0.01, offset: 0 },
6 | { name: 'Z', type: 'signed', size: 4, scale: 0.01, offset: 0 },
7 | ]
8 |
9 | const rgb: Ept.Schema = [
10 | { name: 'Red', type: 'unsigned', size: 2 },
11 | { name: 'Green', type: 'unsigned', size: 2 },
12 | { name: 'Blue', type: 'unsigned', size: 2 },
13 | ]
14 |
15 | const xyzrgb: Ept.Schema = [...xyz, ...rgb]
16 | const xyzrgbi: Ept.Schema = [
17 | ...xyzrgb,
18 | { name: 'Intensity', type: 'signed', size: 2 },
19 | ]
20 |
21 | export const Schema = { xyz, rgb, xyzrgb, xyzrgbi }
22 |
--------------------------------------------------------------------------------
/src/test/index.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'path'
2 |
3 | export * as Dummy from './dummy'
4 | export * as Ellipsoid from './ellipsoid'
5 | export * as Pnts from './pnts'
6 | export * as Server from './server'
7 |
8 | export const testdir = join(__dirname, 'data')
9 | export const keyfile = join(__dirname, 'ssl/fake.key')
10 | export const certfile = join(__dirname, 'ssl/fake.cert')
11 | export const cafile = join(__dirname, 'ssl/ca.pem')
12 |
--------------------------------------------------------------------------------
/src/test/pnts.ts:
--------------------------------------------------------------------------------
1 | import { Options } from '3d-tiles'
2 | export const defaultOptions: Options = {
3 | zOffset: 0,
4 | dimensions: [],
5 | addons: [],
6 | truncate: false,
7 | }
8 |
--------------------------------------------------------------------------------
/src/test/server.ts:
--------------------------------------------------------------------------------
1 | import { Server } from 'http'
2 | import Koa from 'koa'
3 |
4 | const portBase = process.env.HTTP_PORT ? parseInt(process.env.HTTP_PORT) : 36363
5 |
6 | // For each test suite that creates a fake server, we grab a dedicated port so
7 | // we can run all the tests in parallel without EADDRINUSE errors.
8 | export function getPort(offset: number) {
9 | return portBase + offset
10 | }
11 |
12 | export async function listen(app: Koa, port: number): Promise {
13 | return new Promise((resolve, reject) => {
14 | const server = app.listen(port, () => resolve(server))
15 | app.on('error', reject)
16 | })
17 | }
18 | export async function destroy(server: Server) {
19 | await new Promise((resolve) => server.close(resolve))
20 | }
21 |
--------------------------------------------------------------------------------
/src/test/ssl/ca.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
4 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
5 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
6 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
7 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
9 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
10 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
11 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
12 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
13 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
14 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
15 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
16 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
17 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
18 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
19 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
20 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
21 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
22 | -----END CERTIFICATE-----
23 |
--------------------------------------------------------------------------------
/src/test/ssl/fake.cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICsDCCAhmgAwIBAgIJAPWGe2TraHlSMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
4 | aWRnaXRzIFB0eSBMdGQwHhcNMjAwNDEwMTcyMzEyWhcNMjAwNTEwMTcyMzEyWjBF
5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
7 | gQDMtuGHsRTGMzX9gj2ZOgpqEFbdJmr7/3qRXiRIkb7ahXsAySMxc8UDs4T+RhMw
8 | mbhOwa0Ky3/sNCJlVtTxYlhCippniruLNaUTBj0SQcjOfHCLBwDGptbbJ9+HmM95
9 | 6ftGy4wci4ayCSyHr1O6eprL5rKaqHMRozZJ1WUP86RngQIDAQABo4GnMIGkMB0G
10 | A1UdDgQWBBQZAg4FouUj9T6dJWCzWNFwaHGgQDB1BgNVHSMEbjBsgBQZAg4FouUj
11 | 9T6dJWCzWNFwaHGgQKFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
12 | U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAPWGe2Tr
13 | aHlSMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEALKhqBg8b/t3TVP5c
14 | IaiuKRAR7/5CiaiO89qY447qzHGbNb9n4obH7W+HCrVnpczPzsAa/8lfMpwQrKqu
15 | oYNk4f3JEUO/oo92pXaBrx22M/n1vQYB2vT+rtc7p/2XDewK1rTE5Zg5W0Qjix4Y
16 | angGPpyYexedkjQPPCsRcQyl0Y8=
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/src/test/ssl/fake.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXwIBAAKBgQDMtuGHsRTGMzX9gj2ZOgpqEFbdJmr7/3qRXiRIkb7ahXsAySMx
3 | c8UDs4T+RhMwmbhOwa0Ky3/sNCJlVtTxYlhCippniruLNaUTBj0SQcjOfHCLBwDG
4 | ptbbJ9+HmM956ftGy4wci4ayCSyHr1O6eprL5rKaqHMRozZJ1WUP86RngQIDAQAB
5 | AoGBALLWjX4CODjSIjd+oRuMNe7kqU33svzE+qmmCaKQGKBEfel4zdL05UdPfD66
6 | rycFE5tR3T8R+oY6IVZQniTmGl0vI//8NjHmTf99m1z2WmF64X/aeTKb+PTFEQ+F
7 | g1JOGjZXqNk7s2/3aalL6ceEsrVL5fi7jol01H7B5HdNfN6VAkEA6vh5tgLFHP88
8 | 57zEcjAmjbB1USeBaeEpCSlfdZjUQ5LtmZsHNiqC0b/uGP0i2iNQyTIUcwbr73cU
9 | 3IGfpD7qrwJBAN8JMMetjfuQ/weSlbraH0peeKTtxFPS6Nqisdg3NztZ+tl5fyjd
10 | 6gN7WjLJfc+4IfUo+MbKUs2mW1ktutbenM8CQQCsj6LC+rHMI96UydrziU0nDYp6
11 | +Spfmc5LPiku8fghUExSXLolG39Lj0rK60ynKvxvZeoCt/iZuriFYGTfeJ1dAkEA
12 | zsMDYKkJDeS+N/PxMJZTGat4plTxg0/ro/vdaPbPEgt5XDCg7G7FKVMqLBjUtEMb
13 | 392KuycHGjSVTJfzNIyMywJBAL9DjMY7rznHnkIV+njRACiUVaAHdGJwTbCU52yy
14 | NxBNCMFq67qpgAYTiyTKykTveNYMFdsLWMWW22rrgtct+Us=
15 | -----END RSA PRIVATE KEY-----
16 |
--------------------------------------------------------------------------------
/src/types/ctype.ts:
--------------------------------------------------------------------------------
1 | export type Ctype =
2 | | 'int8'
3 | | 'int16'
4 | | 'int32'
5 | | 'int64'
6 | | 'uint8'
7 | | 'uint16'
8 | | 'uint32'
9 | | 'uint64'
10 | | 'float'
11 | | 'double'
12 |
--------------------------------------------------------------------------------
/src/types/error.ts:
--------------------------------------------------------------------------------
1 | export class EptToolsError extends Error {}
2 | export class HttpError extends Error {
3 | statusCode: number
4 | constructor(code: number, what: string) {
5 | super(what)
6 | this.statusCode = code
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/types/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'zstd-codec' {
2 | class Streaming {
3 | constructor()
4 | decompress(compressed: Uint8Array): Uint8Array
5 | }
6 |
7 | type Zstd = { Streaming: typeof Streaming }
8 |
9 | class ZstdCodec {
10 | static run(cb: (zstd: Zstd) => void): void
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export { Ctype } from './ctype'
2 | export { EptToolsError, HttpError } from './error'
3 | export { Point } from './point'
4 |
--------------------------------------------------------------------------------
/src/types/point.ts:
--------------------------------------------------------------------------------
1 | export type Point = [number, number, number]
2 |
--------------------------------------------------------------------------------
/src/utils/bytes.ts:
--------------------------------------------------------------------------------
1 | import { Ctype } from 'types'
2 |
3 | export declare namespace Bytes {
4 | type Getter = (offset: number) => number
5 | type Setter = (value: number, offset: number) => void
6 | }
7 |
8 | export const Bytes = { getter, setter }
9 |
10 | function getter(buffer: Buffer, ctype: Ctype): Bytes.Getter {
11 | switch (ctype) {
12 | case 'int8':
13 | return (offset) => buffer.readInt8(offset)
14 | case 'int16':
15 | return (offset) => buffer.readInt16LE(offset)
16 | case 'int32':
17 | return (offset) => buffer.readInt32LE(offset)
18 | case 'int64':
19 | return (offset) => Number(buffer.readBigInt64LE(offset))
20 | case 'uint8':
21 | return (offset) => buffer.readUInt8(offset)
22 | case 'uint16':
23 | return (offset) => buffer.readUInt16LE(offset)
24 | case 'uint32':
25 | return (offset) => buffer.readUInt32LE(offset)
26 | case 'uint64':
27 | return (offset) => Number(buffer.readBigUInt64LE(offset))
28 | case 'float':
29 | return (offset) => buffer.readFloatLE(offset)
30 | case 'double':
31 | return (offset) => buffer.readDoubleLE(offset)
32 | }
33 | }
34 |
35 | function setter(buffer: Buffer, ctype: Ctype): Bytes.Setter {
36 | switch (ctype) {
37 | case 'int8':
38 | return (value, offset) => buffer.writeInt8(value, offset)
39 | case 'int16':
40 | return (value, offset) => buffer.writeInt16LE(value, offset)
41 | case 'int32':
42 | return (value, offset) => buffer.writeInt32LE(value, offset)
43 | case 'int64':
44 | return (value, offset) => buffer.writeBigInt64LE(BigInt(value), offset)
45 | case 'uint8':
46 | return (value, offset) => buffer.writeUInt8(value, offset)
47 | case 'uint16':
48 | return (value, offset) => buffer.writeUInt16LE(value, offset)
49 | case 'uint32':
50 | return (value, offset) => buffer.writeUInt32LE(value, offset)
51 | case 'uint64':
52 | return (value, offset) => buffer.writeBigUInt64LE(BigInt(value), offset)
53 | case 'float':
54 | return (value, offset) => buffer.writeFloatLE(value, offset)
55 | case 'double':
56 | return (value, offset) => buffer.writeDoubleLE(value, offset)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/utils/get.test.ts:
--------------------------------------------------------------------------------
1 | import http from 'http'
2 | import { join } from 'path'
3 | import Koa, { Context } from 'koa'
4 |
5 | import { Server } from 'test'
6 |
7 | import { getBinary, getJson } from './get'
8 |
9 | const port = Server.getPort(0)
10 |
11 | const notafile = join(__dirname, 'test/i-do-not-exist.json')
12 | const textfile = join(__dirname, 'test/data.txt')
13 | const jsonfile = join(__dirname, 'test/data.json')
14 |
15 | test('file: not found', async () => {
16 | await expect(getBinary(notafile)).rejects.toThrow('no such file')
17 | })
18 |
19 | test('file: good binary', async () => {
20 | expect((await getBinary(textfile)).toString()).toEqual('data')
21 | })
22 |
23 | test('file: bad json', async () => {
24 | await expect(getJson(textfile)).rejects.toThrow(/unexpected token/i)
25 | })
26 |
27 | test('file: good json', async () => {
28 | expect(await getJson(jsonfile)).toEqual({ data: 42 })
29 | })
30 |
31 | test('http: failure', async () => {
32 | const app = new Koa()
33 | app.use((ctx: Context) => (ctx.status = 412))
34 | const server = await Server.listen(app, port)
35 |
36 | try {
37 | await expect(getBinary(`http://localhost:${port}`)).rejects.toThrow(
38 | /precondition failed/i
39 | )
40 | } finally {
41 | await Server.destroy(server)
42 | }
43 | })
44 |
45 | test('http: success', async () => {
46 | const app = new Koa()
47 | app.use((ctx: Context) => (ctx.body = Buffer.from('asdf')))
48 | const server = await Server.listen(app, port)
49 |
50 | try {
51 | expect((await getBinary(`http://localhost:${port}`)).toString()).toEqual(
52 | 'asdf'
53 | )
54 | } finally {
55 | await Server.destroy(server)
56 | }
57 | })
58 |
59 | test('http: success json', async () => {
60 | const app = new Koa()
61 | app.use((ctx: Context) => (ctx.body = { a: 1 }))
62 | const server = await Server.listen(app, port)
63 |
64 | try {
65 | expect(await getJson(`http://localhost:${port}`)).toEqual({ a: 1 })
66 | } finally {
67 | await Server.destroy(server)
68 | }
69 | })
70 |
--------------------------------------------------------------------------------
/src/utils/get.ts:
--------------------------------------------------------------------------------
1 | import { Forager } from 'forager'
2 |
3 | export async function getBinary(path: string) {
4 | return Forager.read(path)
5 | }
6 |
7 | export async function getJson(path: string): Promise {
8 | return Forager.readJson(path)
9 | }
10 |
11 | export async function isReadable(path: string) {
12 | try {
13 | await Forager.read(path)
14 | return true
15 | } catch (e) {
16 | return false
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './get'
2 | export { Bytes } from './bytes'
3 | export { JsonSchema, ValidationError } from './json-schema'
4 | export { Pool } from './pool'
5 | export { Reproject } from './reproject'
6 | export { Scale } from './scale'
7 |
--------------------------------------------------------------------------------
/src/utils/json-schema.test.ts:
--------------------------------------------------------------------------------
1 | import { Ept, Hierarchy } from 'ept'
2 | import { JsonSchema } from 'utils'
3 |
4 | import { Ellipsoid } from 'test'
5 |
6 | const ept = { ...Ellipsoid.ept, dataType: 'laszip' }
7 |
8 | test('valid ept', () => {
9 | const [result, errors] = JsonSchema.validate(Ept.schema, ept)
10 | expect(result).toEqual(ept)
11 | expect(errors).toHaveLength(0)
12 | })
13 |
14 | test('missing data type', () => {
15 | const partial = Ellipsoid.ept // Note that this doesn't include dataType.
16 | const [result, errors] = JsonSchema.validate(Ept.schema, partial)
17 | expect(result).toEqual(partial)
18 | expect(errors).toHaveLength(1)
19 | expect(errors[0]).toMatch('dataType')
20 | })
21 |
22 | test('valid hierarchy', () => {
23 | const hierarchy = { '0-0-0-0': 2, '1-0-0-0': -1 }
24 | const [result, errors] = JsonSchema.validate(
25 | Hierarchy.schema,
26 | hierarchy
27 | )
28 | expect(result).toEqual(hierarchy)
29 | expect(errors).toHaveLength(0)
30 | })
31 |
32 | test('invalid hierarchy', () => {
33 | const hierarchy = { '0-0-0-0': 2, '1-0-0-0': 'f' }
34 | const [result, errors] = JsonSchema.validate(
35 | Hierarchy.schema,
36 | hierarchy
37 | )
38 | expect(result).toEqual(hierarchy)
39 | expect(errors).toHaveLength(1)
40 | expect(errors[0]).toMatch('integer')
41 | })
42 |
--------------------------------------------------------------------------------
/src/utils/json-schema.ts:
--------------------------------------------------------------------------------
1 | import Ajv, { Schema } from 'ajv'
2 |
3 | const ajv = new Ajv()
4 | export const JsonSchema = { validate }
5 |
6 | export class ValidationError extends Error {
7 | errors: string[]
8 |
9 | constructor(message: string, errors: string[]) {
10 | super(message)
11 | this.errors = errors
12 | }
13 | }
14 |
15 | export type Validation = [T, string[]]
16 | function validate(schema: Schema, value: unknown): Validation {
17 | if (typeof schema === 'boolean') throw new Error('Invalid JSON schema')
18 |
19 | const validate = ajv.compile(schema)
20 | const isValid = validate(value)
21 | const errors =
22 | isValid || !validate.errors
23 | ? []
24 | : validate.errors.map((v) => {
25 | const prefix = schema.title || v.dataPath.slice(1)
26 | return (prefix.length ? `${prefix}: ` : '') + v.message
27 | })
28 |
29 | return [value as T, errors]
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/pool.test.ts:
--------------------------------------------------------------------------------
1 | import { Pool } from './pool'
2 |
3 | test('limit', async () => {
4 | const limit = 5
5 | const total = 100
6 |
7 | const min = 5
8 | const max = 50
9 |
10 | let running = 0
11 | let runmax = 0
12 | let runmin = 0
13 |
14 | type F = () => Promise
15 | const tasks: F[] = []
16 | for (let i = 0; i < total; ++i) {
17 | tasks.push(async () => {
18 | runmax = Math.max(++running, runmax)
19 | const timeout = Math.random() * (max - min) + min
20 | await new Promise((resolve) => setTimeout(resolve, timeout))
21 | runmin = Math.min(--running, runmin)
22 | return timeout
23 | })
24 | }
25 |
26 | {
27 | const results = await Pool.all(tasks, limit)
28 | expect(results.every((v) => v >= min && v < max))
29 | expect(runmax === limit)
30 | expect(runmin >= 0)
31 | }
32 |
33 | {
34 | const results = await Pool.all(tasks)
35 | expect(results.every((v) => v >= min && v < max))
36 | expect(runmin >= 0)
37 | }
38 | })
39 |
--------------------------------------------------------------------------------
/src/utils/pool.ts:
--------------------------------------------------------------------------------
1 | export const Pool = { all }
2 |
3 | type F = () => Promise
4 | async function all(list: F[], limit = Infinity) {
5 | const results: Promise[] = []
6 | const running: Promise[] = []
7 | for (const f of list) {
8 | const execution = f()
9 | results.push(execution)
10 |
11 | const watcher: Promise = execution
12 | .then(() => {
13 | running.splice(running.indexOf(watcher), 1)
14 | })
15 | .catch(() => {})
16 | running.push(watcher)
17 |
18 | if (running.length >= limit) await Promise.race(running)
19 | }
20 | return Promise.all(results)
21 | }
22 |
--------------------------------------------------------------------------------
/src/utils/reproject.test.ts:
--------------------------------------------------------------------------------
1 | import { Reproject } from '.'
2 |
3 | const point3857 = [-8242596, 4966606]
4 | const point4326 = [-74.0444996762243, 40.68919824733844]
5 |
6 | test('create: implicit 4326', () => {
7 | const reproject = Reproject.create('EPSG:3857')
8 | expect(reproject(point3857)).toEqual(point4326)
9 | expect(reproject([...point3857, 42])).toEqual([...point4326, 42])
10 | })
11 |
12 | test('create: explicit 4326', () => {
13 | const reproject = Reproject.create('EPSG:3857', 'EPSG:4326')
14 | expect(reproject(point3857)).toEqual(point4326)
15 | expect(reproject([...point3857, 42])).toEqual([...point4326, 42])
16 | })
17 |
--------------------------------------------------------------------------------
/src/utils/reproject.ts:
--------------------------------------------------------------------------------
1 | import proj from 'fatproj'
2 |
3 | type Point2d = [number, number]
4 | type Point3d = [number, number, number]
5 |
6 | export type Reproject = (p: P) => P
7 | export const Reproject = { create }
8 |
9 | function create(src: string, dst = 'EPSG:4326'): Reproject {
10 | return proj(src, dst).forward as Reproject
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/scale.test.ts:
--------------------------------------------------------------------------------
1 | import { Scale } from './scale'
2 |
3 | test('apply', () => {
4 | expect(Scale.apply(100)).toEqual(100)
5 | expect(Scale.apply(100, 0.1)).toEqual(1000)
6 | expect(Scale.apply(100, 0.1, 50)).toEqual(500)
7 | })
8 |
9 | test('unapply', () => {
10 | expect(Scale.unapply(100)).toEqual(100)
11 | expect(Scale.unapply(1000, 0.1)).toEqual(100)
12 | expect(Scale.unapply(500, 0.1, 50)).toEqual(100)
13 | })
14 |
--------------------------------------------------------------------------------
/src/utils/scale.ts:
--------------------------------------------------------------------------------
1 | export const Scale = {
2 | apply: (v: number, scale = 1, offset = 0) => (v - offset) / scale,
3 | unapply: (v: number, scale = 1, offset = 0) => v * scale + offset,
4 | }
5 |
--------------------------------------------------------------------------------
/src/utils/test/data.json:
--------------------------------------------------------------------------------
1 | { "data": 42 }
2 |
--------------------------------------------------------------------------------
/src/utils/test/data.txt:
--------------------------------------------------------------------------------
1 | data
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["src/**/*.test.ts", "src/**/test/*"],
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src"],
3 | "compilerOptions": {
4 | "baseUrl": "src",
5 | "rootDir": "src",
6 | "outDir": "lib",
7 |
8 | "target": "es2017",
9 | "module": "commonjs",
10 | "lib": ["es2020"],
11 | "declaration": true,
12 | "declarationMap": true,
13 | "skipLibCheck": true,
14 |
15 | "moduleResolution": "node",
16 | "allowSyntheticDefaultImports": true,
17 | "strict": true,
18 | "noImplicitAny": true,
19 | "esModuleInterop": true,
20 | "plugins": [{ "transform": "@zerollup/ts-transform-paths" }]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------