├── .github └── workflows │ └── test.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── doc ├── serve.md ├── tile.md ├── tiling-options.md └── validate.md ├── jest.config.js ├── license ├── package.json ├── readme.md ├── src ├── 3d-tiles │ ├── batch-table │ │ ├── batch-table.test.ts │ │ ├── batch-table.ts │ │ ├── header.ts │ │ └── index.ts │ ├── bounding-volume │ │ ├── box.test.ts │ │ ├── box.ts │ │ ├── index.ts │ │ ├── point.ts │ │ ├── region.test.ts │ │ ├── region.ts │ │ ├── sphere.test.ts │ │ └── sphere.ts │ ├── cache.ts │ ├── feature-table │ │ ├── binary.ts │ │ ├── feature-table.test.ts │ │ ├── feature-table.ts │ │ ├── header.ts │ │ ├── index.ts │ │ ├── rgb.test.ts │ │ ├── rgb.ts │ │ ├── xyz.test.ts │ │ └── xyz.ts │ ├── index.ts │ ├── pnts │ │ ├── constants.ts │ │ ├── header.test.ts │ │ ├── header.ts │ │ ├── index.ts │ │ └── pnts.ts │ ├── server │ │ ├── cors.test.ts │ │ ├── cors.ts │ │ ├── httpx.test.ts │ │ ├── httpx.ts │ │ ├── index.ts │ │ ├── ssl.test.ts │ │ ├── ssl.ts │ │ └── utils.ts │ ├── tileset │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── tile.ts │ │ ├── tileset.test.ts │ │ └── tileset.ts │ ├── translate.test.ts │ ├── translate.ts │ ├── types.ts │ ├── utils.test.ts │ └── utils.ts ├── app │ ├── cli.ts │ ├── index.ts │ ├── lambda.ts │ ├── tile.ts │ ├── upgrade.test.ts │ ├── upgrade.ts │ └── validate.ts ├── ept │ ├── bounds.test.ts │ ├── bounds.ts │ ├── data-type │ │ ├── binary.test.ts │ │ ├── binary.ts │ │ ├── index.test.ts │ │ ├── index.ts │ │ ├── laszip │ │ │ ├── format.ts │ │ │ ├── header.ts │ │ │ ├── index.test.ts │ │ │ └── index.ts │ │ ├── zstandard.test.ts │ │ └── zstandard.ts │ ├── dimension.test.ts │ ├── dimension.ts │ ├── ept.ts │ ├── hierarchy-type.ts │ ├── hierarchy.ts │ ├── index.ts │ ├── key.test.ts │ ├── key.ts │ ├── schema.test.ts │ ├── schema.ts │ ├── source │ │ └── index.ts │ ├── srs.ts │ ├── step.ts │ └── view.ts ├── index.ts ├── lib │ ├── laz-perf.d.ts │ ├── laz-perf.js │ └── laz-perf.wasm ├── test │ ├── data │ │ ├── ellipsoid-bin │ │ │ ├── ept-build.json │ │ │ ├── ept-data │ │ │ │ ├── 0-0-0-0.bin │ │ │ │ ├── 1-0-0-0.bin │ │ │ │ ├── 1-0-0-1.bin │ │ │ │ ├── 1-0-1-0.bin │ │ │ │ ├── 1-0-1-1.bin │ │ │ │ ├── 1-1-0-0.bin │ │ │ │ ├── 1-1-0-1.bin │ │ │ │ ├── 1-1-1-0.bin │ │ │ │ ├── 1-1-1-1.bin │ │ │ │ ├── 2-0-1-1.bin │ │ │ │ ├── 2-0-1-2.bin │ │ │ │ ├── 2-0-2-1.bin │ │ │ │ ├── 2-0-2-2.bin │ │ │ │ ├── 2-1-0-1.bin │ │ │ │ ├── 2-1-1-1.bin │ │ │ │ ├── 2-1-1-2.bin │ │ │ │ ├── 2-1-2-1.bin │ │ │ │ ├── 2-1-2-2.bin │ │ │ │ ├── 2-1-3-1.bin │ │ │ │ ├── 2-2-0-1.bin │ │ │ │ ├── 2-2-1-1.bin │ │ │ │ ├── 2-2-1-2.bin │ │ │ │ ├── 2-2-2-1.bin │ │ │ │ ├── 2-2-2-2.bin │ │ │ │ ├── 2-2-3-1.bin │ │ │ │ ├── 2-3-1-1.bin │ │ │ │ ├── 2-3-1-2.bin │ │ │ │ ├── 2-3-2-1.bin │ │ │ │ ├── 2-3-2-2.bin │ │ │ │ ├── 3-0-3-3.bin │ │ │ │ ├── 3-0-3-4.bin │ │ │ │ ├── 3-0-4-3.bin │ │ │ │ ├── 3-0-4-4.bin │ │ │ │ ├── 3-7-3-3.bin │ │ │ │ ├── 3-7-3-4.bin │ │ │ │ ├── 3-7-4-3.bin │ │ │ │ └── 3-7-4-4.bin │ │ │ ├── ept-hierarchy │ │ │ │ ├── 0-0-0-0.json │ │ │ │ ├── 2-0-1-1.json │ │ │ │ ├── 2-0-1-2.json │ │ │ │ ├── 2-0-2-1.json │ │ │ │ ├── 2-0-2-2.json │ │ │ │ ├── 2-1-0-1.json │ │ │ │ ├── 2-1-1-1.json │ │ │ │ ├── 2-1-1-2.json │ │ │ │ ├── 2-1-2-1.json │ │ │ │ ├── 2-1-2-2.json │ │ │ │ ├── 2-1-3-1.json │ │ │ │ ├── 2-2-0-1.json │ │ │ │ ├── 2-2-1-1.json │ │ │ │ ├── 2-2-1-2.json │ │ │ │ ├── 2-2-2-1.json │ │ │ │ ├── 2-2-2-2.json │ │ │ │ ├── 2-2-3-1.json │ │ │ │ ├── 2-3-1-1.json │ │ │ │ ├── 2-3-1-2.json │ │ │ │ ├── 2-3-2-1.json │ │ │ │ └── 2-3-2-2.json │ │ │ ├── ept-sources │ │ │ │ ├── ellipsoid.json │ │ │ │ └── manifest.json │ │ │ └── ept.json │ │ ├── ellipsoid-laz │ │ │ ├── ept-build.json │ │ │ ├── ept-data │ │ │ │ ├── 0-0-0-0.laz │ │ │ │ ├── 1-0-0-0.laz │ │ │ │ ├── 1-0-0-1.laz │ │ │ │ ├── 1-0-1-0.laz │ │ │ │ ├── 1-0-1-1.laz │ │ │ │ ├── 1-1-0-0.laz │ │ │ │ ├── 1-1-0-1.laz │ │ │ │ ├── 1-1-1-0.laz │ │ │ │ ├── 1-1-1-1.laz │ │ │ │ ├── 2-0-1-1.laz │ │ │ │ ├── 2-0-1-2.laz │ │ │ │ ├── 2-0-2-1.laz │ │ │ │ ├── 2-0-2-2.laz │ │ │ │ ├── 2-1-0-1.laz │ │ │ │ ├── 2-1-1-1.laz │ │ │ │ ├── 2-1-1-2.laz │ │ │ │ ├── 2-1-2-1.laz │ │ │ │ ├── 2-1-2-2.laz │ │ │ │ ├── 2-1-3-1.laz │ │ │ │ ├── 2-2-0-1.laz │ │ │ │ ├── 2-2-1-1.laz │ │ │ │ ├── 2-2-1-2.laz │ │ │ │ ├── 2-2-2-1.laz │ │ │ │ ├── 2-2-2-2.laz │ │ │ │ ├── 2-2-3-1.laz │ │ │ │ ├── 2-3-1-1.laz │ │ │ │ ├── 2-3-1-2.laz │ │ │ │ ├── 2-3-2-1.laz │ │ │ │ ├── 2-3-2-2.laz │ │ │ │ ├── 3-0-3-3.laz │ │ │ │ ├── 3-0-3-4.laz │ │ │ │ ├── 3-0-4-3.laz │ │ │ │ ├── 3-0-4-4.laz │ │ │ │ ├── 3-7-3-3.laz │ │ │ │ ├── 3-7-3-4.laz │ │ │ │ ├── 3-7-4-3.laz │ │ │ │ └── 3-7-4-4.laz │ │ │ ├── ept-hierarchy │ │ │ │ ├── 0-0-0-0.json │ │ │ │ ├── 2-0-1-1.json │ │ │ │ ├── 2-0-1-2.json │ │ │ │ ├── 2-0-2-1.json │ │ │ │ ├── 2-0-2-2.json │ │ │ │ ├── 2-1-0-1.json │ │ │ │ ├── 2-1-1-1.json │ │ │ │ ├── 2-1-1-2.json │ │ │ │ ├── 2-1-2-1.json │ │ │ │ ├── 2-1-2-2.json │ │ │ │ ├── 2-1-3-1.json │ │ │ │ ├── 2-2-0-1.json │ │ │ │ ├── 2-2-1-1.json │ │ │ │ ├── 2-2-1-2.json │ │ │ │ ├── 2-2-2-1.json │ │ │ │ ├── 2-2-2-2.json │ │ │ │ ├── 2-2-3-1.json │ │ │ │ ├── 2-3-1-1.json │ │ │ │ ├── 2-3-1-2.json │ │ │ │ ├── 2-3-2-1.json │ │ │ │ └── 2-3-2-2.json │ │ │ ├── ept-sources │ │ │ │ ├── ellipsoid.json │ │ │ │ └── manifest.json │ │ │ └── ept.json │ │ ├── ellipsoid-zst │ │ │ ├── ept-build.json │ │ │ ├── ept-data │ │ │ │ ├── 0-0-0-0.zst │ │ │ │ ├── 1-0-0-0.zst │ │ │ │ ├── 1-0-0-1.zst │ │ │ │ ├── 1-0-1-0.zst │ │ │ │ ├── 1-0-1-1.zst │ │ │ │ ├── 1-1-0-0.zst │ │ │ │ ├── 1-1-0-1.zst │ │ │ │ ├── 1-1-1-0.zst │ │ │ │ ├── 1-1-1-1.zst │ │ │ │ ├── 2-0-1-1.zst │ │ │ │ ├── 2-0-1-2.zst │ │ │ │ ├── 2-0-2-1.zst │ │ │ │ ├── 2-0-2-2.zst │ │ │ │ ├── 2-1-0-1.zst │ │ │ │ ├── 2-1-1-1.zst │ │ │ │ ├── 2-1-1-2.zst │ │ │ │ ├── 2-1-2-1.zst │ │ │ │ ├── 2-1-2-2.zst │ │ │ │ ├── 2-1-3-1.zst │ │ │ │ ├── 2-2-0-1.zst │ │ │ │ ├── 2-2-1-1.zst │ │ │ │ ├── 2-2-1-2.zst │ │ │ │ ├── 2-2-2-1.zst │ │ │ │ ├── 2-2-2-2.zst │ │ │ │ ├── 2-2-3-1.zst │ │ │ │ ├── 2-3-1-1.zst │ │ │ │ ├── 2-3-1-2.zst │ │ │ │ ├── 2-3-2-1.zst │ │ │ │ ├── 2-3-2-2.zst │ │ │ │ ├── 3-0-3-3.zst │ │ │ │ ├── 3-0-3-4.zst │ │ │ │ ├── 3-0-4-3.zst │ │ │ │ ├── 3-0-4-4.zst │ │ │ │ ├── 3-7-3-3.zst │ │ │ │ ├── 3-7-3-4.zst │ │ │ │ ├── 3-7-4-3.zst │ │ │ │ └── 3-7-4-4.zst │ │ │ ├── ept-hierarchy │ │ │ │ ├── 0-0-0-0.json │ │ │ │ ├── 2-0-1-1.json │ │ │ │ ├── 2-0-1-2.json │ │ │ │ ├── 2-0-2-1.json │ │ │ │ ├── 2-0-2-2.json │ │ │ │ ├── 2-1-0-1.json │ │ │ │ ├── 2-1-1-1.json │ │ │ │ ├── 2-1-1-2.json │ │ │ │ ├── 2-1-2-1.json │ │ │ │ ├── 2-1-2-2.json │ │ │ │ ├── 2-1-3-1.json │ │ │ │ ├── 2-2-0-1.json │ │ │ │ ├── 2-2-1-1.json │ │ │ │ ├── 2-2-1-2.json │ │ │ │ ├── 2-2-2-1.json │ │ │ │ ├── 2-2-2-2.json │ │ │ │ ├── 2-2-3-1.json │ │ │ │ ├── 2-3-1-1.json │ │ │ │ ├── 2-3-1-2.json │ │ │ │ ├── 2-3-2-1.json │ │ │ │ └── 2-3-2-2.json │ │ │ ├── ept-sources │ │ │ │ ├── ellipsoid.json │ │ │ │ └── manifest.json │ │ │ └── ept.json │ │ ├── no-srs-code │ │ │ └── ept.json │ │ ├── v1.0.0 │ │ │ ├── ept-hierarchy │ │ │ │ └── 0-0-0-0.json │ │ │ ├── ept-sources │ │ │ │ ├── 0.json │ │ │ │ └── list.json │ │ │ └── ept.json │ │ ├── v1.1.0 │ │ │ ├── ept-hierarchy │ │ │ │ └── 0-0-0-0.json │ │ │ ├── ept-sources │ │ │ │ ├── USGS_LPC_AL_25_County_Lidar_2017_B17_16R_DV_5769.json │ │ │ │ ├── USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_7398.json │ │ │ │ ├── USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_8086.json │ │ │ │ ├── USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_8088.json │ │ │ │ └── manifest.json │ │ │ └── ept.json │ │ └── vmixed │ │ │ ├── ept-hierarchy │ │ │ └── 0-0-0-0.json │ │ │ ├── ept-sources │ │ │ ├── USGS_LPC_AL_25_County_Lidar_2017_B17_16R_DV_5769.json │ │ │ ├── USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_7398.json │ │ │ ├── USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_8086.json │ │ │ ├── USGS_LPC_AL_25_County_Lidar_2017_B17_16S_EA_8088.json │ │ │ └── manifest.json │ │ │ └── ept.json │ ├── dummy.ts │ ├── ellipsoid.ts │ ├── index.ts │ ├── pnts.ts │ ├── server.ts │ └── ssl │ │ ├── ca.pem │ │ ├── fake.cert │ │ └── fake.key ├── types │ ├── ctype.ts │ ├── error.ts │ ├── global.d.ts │ ├── index.ts │ └── point.ts └── utils │ ├── bytes.ts │ ├── get.test.ts │ ├── get.ts │ ├── index.ts │ ├── json-schema.test.ts │ ├── json-schema.ts │ ├── pool.test.ts │ ├── pool.ts │ ├── reproject.test.ts │ ├── reproject.ts │ ├── scale.test.ts │ ├── scale.ts │ └── test │ ├── data.json │ └── data.txt ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: test 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: '12.x' 16 | - run: npm install 17 | - run: npm run build 18 | - run: npm test --coverage --all 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | lib/** 3 | coverage/** 4 | lambda/** 5 | src/test/data/tmp/** 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !package.json 3 | !lib/** 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /doc/serve.md: -------------------------------------------------------------------------------- 1 | # ept serve 2 | 3 | Create an HTTP server that creates on-demand [3D Tiles](https://github.com/AnalyticalGraphicsInc/3d-tiles) from EPT datasets. The only restriction for 4 | EPT data is that the spatial reference must contain an EPSG code. 5 | 6 | ```bash 7 | ept serve --help 8 | 9 | Serve 3D Tiles on the fly from EPT resources 10 | 11 | Options: 12 | --roots Allowed endpoint roots - "*" for anything 13 | [array] [default: ["*"]] 14 | -p, --port Server port [number] [default: 3000] 15 | --origins Access-Control-Allow-Origin list [array] [default: ["*"]] 16 | --keyfile SSL key file [string] 17 | --certfile SSL cert file [string] 18 | --cafile SSL CA file [string] 19 | ``` 20 | 21 | # Usage 22 | 23 | ```bash 24 | ept serve 25 | ``` 26 | 27 | By default, this application allows any accessible EPT data to be proxied to 28 | 3D Tiles. The selection of a dataset is controlled via the `ept` query 29 | parameter. For example, after running `ept serve`, we should be able to access 30 | [this public dataset](http://na.entwine.io/red-rocks/ept.json) in 3D Tiles 31 | format 32 | [here](http://localhost:3000/tileset.json?ept=http://na.entwine.io/red-rocks/ept.json). 33 | 34 | We can also place restrictions on which locations may be proxied using the 35 | `--roots` option. By default, this is set to `*`, meaning to allow all 36 | endpoints, but with an invocation like `ept serve --roots http://na.entwine.io` 37 | we can restrict the proxy to serve only this one endpoint. Multiple roots may 38 | be passed to allow more than one specific endpoint. 39 | 40 | Some aspects of the resulting tileset may be controlled by query parameters, for 41 | example the selection of additional dimensions beyond XYZ/RGB. See the 42 | [tiling options](tiling-options.md). 43 | 44 | Other query parameters, aside from `ept` and any tiling options specifications, 45 | will be forwarded to the target EPT root. 46 | -------------------------------------------------------------------------------- /doc/tile.md: -------------------------------------------------------------------------------- 1 | # ept tile 2 | 3 | Translate an Entwine Point Tile dataset to [3D Tiles](https://github.com/AnalyticalGraphicsInc/3d-tiles) on disk. See [`ept serve`](serve.md) for on-the-fly tileset generation. 4 | 5 | ```bash 6 | index.ts tile [input] 7 | 8 | Translate EPT to 3D Tiles at rest 9 | 10 | Options: 11 | --version Show version number [boolean] 12 | --help Show help [boolean] 13 | -i, --input Path to ept.json file [string] [required] 14 | -o, --output Tileset output path [string] [default: /ept-tileset] 15 | -t, --threads Number of parallel threads [number] [default: 8] 16 | -f, --force Overwrite existing output, if present 17 | [boolean] [default: false] 18 | -v, --verbose Enable verbose logs [boolean] [default: false] 19 | --dimensions Dimensions to be added to the batch table [array] 20 | --z-offset Elevation offset to raise/lower the resulting point cloud 21 | [number] 22 | --truncate Truncate 16-bit colors to 8-bit [boolean] [default: false] 23 | ``` 24 | 25 | # Usage 26 | 27 | ```bash 28 | ept tile [path/to/ept.json] 29 | ``` 30 | 31 | This command creates a [3D Tiles tileset](https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification) in `/ept-tileset/`. 32 | -------------------------------------------------------------------------------- /doc/tiling-options.md: -------------------------------------------------------------------------------- 1 | # Tiling options 2 | 3 | The translation of [EPT](https://entwine.io/entwine-point-tile.html) [3D Tiles](https://github.com/AnalyticalGraphicsInc/3d-tiles) may require some decisions by the user. For example, XYZ and RGB (if present) attributes are always included, but in some cases other attributes may be desired as well. `Intensity`, `Classification`, or native `Z` coordinates (prior to earth-centered earth-fixed reprojection) may be useful for coloring. Another example may be a dynamic height offset. Options like these can be controlled by query parameters for the [serve](serve.md) application or by command line for the [tile](tile.md) application. 4 | 5 | ## Options 6 | 7 | ### dimensions 8 | 9 | The `dimensions` option selects additional dimensions from the EPT schema to be included in the 3D Tiles [Batch Table](https://github.com/CesiumGS/3d-tiles/tree/master/specification/TileFormats/PointCloud#batch-table). These dimensions will be populated directly from the EPT data, so for example, choosing `X`, `Y`, or `Z` will include these attributes in their native projection as stored in EPT, even though the `POSITION` attribute from the [Feature Table](https://github.com/CesiumGS/3d-tiles/tree/master/specification/TileFormats/PointCloud#feature-table) represents the XYZ values in EPSG:4978. For this reason, the `Z` value may be used to represent elevation. 10 | 11 | Examples: 12 | 13 | ``` 14 | ?dimensions=Z,Classification,Intensity # serve 15 | --dimensions Z Classification Intensity # tile 16 | ``` 17 | 18 | ### z-offset 19 | 20 | The `z-offset` option dynamically offsets the height of the resulting point cloud. This may be useful to correct the height of a point cloud whose vertical spatial reference is improperly transformed to ECEF, or perhaps just to lift the point cloud for easier viewing if it partially overlaps the baselayer terrain model. 21 | 22 | Examples: 23 | 24 | ``` 25 | ?z-offset=50 # serve 26 | --z-offset 50 # tile 27 | ``` 28 | 29 | ### truncate 30 | 31 | When RGB attributes are present, they must be written as 8-bit values in compliance with the 3D Tiles specification. In general point cloud formats, these values are often stored as 16-bit. In this case, the `truncate` option should be set to truncate these 16-bit values down to 8-bit for conversion. 32 | 33 | Examples: 34 | 35 | ``` 36 | ?truncate # serve 37 | --truncate # tile 38 | ``` 39 | -------------------------------------------------------------------------------- /doc/validate.md: -------------------------------------------------------------------------------- 1 | # ept validate 2 | 3 | Validate metadata structure of an [Entwine Point Tile](https://entwine.io/entwine-point-tile.html) dataset. 4 | 5 | ```bash 6 | ept validate --help 7 | 8 | Validate EPT metadata 9 | 10 | Options: 11 | --version Show version number [boolean] 12 | --help Show help [boolean] 13 | -i, --input Path to ept.json file [string] [required] 14 | ``` 15 | 16 | # Usage 17 | 18 | ```bash 19 | ept validate [path/to/ept.json] 20 | ``` 21 | 22 | This command will log messages for any errors related to the EPT metadata, and exit with a non-zero status code if the dataset is not valid. 23 | 24 | ```bash 25 | $ ept validate ~/entwine/bad/ept.json 26 | ✖ Errors: 27 | • schema: should have required property 'bounds' 28 | ✖ EPT is not valid 29 | ``` 30 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | moduleDirectories: ['node_modules', 'src'], 5 | testPathIgnorePatterns: ['lib/'], 6 | collectCoverageFrom: ['src/**'], 7 | coveragePathIgnorePatterns: ['/test/', '.asm.'], 8 | watchPathIgnorePatterns: ['/test/data/'] 9 | } 10 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Connor Manning 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ept-tools", 3 | "version": "0.0.13", 4 | "description": "Entwine Point Tiles utility library", 5 | "repository": "github:connormanning/ept-tools", 6 | "main": "lib/index.js", 7 | "bin": { 8 | "ept": "lib/app/index.js" 9 | }, 10 | "scripts": { 11 | "build": "ttsc -p tsconfig.build.json && copyfiles -f src/lib/* lib/lib/", 12 | "dev": "ts-node -r tsconfig-paths/register src/app/index.ts", 13 | "lambda": "ncc build src/app/lambda.ts -o ./lambda && copyfiles -f src/lib/* lambda/lib/", 14 | "lambda-zip": "(cd lambda && zip -r ../ept-tools.zip .)", 15 | "prepublish": "$npm_execpath build", 16 | "test": "jest" 17 | }, 18 | "dependencies": { 19 | "@koa/router": "^10.0.0", 20 | "ajv": "^7.1.1", 21 | "fatproj": "^0.0.2", 22 | "forager": "^0.0.6", 23 | "fs-extra": "^9.1.0", 24 | "koa": "^2.13.1", 25 | "koa-logger": "^3.2.1", 26 | "log-symbols": "^4.0.0", 27 | "node-fetch": "^2.6.1", 28 | "protopath": "^0.0.0", 29 | "querystring": "^0.2.1", 30 | "yargs": "^17.0.1", 31 | "zlib": "^1.0.5", 32 | "zstd-codec": "^0.1.2" 33 | }, 34 | "devDependencies": { 35 | "@types/aws-lambda": "^8.10.72", 36 | "@types/fs-extra": "^9.0.7", 37 | "@types/jest": "^26.0.20", 38 | "@types/koa": "^2.13.0", 39 | "@types/koa-logger": "^3.1.1", 40 | "@types/koa__router": "^8.0.4", 41 | "@types/node": "^14.14.31", 42 | "@types/node-fetch": "^2.5.8", 43 | "@types/yargs": "^16.0.1", 44 | "@vercel/ncc": "^0.27.0", 45 | "@zerollup/ts-transform-paths": "^1.7.18", 46 | "aws-lambda": "^1.0.6", 47 | "copyfiles": "^2.4.1", 48 | "husky": "^5.1.1", 49 | "jest": "^26.6.3", 50 | "prettier": "^2.2.1", 51 | "pretty-quick": "^3.1.0", 52 | "ts-jest": "^26.5.2", 53 | "ts-node": "^9.1.1", 54 | "tsconfig-paths": "^3.9.0", 55 | "ttypescript": "^1.5.12", 56 | "typescript": "^4.2.2" 57 | }, 58 | "husky": { 59 | "hooks": { 60 | "pre-commit": "pretty-quick --staged" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | Entwine Icon 3 |

4 | 5 | # THIS PROJECT IS DEPRECATED 6 | 7 | EPT and COPC data no longer need a live server to be transformed to 3D Tiles. Instead, this transformation can be done on-demand in the browser with the [COPC Viewer](https://viewer.copc.io). See the "Sample Data" section of the `Storage` tool, or the "Cesium" links on the [USGS Lidar explorer](https://usgs.entwine.io/) for examples. 8 | 9 | # EPT Tools 10 | 11 | A suite of tools for working with [Entwine Point Tile](entwine.io) data. 12 | 13 | # Installation 14 | 15 | Install the `ept` application via [npm](https://www.npmjs.com/): 16 | 17 | ```bash 18 | npm install ept-tools -g 19 | ``` 20 | 21 | # Usage 22 | 23 | ```bash 24 | ept --help 25 | ``` 26 | 27 | Individual tools are available as subcommands under the application `ept`: 28 | 29 | ```bash 30 | ept [...options] 31 | ``` 32 | 33 | Usage for an individual tool can be viewed with: 34 | 35 | ```bash 36 | ept --help 37 | ``` 38 | 39 | # Tools 40 | 41 | Available tools are: 42 | 43 | - [serve](doc/serve.md): serve EPT data as on-demand 3D Tiles over HTTP 44 | - [tile](doc/tile.md): translate EPT to 3D Tiles on disk 45 | - [validate](doc/validate.md): validate an EPT dataset 46 | -------------------------------------------------------------------------------- /src/3d-tiles/batch-table/batch-table.test.ts: -------------------------------------------------------------------------------- 1 | import { DataType, Schema } from 'ept' 2 | import { Dummy } from 'test' 3 | 4 | import { BatchTable } from '.' 5 | 6 | test('create: empty', async () => { 7 | const schema = Dummy.Schema.xyz 8 | const buffer = Buffer.alloc(Schema.pointSize(schema)) 9 | const view = await DataType.view('binary', buffer, schema) 10 | 11 | const { header, binary } = BatchTable.create(view) 12 | expect(header).toEqual({}) 13 | expect(binary).toHaveLength(0) 14 | }) 15 | -------------------------------------------------------------------------------- /src/3d-tiles/batch-table/batch-table.ts: -------------------------------------------------------------------------------- 1 | import { Options } from '3d-tiles/types' 2 | import { Dimension, Schema, View } from 'ept' 3 | import { EptToolsError } from 'types' 4 | 5 | import { Header } from './header' 6 | 7 | // Work around TS namespaced re-export deficiency. 8 | type _Header = Header 9 | export declare namespace BatchTable { 10 | export type Header = _Header 11 | } 12 | export type BatchTable = { header: Header; binary: Buffer } 13 | export const BatchTable = { create } 14 | 15 | const specials = [ 16 | 'ReturnNumber', 17 | 'NumberOfReturns', 18 | 'ScanDirectionFlag', 19 | 'EdgeOfFlightLine', 20 | 'Synthetic', 21 | 'KeyPoint', 22 | 'Withheld', 23 | ] 24 | 25 | function create( 26 | srcView: View.Readable, 27 | { dimensions = [] }: Partial = {} 28 | ): BatchTable { 29 | const { length } = srcView 30 | 31 | const header: Header = {} 32 | const buffers: Buffer[] = [] 33 | 34 | dimensions 35 | .filter( 36 | (name) => Schema.has(srcView.schema, name) || specials.includes(name) 37 | ) 38 | .forEach((name) => { 39 | const get = srcView.getter(name) 40 | 41 | let dimension = Schema.find(srcView.schema, name) 42 | if (!dimension && get && specials.includes(name)) { 43 | dimension = { name, type: 'unsigned', size: 1 } 44 | } 45 | if (!dimension) throw new EptToolsError(`Invalid dimension: ${name}`) 46 | const outputDimension = getOutputDimension(dimension) 47 | 48 | // Each binary buffer must be padded such that the subsequent buffer meets 49 | // its alignment requirement. To make this trivial, just pad everything 50 | // out to a multiple of 8 bytes. Note that our ascending byteOffset needs 51 | // to account for this, so we'll just perform this padding up front. Also 52 | // note that since we're not using our padding utility which zero-fills 53 | // properly, we need to make sure to zero out the pad bytes here: so we're 54 | // using Buffer.alloc instead of Buffer.allocUnsafe. 55 | const byteLength = length * outputDimension.size 56 | const rem = byteLength % 8 57 | const pad = rem ? 8 - rem : 0 58 | const buffer = Buffer.alloc(length * outputDimension.size + pad) 59 | 60 | const dstView = View.Writable.create(buffer, [outputDimension]) 61 | const set = dstView.setter(name) 62 | 63 | for (let i = 0; i < length; ++i) { 64 | set(get(i), i) 65 | } 66 | 67 | const byteOffset = buffers.reduce((sum, b) => sum + b.length, 0) 68 | header[name] = { 69 | byteOffset, 70 | componentType: getComponentType(outputDimension), 71 | type: 'SCALAR', 72 | } 73 | 74 | buffers.push(buffer) 75 | }) 76 | 77 | const binary = Buffer.concat(buffers) 78 | return { header, binary } 79 | } 80 | 81 | function getOutputDimension(dimension: Dimension): Dimension { 82 | const { name, type, size, scale = 1 } = dimension 83 | 84 | // If the value is scaled, or if it has size 8, then we always use a float. 85 | // The 64-bit integral types are not allowed. 86 | if (scale !== 1 || size === 8) return { name, type: 'float', size: 4 } 87 | return { name, type, size } 88 | } 89 | 90 | function getComponentType(dimension: Dimension): Header.ComponentType { 91 | // We should never see a ctype of (u)int64 here, as it is not allowed as a 92 | // component type in the batch table. 93 | const ctype = Dimension.ctype(dimension) 94 | 95 | switch (ctype) { 96 | case 'int8': 97 | return 'BYTE' 98 | case 'int16': 99 | return 'SHORT' 100 | case 'int32': 101 | return 'INT' 102 | case 'int64': 103 | throw new EptToolsError('Invalid dimension type') 104 | case 'uint8': 105 | return 'UNSIGNED_BYTE' 106 | case 'uint16': 107 | return 'UNSIGNED_SHORT' 108 | case 'uint32': 109 | return 'UNSIGNED_INT' 110 | case 'uint64': 111 | throw new EptToolsError('Invalid dimension type') 112 | case 'float': 113 | return 'FLOAT' 114 | default: 115 | return 'DOUBLE' 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/3d-tiles/batch-table/header.ts: -------------------------------------------------------------------------------- 1 | type JsonSerializable = 2 | | number 3 | | string 4 | | boolean 5 | | null 6 | | { [key: string]: JsonSerializable } 7 | | JsonSerializable[] 8 | 9 | export declare namespace Header { 10 | // https://git.io/JLtEm 11 | export type ComponentType = 12 | | 'BYTE' 13 | | 'UNSIGNED_BYTE' 14 | | 'SHORT' 15 | | 'UNSIGNED_SHORT' 16 | | 'INT' 17 | | 'UNSIGNED_INT' 18 | | 'FLOAT' 19 | | 'DOUBLE' 20 | export type Type = 'SCALAR' | 'VEC2' | 'VEC3' | 'VEC4' 21 | 22 | export type InlineDimension = JsonSerializable[] 23 | export type BinaryDimension = { 24 | byteOffset: number 25 | componentType: ComponentType 26 | type: Type 27 | } 28 | export type Dimension = InlineDimension | BinaryDimension 29 | } 30 | 31 | export type Header = { [name: string]: Header.Dimension } 32 | -------------------------------------------------------------------------------- /src/3d-tiles/batch-table/index.ts: -------------------------------------------------------------------------------- 1 | export { BatchTable } from './batch-table' 2 | -------------------------------------------------------------------------------- /src/3d-tiles/bounding-volume/box.test.ts: -------------------------------------------------------------------------------- 1 | import { Box } from './box' 2 | 3 | test('create', () => { 4 | const expected = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 5 | expect(Box.create([0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11])).toEqual( 6 | expected 7 | ) 8 | }) 9 | -------------------------------------------------------------------------------- /src/3d-tiles/bounding-volume/box.ts: -------------------------------------------------------------------------------- 1 | import { Point } from './point' 2 | 3 | export type Box = [...Point, ...Point, ...Point, ...Point] 4 | export const Box = { 5 | create: (center: Point, xAxis: Point, yAxis: Point, zAxis: Point): Box => [ 6 | ...center, 7 | ...xAxis, 8 | ...yAxis, 9 | ...zAxis, 10 | ], 11 | } 12 | -------------------------------------------------------------------------------- /src/3d-tiles/bounding-volume/index.ts: -------------------------------------------------------------------------------- 1 | import { Box } from './box' 2 | import { Region } from './region' 3 | import { Sphere } from './sphere' 4 | 5 | declare namespace Exports { 6 | export type { Box, Region, Sphere } 7 | } 8 | 9 | export declare namespace BoundingVolume { 10 | export type Box = Exports.Box 11 | export type Region = Exports.Region 12 | export type Sphere = Exports.Sphere 13 | } 14 | 15 | export type BoundingVolume = 16 | | { box: Box } 17 | | { region: Region } 18 | | { sphere: Sphere } 19 | 20 | export const BoundingVolume = { Box, Region, Sphere } 21 | -------------------------------------------------------------------------------- /src/3d-tiles/bounding-volume/point.ts: -------------------------------------------------------------------------------- 1 | export type Point = [number, number, number] 2 | -------------------------------------------------------------------------------- /src/3d-tiles/bounding-volume/region.test.ts: -------------------------------------------------------------------------------- 1 | import { Region } from './region' 2 | 3 | test('from wgs84', () => { 4 | const west = -90 5 | const east = 45 6 | const south = -45 7 | const north = 30 8 | const minz = -5 9 | const maxz = 42 10 | 11 | expect(Region.fromWgs84([west, south, minz, east, north, maxz])).toEqual([ 12 | (west * Math.PI) / 180, 13 | (south * Math.PI) / 180, 14 | (east * Math.PI) / 180, 15 | (north * Math.PI) / 180, 16 | minz, 17 | maxz, 18 | ]) 19 | }) 20 | -------------------------------------------------------------------------------- /src/3d-tiles/bounding-volume/region.ts: -------------------------------------------------------------------------------- 1 | import { Bounds } from 'ept' 2 | 3 | export type Region = [number, number, number, number, number, number] 4 | export const Region = { fromWgs84 } 5 | 6 | function fromWgs84([minx, miny, minz, maxx, maxy, maxz]: Bounds): Region { 7 | // https://git.io/fjXUz 8 | return [ 9 | (minx * Math.PI) / 180, 10 | (miny * Math.PI) / 180, 11 | (maxx * Math.PI) / 180, 12 | (maxy * Math.PI) / 180, 13 | minz, 14 | maxz, 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/3d-tiles/bounding-volume/sphere.test.ts: -------------------------------------------------------------------------------- 1 | import { Sphere } from './sphere' 2 | 3 | test('create', () => { 4 | expect(Sphere.create([0, 1, 2], 3)).toEqual([0, 1, 2, 3]) 5 | }) 6 | -------------------------------------------------------------------------------- /src/3d-tiles/bounding-volume/sphere.ts: -------------------------------------------------------------------------------- 1 | import { Point } from './point' 2 | 3 | export type Sphere = [...Point, number] 4 | export const Sphere = { create } 5 | 6 | function create(center: Point, radius: number): Sphere { 7 | return [...center, radius] 8 | } 9 | -------------------------------------------------------------------------------- /src/3d-tiles/cache.ts: -------------------------------------------------------------------------------- 1 | import { Ept } from 'ept' 2 | import { JsonSchema, getJson } from 'utils' 3 | 4 | type Entry = { 5 | promise: Promise 6 | createdAt: Date 7 | } 8 | type Map = Record 9 | 10 | export const Cache = { create } 11 | export type Cache = ReturnType 12 | 13 | function create(timeout = 60000) { 14 | const cache: Map = {} 15 | 16 | async function get(filename: string): Promise { 17 | const existing = cache[filename] 18 | if (existing) return existing.promise 19 | 20 | const promise = fetch(filename) 21 | cache[filename] = { promise, createdAt: new Date() } 22 | 23 | return promise 24 | } 25 | 26 | let interval = 27 | timeout && 28 | setInterval(() => { 29 | const now = new Date() 30 | 31 | Object.entries(cache).forEach(([filename, entry]) => { 32 | if (!entry) return 33 | const { createdAt } = entry 34 | 35 | if (now.getTime() - createdAt.getTime() > timeout) 36 | delete cache[filename] 37 | }) 38 | }, 60000) 39 | 40 | function destroy() { 41 | if (interval) clearInterval(interval) 42 | } 43 | 44 | return { get, destroy } 45 | } 46 | 47 | async function fetch(filename: string) { 48 | const [result, errors] = JsonSchema.validate( 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 | --------------------------------------------------------------------------------