Skip to content

Commit

Permalink
Merge pull request #15 from Rennzie/10-add-tests-for-the-js-wrapper
Browse files Browse the repository at this point in the history
10 add tests for the js wrapper
  • Loading branch information
Rennzie authored Sep 15, 2023
2 parents 335101b + 426da03 commit bac5656
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 921 deletions.
36 changes: 26 additions & 10 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
name: Rust
name: Build and Check

on:
push:
branches: [ "master" ]
branches: ['master']
pull_request:
branches: [ "master" ]
branches: ['master']

env:
CARGO_TERM_COLOR: always

jobs:
build:

bindings:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

wrapper:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- uses: jetli/[email protected]
with:
version: 'latest'

- name: Build Dev Wrapper
run: bun build:wrapper-dev

- name: Run Wrapper Tests
run: bun test:wrapper
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for NTv2 Grids [#2](https://github.com/Rennzie/geodesy-wasm/issues/2) via Geodesy [branch](https://github.com/busstoptaktik/geodesy/pull/60)
- The ability to supply a gridshift file via `RawGrids` struct [#2](https://github.com/Rennzie/geodesy-wasm/issues/2)
- Update the README with usage examples and better documentation [#9](https://github.com/Rennzie/geodesy-wasm/issues/9)
- Add tests for the wrapper [#10](https://github.com/Rennzie/geodesy-wasm/issues/10)
- Replaced yarn with [bun](https://bun.sh/docs/cli/test) in the process

[unreleased]:
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ version = "0.2.0--alpha1"
keywords = ["geospatial", "geodesy", "cartography", "geography"]
categories = ["science"]
authors = ["Sean Rennie <[email protected]>"]
description = "A WASM wrapper around the Rust Geodesy crate"
repository = "https://github.com/Rennzie/geodesy-wasm"
license = "MIT OR Apache-2.0"
edition = "2021"

Expand Down
40 changes: 32 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ _Note: Please see the [warnings](https://github.com/busstoptaktik/geodesy?tab=re
3. [x] Proj string to geodesy_rs parser so that proj strings can be used with geodesy_wasm - completed by @busstoptaktik in the geodesy crate
4. [x] NTv2 support
1. Being added in the geodesy crate [here](https://github.com/busstoptaktik/geodesy/pull/60) but is already being used in geodesy-wasm in place of gravsoft grid support.
5. [ ] Usage guide and examples
5. [x] Usage guide and examples
6. [ ] Documentation
7. [ ] Publish v1.0.0

Expand Down Expand Up @@ -170,25 +170,27 @@ console.log(resultFwd);

## Development

For convenience all scripts can be run with `yarn <script>`. Make sure all the javascript related dependencies are installed with `yarn install`. Rust dependencies are managed by [cargo](https://doc.rust-lang.org/cargo/) and don't require and explicit install step.
For convenience all scripts can be run with `bun <script>`. Make sure all the javascript related dependencies are installed with `bun install`. Rust dependencies are managed by [cargo](https://doc.rust-lang.org/cargo/) and don't require and explicit install step.

### 🛠️ Build the bindings

```sh
yarn build
bun build

# Or for a specific target run. Only `node` and `bundler` targets are supported
TARGET=node ENV=debug yarn build
TARGET=node ENV=debug bun build
```

### 🔧 Developing the JS wrapper

The wrapper is intended to abstract some of the complexities of using a wasm library - like dealing with pointers and managing WASM memory. It's also written to provide a more familiar API for JS developers - see [examples](#📝-examples).

**Note: This project uses [Bun](https://bun.sh/) for building and testing of the Javascript wrapper. Follow the instructions on the website to install it.**

To develop the wrapper you must first build the wasm bindings:

```sh
yarn build:wrapper-dev
bun build:wrapper-dev
```

This will ensure that you get all the linting goodness from typescript while making changes to the wrapper.
Expand All @@ -197,18 +199,40 @@ Todo: tests etc

### 🔬 Testing during alpha development

Nothing fancy here, just comparing output.
#### Wasm bindings

Written in rust so we use cargo

```bash
cargo test
```

#### JS wrapper

First we need to build the bindings:

```sh
bun build:wrapper-dev
```

Then we can run the tests:

```sh
bun test:wrapper-dev
```

During alhpa and beta dev there is a scrappy script in `test.js` for quickly iterating on the wrapper and debugging the bindings.

- Build the project with

```sh
TARGET=node ENV=debug npm run build
TARGET=node ENV=DEV bun run build
```

- Run the test script with

```sh
node ./test.js
bun ./test.js
```

---
Expand Down
Binary file added bun.lockb
Binary file not shown.
File renamed without changes.
85 changes: 85 additions & 0 deletions js/geodesy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {test, describe, expect} from 'bun:test';
import {Geodesy} from './geodesy';

const DEG_TO_RAD = Math.PI / 180;
const stdPipelineDefinition = `
+proj=pipeline
+step +inv +proj=tmerc +lat_0=49 +lon_0=-2 +k_0=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy
+step +proj=webmerc +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84
`;

const gsbPipelineDefinition = `
| tmerc inv lat_0=49 lon_0=-2 k_0=0.9996012717 x_0=400000 y_0=-100000 ellps=airy
| gridshift grids=OSTN15_NTv2_OSGBtoETRS.gsb
| webmerc lat_0=0 lon_0=0 x_0=0 y_0=0 ellps=WGS84
`;

const COPENHAGEN_GEO_2D = [55, 12];
const LONDON_BNG_3D = [544748.5367636156, 258372.49178149243, 9.61];

describe('Geodesy', () => {
describe('Transform', () => {
test('Basic geographic transform', () => {
const ctx = new Geodesy('utm zone=32');
const res = ctx.forward([COPENHAGEN_GEO_2D.map(v => v * DEG_TO_RAD)]);
expect(res).toEqual([[6080642.1129675, 1886936.9691340544]]);
ctx['ctx'].free();
});

test('Should be able to use a PROJ string', () => {
const ctx = new Geodesy('+proj=utm +zone=32');
const res = ctx.forward([COPENHAGEN_GEO_2D.map(v => v * DEG_TO_RAD)]);
expect(res).toEqual([[6080642.1129675, 1886936.9691340544]]);
ctx['ctx'].free();
});

test('Should be able to use a PROJ pipeline', () => {
const ctx = new Geodesy(stdPipelineDefinition);
const res = ctx.roundTrip([LONDON_BNG_3D])[0];

for (const coord of res) {
expect(coord).toBeCloseTo(0, 5);
}

ctx['ctx'].free();
});

test('Should be able to use an NTv2 gridshift file', async () => {
// TODO: How can we use __dirname here?
const file = Bun.file('./js/fixtures/OSTN15_NTv2_OSGBtoETRS.gsb');
const buf = Buffer.from(await file.arrayBuffer());
const gsb = new DataView(buf.buffer);

const ctx = new Geodesy(gsbPipelineDefinition, {
'OSTN15_NTv2_OSGBtoETRS.gsb': gsb,
});

const res = ctx.roundTrip([LONDON_BNG_3D])[0];

for (const coord of res) {
expect(coord).toBeCloseTo(0, 5);
}

ctx['ctx'].free();
});
});

describe('Errors', () => {
test('Should error if coordinate is not 2D or 3D', () => {
const ctx = new Geodesy('utm zone=32');
expect(() => ctx.forward([[1, 2, 3, 4]])).toThrow();
ctx['ctx'].free();
});

test('Should error if coordinate dimensions are not consistent', () => {
const ctx = new Geodesy('utm zone=32');
expect(() =>
ctx.inverse([
[1, 2],
[1, 2, 3],
]),
).toThrow();
ctx['ctx'].free();
});
});
});
43 changes: 34 additions & 9 deletions js/geodesy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export class Geodesy {

/**
* Transform an array of coordinates in the forward direction of the specified definition.
* @param coordinates - Coordinates can be 2D or 3D and are ordered [east, north, up] or [lon, lat, h]. Note, if inputs are angular they MUST be in radians.
* @param coordinates - Coordinates can be 2D or 3D and are ordered [east, north, up] or [lon, lat, h].
* Note, if inputs are angular they MUST be in radians.
* Dimensionality of the coordinates must be consistent.
* @returns
*/
public forward(coordinates: number[][]): number[][] {
Expand All @@ -48,7 +50,9 @@ export class Geodesy {

/**
* Transform an array of coordinates in the inverse direction of the specified definition.
* @param coordinates - Coordinates can be 2D or 3D and are ordered [east, north, up] or [lon, lat, h]. Note, if inputs are angular they MUST be in radians.
* @param coordinates - Coordinates can be 2D or 3D and are ordered [east, north, up] or [lon, lat, h].
* Note, if inputs are angular they MUST be in radians.
* Dimensionality of the coordinates must be consistent.
* @returns
*/
public inverse(coordinates: number[][]): number[][] {
Expand All @@ -64,7 +68,9 @@ export class Geodesy {
/**
* Returns the difference between input and the result of a roundtrip transformation.
* A helper method primarily used for testing.
* @param coordinates - Coordinates can be 2D or 3D and are ordered [east, north, up] or [lon, lat, h]. Note, if inputs are angular they MUST be in radians.
* @param coordinates - Coordinates can be 2D or 3D and are ordered [east, north, up] or [lon, lat, h].
* Note, if inputs are angular they MUST be in radians.
* Dimensionality of the coordinates must be consistent.
* @param diff - If true, return the difference between input and output. If false, return the output.
* @returns
*/
Expand Down Expand Up @@ -102,16 +108,35 @@ function prepareCoordinates(
function getCoordinateDimensions(
coords: number[][],
): GeodesyWasm.CoordDimension {
const dimensions = coords[0].length;
if (dimensions === 2) {
return GeodesyWasm.CoordDimension.Two;
} else if (dimensions === 3) {
return GeodesyWasm.CoordDimension.Three;
const firstCoordLength = coords[0].length;
let dimensions: GeodesyWasm.CoordDimension;
if (firstCoordLength === 2) {
dimensions = GeodesyWasm.CoordDimension.Two;
} else if (firstCoordLength === 3) {
dimensions = GeodesyWasm.CoordDimension.Three;
} else {
throw new Error(
`Invalid coordinate dimensions: ${dimensions}. Coordinates must be 2D or 3D`,
`Invalid coordinate dimensions: ${firstCoordLength}. Coordinates must be 2D or 3D`,
);
}

dimensionsAreConsistent(coords, dimensions);

return dimensions;
}

function dimensionsAreConsistent(
coords: number[][],
dimensions: GeodesyWasm.CoordDimension,
): void {
const dim = dimensions === GeodesyWasm.CoordDimension.Two ? 2 : 3;
for (const coord of coords) {
if (coord.length !== dim) {
throw new Error(
`Coordinate dimensions are not consistent. Expected ${dim}, got ${coord.length}`,
);
}
}
}

function flattenCoords(coords: number[][]): number[] {
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
"main": "./geodesy.ts",
"license": "(Apache-2.0 OR MIT)",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build:wrapper-dev": "wasm-pack build --target web --dev --out-dir pkg-js-dev",
"test:wrapper": "bun test --coverage",
"build:wrapper-dev": "wasm-pack build --target nodejs --dev --out-dir pkg-js-dev",
"build": "bash ./scripts/build.sh",
"publish": "cd pkg && npm publish --access public"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"bun-types": "^1.0.1",
"eslint": "^8.46.0",
"eslint-config-prettier": "^9.0.0",
"prettier": "^3.0.1",
Expand Down
6 changes: 3 additions & 3 deletions scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mkdir -p tmp_build
if [ "$ENV" == "DEV" ]; then
echo "Building debug version"
BUILD="--dev"
FLAGS="--features debug"
# FLAGS="--features debug"
else
echo "Building release version"
BUILD="--release"
Expand Down Expand Up @@ -47,7 +47,7 @@ fi
# Compile geodesy.ts for bundler
if [ -z "${TARGET+x}" ] || [ "$TARGET" == "bundler" ]; then
sed 's/@geodesy-wasm/\.\/geodesy-wasm.js/g' js/geodesy.ts > tmp_build/bundler/index.ts
yarn tsc tmp_build/bundler/index.ts --outDir tmp_build/bundler --declaration --declarationDir tmp_build/bundler --target es2020 --module ES2020
bun tsc tmp_build/bundler/index.ts --outDir tmp_build/bundler --declaration --declarationDir tmp_build/bundler --target es2020 --module ES2020
rm tmp_build/bundler/index.ts
else
echo "Skipping bundler target TS compilation"
Expand All @@ -56,7 +56,7 @@ fi
# Compile geodesy.ts for Node
if [ -z "${TARGET+x}" ] || [ "$TARGET" == "node" ]; then
sed 's/@geodesy-wasm/\.\/geodesy-wasm.js/g' js/geodesy.ts > tmp_build/node/index.ts
yarn tsc tmp_build/node/index.ts --outDir tmp_build/node --declaration --declarationDir tmp_build/node --target es2020 --module CommonJS
bun tsc tmp_build/node/index.ts --outDir tmp_build/node --declaration --declarationDir tmp_build/node --target es2020 --module CommonJS
rm tmp_build/node/index.ts
else
echo "Skipping node target TS compilation"
Expand Down
3 changes: 1 addition & 2 deletions src/geodesy/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ impl RawGrids {
/// as the grid=<key> parameter in the definition string.
///
/// Supported Grid Types:
///
/// - NTv2 (gsb)
/// - `NTv2` (.gsb)
#[wasm_bindgen]
pub fn add(&mut self, key: &str, data_view: DataView) -> WasmResult<()> {
let mut result = Vec::with_capacity(data_view.byte_length() as usize);
Expand Down
12 changes: 7 additions & 5 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */

// add Bun type definitions
"types": ["bun-types"],

/* Language and Environment */
"target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,

/* Modules */
"module": "NodeNext" /* Specify what module code is generated. */,
"moduleResolution": "NodeNext",
"rootDir": "./" /* Specify the root folder within your source files. */,
"baseUrl": "./",
"paths": {
"@geodesy-wasm": ["./pkg-js-dev"]
},
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,

/* Type Checking */
/* Best Practices */
"strict": true /* Enable all strict type-checking options. */,

/* Completeness */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
Loading

0 comments on commit bac5656

Please sign in to comment.